From 7a6c0394b89fa728876963872d5c4158c5861b40 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 9 Dec 2025 09:26:48 -0600 Subject: [PATCH 001/188] [deps]: Pin dependencies (#17030) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/build-desktop.yml | 6 +++--- apps/desktop/desktop_native/macos_provider/Cargo.toml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-desktop.yml b/.github/workflows/build-desktop.yml index 5086cd75ab7..c99d2183d71 100644 --- a/.github/workflows/build-desktop.yml +++ b/.github/workflows/build-desktop.yml @@ -1019,7 +1019,7 @@ jobs: - name: Set up Python uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 with: - python-version: '3.14' + python-version: '3.14.2' - name: Set up Node-gyp run: python -m pip install setuptools @@ -1257,7 +1257,7 @@ jobs: - name: Set up Python uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 with: - python-version: '3.14' + python-version: '3.14.2' - name: Set up Node-gyp run: python -m pip install setuptools @@ -1530,7 +1530,7 @@ jobs: - name: Set up Python uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 with: - python-version: '3.14' + python-version: '3.14.2' - name: Set up Node-gyp run: python -m pip install setuptools diff --git a/apps/desktop/desktop_native/macos_provider/Cargo.toml b/apps/desktop/desktop_native/macos_provider/Cargo.toml index 50f1834851d..8a34460268a 100644 --- a/apps/desktop/desktop_native/macos_provider/Cargo.toml +++ b/apps/desktop/desktop_native/macos_provider/Cargo.toml @@ -24,7 +24,7 @@ serde_json = { workspace = true } tokio = { workspace = true, features = ["sync"] } tracing = { workspace = true } tracing-subscriber = { workspace = true } -tracing-oslog = "0.3.0" +tracing-oslog = "=0.3.0" [build-dependencies] uniffi = { workspace = true, features = ["build"] } From bbf9157ec0b233065f3871a384910c16c9fa5b4d Mon Sep 17 00:00:00 2001 From: Kyle Denney <4227399+kdenney@users.noreply.github.com> Date: Tue, 9 Dec 2025 09:27:37 -0600 Subject: [PATCH 002/188] [PM-24581] new styling for premium badge (#17793) * [PM-24581] new styling for premium badge * stories file * translations for browser and desktop * design review feedback * color fixes, thanks claude --- apps/browser/src/_locales/en/messages.json | 3 +++ apps/desktop/src/locales/en/messages.json | 3 +++ .../components/premium-badge/premium-badge.component.ts | 5 +++-- .../components/premium-badge/premium-badge.stories.ts | 2 +- .../login-credentials/login-credentials-view.component.html | 2 +- 5 files changed, 11 insertions(+), 4 deletions(-) diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index e3b7c69e163..a5c204ffc99 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -5934,5 +5934,8 @@ }, "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { "message": "Set an unlock method to change your timeout action" + }, + "upgrade": { + "message": "Upgrade" } } diff --git a/apps/desktop/src/locales/en/messages.json b/apps/desktop/src/locales/en/messages.json index 92e350fab90..4c3833e28c3 100644 --- a/apps/desktop/src/locales/en/messages.json +++ b/apps/desktop/src/locales/en/messages.json @@ -4380,5 +4380,8 @@ }, "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { "message": "Set an unlock method to change your timeout action" + }, + "upgrade": { + "message": "Upgrade" } } diff --git a/libs/angular/src/billing/components/premium-badge/premium-badge.component.ts b/libs/angular/src/billing/components/premium-badge/premium-badge.component.ts index 8890584186d..2ae9c801eec 100644 --- a/libs/angular/src/billing/components/premium-badge/premium-badge.component.ts +++ b/libs/angular/src/billing/components/premium-badge/premium-badge.component.ts @@ -14,10 +14,11 @@ import { BadgeModule } from "@bitwarden/components"; type="button" *appNotPremium bitBadge - variant="success" + [variant]="'primary'" + class="!tw-text-primary-600 !tw-border-primary-600" (click)="promptForPremium($event)" > - {{ "premium" | i18n }} + {{ "upgrade" | i18n }} `, imports: [BadgeModule, JslibModule], diff --git a/libs/angular/src/billing/components/premium-badge/premium-badge.stories.ts b/libs/angular/src/billing/components/premium-badge/premium-badge.stories.ts index bf50d16d3c4..f6e45dbd5e1 100644 --- a/libs/angular/src/billing/components/premium-badge/premium-badge.stories.ts +++ b/libs/angular/src/billing/components/premium-badge/premium-badge.stories.ts @@ -29,7 +29,7 @@ export default { provide: I18nService, useFactory: () => { return new I18nMockService({ - premium: "Premium", + upgrade: "Upgrade", }); }, }, diff --git a/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.html b/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.html index be33f7a5562..2566752813c 100644 --- a/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.html +++ b/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.html @@ -122,7 +122,7 @@ -
+
{{ "verificationCodeTotp" | i18n }}
From 456f02958a2938e9d8538b9610039bf28b99e095 Mon Sep 17 00:00:00 2001 From: Nick Krantz <125900171+nick-livefront@users.noreply.github.com> Date: Tue, 9 Dec 2025 09:31:12 -0600 Subject: [PATCH 003/188] account for pre-set value of an angular form before the options are set (#17872) --- .../chip-select/chip-select.component.spec.ts | 18 ++++++++++++++++++ .../src/chip-select/chip-select.component.ts | 17 +++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/libs/components/src/chip-select/chip-select.component.spec.ts b/libs/components/src/chip-select/chip-select.component.spec.ts index 3c2f71ef8d7..3a66b799652 100644 --- a/libs/components/src/chip-select/chip-select.component.spec.ts +++ b/libs/components/src/chip-select/chip-select.component.spec.ts @@ -451,6 +451,24 @@ describe("ChipSelectComponent", () => { expect(disabledMenuItem?.disabled).toBe(true); }); + + it("should handle writeValue called before options are initialized", async () => { + const testApp = fixture.componentInstance; + + component["rootTree"] = null; + + component.writeValue("opt1"); + + expect(component["pendingValue"]).toBe("opt1"); + expect(component["selectedOption"]).toBeUndefined(); + + testApp.options.set(testOptions); + fixture.detectChanges(); + await fixture.whenStable(); + + expect(component["selectedOption"]?.value).toBe("opt1"); + expect(component["pendingValue"]).toBeUndefined(); + }); }); }); diff --git a/libs/components/src/chip-select/chip-select.component.ts b/libs/components/src/chip-select/chip-select.component.ts index bf6c6fb2aad..50e462dc815 100644 --- a/libs/components/src/chip-select/chip-select.component.ts +++ b/libs/components/src/chip-select/chip-select.component.ts @@ -100,10 +100,21 @@ export class ChipSelectComponent implements ControlValueAccessor { /** Tree constructed from `this.options` */ private rootTree?: ChipSelectOption | null; + /** Store the pending value when writeValue is called before options are initialized */ + private pendingValue?: T; + constructor() { // Initialize the root tree whenever options change effect(() => { this.initializeRootTree(this.options()); + + // If there's a pending value, apply it now that options are available + if (this.pendingValue !== undefined) { + this.selectedOption = this.findOption(this.rootTree, this.pendingValue); + this.setOrResetRenderedOptions(); + this.pendingValue = undefined; + this.cdr.markForCheck(); + } }); // Focus the first menu item when menuItems change (e.g., navigating submenus) @@ -255,6 +266,12 @@ export class ChipSelectComponent implements ControlValueAccessor { /** Implemented as part of NG_VALUE_ACCESSOR */ writeValue(obj: T): void { + // If rootTree is not yet initialized, store the value to apply it later + if (!this.rootTree) { + this.pendingValue = obj; + return; + } + this.selectedOption = this.findOption(this.rootTree, obj); this.setOrResetRenderedOptions(); // OnPush components require manual change detection when writeValue() is called From 093e06e7874c170c8d7a59dbcbee686ccabdfa69 Mon Sep 17 00:00:00 2001 From: neuronull <9162534+neuronull@users.noreply.github.com> Date: Tue, 9 Dec 2025 07:46:40 -0800 Subject: [PATCH 004/188] Bump Rust version to 1.91.1 (#17864) * Bump Rust version to 1.91.1 * clippy * clippy --- .../desktop/desktop_native/core/src/biometric_v2/windows.rs | 6 +++--- apps/desktop/desktop_native/rust-toolchain.toml | 2 +- .../desktop_native/windows_plugin_authenticator/src/lib.rs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/desktop/desktop_native/core/src/biometric_v2/windows.rs b/apps/desktop/desktop_native/core/src/biometric_v2/windows.rs index 32d2eb7e6e6..669dd757c40 100644 --- a/apps/desktop/desktop_native/core/src/biometric_v2/windows.rs +++ b/apps/desktop/desktop_native/core/src/biometric_v2/windows.rs @@ -285,8 +285,8 @@ async fn windows_hello_authenticate_with_crypto( return Err(anyhow!("Failed to sign data")); } - let signature_buffer = signature.Result()?; - let signature_value = unsafe { as_mut_bytes(&signature_buffer)? }; + let mut signature_buffer = signature.Result()?; + let signature_value = unsafe { as_mut_bytes(&mut signature_buffer)? }; // The signature is deterministic based on the challenge and keychain key. Thus, it can be // hashed to a key. It is unclear what entropy this key provides. @@ -368,7 +368,7 @@ fn decrypt_data( Ok(plaintext) } -unsafe fn as_mut_bytes(buffer: &IBuffer) -> Result<&mut [u8]> { +unsafe fn as_mut_bytes(buffer: &mut IBuffer) -> Result<&mut [u8]> { let interop = buffer.cast::()?; unsafe { diff --git a/apps/desktop/desktop_native/rust-toolchain.toml b/apps/desktop/desktop_native/rust-toolchain.toml index c1ab6b3240a..0992ce9d294 100644 --- a/apps/desktop/desktop_native/rust-toolchain.toml +++ b/apps/desktop/desktop_native/rust-toolchain.toml @@ -1,4 +1,4 @@ [toolchain] -channel = "1.87.0" +channel = "1.91.1" components = [ "rustfmt", "clippy" ] profile = "minimal" diff --git a/apps/desktop/desktop_native/windows_plugin_authenticator/src/lib.rs b/apps/desktop/desktop_native/windows_plugin_authenticator/src/lib.rs index 893fdf765fc..b38a1c725f2 100644 --- a/apps/desktop/desktop_native/windows_plugin_authenticator/src/lib.rs +++ b/apps/desktop/desktop_native/windows_plugin_authenticator/src/lib.rs @@ -153,7 +153,7 @@ fn add_authenticator() -> std::result::Result<(), String> { } } -type EXPERIMENTAL_WebAuthNPluginAddAuthenticatorFnDeclaration = unsafe extern "cdecl" fn( +type EXPERIMENTAL_WebAuthNPluginAddAuthenticatorFnDeclaration = unsafe extern "C" fn( pPluginAddAuthenticatorOptions: *const webauthn::ExperimentalWebAuthnPluginAddAuthenticatorOptions, ppPluginAddAuthenticatorResponse: *mut *mut webauthn::ExperimentalWebAuthnPluginAddAuthenticatorResponse, ) -> HRESULT; From c1c3e432f7db97fc91c23875b5ba4db14ea5d795 Mon Sep 17 00:00:00 2001 From: Mike Amirault Date: Tue, 9 Dec 2025 10:55:30 -0500 Subject: [PATCH 005/188] [PM-20080] Add spacing to new cipher/send buttons, unify sizing (#17806) * [PM-20080] Add spacing to new cipher/send buttons, unify sizing * [PM-20080] Use logical css properties and increase spacing --- .../new-item-dropdown/new-item-dropdown-v2.component.html | 4 ++-- .../src/new-send-dropdown/new-send-dropdown.component.html | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/browser/src/vault/popup/components/vault-v2/new-item-dropdown/new-item-dropdown-v2.component.html b/apps/browser/src/vault/popup/components/vault-v2/new-item-dropdown/new-item-dropdown-v2.component.html index 7dd0a5a3bc7..fa8683c12dc 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/new-item-dropdown/new-item-dropdown-v2.component.html +++ b/apps/browser/src/vault/popup/components/vault-v2/new-item-dropdown/new-item-dropdown-v2.component.html @@ -1,5 +1,5 @@ - diff --git a/libs/tools/send/send-ui/src/new-send-dropdown/new-send-dropdown.component.html b/libs/tools/send/send-ui/src/new-send-dropdown/new-send-dropdown.component.html index bcced7e012b..fb9b82c44e5 100644 --- a/libs/tools/send/send-ui/src/new-send-dropdown/new-send-dropdown.component.html +++ b/libs/tools/send/send-ui/src/new-send-dropdown/new-send-dropdown.component.html @@ -1,5 +1,5 @@ From ee582b2ebeb90e14fb0d5279dbc3352de0c92b6a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 9 Dec 2025 16:07:01 +0000 Subject: [PATCH 006/188] [deps] Platform: Update Rust crate sysinfo to v0.37.2 (#15699) 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/desktop/desktop_native/Cargo.lock | 4 ++-- apps/desktop/desktop_native/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/desktop/desktop_native/Cargo.lock b/apps/desktop/desktop_native/Cargo.lock index 095659eb329..3d528d49d67 100644 --- a/apps/desktop/desktop_native/Cargo.lock +++ b/apps/desktop/desktop_native/Cargo.lock @@ -3188,9 +3188,9 @@ dependencies = [ [[package]] name = "sysinfo" -version = "0.35.0" +version = "0.37.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b897c8ea620e181c7955369a31be5f48d9a9121cb59fd33ecef9ff2a34323422" +checksum = "16607d5caffd1c07ce073528f9ed972d88db15dd44023fa57142963be3feb11f" dependencies = [ "libc", "memchr", diff --git a/apps/desktop/desktop_native/Cargo.toml b/apps/desktop/desktop_native/Cargo.toml index 3ff9b6ac722..f8ee329ed5e 100644 --- a/apps/desktop/desktop_native/Cargo.toml +++ b/apps/desktop/desktop_native/Cargo.toml @@ -60,7 +60,7 @@ serde_json = "=1.0.127" sha2 = "=0.10.8" ssh-encoding = "=0.2.0" ssh-key = { version = "=0.6.7", default-features = false } -sysinfo = "=0.35.0" +sysinfo = "=0.37.2" thiserror = "=2.0.12" tokio = "=1.45.0" tokio-util = "=0.7.13" From 5dbcb18b6a77d12b02ba3b6a72b218d5e8742bc3 Mon Sep 17 00:00:00 2001 From: Alex <55413326+AlexRubik@users.noreply.github.com> Date: Tue, 9 Dec 2025 12:16:21 -0500 Subject: [PATCH 007/188] [PM-25037] add optional size input to app-vault-icon to prevent zoom issues (#17640) --- .../app-table-row-scrollable.component.html | 6 ++++- .../src/vault/components/icon.component.html | 11 +++----- .../src/vault/components/icon.component.ts | 26 ++++++++++++++++++- 3 files changed, 34 insertions(+), 9 deletions(-) diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/app-table-row-scrollable.component.html b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/app-table-row-scrollable.component.html index 76a03e0c525..0494f77bd46 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/app-table-row-scrollable.component.html +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/app-table-row-scrollable.component.html @@ -45,7 +45,11 @@ tabindex="0" [attr.aria-label]="'viewItem' | i18n" > - + - } @else { -
- + +
+
+ +
+
- } diff --git a/apps/browser/src/popup/scss/base.scss b/apps/browser/src/popup/scss/base.scss deleted file mode 100644 index 01b9d3f05d5..00000000000 --- a/apps/browser/src/popup/scss/base.scss +++ /dev/null @@ -1,453 +0,0 @@ -@import "variables.scss"; - -* { - box-sizing: border-box; - padding: 0; - margin: 0; -} - -html { - overflow: hidden; - min-height: 600px; - height: 100%; - - &.body-sm { - min-height: 500px; - } - - &.body-xs { - min-height: 400px; - } - - &.body-xxs { - min-height: 300px; - } - - &.body-3xs { - min-height: 240px; - } - - &.body-full { - min-height: unset; - width: 100%; - height: 100%; - - & body { - width: 100%; - } - } -} - -html, -body { - font-family: $font-family-sans-serif; - font-size: $font-size-base; - line-height: $line-height-base; - -webkit-font-smoothing: antialiased; -} - -body { - width: 380px; - height: 100%; - position: relative; - min-height: inherit; - overflow: hidden; - color: $text-color; - background-color: $background-color; - - @include themify($themes) { - color: themed("textColor"); - background-color: themed("backgroundColor"); - } -} - -h1, -h2, -h3, -h4, -h5, -h6 { - font-family: $font-family-sans-serif; - font-size: $font-size-base; - font-weight: normal; -} - -p { - margin-bottom: 10px; -} - -ul, -ol { - margin-bottom: 10px; -} - -img { - border: none; -} - -a:not(popup-page a, popup-tab-navigation a) { - text-decoration: none; - - @include themify($themes) { - color: themed("primaryColor"); - } - - &:hover, - &:focus { - @include themify($themes) { - color: darken(themed("primaryColor"), 6%); - } - } -} - -input:not(bit-form-field input, bit-search input, input[bitcheckbox]), -select:not(bit-form-field select), -textarea:not(bit-form-field textarea) { - @include themify($themes) { - color: themed("textColor"); - background-color: themed("inputBackgroundColor"); - } -} - -input:not(input[bitcheckbox]), -select, -textarea, -button:not(bit-chip-select button) { - font-size: $font-size-base; - font-family: $font-family-sans-serif; -} - -input[type*="date"] { - @include themify($themes) { - color-scheme: themed("dateInputColorScheme"); - } -} - -::-webkit-calendar-picker-indicator { - @include themify($themes) { - filter: themed("webkitCalendarPickerFilter"); - } -} - -::-webkit-calendar-picker-indicator:hover { - @include themify($themes) { - filter: themed("webkitCalendarPickerHoverFilter"); - } - cursor: pointer; -} - -select { - width: 100%; - padding: 0.35rem; -} - -button { - cursor: pointer; -} - -textarea { - resize: vertical; -} - -app-root > div { - height: 100%; - width: 100%; -} - -main::-webkit-scrollbar, -cdk-virtual-scroll-viewport::-webkit-scrollbar, -.vault-select::-webkit-scrollbar { - width: 10px; - height: 10px; -} - -main::-webkit-scrollbar-track, -.vault-select::-webkit-scrollbar-track { - background-color: transparent; -} - -cdk-virtual-scroll-viewport::-webkit-scrollbar-track { - @include themify($themes) { - background-color: themed("backgroundColor"); - } -} - -main::-webkit-scrollbar-thumb, -cdk-virtual-scroll-viewport::-webkit-scrollbar-thumb, -.vault-select::-webkit-scrollbar-thumb { - border-radius: 10px; - margin-right: 1px; - - @include themify($themes) { - background-color: themed("scrollbarColor"); - } - - &:hover { - @include themify($themes) { - background-color: themed("scrollbarHoverColor"); - } - } -} - -header:not(bit-callout header, bit-dialog header, popup-page header) { - height: 44px; - display: flex; - - &:not(.no-theme) { - border-bottom: 1px solid #000000; - - @include themify($themes) { - color: themed("headerColor"); - background-color: themed("headerBackgroundColor"); - border-bottom-color: themed("headerBorderColor"); - } - } - - .header-content { - display: flex; - flex: 1 1 auto; - } - - .header-content > .right, - .header-content > .right > .right { - height: 100%; - } - - .left, - .right { - flex: 1; - display: flex; - min-width: -webkit-min-content; /* Workaround to Chrome bug */ - .header-icon { - margin-right: 5px; - } - } - - .right { - justify-content: flex-end; - align-items: center; - app-avatar { - max-height: 30px; - margin-right: 5px; - } - } - - .center { - display: flex; - align-items: center; - text-align: center; - min-width: 0; - } - - .login-center { - margin: auto; - } - - app-pop-out > button, - div > button:not(app-current-account button):not(.home-acc-switcher-btn), - div > a { - border: none; - padding: 0 10px; - text-decoration: none; - display: flex; - flex-direction: row; - justify-content: center; - align-items: center; - height: 100%; - white-space: pre; - - &:not(.home-acc-switcher-btn):hover, - &:not(.home-acc-switcher-btn):focus { - @include themify($themes) { - background-color: themed("headerBackgroundHoverColor"); - color: themed("headerColor"); - } - } - - &[disabled] { - opacity: 0.65; - cursor: default !important; - background-color: inherit !important; - } - - i + span { - margin-left: 5px; - } - } - - app-pop-out { - display: flex; - padding-right: 0.5em; - } - - .title { - font-weight: bold; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } - - .search { - padding: 7px 10px; - width: 100%; - text-align: left; - position: relative; - display: flex; - - .bwi { - position: absolute; - top: 15px; - left: 20px; - - @include themify($themes) { - color: themed("headerInputPlaceholderColor"); - } - } - - input:not(bit-form-field input) { - width: 100%; - margin: 0; - border: none; - padding: 5px 10px 5px 30px; - border-radius: $border-radius; - - @include themify($themes) { - background-color: themed("headerInputBackgroundColor"); - color: themed("headerInputColor"); - } - - &::selection { - @include themify($themes) { - // explicitly set text selection to invert foreground/background - background-color: themed("headerInputColor"); - color: themed("headerInputBackgroundColor"); - } - } - - &:focus { - border-radius: $border-radius; - outline: none; - - @include themify($themes) { - background-color: themed("headerInputBackgroundFocusColor"); - } - } - - &::-webkit-input-placeholder { - @include themify($themes) { - color: themed("headerInputPlaceholderColor"); - } - } - /** make the cancel button visible in both dark/light themes **/ - &[type="search"]::-webkit-search-cancel-button { - -webkit-appearance: none; - appearance: none; - height: 15px; - width: 15px; - background-repeat: no-repeat; - mask-image: url("../images/close-button-white.svg"); - -webkit-mask-image: url("../images/close-button-white.svg"); - @include themify($themes) { - background-color: themed("headerInputColor"); - } - } - } - } - - .left + .search, - .left + .sr-only + .search { - padding-left: 0; - - .bwi { - left: 10px; - } - } - - .search + .right { - margin-left: -10px; - } -} - -.content { - padding: 15px 5px; -} - -app-root { - width: 100%; - height: 100vh; - display: flex; - - @include themify($themes) { - background-color: themed("backgroundColor"); - } -} - -main:not(popup-page main):not(auth-anon-layout main) { - position: absolute; - top: 44px; - bottom: 0; - left: 0; - right: 0; - overflow-y: auto; - overflow-x: hidden; - - @include themify($themes) { - background-color: themed("backgroundColor"); - } - - &.no-header { - top: 0; - } - - &.flex { - display: flex; - flex-flow: column; - height: calc(100% - 44px); - } -} - -.center-content, -.no-items, -.full-loading-spinner { - display: flex; - justify-content: center; - align-items: center; - height: 100%; - flex-direction: column; - flex-grow: 1; -} - -.no-items, -.full-loading-spinner { - text-align: center; - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - - .no-items-image { - @include themify($themes) { - content: url("../images/search-desktop" + themed("svgSuffix")); - } - } - - .bwi { - margin-bottom: 10px; - - @include themify($themes) { - color: themed("disabledIconColor"); - } - } -} - -// cdk-virtual-scroll -.cdk-virtual-scroll-viewport { - width: 100%; - height: 100%; - overflow-y: auto; - overflow-x: hidden; -} - -.cdk-virtual-scroll-content-wrapper { - width: 100%; -} diff --git a/apps/browser/src/popup/scss/box.scss b/apps/browser/src/popup/scss/box.scss deleted file mode 100644 index 763f73a15cb..00000000000 --- a/apps/browser/src/popup/scss/box.scss +++ /dev/null @@ -1,620 +0,0 @@ -@import "variables.scss"; - -.box { - position: relative; - width: 100%; - - &.first { - margin-top: 0; - } - - .box-header { - margin: 0 10px 5px 10px; - text-transform: uppercase; - display: flex; - - @include themify($themes) { - color: themed("headingColor"); - } - } - - .box-content { - @include themify($themes) { - background-color: themed("backgroundColor"); - border-color: themed("borderColor"); - } - - &.box-content-padded { - padding: 10px 15px; - } - - &.condensed .box-content-row, - .box-content-row.condensed { - padding-top: 5px; - padding-bottom: 5px; - } - - &.no-hover .box-content-row, - .box-content-row.no-hover { - &:hover, - &:focus { - @include themify($themes) { - background-color: themed("boxBackgroundColor") !important; - } - } - } - - &.single-line .box-content-row, - .box-content-row.single-line { - padding-top: 10px; - padding-bottom: 10px; - margin: 5px; - } - - &.row-top-padding { - padding-top: 10px; - } - } - - .box-footer { - margin: 0 5px 5px 5px; - padding: 0 10px 5px 10px; - font-size: $font-size-small; - - button.btn { - font-size: $font-size-small; - padding: 0; - } - - button.btn.primary { - font-size: $font-size-base; - padding: 7px 15px; - width: 100%; - - &:hover { - @include themify($themes) { - border-color: themed("borderHoverColor") !important; - } - } - } - - @include themify($themes) { - color: themed("mutedColor"); - } - } - - &.list { - margin: 10px 0 20px 0; - .box-content { - .virtual-scroll-item { - display: inline-block; - width: 100%; - } - - .box-content-row { - text-decoration: none; - border-radius: $border-radius; - // background-color: $background-color; - - @include themify($themes) { - color: themed("textColor"); - background-color: themed("boxBackgroundColor"); - } - - &.padded { - padding-top: 10px; - padding-bottom: 10px; - } - - &.no-hover { - &:hover { - @include themify($themes) { - background-color: themed("boxBackgroundColor") !important; - } - } - } - - &:hover, - &:focus, - &.active { - @include themify($themes) { - background-color: themed("listItemBackgroundHoverColor"); - } - } - - &:focus { - border-left: 5px solid #000000; - padding-left: 5px; - - @include themify($themes) { - border-left-color: themed("mutedColor"); - } - } - - .action-buttons { - .row-btn { - padding-left: 5px; - padding-right: 5px; - } - } - - .text:not(.no-ellipsis), - .detail { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } - - .row-main { - display: flex; - min-width: 0; - align-items: normal; - - .row-main-content { - min-width: 0; - } - } - } - - &.single-line { - .box-content-row { - display: flex; - padding-top: 10px; - padding-bottom: 10px; - margin: 5px; - border-radius: $border-radius; - } - } - } - } -} - -.box-content-row { - display: block; - padding: 5px 10px; - position: relative; - z-index: 1; - border-radius: $border-radius; - margin: 3px 5px; - - @include themify($themes) { - background-color: themed("boxBackgroundColor"); - } - - &:last-child { - &:before { - border: none; - height: 0; - } - } - - &.override-last:last-child:before { - border-bottom: 1px solid #000000; - @include themify($themes) { - border-bottom-color: themed("boxBorderColor"); - } - } - - &.last:last-child:before { - border-bottom: 1px solid #000000; - @include themify($themes) { - border-bottom-color: themed("boxBorderColor"); - } - } - - &:after { - content: ""; - display: table; - clear: both; - } - - &:hover, - &:focus, - &.active { - @include themify($themes) { - background-color: themed("boxBackgroundHoverColor"); - } - } - - &.pre { - white-space: pre; - overflow-x: auto; - } - - &.pre-wrap { - white-space: pre-wrap; - overflow-x: auto; - } - - .row-label, - label { - font-size: $font-size-small; - display: block; - width: 100%; - margin-bottom: 5px; - - @include themify($themes) { - color: themed("mutedColor"); - } - - .sub-label { - margin-left: 10px; - } - } - - .flex-label { - font-size: $font-size-small; - display: flex; - flex-grow: 1; - margin-bottom: 5px; - - @include themify($themes) { - color: themed("mutedColor"); - } - - > a { - flex-grow: 0; - } - } - - .text, - .detail { - display: block; - text-align: left; - - @include themify($themes) { - color: themed("textColor"); - } - } - - .detail { - font-size: $font-size-small; - - @include themify($themes) { - color: themed("mutedColor"); - } - } - - .img-right, - .txt-right { - float: right; - margin-left: 10px; - } - - .row-main { - flex-grow: 1; - min-width: 0; - } - - &.box-content-row-flex, - .box-content-row-flex, - &.box-content-row-checkbox, - &.box-content-row-link, - &.box-content-row-input, - &.box-content-row-slider, - &.box-content-row-multi { - display: flex; - align-items: center; - word-break: break-all; - - &.box-content-row-word-break { - word-break: normal; - } - } - - &.box-content-row-multi { - input:not([type="checkbox"]) { - width: 100%; - } - - input + label.sr-only + select { - margin-top: 5px; - } - - > a, - > button { - padding: 8px 8px 8px 4px; - margin: 0; - - @include themify($themes) { - color: themed("dangerColor"); - } - } - } - - &.box-content-row-multi, - &.box-content-row-newmulti { - padding-left: 10px; - } - - &.box-content-row-newmulti { - @include themify($themes) { - color: themed("primaryColor"); - } - } - - &.box-content-row-checkbox, - &.box-content-row-link, - &.box-content-row-input, - &.box-content-row-slider { - padding-top: 10px; - padding-bottom: 10px; - margin: 5px; - - label, - .row-label { - font-size: $font-size-base; - display: block; - width: initial; - margin-bottom: 0; - - @include themify($themes) { - color: themed("textColor"); - } - } - - > span { - @include themify($themes) { - color: themed("mutedColor"); - } - } - - > input { - margin: 0 0 0 auto; - padding: 0; - } - - > * { - margin-right: 15px; - - &:last-child { - margin-right: 0; - } - } - } - - &.box-content-row-checkbox-left { - justify-content: flex-start; - - > input { - margin: 0 15px 0 0; - } - } - - &.box-content-row-input { - label { - white-space: nowrap; - } - - input { - text-align: right; - - &[type="number"] { - max-width: 50px; - } - } - } - - &.box-content-row-slider { - input[type="range"] { - height: 10px; - } - - input[type="number"] { - width: 45px; - } - - label { - white-space: nowrap; - } - } - - input:not([type="checkbox"]):not([type="radio"]), - textarea { - border: none; - width: 100%; - background-color: transparent !important; - - &::-webkit-input-placeholder { - @include themify($themes) { - color: themed("inputPlaceholderColor"); - } - } - - &:not([type="file"]):focus { - outline: none; - } - } - - select { - width: 100%; - border: 1px solid #000000; - border-radius: $border-radius; - padding: 7px 4px; - - @include themify($themes) { - border-color: themed("inputBorderColor"); - } - } - - .action-buttons { - display: flex; - margin-left: 5px; - - &.action-buttons-fixed { - align-self: start; - margin-top: 2px; - } - - .row-btn { - cursor: pointer; - padding: 10px 8px; - background: none; - border: none; - - @include themify($themes) { - color: themed("boxRowButtonColor"); - } - - &:hover, - &:focus { - @include themify($themes) { - color: themed("boxRowButtonHoverColor"); - } - } - - &.disabled, - &[disabled] { - @include themify($themes) { - color: themed("disabledIconColor"); - opacity: themed("disabledBoxOpacity"); - } - - &:hover { - @include themify($themes) { - color: themed("disabledIconColor"); - opacity: themed("disabledBoxOpacity"); - } - } - cursor: default !important; - } - } - - &.no-pad .row-btn { - padding-top: 0; - padding-bottom: 0; - } - } - - &:not(.box-draggable-row) { - .action-buttons .row-btn:last-child { - margin-right: -3px; - } - } - - &.box-draggable-row { - &.box-content-row-checkbox { - input[type="checkbox"] + .drag-handle { - margin-left: 10px; - } - } - } - - .drag-handle { - cursor: move; - padding: 10px 2px 10px 8px; - user-select: none; - - @include themify($themes) { - color: themed("mutedColor"); - } - } - - &.cdk-drag-preview { - position: relative; - display: flex; - align-items: center; - opacity: 0.8; - - @include themify($themes) { - background-color: themed("boxBackgroundColor"); - } - } - - select.field-type { - margin: 5px 0 0 25px; - width: calc(100% - 25px); - } - - .icon { - display: flex; - justify-content: center; - align-items: center; - min-width: 34px; - margin-left: -5px; - - @include themify($themes) { - color: themed("mutedColor"); - } - - &.icon-small { - min-width: 25px; - } - - img { - border-radius: $border-radius; - max-height: 20px; - max-width: 20px; - } - } - - .progress { - display: flex; - height: 5px; - overflow: hidden; - margin: 5px -15px -10px; - - .progress-bar { - display: flex; - flex-direction: column; - justify-content: center; - white-space: nowrap; - background-color: $brand-primary; - } - } - - .radio-group { - display: flex; - justify-content: flex-start; - align-items: center; - margin-bottom: 5px; - - input { - flex-grow: 0; - } - - label { - margin: 0 0 0 5px; - flex-grow: 1; - font-size: $font-size-base; - display: block; - width: 100%; - - @include themify($themes) { - color: themed("textColor"); - } - } - - &.align-start { - align-items: start; - margin-top: 10px; - - label { - margin-top: -4px; - } - } - } -} - -.truncate { - display: inline-block; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -form { - .box { - .box-content { - .box-content-row { - &.no-hover { - &:hover { - @include themify($themes) { - background-color: themed("transparentColor") !important; - } - } - } - } - } - } -} diff --git a/apps/browser/src/popup/scss/buttons.scss b/apps/browser/src/popup/scss/buttons.scss deleted file mode 100644 index e9af536dc3d..00000000000 --- a/apps/browser/src/popup/scss/buttons.scss +++ /dev/null @@ -1,118 +0,0 @@ -@import "variables.scss"; - -.btn { - border-radius: $border-radius; - padding: 7px 15px; - border: 1px solid #000000; - font-size: $font-size-base; - text-align: center; - cursor: pointer; - - @include themify($themes) { - background-color: themed("buttonBackgroundColor"); - border-color: themed("buttonBorderColor"); - color: themed("buttonColor"); - } - - &.primary { - @include themify($themes) { - color: themed("buttonPrimaryColor"); - } - } - - &.danger { - @include themify($themes) { - color: themed("buttonDangerColor"); - } - } - - &.callout-half { - font-weight: bold; - max-width: 50%; - } - - &:hover:not([disabled]) { - cursor: pointer; - - @include themify($themes) { - background-color: darken(themed("buttonBackgroundColor"), 1.5%); - border-color: darken(themed("buttonBorderColor"), 17%); - color: darken(themed("buttonColor"), 10%); - } - - &.primary { - @include themify($themes) { - color: darken(themed("buttonPrimaryColor"), 6%); - } - } - - &.danger { - @include themify($themes) { - color: darken(themed("buttonDangerColor"), 6%); - } - } - } - - &:focus:not([disabled]) { - cursor: pointer; - outline: 0; - - @include themify($themes) { - background-color: darken(themed("buttonBackgroundColor"), 6%); - border-color: darken(themed("buttonBorderColor"), 25%); - } - } - - &[disabled] { - opacity: 0.65; - cursor: default !important; - } - - &.block { - display: block; - width: calc(100% - 10px); - margin: 0 auto; - } - - &.link, - &.neutral { - border: none !important; - background: none !important; - - &:focus { - text-decoration: underline; - } - } -} - -.action-buttons { - .btn { - &:focus { - outline: auto; - } - } -} - -button.box-content-row { - display: block; - width: calc(100% - 10px); - text-align: left; - border-color: none; - - @include themify($themes) { - background-color: themed("boxBackgroundColor"); - } -} - -button { - border: none; - background: transparent; - color: inherit; -} - -.login-buttons { - .btn.block { - width: 100%; - margin-bottom: 10px; - } -} diff --git a/apps/browser/src/popup/scss/environment.scss b/apps/browser/src/popup/scss/environment.scss deleted file mode 100644 index cd8f6379e2c..00000000000 --- a/apps/browser/src/popup/scss/environment.scss +++ /dev/null @@ -1,43 +0,0 @@ -@import "variables.scss"; - -html.browser_safari { - &.safari_height_fix { - body { - height: 360px !important; - - &.body-xs { - height: 300px !important; - } - - &.body-full { - height: 100% !important; - } - } - } - - header { - .search .bwi { - left: 20px; - } - - .left + .search .bwi { - left: 10px; - } - } - - .content { - &.login-page { - padding-top: 100px; - } - } - - app-root { - border-width: 1px; - border-style: solid; - border-color: #000000; - } - - &.theme_light app-root { - border-color: #777777; - } -} diff --git a/apps/browser/src/popup/scss/grid.scss b/apps/browser/src/popup/scss/grid.scss deleted file mode 100644 index 8cdb29bb52c..00000000000 --- a/apps/browser/src/popup/scss/grid.scss +++ /dev/null @@ -1,11 +0,0 @@ -.row { - display: flex; - margin: 0 -15px; - width: 100%; -} - -.col { - flex-basis: 0; - flex-grow: 1; - padding: 0 15px; -} diff --git a/apps/browser/src/popup/scss/misc.scss b/apps/browser/src/popup/scss/misc.scss deleted file mode 100644 index 006e1d35f6a..00000000000 --- a/apps/browser/src/popup/scss/misc.scss +++ /dev/null @@ -1,348 +0,0 @@ -@import "variables.scss"; - -small, -.small { - font-size: $font-size-small; -} - -.bg-primary { - @include themify($themes) { - background-color: themed("primaryColor") !important; - } -} - -.bg-success { - @include themify($themes) { - background-color: themed("successColor") !important; - } -} - -.bg-danger { - @include themify($themes) { - background-color: themed("dangerColor") !important; - } -} - -.bg-info { - @include themify($themes) { - background-color: themed("infoColor") !important; - } -} - -.bg-warning { - @include themify($themes) { - background-color: themed("warningColor") !important; - } -} - -.text-primary { - @include themify($themes) { - color: themed("primaryColor") !important; - } -} - -.text-success { - @include themify($themes) { - color: themed("successColor") !important; - } -} - -.text-muted { - @include themify($themes) { - color: themed("mutedColor") !important; - } -} - -.text-default { - @include themify($themes) { - color: themed("textColor") !important; - } -} - -.text-danger { - @include themify($themes) { - color: themed("dangerColor") !important; - } -} - -.text-info { - @include themify($themes) { - color: themed("infoColor") !important; - } -} - -.text-warning { - @include themify($themes) { - color: themed("warningColor") !important; - } -} - -.text-center { - text-align: center; -} - -.font-weight-semibold { - font-weight: 600; -} - -p.lead { - font-size: $font-size-large; - margin-bottom: 20px; - font-weight: normal; -} - -.flex-right { - margin-left: auto; -} - -.flex-bottom { - margin-top: auto; -} - -.no-margin { - margin: 0 !important; -} - -.display-block { - display: block !important; -} - -.monospaced { - font-family: $font-family-monospace; -} - -.show-whitespace { - white-space: pre-wrap; -} - -.img-responsive { - display: block; - max-width: 100%; - height: auto; -} - -.img-rounded { - border-radius: $border-radius; -} - -.select-index-top { - position: relative; - z-index: 100; -} - -.sr-only { - position: absolute !important; - width: 1px !important; - height: 1px !important; - padding: 0 !important; - margin: -1px !important; - overflow: hidden !important; - clip: rect(0, 0, 0, 0) !important; - border: 0 !important; -} - -:not(:focus) > .exists-only-on-parent-focus { - display: none; -} - -.password-wrapper { - overflow-wrap: break-word; - white-space: pre-wrap; - min-width: 0; -} - -.password-number { - @include themify($themes) { - color: themed("passwordNumberColor"); - } -} - -.password-special { - @include themify($themes) { - color: themed("passwordSpecialColor"); - } -} - -.password-character { - display: inline-flex; - flex-direction: column; - align-items: center; - width: 30px; - height: 36px; - font-weight: 600; - - &:nth-child(odd) { - @include themify($themes) { - background-color: themed("backgroundColor"); - } - } -} - -.password-count { - white-space: nowrap; - font-size: 8px; - - @include themify($themes) { - color: themed("passwordCountText") !important; - } -} - -#duo-frame { - background: url("../images/loading.svg") 0 0 no-repeat; - width: 100%; - height: 470px; - margin-bottom: -10px; - - iframe { - width: 100%; - height: 100%; - border: none; - } -} - -#web-authn-frame { - width: 100%; - height: 40px; - - iframe { - border: none; - height: 100%; - width: 100%; - } -} - -body.linux-webauthn { - width: 485px !important; - #web-authn-frame { - iframe { - width: 375px; - margin: 0 55px; - } - } -} - -app-root > #loading { - display: flex; - text-align: center; - justify-content: center; - align-items: center; - height: 100%; - width: 100%; - color: $text-muted; - - @include themify($themes) { - color: themed("mutedColor"); - } -} - -app-vault-icon, -.app-vault-icon { - display: flex; -} - -.logo-image { - margin: 0 auto; - width: 142px; - height: 21px; - background-size: 142px 21px; - background-repeat: no-repeat; - @include themify($themes) { - background-image: url("../images/logo-" + themed("logoSuffix") + "@2x.png"); - } - @media (min-width: 219px) { - width: 189px; - height: 28px; - background-size: 189px 28px; - } - @media (min-width: 314px) { - width: 284px; - height: 43px; - background-size: 284px 43px; - } -} - -[hidden] { - display: none !important; -} - -.draggable { - cursor: move; -} - -input[type="password"]::-ms-reveal { - display: none; -} - -.flex { - display: flex; - - &.flex-grow { - > * { - flex: 1; - } - } -} - -// Text selection styles -// Set explicit selection styles (assumes primary accent color has sufficient -// contrast against the background, so its inversion is also still readable) -// and suppress user selection for most elements (to make it more app-like) - -:not(bit-form-field input)::selection { - @include themify($themes) { - color: themed("backgroundColor"); - background-color: themed("primaryAccentColor"); - } -} - -h1, -h2, -h3, -label, -a, -button, -p, -img, -.box-header, -.box-footer, -.callout, -.row-label, -.modal-title, -.overlay-container { - user-select: none; - - &.user-select { - user-select: auto; - } -} - -/* tweak for inconsistent line heights in cipher view */ -.box-footer button, -.box-footer a { - line-height: 1; -} - -// Workaround for slow performance on external monitors on Chrome + MacOS -// See: https://bugs.chromium.org/p/chromium/issues/detail?id=971701#c64 -@keyframes redraw { - 0% { - opacity: 0.99; - } - 100% { - opacity: 1; - } -} -html.force_redraw { - animation: redraw 1s linear infinite; -} - -/* override for vault icon in browser (pre extension refresh) */ -app-vault-icon:not(app-vault-list-items-container app-vault-icon) > div { - display: flex; - justify-content: center; - align-items: center; - float: left; - height: 36px; - width: 34px; - margin-left: -5px; -} diff --git a/apps/browser/src/popup/scss/pages.scss b/apps/browser/src/popup/scss/pages.scss deleted file mode 100644 index 56c5f80c86c..00000000000 --- a/apps/browser/src/popup/scss/pages.scss +++ /dev/null @@ -1,144 +0,0 @@ -@import "variables.scss"; - -app-home { - position: fixed; - height: 100%; - width: 100%; - - .center-content { - margin-top: -50px; - height: calc(100% + 50px); - } - - img { - width: 284px; - margin: 0 auto; - } - - p.lead { - margin: 30px 0; - } - - .btn + .btn { - margin-top: 10px; - } - - button.settings-icon { - position: absolute; - top: 10px; - left: 10px; - - @include themify($themes) { - color: themed("mutedColor"); - } - - &:not(:hover):not(:focus) { - span { - clip: rect(0 0 0 0); - clip-path: inset(50%); - height: 1px; - overflow: hidden; - position: absolute; - white-space: nowrap; - width: 1px; - } - } - - &:hover, - &:focus { - text-decoration: none; - - @include themify($themes) { - color: themed("primaryColor"); - } - } - } -} - -body.body-sm, -body.body-xs { - app-home { - .center-content { - margin-top: 0; - height: 100%; - } - - p.lead { - margin: 15px 0; - } - } -} - -body.body-full { - app-home { - .center-content { - margin-top: -80px; - height: calc(100% + 80px); - } - } -} - -.createAccountLink { - padding: 30px 10px 0 10px; -} - -.remember-email-check { - padding-top: 18px; - padding-left: 10px; - padding-bottom: 18px; -} - -.login-buttons > button { - margin: 15px 0 15px 0; -} - -.useBrowserlink { - margin-left: 5px; - margin-top: 20px; - - span { - font-weight: 700; - font-size: $font-size-small; - } -} - -.fido2-browser-selector-dropdown { - @include themify($themes) { - background-color: themed("boxBackgroundColor"); - } - padding: 8px; - width: 100%; - box-shadow: - 0 2px 2px 0 rgba(0, 0, 0, 0.14), - 0 3px 1px -2px rgba(0, 0, 0, 0.12), - 0 1px 5px 0 rgba(0, 0, 0, 0.2); - border-radius: $border-radius; -} - -.fido2-browser-selector-dropdown-item { - @include themify($themes) { - color: themed("textColor") !important; - } - width: 100%; - text-align: left; - padding: 0px 15px 0px 5px; - margin-bottom: 5px; - border-radius: 3px; - border: 1px solid transparent; - transition: all 0.2s ease-in-out; - - &:hover { - @include themify($themes) { - background-color: themed("listItemBackgroundHoverColor") !important; - } - } - - &:last-child { - 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/apps/browser/src/popup/scss/plugins.scss b/apps/browser/src/popup/scss/plugins.scss deleted file mode 100644 index 591e8a1bd0c..00000000000 --- a/apps/browser/src/popup/scss/plugins.scss +++ /dev/null @@ -1,23 +0,0 @@ -@import "variables.scss"; - -@each $mfaType in $mfaTypes { - .mfaType#{$mfaType} { - content: url("../images/two-factor/" + $mfaType + ".png"); - max-width: 100px; - } -} - -.mfaType1 { - @include themify($themes) { - content: url("../images/two-factor/1" + themed("mfaLogoSuffix")); - max-width: 100px; - max-height: 45px; - } -} - -.mfaType7 { - @include themify($themes) { - content: url("../images/two-factor/7" + themed("mfaLogoSuffix")); - max-width: 100px; - } -} diff --git a/apps/browser/src/popup/scss/popup.scss b/apps/browser/src/popup/scss/popup.scss index b150de2c75d..59b4d472f23 100644 --- a/apps/browser/src/popup/scss/popup.scss +++ b/apps/browser/src/popup/scss/popup.scss @@ -1,13 +1,50 @@ @import "../../../../../libs/angular/src/scss/bwicons/styles/style.scss"; @import "variables.scss"; @import "../../../../../libs/angular/src/scss/icons.scss"; -@import "base.scss"; -@import "grid.scss"; -@import "box.scss"; -@import "buttons.scss"; -@import "misc.scss"; -@import "environment.scss"; -@import "pages.scss"; -@import "plugins.scss"; @import "@angular/cdk/overlay-prebuilt.css"; @import "../../../../../libs/components/src/multi-select/scss/bw.theme"; + +.cdk-virtual-scroll-content-wrapper { + width: 100%; +} + +// MFA Types for logo styling with no dark theme alternative +$mfaTypes: 0, 2, 3, 4, 6; + +@each $mfaType in $mfaTypes { + .mfaType#{$mfaType} { + content: url("../images/two-factor/" + $mfaType + ".png"); + max-width: 100px; + } +} + +.mfaType0 { + content: url("../images/two-factor/0.png"); + max-width: 100px; + max-height: 45px; +} + +.mfaType1 { + max-width: 100px; + max-height: 45px; + + &:is(.theme_light *) { + content: url("../images/two-factor/1.png"); + } + + &:is(.theme_dark *) { + content: url("../images/two-factor/1-w.png"); + } +} + +.mfaType7 { + max-width: 100px; + + &:is(.theme_light *) { + content: url("../images/two-factor/7.png"); + } + + &:is(.theme_dark *) { + content: url("../images/two-factor/7-w.png"); + } +} diff --git a/apps/browser/src/popup/scss/tailwind.css b/apps/browser/src/popup/scss/tailwind.css index 54139990356..f58950cc86a 100644 --- a/apps/browser/src/popup/scss/tailwind.css +++ b/apps/browser/src/popup/scss/tailwind.css @@ -1,4 +1,104 @@ -@import "../../../../../libs/components/src/tw-theme.css"; +@import "../../../../../libs/components/src/tw-theme-preflight.css"; + +@layer base { + html { + overflow: hidden; + min-height: 600px; + height: 100%; + + &.body-sm { + min-height: 500px; + } + + &.body-xs { + min-height: 400px; + } + + &.body-xxs { + min-height: 300px; + } + + &.body-3xs { + min-height: 240px; + } + + &.body-full { + min-height: unset; + width: 100%; + height: 100%; + + & body { + width: 100%; + } + } + } + + html.browser_safari { + &.safari_height_fix { + body { + height: 360px !important; + + &.body-xs { + height: 300px !important; + } + + &.body-full { + height: 100% !important; + } + } + } + + app-root { + border-width: 1px; + border-style: solid; + border-color: #000000; + } + + &.theme_light app-root { + border-color: #777777; + } + } + + body { + width: 380px; + height: 100%; + position: relative; + min-height: inherit; + overflow: hidden; + @apply tw-bg-background-alt; + } + + /** + * Workaround for slow performance on external monitors on Chrome + MacOS + * See: https://bugs.chromium.org/p/chromium/issues/detail?id=971701#c64 + */ + @keyframes redraw { + 0% { + opacity: 0.99; + } + 100% { + opacity: 1; + } + } + html.force_redraw { + animation: redraw 1s linear infinite; + } + + /** + * Text selection style: + * suppress user selection for most elements (to make it more app-like) + */ + h1, + h2, + h3, + label, + a, + button, + p, + img { + user-select: none; + } +} @layer components { /** Safari Support */ @@ -19,4 +119,59 @@ html:not(.browser_safari) .tw-styled-scrollbar { scrollbar-color: rgb(var(--color-secondary-500)) rgb(var(--color-background-alt)); } + + #duo-frame { + background: url("../images/loading.svg") 0 0 no-repeat; + width: 100%; + height: 470px; + margin-bottom: -10px; + + iframe { + width: 100%; + height: 100%; + border: none; + } + } + + #web-authn-frame { + width: 100%; + height: 40px; + + iframe { + border: none; + height: 100%; + width: 100%; + } + } + + body.linux-webauthn { + width: 485px !important; + #web-authn-frame { + iframe { + width: 375px; + margin: 0 55px; + } + } + } + + app-root > #loading { + display: flex; + text-align: center; + justify-content: center; + align-items: center; + height: 100%; + width: 100%; + + @apply tw-text-muted; + } + + /** + * Text selection style: + * Set explicit selection styles (assumes primary accent color has sufficient + * contrast against the background, so its inversion is also still readable) + */ + :not(bit-form-field input)::selection { + @apply tw-text-contrast; + @apply tw-bg-primary-700; + } } diff --git a/apps/browser/src/popup/scss/variables.scss b/apps/browser/src/popup/scss/variables.scss index e57e98fd0cc..02a10521bca 100644 --- a/apps/browser/src/popup/scss/variables.scss +++ b/apps/browser/src/popup/scss/variables.scss @@ -1,178 +1,42 @@ -$dark-icon-themes: "theme_dark"; +/** + * DEPRECATED: DO NOT MODIFY OR USE! + */ + +$dark-icon-themes: "theme_dark"; $font-family-sans-serif: Inter, "Helvetica Neue", Helvetica, Arial, sans-serif; $font-family-monospace: Menlo, Monaco, Consolas, "Courier New", monospace; -$font-size-base: 16px; -$font-size-large: 18px; -$font-size-xlarge: 22px; -$font-size-xxlarge: 28px; -$font-size-small: 12px; $text-color: #000000; -$border-color: #f0f0f0; $border-color-dark: #ddd; -$list-item-hover: #fbfbfb; -$list-icon-color: #767679; -$disabled-box-opacity: 1; -$border-radius: 6px; -$line-height-base: 1.42857143; -$icon-hover-color: lighten($text-color, 50%); - -$mfaTypes: 0, 2, 3, 4, 6; - -$gray: #555; -$gray-light: #777; -$text-muted: $gray-light; - $brand-primary: #175ddc; -$brand-danger: #c83522; $brand-success: #017e45; -$brand-info: #555555; -$brand-warning: #8b6609; -$brand-primary-accent: #1252a3; - $background-color: #f0f0f0; - -$box-background-color: white; -$box-background-hover-color: $list-item-hover; -$box-border-color: $border-color; -$border-color-alt: #c3c5c7; - -$button-border-color: darken($border-color-dark, 12%); -$button-background-color: white; -$button-color: lighten($text-color, 40%); $button-color-primary: darken($brand-primary, 8%); -$button-color-danger: darken($brand-danger, 10%); - -$code-color: #c01176; -$code-color-dark: #f08dc7; $themes: ( light: ( textColor: $text-color, - hoverColorTransparent: rgba($text-color, 0.15), borderColor: $border-color-dark, backgroundColor: $background-color, - borderColorAlt: $border-color-alt, - backgroundColorAlt: #ffffff, - scrollbarColor: rgba(100, 100, 100, 0.2), - scrollbarHoverColor: rgba(100, 100, 100, 0.4), - boxBackgroundColor: $box-background-color, - boxBackgroundHoverColor: $box-background-hover-color, - boxBorderColor: $box-border-color, - tabBackgroundColor: #ffffff, - tabBackgroundHoverColor: $list-item-hover, - headerColor: #ffffff, - headerBackgroundColor: $brand-primary, - headerBackgroundHoverColor: rgba(255, 255, 255, 0.1), - headerBorderColor: $brand-primary, - headerInputBackgroundColor: darken($brand-primary, 8%), - headerInputBackgroundFocusColor: darken($brand-primary, 10%), - headerInputColor: #ffffff, - headerInputPlaceholderColor: lighten($brand-primary, 35%), - listItemBackgroundHoverColor: $list-item-hover, - disabledIconColor: $list-icon-color, - disabledBoxOpacity: $disabled-box-opacity, - headingColor: $gray-light, - labelColor: $gray-light, - mutedColor: $text-muted, - totpStrokeColor: $brand-primary, - boxRowButtonColor: $brand-primary, - boxRowButtonHoverColor: darken($brand-primary, 10%), inputBorderColor: darken($border-color-dark, 7%), inputBackgroundColor: #ffffff, - inputPlaceholderColor: lighten($gray-light, 35%), - buttonBackgroundColor: $button-background-color, - buttonBorderColor: $button-border-color, - buttonColor: $button-color, buttonPrimaryColor: $button-color-primary, - buttonDangerColor: $button-color-danger, primaryColor: $brand-primary, - primaryAccentColor: $brand-primary-accent, - dangerColor: $brand-danger, successColor: $brand-success, - infoColor: $brand-info, - warningColor: $brand-warning, - logoSuffix: "dark", - mfaLogoSuffix: ".png", passwordNumberColor: #007fde, passwordSpecialColor: #c40800, - passwordCountText: #212529, - calloutBorderColor: $border-color-dark, - calloutBackgroundColor: $box-background-color, - toastTextColor: #ffffff, - svgSuffix: "-light.svg", - transparentColor: rgba(0, 0, 0, 0), - dateInputColorScheme: light, - // https://stackoverflow.com/a/53336754 - webkitCalendarPickerFilter: invert(46%) sepia(69%) saturate(6397%) hue-rotate(211deg) - brightness(85%) contrast(103%), - // light has no hover so use same color - webkitCalendarPickerHoverFilter: invert(46%) sepia(69%) saturate(6397%) hue-rotate(211deg) - brightness(85%) contrast(103%), - codeColor: $code-color, ), dark: ( textColor: #ffffff, - hoverColorTransparent: rgba($text-color, 0.15), borderColor: #161c26, backgroundColor: #161c26, - borderColorAlt: #6e788a, - backgroundColorAlt: #2f343d, - scrollbarColor: #6e788a, - scrollbarHoverColor: #8d94a5, - boxBackgroundColor: #2f343d, - boxBackgroundHoverColor: #3c424e, - boxBorderColor: #4c525f, - tabBackgroundColor: #2f343d, - tabBackgroundHoverColor: #3c424e, - headerColor: #ffffff, - headerBackgroundColor: #2f343d, - headerBackgroundHoverColor: #3c424e, - headerBorderColor: #161c26, - headerInputBackgroundColor: #3c424e, - headerInputBackgroundFocusColor: #4c525f, - headerInputColor: #ffffff, - headerInputPlaceholderColor: #bac0ce, - listItemBackgroundHoverColor: #3c424e, - disabledIconColor: #bac0ce, - disabledBoxOpacity: 0.5, - headingColor: #bac0ce, - labelColor: #bac0ce, - mutedColor: #bac0ce, - totpStrokeColor: #4c525f, - boxRowButtonColor: #bac0ce, - boxRowButtonHoverColor: #ffffff, inputBorderColor: #4c525f, inputBackgroundColor: #2f343d, - inputPlaceholderColor: #bac0ce, - buttonBackgroundColor: #3c424e, - buttonBorderColor: #4c525f, - buttonColor: #bac0ce, buttonPrimaryColor: #6f9df1, - buttonDangerColor: #ff8d85, primaryColor: #6f9df1, - primaryAccentColor: #6f9df1, - dangerColor: #ff8d85, successColor: #52e07c, - infoColor: #a4b0c6, - warningColor: #ffeb66, - logoSuffix: "white", - mfaLogoSuffix: "-w.png", passwordNumberColor: #6f9df1, passwordSpecialColor: #ff8d85, - passwordCountText: #ffffff, - calloutBorderColor: #4c525f, - calloutBackgroundColor: #3c424e, - toastTextColor: #1f242e, - svgSuffix: "-dark.svg", - transparentColor: rgba(0, 0, 0, 0), - dateInputColorScheme: dark, - // https://stackoverflow.com/a/53336754 - must prepend brightness(0) saturate(100%) to dark themed date inputs - webkitCalendarPickerFilter: brightness(0) saturate(100%) invert(86%) sepia(19%) saturate(152%) - hue-rotate(184deg) brightness(87%) contrast(93%), - webkitCalendarPickerHoverFilter: brightness(0) saturate(100%) invert(100%) sepia(0%) - saturate(0%) hue-rotate(93deg) brightness(103%) contrast(103%), - codeColor: $code-color-dark, ), ); diff --git a/apps/browser/tailwind.config.js b/apps/browser/tailwind.config.js index 134001bbf13..faaa7fa4128 100644 --- a/apps/browser/tailwind.config.js +++ b/apps/browser/tailwind.config.js @@ -12,5 +12,6 @@ config.content = [ "../../libs/vault/src/**/*.{html,ts}", "../../libs/pricing/src/**/*.{html,ts}", ]; +config.corePlugins.preflight = true; module.exports = config; From d95dd709b1cd6cb7d6cac1d177cf3d2b48947092 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 9 Dec 2025 14:44:13 -0600 Subject: [PATCH 016/188] [deps]: Update Rust crate thiserror to v2.0.17 (#17574) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- apps/desktop/desktop_native/Cargo.lock | 16 ++++++++-------- apps/desktop/desktop_native/Cargo.toml | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/apps/desktop/desktop_native/Cargo.lock b/apps/desktop/desktop_native/Cargo.lock index 7b763e274b4..8cf64c9ea76 100644 --- a/apps/desktop/desktop_native/Cargo.lock +++ b/apps/desktop/desktop_native/Cargo.lock @@ -877,7 +877,7 @@ dependencies = [ "sha2", "ssh-key", "sysinfo", - "thiserror 2.0.12", + "thiserror 2.0.17", "tokio", "tokio-util", "tracing", @@ -2648,7 +2648,7 @@ checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" dependencies = [ "getrandom 0.2.16", "libredox", - "thiserror 2.0.12", + "thiserror 2.0.17", ] [[package]] @@ -2858,7 +2858,7 @@ dependencies = [ "libc", "rustix 1.0.7", "rustix-linux-procfs", - "thiserror 2.0.12", + "thiserror 2.0.17", "windows", ] @@ -3227,11 +3227,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.12" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" dependencies = [ - "thiserror-impl 2.0.12", + "thiserror-impl 2.0.17", ] [[package]] @@ -3247,9 +3247,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.12" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", diff --git a/apps/desktop/desktop_native/Cargo.toml b/apps/desktop/desktop_native/Cargo.toml index c1a3e98a0de..d85f35141b8 100644 --- a/apps/desktop/desktop_native/Cargo.toml +++ b/apps/desktop/desktop_native/Cargo.toml @@ -61,7 +61,7 @@ sha2 = "=0.10.8" ssh-encoding = "=0.2.0" ssh-key = { version = "=0.6.7", default-features = false } sysinfo = "=0.37.2" -thiserror = "=2.0.12" +thiserror = "=2.0.17" tokio = "=1.45.0" tokio-util = "=0.7.13" tracing = "=0.1.41" From 717cf93cc8526e6a8c1aacd5ab7a7669f6fb3856 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 9 Dec 2025 20:53:30 +0000 Subject: [PATCH 017/188] [deps]: Update Rust crate cc to v1.2.49 (#17893) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- apps/desktop/desktop_native/Cargo.lock | 4 ++-- apps/desktop/desktop_native/objc/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/desktop/desktop_native/Cargo.lock b/apps/desktop/desktop_native/Cargo.lock index 8cf64c9ea76..8ac04eb9a65 100644 --- a/apps/desktop/desktop_native/Cargo.lock +++ b/apps/desktop/desktop_native/Cargo.lock @@ -556,9 +556,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.48" +version = "1.2.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c481bdbf0ed3b892f6f806287d72acd515b352a4ec27a208489b8c1bc839633a" +checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215" dependencies = [ "find-msvc-tools", "shlex", diff --git a/apps/desktop/desktop_native/objc/Cargo.toml b/apps/desktop/desktop_native/objc/Cargo.toml index 5d7174894e7..dd808537c28 100644 --- a/apps/desktop/desktop_native/objc/Cargo.toml +++ b/apps/desktop/desktop_native/objc/Cargo.toml @@ -14,7 +14,7 @@ tokio = { workspace = true } tracing = { workspace = true } [target.'cfg(target_os = "macos")'.build-dependencies] -cc = "=1.2.48" +cc = "=1.2.49" glob = "=0.3.3" [lints] From 22338632be9e6628b166b962f4a776ed0d29328f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 9 Dec 2025 20:57:23 +0000 Subject: [PATCH 018/188] [deps] Platform: Update Rust crate zbus to v5.12.0 (#17035) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- apps/desktop/desktop_native/Cargo.lock | 89 ++++++++++++++++++++++++-- apps/desktop/desktop_native/Cargo.toml | 2 +- 2 files changed, 85 insertions(+), 6 deletions(-) diff --git a/apps/desktop/desktop_native/Cargo.lock b/apps/desktop/desktop_native/Cargo.lock index 8ac04eb9a65..cf946f7f204 100644 --- a/apps/desktop/desktop_native/Cargo.lock +++ b/apps/desktop/desktop_native/Cargo.lock @@ -501,6 +501,12 @@ dependencies = [ "cipher", ] +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + [[package]] name = "byteorder" version = "1.5.0" @@ -1663,6 +1669,16 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +[[package]] +name = "js-sys" +version = "0.3.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -2786,6 +2802,12 @@ dependencies = [ "rustix 1.0.7", ] +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + [[package]] name = "ryu" version = "1.0.20" @@ -3668,6 +3690,17 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "uuid" +version = "1.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" +dependencies = [ + "js-sys", + "serde", + "wasm-bindgen", +] + [[package]] name = "valuable" version = "0.1.1" @@ -3733,6 +3766,51 @@ dependencies = [ "wit-bindgen-rt", ] +[[package]] +name = "wasm-bindgen" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" +dependencies = [ + "unicode-ident", +] + [[package]] name = "wayland-backend" version = "0.3.10" @@ -4369,9 +4447,9 @@ dependencies = [ [[package]] name = "zbus" -version = "5.11.0" +version = "5.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d07e46d035fb8e375b2ce63ba4e4ff90a7f73cf2ffb0138b29e1158d2eaadf7" +checksum = "b622b18155f7a93d1cd2dc8c01d2d6a44e08fb9ebb7b3f9e6ed101488bad6c91" dependencies = [ "async-broadcast", "async-executor", @@ -4394,7 +4472,8 @@ dependencies = [ "tokio", "tracing", "uds_windows", - "windows-sys 0.60.2", + "uuid", + "windows-sys 0.61.2", "winnow", "zbus_macros", "zbus_names", @@ -4403,9 +4482,9 @@ dependencies = [ [[package]] name = "zbus_macros" -version = "5.11.0" +version = "5.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57e797a9c847ed3ccc5b6254e8bcce056494b375b511b3d6edcec0aeb4defaca" +checksum = "1cdb94821ca8a87ca9c298b5d1cbd80e2a8b67115d99f6e4551ac49e42b6a314" dependencies = [ "proc-macro-crate", "proc-macro2", diff --git a/apps/desktop/desktop_native/Cargo.toml b/apps/desktop/desktop_native/Cargo.toml index d85f35141b8..59df7ba57fb 100644 --- a/apps/desktop/desktop_native/Cargo.toml +++ b/apps/desktop/desktop_native/Cargo.toml @@ -77,7 +77,7 @@ windows = "=0.61.1" windows-core = "=0.61.0" windows-future = "=0.2.0" windows-registry = "=0.6.1" -zbus = "=5.11.0" +zbus = "=5.12.0" zbus_polkit = "=5.0.0" zeroizing-alloc = "=0.1.0" From 6dba3ac3772ee7c718d5b56cbe94e4249aa29ec1 Mon Sep 17 00:00:00 2001 From: SmithThe4th Date: Tue, 9 Dec 2025 17:28:04 -0500 Subject: [PATCH 019/188] [PM-27663] Create VaultItemTransferModalComponent and confirmation dialogs (#17883) * Created item transfer dialogs * Added empty line --- apps/browser/src/_locales/en/messages.json | 48 ++++++++++++++ apps/desktop/src/locales/en/messages.json | 48 ++++++++++++++ apps/web/src/locales/en/messages.json | 48 ++++++++++++++ .../components/vault-items-transfer/index.ts | 13 ++++ .../leave-confirmation-dialog.component.html | 33 ++++++++++ .../leave-confirmation-dialog.component.ts | 64 +++++++++++++++++++ .../transfer-items-dialog.component.html | 22 +++++++ .../transfer-items-dialog.component.ts | 64 +++++++++++++++++++ libs/vault/src/index.ts | 1 + 9 files changed, 341 insertions(+) create mode 100644 libs/vault/src/components/vault-items-transfer/index.ts create mode 100644 libs/vault/src/components/vault-items-transfer/leave-confirmation-dialog.component.html create mode 100644 libs/vault/src/components/vault-items-transfer/leave-confirmation-dialog.component.ts create mode 100644 libs/vault/src/components/vault-items-transfer/transfer-items-dialog.component.html create mode 100644 libs/vault/src/components/vault-items-transfer/transfer-items-dialog.component.ts diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index a5c204ffc99..2d229092787 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -5937,5 +5937,53 @@ }, "upgrade": { "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/desktop/src/locales/en/messages.json b/apps/desktop/src/locales/en/messages.json index 4c3833e28c3..f6a679d9c7c 100644 --- a/apps/desktop/src/locales/en/messages.json +++ b/apps/desktop/src/locales/en/messages.json @@ -4383,5 +4383,53 @@ }, "upgrade": { "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 4be70b102d1..bbbf548b8e1 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -12287,5 +12287,53 @@ }, "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { "message": "Set an unlock method to change your timeout action" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/libs/vault/src/components/vault-items-transfer/index.ts b/libs/vault/src/components/vault-items-transfer/index.ts new file mode 100644 index 00000000000..f2ffb9f9c22 --- /dev/null +++ b/libs/vault/src/components/vault-items-transfer/index.ts @@ -0,0 +1,13 @@ +export { + TransferItemsDialogComponent, + TransferItemsDialogParams, + TransferItemsDialogResult, + TransferItemsDialogResultType, +} from "./transfer-items-dialog.component"; + +export { + LeaveConfirmationDialogComponent, + LeaveConfirmationDialogParams, + LeaveConfirmationDialogResult, + LeaveConfirmationDialogResultType, +} from "./leave-confirmation-dialog.component"; diff --git a/libs/vault/src/components/vault-items-transfer/leave-confirmation-dialog.component.html b/libs/vault/src/components/vault-items-transfer/leave-confirmation-dialog.component.html new file mode 100644 index 00000000000..f0d644fecff --- /dev/null +++ b/libs/vault/src/components/vault-items-transfer/leave-confirmation-dialog.component.html @@ -0,0 +1,33 @@ + + + + {{ "leaveConfirmationDialogTitle" | i18n }} + + +

+ {{ "leaveConfirmationDialogContentOne" | i18n }} +

+

+ {{ "leaveConfirmationDialogContentTwo" | i18n }} +

+
+ + + + + + + + {{ "howToManageMyVault" | i18n }} + + + +
diff --git a/libs/vault/src/components/vault-items-transfer/leave-confirmation-dialog.component.ts b/libs/vault/src/components/vault-items-transfer/leave-confirmation-dialog.component.ts new file mode 100644 index 00000000000..bd32a1ea6dd --- /dev/null +++ b/libs/vault/src/components/vault-items-transfer/leave-confirmation-dialog.component.ts @@ -0,0 +1,64 @@ +import { ChangeDetectionStrategy, Component, inject } from "@angular/core"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { UnionOfValues } from "@bitwarden/common/vault/types/union-of-values"; +import { + DIALOG_DATA, + DialogConfig, + DialogRef, + DialogService, + ButtonModule, + DialogModule, + LinkModule, + TypographyModule, +} from "@bitwarden/components"; + +export interface LeaveConfirmationDialogParams { + organizationName: string; +} + +export const LeaveConfirmationDialogResult = Object.freeze({ + /** + * User confirmed they want to leave the organization. + */ + Confirmed: "confirmed", + /** + * User chose to go back instead of leaving the organization. + */ + Back: "back", +} as const); + +export type LeaveConfirmationDialogResultType = UnionOfValues; + +@Component({ + templateUrl: "./leave-confirmation-dialog.component.html", + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [ButtonModule, DialogModule, LinkModule, TypographyModule, JslibModule], +}) +export class LeaveConfirmationDialogComponent { + private readonly params = inject(DIALOG_DATA); + private readonly dialogRef = inject(DialogRef); + private readonly platformUtilsService = inject(PlatformUtilsService); + + protected readonly organizationName = this.params.organizationName; + + protected confirmLeave() { + this.dialogRef.close(LeaveConfirmationDialogResult.Confirmed); + } + + protected goBack() { + this.dialogRef.close(LeaveConfirmationDialogResult.Back); + } + + protected openLearnMore(e: Event) { + e.preventDefault(); + this.platformUtilsService.launchUri("https://bitwarden.com/help/transfer-ownership/"); + } + + static open(dialogService: DialogService, config: DialogConfig) { + return dialogService.open(LeaveConfirmationDialogComponent, { + ...config, + }); + } +} diff --git a/libs/vault/src/components/vault-items-transfer/transfer-items-dialog.component.html b/libs/vault/src/components/vault-items-transfer/transfer-items-dialog.component.html new file mode 100644 index 00000000000..0b77d4ba7d8 --- /dev/null +++ b/libs/vault/src/components/vault-items-transfer/transfer-items-dialog.component.html @@ -0,0 +1,22 @@ + + {{ "transferItemsToOrganizationTitle" | i18n: organizationName }} + + + {{ "transferItemsToOrganizationContent" | i18n: organizationName }} + + + + + + + + + {{ "whyAmISeeingThis" | i18n }} + + + + diff --git a/libs/vault/src/components/vault-items-transfer/transfer-items-dialog.component.ts b/libs/vault/src/components/vault-items-transfer/transfer-items-dialog.component.ts new file mode 100644 index 00000000000..f28ad2ab3ec --- /dev/null +++ b/libs/vault/src/components/vault-items-transfer/transfer-items-dialog.component.ts @@ -0,0 +1,64 @@ +import { ChangeDetectionStrategy, Component, inject } from "@angular/core"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { UnionOfValues } from "@bitwarden/common/vault/types/union-of-values"; +import { + DIALOG_DATA, + DialogConfig, + DialogRef, + DialogService, + ButtonModule, + DialogModule, + LinkModule, + TypographyModule, +} from "@bitwarden/components"; + +export interface TransferItemsDialogParams { + organizationName: string; +} + +export const TransferItemsDialogResult = Object.freeze({ + /** + * User accepted the transfer of items. + */ + Accepted: "accepted", + /** + * User declined the transfer of items. + */ + Declined: "declined", +} as const); + +export type TransferItemsDialogResultType = UnionOfValues; + +@Component({ + templateUrl: "./transfer-items-dialog.component.html", + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [ButtonModule, DialogModule, LinkModule, TypographyModule, JslibModule], +}) +export class TransferItemsDialogComponent { + private readonly params = inject(DIALOG_DATA); + private readonly dialogRef = inject(DialogRef); + private readonly platformUtilsService = inject(PlatformUtilsService); + + protected readonly organizationName = this.params.organizationName; + + protected acceptTransfer() { + this.dialogRef.close(TransferItemsDialogResult.Accepted); + } + + protected decline() { + this.dialogRef.close(TransferItemsDialogResult.Declined); + } + + protected openLearnMore(e: Event) { + e.preventDefault(); + this.platformUtilsService.launchUri("https://bitwarden.com/help/transfer-ownership/"); + } + + static open(dialogService: DialogService, config: DialogConfig) { + return dialogService.open(TransferItemsDialogComponent, { + ...config, + }); + } +} diff --git a/libs/vault/src/index.ts b/libs/vault/src/index.ts index 93a72ba14e0..be0daad3637 100644 --- a/libs/vault/src/index.ts +++ b/libs/vault/src/index.ts @@ -29,6 +29,7 @@ export * from "./components/add-edit-folder-dialog/add-edit-folder-dialog.compon export * from "./components/carousel"; export * from "./components/new-cipher-menu/new-cipher-menu.component"; export * from "./components/permit-cipher-details-popover/permit-cipher-details-popover.component"; +export * from "./components/vault-items-transfer"; export { DefaultSshImportPromptService } from "./services/default-ssh-import-prompt.service"; export { SshImportPromptService } from "./services/ssh-import-prompt.service"; From f161a8c454afdaf4d84679fc683157032236fdba Mon Sep 17 00:00:00 2001 From: Shane Melton Date: Tue, 9 Dec 2025 15:14:40 -0800 Subject: [PATCH 020/188] [PM-27662] Introduce vault item transfer service (#17876) * [PM-27662] Add revision date to policy response * [PM-27662] Introduce vault item transfer service * [PM-27662] Add feature flag check * [PM-27662] Add tests * [PM-27662] Add basic implementation to Web vault * [PM-27662] Remove redundant for loop * [PM-27662] Remove unnecessary distinctUntilChanged * [PM-27662] Avoid subscribing to userMigrationInfo$ if feature flag disabled * [PM-27662] Make UserMigrationInfo type more strict * [PM-27662] Typo * [PM-27662] Fix missing i18n * [PM-27662] Fix tests * [PM-27662] Fix tests/types related to policy changes * [PM-27662] Use getById operator --- apps/browser/src/_locales/en/messages.json | 3 + apps/desktop/src/locales/en/messages.json | 3 + .../vault/individual-vault/vault.component.ts | 9 +- apps/web/src/locales/en/messages.json | 3 + .../admin-console/models/data/policy.data.ts | 2 + .../src/admin-console/models/domain/policy.ts | 3 + .../models/response/policy.response.ts | 2 + .../policy/default-policy.service.spec.ts | 40 + libs/common/src/enums/feature-flag.enum.ts | 2 + .../available-algorithms-policy.spec.ts | 7 + .../passphrase-least-privilege.spec.ts | 1 + .../policies/password-least-privilege.spec.ts | 1 + .../generator-profile-provider.spec.ts | 1 + ...fault-generator-navigation.service.spec.ts | 1 + .../src/generator-navigation-policy.spec.ts | 1 + .../vault-items-transfer.service.ts | 59 ++ libs/vault/src/index.ts | 2 + ...fault-vault-items-transfer.service.spec.ts | 721 ++++++++++++++++++ .../default-vault-items-transfer.service.ts | 231 ++++++ 19 files changed, 1090 insertions(+), 2 deletions(-) create mode 100644 libs/vault/src/abstractions/vault-items-transfer.service.ts create mode 100644 libs/vault/src/services/default-vault-items-transfer.service.spec.ts create mode 100644 libs/vault/src/services/default-vault-items-transfer.service.ts diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index 2d229092787..a90fbcbf332 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -1475,6 +1475,9 @@ "selectFile": { "message": "Select a file" }, + "itemsTransferred": { + "message": "Items transferred" + }, "maxFileSize": { "message": "Maximum file size is 500 MB." }, diff --git a/apps/desktop/src/locales/en/messages.json b/apps/desktop/src/locales/en/messages.json index f6a679d9c7c..7a3abe528e8 100644 --- a/apps/desktop/src/locales/en/messages.json +++ b/apps/desktop/src/locales/en/messages.json @@ -708,6 +708,9 @@ "addAttachment": { "message": "Add attachment" }, + "itemsTransferred": { + "message": "Items transferred" + }, "fixEncryption": { "message": "Fix encryption" }, 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 b0685a028df..78a8889bc8f 100644 --- a/apps/web/src/app/vault/individual-vault/vault.component.ts +++ b/apps/web/src/app/vault/individual-vault/vault.component.ts @@ -84,7 +84,7 @@ import { CipherViewLikeUtils, } from "@bitwarden/common/vault/utils/cipher-view-like-utils"; import { filterOutNullish } from "@bitwarden/common/vault/utils/observable-utilities"; -import { DialogRef, DialogService, ToastService, BannerComponent } from "@bitwarden/components"; +import { DialogRef, DialogService, ToastService } from "@bitwarden/components"; import { CipherListView } from "@bitwarden/sdk-internal"; import { AddEditFolderDialogComponent, @@ -97,6 +97,8 @@ import { DecryptionFailureDialogComponent, DefaultCipherFormConfigService, PasswordRepromptService, + VaultItemsTransferService, + DefaultVaultItemsTransferService, } from "@bitwarden/vault"; import { UnifiedUpgradePromptService } from "@bitwarden/web-vault/app/billing/individual/upgrade/services"; import { OrganizationWarningsModule } from "@bitwarden/web-vault/app/billing/organizations/warnings/organization-warnings.module"; @@ -177,12 +179,12 @@ type EmptyStateMap = Record; VaultItemsModule, SharedModule, OrganizationWarningsModule, - BannerComponent, ], providers: [ RoutedVaultFilterService, RoutedVaultFilterBridgeService, DefaultCipherFormConfigService, + { provide: VaultItemsTransferService, useClass: DefaultVaultItemsTransferService }, ], }) export class VaultComponent implements OnInit, OnDestroy { @@ -349,6 +351,7 @@ export class VaultComponent implements OnInit, OnDestr private premiumUpgradePromptService: PremiumUpgradePromptService, private autoConfirmService: AutomaticUserConfirmationService, private configService: ConfigService, + private vaultItemTransferService: VaultItemsTransferService, ) {} async ngOnInit() { @@ -644,6 +647,8 @@ export class VaultComponent implements OnInit, OnDestr void this.unifiedUpgradePromptService.displayUpgradePromptConditionally(); this.setupAutoConfirm(); + + void this.vaultItemTransferService.enforceOrganizationDataOwnership(activeUserId); } ngOnDestroy() { diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index bbbf548b8e1..a755e4de556 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -5185,6 +5185,9 @@ "oldAttachmentsNeedFixDesc": { "message": "There are old file attachments in your vault that need to be fixed before you can rotate your account's encryption key." }, + "itemsTransferred": { + "message": "Items transferred" + }, "yourAccountsFingerprint": { "message": "Your account's fingerprint phrase", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." diff --git a/libs/common/src/admin-console/models/data/policy.data.ts b/libs/common/src/admin-console/models/data/policy.data.ts index a8628e2f1ab..639fda1fa92 100644 --- a/libs/common/src/admin-console/models/data/policy.data.ts +++ b/libs/common/src/admin-console/models/data/policy.data.ts @@ -11,6 +11,7 @@ export class PolicyData { type: PolicyType; data: Record; enabled: boolean; + revisionDate: string; constructor(response?: PolicyResponse) { if (response == null) { @@ -22,6 +23,7 @@ export class PolicyData { this.type = response.type; this.data = response.data; this.enabled = response.enabled; + this.revisionDate = response.revisionDate; } static fromPolicy(policy: Policy): PolicyData { diff --git a/libs/common/src/admin-console/models/domain/policy.ts b/libs/common/src/admin-console/models/domain/policy.ts index eb67c4412e9..6b2d587a262 100644 --- a/libs/common/src/admin-console/models/domain/policy.ts +++ b/libs/common/src/admin-console/models/domain/policy.ts @@ -19,6 +19,8 @@ export class Policy extends Domain { */ enabled: boolean; + revisionDate: Date; + constructor(obj?: PolicyData) { super(); if (obj == null) { @@ -30,6 +32,7 @@ export class Policy extends Domain { this.type = obj.type; this.data = obj.data; this.enabled = obj.enabled; + this.revisionDate = new Date(obj.revisionDate); } static fromResponse(response: PolicyResponse): Policy { diff --git a/libs/common/src/admin-console/models/response/policy.response.ts b/libs/common/src/admin-console/models/response/policy.response.ts index 0544cd996f4..7cca63a19d3 100644 --- a/libs/common/src/admin-console/models/response/policy.response.ts +++ b/libs/common/src/admin-console/models/response/policy.response.ts @@ -9,6 +9,7 @@ export class PolicyResponse extends BaseResponse { data: any; enabled: boolean; canToggleState: boolean; + revisionDate: string; constructor(response: any) { super(response); @@ -18,5 +19,6 @@ export class PolicyResponse extends BaseResponse { this.data = this.getResponseProperty("Data"); this.enabled = this.getResponseProperty("Enabled"); this.canToggleState = this.getResponseProperty("CanToggleState") ?? true; + this.revisionDate = this.getResponseProperty("RevisionDate"); } } diff --git a/libs/common/src/admin-console/services/policy/default-policy.service.spec.ts b/libs/common/src/admin-console/services/policy/default-policy.service.spec.ts index 4b59683ec0a..2ff649e6533 100644 --- a/libs/common/src/admin-console/services/policy/default-policy.service.spec.ts +++ b/libs/common/src/admin-console/services/policy/default-policy.service.spec.ts @@ -83,12 +83,15 @@ describe("PolicyService", () => { type: PolicyType.MaximumVaultTimeout, enabled: true, data: { minutes: 14 }, + revisionDate: expect.any(Date), }, { id: "99", organizationId: "test-organization", type: PolicyType.DisableSend, enabled: true, + data: undefined, + revisionDate: expect.any(Date), }, ]); }); @@ -113,6 +116,8 @@ describe("PolicyService", () => { organizationId: "test-organization", type: PolicyType.DisableSend, enabled: true, + data: undefined, + revisionDate: expect.any(Date), }, ]); }); @@ -242,6 +247,8 @@ describe("PolicyService", () => { organizationId: "org1", type: PolicyType.DisablePersonalVaultExport, enabled: true, + data: undefined, + revisionDate: expect.any(Date), }); }); @@ -331,24 +338,32 @@ describe("PolicyService", () => { organizationId: "org4", type: PolicyType.DisablePersonalVaultExport, enabled: true, + data: undefined, + revisionDate: expect.any(Date), }, { id: "policy2", organizationId: "org1", type: PolicyType.ActivateAutofill, enabled: true, + data: undefined, + revisionDate: expect.any(Date), }, { id: "policy3", organizationId: "org5", type: PolicyType.DisablePersonalVaultExport, enabled: true, + data: undefined, + revisionDate: expect.any(Date), }, { id: "policy4", organizationId: "org1", type: PolicyType.DisablePersonalVaultExport, enabled: true, + data: undefined, + revisionDate: expect.any(Date), }, ]); }); @@ -371,24 +386,32 @@ describe("PolicyService", () => { organizationId: "org4", type: PolicyType.DisablePersonalVaultExport, enabled: true, + data: undefined, + revisionDate: expect.any(Date), }, { id: "policy2", organizationId: "org1", type: PolicyType.ActivateAutofill, enabled: true, + data: undefined, + revisionDate: expect.any(Date), }, { id: "policy3", organizationId: "org5", type: PolicyType.DisablePersonalVaultExport, enabled: false, + data: undefined, + revisionDate: expect.any(Date), }, { id: "policy4", organizationId: "org1", type: PolicyType.DisablePersonalVaultExport, enabled: true, + data: undefined, + revisionDate: expect.any(Date), }, ]); }); @@ -411,24 +434,32 @@ describe("PolicyService", () => { organizationId: "org4", type: PolicyType.DisablePersonalVaultExport, enabled: true, + data: undefined, + revisionDate: expect.any(Date), }, { id: "policy2", organizationId: "org1", type: PolicyType.ActivateAutofill, enabled: true, + data: undefined, + revisionDate: expect.any(Date), }, { id: "policy3", organizationId: "org5", type: PolicyType.DisablePersonalVaultExport, enabled: true, + data: undefined, + revisionDate: expect.any(Date), }, { id: "policy4", organizationId: "org2", type: PolicyType.DisablePersonalVaultExport, enabled: true, + data: undefined, + revisionDate: expect.any(Date), }, ]); }); @@ -451,24 +482,32 @@ describe("PolicyService", () => { organizationId: "org4", type: PolicyType.DisablePersonalVaultExport, enabled: true, + data: undefined, + revisionDate: expect.any(Date), }, { id: "policy2", organizationId: "org1", type: PolicyType.ActivateAutofill, enabled: true, + data: undefined, + revisionDate: expect.any(Date), }, { id: "policy3", organizationId: "org3", type: PolicyType.DisablePersonalVaultExport, enabled: true, + data: undefined, + revisionDate: expect.any(Date), }, { id: "policy4", organizationId: "org1", type: PolicyType.DisablePersonalVaultExport, enabled: true, + data: undefined, + revisionDate: expect.any(Date), }, ]); }); @@ -788,6 +827,7 @@ describe("PolicyService", () => { policyData.type = type; policyData.enabled = enabled; policyData.data = data; + policyData.revisionDate = new Date().toISOString(); return policyData; } diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts index 371081a89d9..1727d3da712 100644 --- a/libs/common/src/enums/feature-flag.enum.ts +++ b/libs/common/src/enums/feature-flag.enum.ts @@ -64,6 +64,7 @@ export enum FeatureFlag { RiskInsightsForPremium = "pm-23904-risk-insights-for-premium", VaultLoadingSkeletons = "pm-25081-vault-skeleton-loaders", BrowserPremiumSpotlight = "pm-23384-browser-premium-spotlight", + MigrateMyVaultToMyItems = "pm-20558-migrate-myvault-to-myitems", /* Platform */ IpcChannelFramework = "ipc-channel-framework", @@ -123,6 +124,7 @@ export const DefaultFeatureFlagValue = { [FeatureFlag.RiskInsightsForPremium]: FALSE, [FeatureFlag.VaultLoadingSkeletons]: FALSE, [FeatureFlag.BrowserPremiumSpotlight]: FALSE, + [FeatureFlag.MigrateMyVaultToMyItems]: FALSE, /* Auth */ [FeatureFlag.PM23801_PrefetchPasswordPrelogin]: FALSE, diff --git a/libs/tools/generator/core/src/policies/available-algorithms-policy.spec.ts b/libs/tools/generator/core/src/policies/available-algorithms-policy.spec.ts index 5f699974fba..7de8c708dcf 100644 --- a/libs/tools/generator/core/src/policies/available-algorithms-policy.spec.ts +++ b/libs/tools/generator/core/src/policies/available-algorithms-policy.spec.ts @@ -24,6 +24,7 @@ describe("availableAlgorithms_vNextPolicy", () => { overridePasswordType: override, }, enabled: true, + revisionDate: new Date().toISOString(), }); const result = availableAlgorithms([policy]); @@ -44,6 +45,7 @@ describe("availableAlgorithms_vNextPolicy", () => { overridePasswordType: override, }, enabled: true, + revisionDate: new Date().toISOString(), }); const result = availableAlgorithms([policy, policy]); @@ -64,6 +66,7 @@ describe("availableAlgorithms_vNextPolicy", () => { overridePasswordType: "password", }, enabled: true, + revisionDate: new Date().toISOString(), }); const passphrase = new Policy({ id: "" as PolicyId, @@ -73,6 +76,7 @@ describe("availableAlgorithms_vNextPolicy", () => { overridePasswordType: "passphrase", }, enabled: true, + revisionDate: new Date().toISOString(), }); const result = availableAlgorithms([password, passphrase]); @@ -93,6 +97,7 @@ describe("availableAlgorithms_vNextPolicy", () => { some: "policy", }, enabled: true, + revisionDate: new Date().toISOString(), }); const result = availableAlgorithms([policy]); @@ -111,6 +116,7 @@ describe("availableAlgorithms_vNextPolicy", () => { some: "policy", }, enabled: false, + revisionDate: new Date().toISOString(), }); const result = availableAlgorithms([policy]); @@ -129,6 +135,7 @@ describe("availableAlgorithms_vNextPolicy", () => { some: "policy", }, enabled: true, + revisionDate: new Date().toISOString(), }); const result = availableAlgorithms([policy]); diff --git a/libs/tools/generator/core/src/policies/passphrase-least-privilege.spec.ts b/libs/tools/generator/core/src/policies/passphrase-least-privilege.spec.ts index 0fbc1796e9e..c6ce189f620 100644 --- a/libs/tools/generator/core/src/policies/passphrase-least-privilege.spec.ts +++ b/libs/tools/generator/core/src/policies/passphrase-least-privilege.spec.ts @@ -17,6 +17,7 @@ function createPolicy( data, enabled, type, + revisionDate: new Date().toISOString(), }); } diff --git a/libs/tools/generator/core/src/policies/password-least-privilege.spec.ts b/libs/tools/generator/core/src/policies/password-least-privilege.spec.ts index 7f8dce19b15..7885641c8e5 100644 --- a/libs/tools/generator/core/src/policies/password-least-privilege.spec.ts +++ b/libs/tools/generator/core/src/policies/password-least-privilege.spec.ts @@ -17,6 +17,7 @@ function createPolicy( data, enabled, type, + revisionDate: new Date().toISOString(), }); } diff --git a/libs/tools/generator/core/src/providers/generator-profile-provider.spec.ts b/libs/tools/generator/core/src/providers/generator-profile-provider.spec.ts index 32d99aa8a1f..924849b1c22 100644 --- a/libs/tools/generator/core/src/providers/generator-profile-provider.spec.ts +++ b/libs/tools/generator/core/src/providers/generator-profile-provider.spec.ts @@ -57,6 +57,7 @@ const somePolicy = new Policy({ id: "" as PolicyId, organizationId: "" as OrganizationId, enabled: true, + revisionDate: new Date().toISOString(), }); const stateProvider = new FakeStateProvider(accountService); diff --git a/libs/tools/generator/extensions/navigation/src/default-generator-navigation.service.spec.ts b/libs/tools/generator/extensions/navigation/src/default-generator-navigation.service.spec.ts index 65f1669ebd1..37e8ec6e379 100644 --- a/libs/tools/generator/extensions/navigation/src/default-generator-navigation.service.spec.ts +++ b/libs/tools/generator/extensions/navigation/src/default-generator-navigation.service.spec.ts @@ -70,6 +70,7 @@ describe("DefaultGeneratorNavigationService", () => { enabled: true, type: PolicyType.PasswordGenerator, data: { overridePasswordType: "password" }, + revisionDate: new Date().toISOString(), }), ]); }, diff --git a/libs/tools/generator/extensions/navigation/src/generator-navigation-policy.spec.ts b/libs/tools/generator/extensions/navigation/src/generator-navigation-policy.spec.ts index e4f0b08a3d5..69a4e75d47d 100644 --- a/libs/tools/generator/extensions/navigation/src/generator-navigation-policy.spec.ts +++ b/libs/tools/generator/extensions/navigation/src/generator-navigation-policy.spec.ts @@ -17,6 +17,7 @@ function createPolicy( data, enabled, type, + revisionDate: new Date().toISOString(), }); } diff --git a/libs/vault/src/abstractions/vault-items-transfer.service.ts b/libs/vault/src/abstractions/vault-items-transfer.service.ts new file mode 100644 index 00000000000..ced9f71eb83 --- /dev/null +++ b/libs/vault/src/abstractions/vault-items-transfer.service.ts @@ -0,0 +1,59 @@ +import { Observable } from "rxjs"; + +import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { OrganizationId, CollectionId } from "@bitwarden/common/types/guid"; +import { UserId } from "@bitwarden/user-core"; + +export type UserMigrationInfo = + | { + /** + * Whether the user requires migration of their vault items from My Vault to a My Items collection due to an + * organizational policy change. (Enforce organization data ownership policy enabled) + */ + requiresMigration: false; + } + | { + /** + * Whether the user requires migration of their vault items from My Vault to a My Items collection due to an + * organizational policy change. (Enforce organization data ownership policy enabled) + */ + requiresMigration: true; + + /** + * The organization that is enforcing data ownership policies for the given user. + */ + enforcingOrganization: Organization; + + /** + * The default collection ID for the user in the enforcing organization, if available. + */ + defaultCollectionId?: CollectionId; + }; + +export abstract class VaultItemsTransferService { + /** + * Gets information about whether the given user requires migration of their vault items + * from My Vault to a My Items collection, and whether they are capable of performing that migration. + * @param userId + */ + abstract userMigrationInfo$(userId: UserId): Observable; + + /** + * Enforces organization data ownership for the given user by transferring vault items. + * Checks if any organization policies require the transfer, and if so, prompts the user to confirm before proceeding. + * + * Rejecting the transfer will result in the user being revoked from the organization. + * + * @param userId + */ + abstract enforceOrganizationDataOwnership(userId: UserId): Promise; + + /** + * Begins transfer of vault items from My Vault to the specified default collection for the given user. + */ + abstract transferPersonalItems( + userId: UserId, + organizationId: OrganizationId, + defaultCollectionId: CollectionId, + ): Promise; +} diff --git a/libs/vault/src/index.ts b/libs/vault/src/index.ts index be0daad3637..391957d26d8 100644 --- a/libs/vault/src/index.ts +++ b/libs/vault/src/index.ts @@ -35,5 +35,7 @@ export { DefaultSshImportPromptService } from "./services/default-ssh-import-pro export { SshImportPromptService } from "./services/ssh-import-prompt.service"; export * from "./abstractions/change-login-password.service"; +export * from "./abstractions/vault-items-transfer.service"; +export * from "./services/default-vault-items-transfer.service"; export * from "./services/default-change-login-password.service"; export * from "./services/archive-cipher-utilities.service"; diff --git a/libs/vault/src/services/default-vault-items-transfer.service.spec.ts b/libs/vault/src/services/default-vault-items-transfer.service.spec.ts new file mode 100644 index 00000000000..d85fe2ffd43 --- /dev/null +++ b/libs/vault/src/services/default-vault-items-transfer.service.spec.ts @@ -0,0 +1,721 @@ +import { mock, MockProxy } from "jest-mock-extended"; +import { firstValueFrom, of } from "rxjs"; + +// eslint-disable-next-line no-restricted-imports +import { CollectionService, CollectionView } from "@bitwarden/admin-console/common"; +import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; +import { PolicyType } from "@bitwarden/common/admin-console/enums"; +import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; +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, CollectionId } from "@bitwarden/common/types/guid"; +import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; +import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; +import { DialogService, ToastService } from "@bitwarden/components"; +import { LogService } from "@bitwarden/logging"; +import { UserId } from "@bitwarden/user-core"; + +import { DefaultVaultItemsTransferService } from "./default-vault-items-transfer.service"; + +describe("DefaultVaultItemsTransferService", () => { + let service: DefaultVaultItemsTransferService; + + let mockCipherService: MockProxy; + let mockPolicyService: MockProxy; + let mockOrganizationService: MockProxy; + let mockCollectionService: MockProxy; + let mockLogService: MockProxy; + let mockI18nService: MockProxy; + let mockDialogService: MockProxy; + let mockToastService: MockProxy; + let mockConfigService: MockProxy; + + const userId = "user-id" as UserId; + const organizationId = "org-id" as OrganizationId; + const collectionId = "collection-id" as CollectionId; + + beforeEach(() => { + mockCipherService = mock(); + mockPolicyService = mock(); + mockOrganizationService = mock(); + mockCollectionService = mock(); + mockLogService = mock(); + mockI18nService = mock(); + mockDialogService = mock(); + mockToastService = mock(); + mockConfigService = mock(); + + mockI18nService.t.mockImplementation((key) => key); + + service = new DefaultVaultItemsTransferService( + mockCipherService, + mockPolicyService, + mockOrganizationService, + mockCollectionService, + mockLogService, + mockI18nService, + mockDialogService, + mockToastService, + mockConfigService, + ); + }); + + describe("userMigrationInfo$", () => { + // Helper to setup common mock scenario + function setupMocksForMigrationScenario(options: { + policies?: Policy[]; + organizations?: Organization[]; + ciphers?: CipherView[]; + collections?: CollectionView[]; + }): void { + mockPolicyService.policiesByType$.mockReturnValue(of(options.policies ?? [])); + mockOrganizationService.organizations$.mockReturnValue(of(options.organizations ?? [])); + mockCipherService.cipherViews$.mockReturnValue(of(options.ciphers ?? [])); + mockCollectionService.decryptedCollections$.mockReturnValue(of(options.collections ?? [])); + } + + it("calls policiesByType$ with correct PolicyType", async () => { + setupMocksForMigrationScenario({ policies: [] }); + + await firstValueFrom(service.userMigrationInfo$(userId)); + + expect(mockPolicyService.policiesByType$).toHaveBeenCalledWith( + PolicyType.OrganizationDataOwnership, + userId, + ); + }); + + describe("when no policy exists", () => { + beforeEach(() => { + setupMocksForMigrationScenario({ policies: [] }); + }); + + it("returns requiresMigration: false", async () => { + const result = await firstValueFrom(service.userMigrationInfo$(userId)); + + expect(result).toEqual({ + requiresMigration: false, + }); + }); + }); + + describe("when policy exists", () => { + const policy = { + organizationId: organizationId, + revisionDate: new Date("2024-01-01"), + } as Policy; + const organization = { + id: organizationId, + name: "Test Org", + } as Organization; + + beforeEach(() => { + setupMocksForMigrationScenario({ + policies: [policy], + organizations: [organization], + }); + }); + + describe("and user has no personal ciphers", () => { + beforeEach(() => { + mockCipherService.cipherViews$.mockReturnValue(of([])); + }); + + it("returns requiresMigration: false", async () => { + const result = await firstValueFrom(service.userMigrationInfo$(userId)); + + expect(result).toEqual({ + requiresMigration: false, + enforcingOrganization: organization, + defaultCollectionId: undefined, + }); + }); + }); + + describe("and user has personal ciphers", () => { + beforeEach(() => { + mockCipherService.cipherViews$.mockReturnValue(of([{ id: "cipher-1" } as CipherView])); + }); + + it("returns requiresMigration: true", async () => { + const result = await firstValueFrom(service.userMigrationInfo$(userId)); + + expect(result).toEqual({ + requiresMigration: true, + enforcingOrganization: organization, + defaultCollectionId: undefined, + }); + }); + + it("includes defaultCollectionId when a default collection exists", async () => { + mockCollectionService.decryptedCollections$.mockReturnValue( + of([ + { + id: collectionId, + organizationId: organizationId, + isDefaultCollection: true, + } as CollectionView, + ]), + ); + + const result = await firstValueFrom(service.userMigrationInfo$(userId)); + + expect(result).toEqual({ + requiresMigration: true, + enforcingOrganization: organization, + defaultCollectionId: collectionId, + }); + }); + + it("returns default collection only for the enforcing organization", async () => { + mockCollectionService.decryptedCollections$.mockReturnValue( + of([ + { + id: "wrong-collection-id" as CollectionId, + organizationId: "wrong-org-id" as OrganizationId, + isDefaultCollection: true, + } as CollectionView, + { + id: collectionId, + organizationId: organizationId, + isDefaultCollection: true, + } as CollectionView, + ]), + ); + + const result = await firstValueFrom(service.userMigrationInfo$(userId)); + + expect(result).toEqual({ + requiresMigration: true, + enforcingOrganization: organization, + defaultCollectionId: collectionId, + }); + }); + }); + + it("filters out organization ciphers when checking for personal ciphers", async () => { + mockCipherService.cipherViews$.mockReturnValue( + of([ + { + id: "cipher-1", + organizationId: organizationId as string, + } as CipherView, + ]), + ); + + const result = await firstValueFrom(service.userMigrationInfo$(userId)); + + expect(result).toEqual({ + requiresMigration: false, + enforcingOrganization: organization, + defaultCollectionId: undefined, + }); + }); + }); + + describe("when multiple policies exist", () => { + const olderPolicy = { + organizationId: "older-org-id" as OrganizationId, + revisionDate: new Date("2024-01-01"), + } as Policy; + const newerPolicy = { + organizationId: organizationId, + revisionDate: new Date("2024-06-01"), + } as Policy; + const olderOrganization = { + id: "older-org-id" as OrganizationId, + name: "Older Org", + } as Organization; + const newerOrganization = { + id: organizationId, + name: "Newer Org", + } as Organization; + + beforeEach(() => { + setupMocksForMigrationScenario({ + policies: [newerPolicy, olderPolicy], + organizations: [olderOrganization, newerOrganization], + ciphers: [{ id: "cipher-1" } as CipherView], + }); + }); + + it("uses the oldest policy when selecting enforcing organization", async () => { + const result = await firstValueFrom(service.userMigrationInfo$(userId)); + + expect(result).toEqual({ + requiresMigration: true, + enforcingOrganization: olderOrganization, + defaultCollectionId: undefined, + }); + }); + }); + }); + + describe("transferPersonalItems", () => { + it("does nothing when user has no personal ciphers", async () => { + mockCipherService.cipherViews$.mockReturnValue(of([])); + + await service.transferPersonalItems(userId, organizationId, collectionId); + + expect(mockCipherService.shareManyWithServer).not.toHaveBeenCalled(); + expect(mockLogService.info).not.toHaveBeenCalled(); + }); + + it("calls shareManyWithServer with correct parameters", async () => { + const personalCiphers = [{ id: "cipher-1" }, { id: "cipher-2" }] as CipherView[]; + + mockCipherService.cipherViews$.mockReturnValue(of(personalCiphers)); + mockCipherService.shareManyWithServer.mockResolvedValue(undefined); + + await service.transferPersonalItems(userId, organizationId, collectionId); + + expect(mockCipherService.shareManyWithServer).toHaveBeenCalledWith( + personalCiphers, + organizationId, + [collectionId], + userId, + ); + }); + + it("transfers only personal ciphers, not organization ciphers", async () => { + const allCiphers = [ + { id: "cipher-1" }, + { id: "cipher-2", organizationId: "other-org-id" }, + { id: "cipher-3" }, + ] as CipherView[]; + + const expectedPersonalCiphers = [allCiphers[0], allCiphers[2]]; + + mockCipherService.cipherViews$.mockReturnValue(of(allCiphers)); + mockCipherService.shareManyWithServer.mockResolvedValue(undefined); + + await service.transferPersonalItems(userId, organizationId, collectionId); + + expect(mockCipherService.shareManyWithServer).toHaveBeenCalledWith( + expectedPersonalCiphers, + organizationId, + [collectionId], + userId, + ); + }); + + it("propagates errors from shareManyWithServer", async () => { + const personalCiphers = [{ id: "cipher-1" }] as CipherView[]; + + const error = new Error("Transfer failed"); + + mockCipherService.cipherViews$.mockReturnValue(of(personalCiphers)); + mockCipherService.shareManyWithServer.mockRejectedValue(error); + + await expect( + service.transferPersonalItems(userId, organizationId, collectionId), + ).rejects.toThrow("Transfer failed"); + }); + }); + + describe("upgradeOldAttachments", () => { + it("upgrades old attachments before transferring", async () => { + const cipherWithOldAttachment = { + id: "cipher-1", + name: "Cipher 1", + hasOldAttachments: true, + attachments: [{ key: null }], + } as unknown as CipherView; + + const upgradedCipher = { + id: "cipher-1", + name: "Cipher 1", + hasOldAttachments: false, + attachments: [{ key: "new-key" }], + } as unknown as CipherView; + + mockCipherService.cipherViews$ + .mockReturnValueOnce(of([cipherWithOldAttachment])) + .mockReturnValueOnce(of([upgradedCipher])); + mockCipherService.upgradeOldCipherAttachments.mockResolvedValue(upgradedCipher); + mockCipherService.shareManyWithServer.mockResolvedValue(undefined); + + await service.transferPersonalItems(userId, organizationId, collectionId); + + expect(mockCipherService.upgradeOldCipherAttachments).toHaveBeenCalledWith( + cipherWithOldAttachment, + userId, + ); + expect(mockCipherService.shareManyWithServer).toHaveBeenCalledWith( + [upgradedCipher], + organizationId, + [collectionId], + userId, + ); + }); + + it("upgrades multiple ciphers with old attachments", async () => { + const cipher1 = { + id: "cipher-1", + name: "Cipher 1", + hasOldAttachments: true, + attachments: [{ key: null }], + } as unknown as CipherView; + + const cipher2 = { + id: "cipher-2", + name: "Cipher 2", + hasOldAttachments: true, + attachments: [{ key: null }], + } as unknown as CipherView; + + const upgradedCipher1 = { ...cipher1, hasOldAttachments: false } as CipherView; + const upgradedCipher2 = { ...cipher2, hasOldAttachments: false } as CipherView; + + mockCipherService.cipherViews$ + .mockReturnValueOnce(of([cipher1, cipher2])) + .mockReturnValueOnce(of([upgradedCipher1, upgradedCipher2])); + mockCipherService.upgradeOldCipherAttachments + .mockResolvedValueOnce(upgradedCipher1) + .mockResolvedValueOnce(upgradedCipher2); + mockCipherService.shareManyWithServer.mockResolvedValue(undefined); + + await service.transferPersonalItems(userId, organizationId, collectionId); + + expect(mockCipherService.upgradeOldCipherAttachments).toHaveBeenCalledTimes(2); + expect(mockCipherService.upgradeOldCipherAttachments).toHaveBeenCalledWith(cipher1, userId); + expect(mockCipherService.upgradeOldCipherAttachments).toHaveBeenCalledWith(cipher2, userId); + }); + + it("skips attachments that already have keys", async () => { + const cipherWithMixedAttachments = { + id: "cipher-1", + name: "Cipher 1", + hasOldAttachments: true, + attachments: [{ key: "existing-key" }, { key: null }], + } as unknown as CipherView; + + const upgradedCipher = { + ...cipherWithMixedAttachments, + hasOldAttachments: false, + } as unknown as CipherView; + + mockCipherService.cipherViews$ + .mockReturnValueOnce(of([cipherWithMixedAttachments])) + .mockReturnValueOnce(of([upgradedCipher])); + mockCipherService.upgradeOldCipherAttachments.mockResolvedValue(upgradedCipher); + mockCipherService.shareManyWithServer.mockResolvedValue(undefined); + + await service.transferPersonalItems(userId, organizationId, collectionId); + + // Should only be called once for the attachment without a key + expect(mockCipherService.upgradeOldCipherAttachments).toHaveBeenCalledTimes(1); + }); + + it("throws error when upgradeOldCipherAttachments fails", async () => { + const cipherWithOldAttachment = { + id: "cipher-1", + name: "Cipher 1", + hasOldAttachments: true, + attachments: [{ key: null }], + } as unknown as CipherView; + + mockCipherService.cipherViews$.mockReturnValue(of([cipherWithOldAttachment])); + mockCipherService.upgradeOldCipherAttachments.mockRejectedValue(new Error("Upgrade failed")); + + await expect( + service.transferPersonalItems(userId, organizationId, collectionId), + ).rejects.toThrow("Failed to upgrade old attachments for cipher cipher-1"); + + expect(mockCipherService.shareManyWithServer).not.toHaveBeenCalled(); + }); + + it("throws error when upgrade returns cipher still having old attachments", async () => { + const cipherWithOldAttachment = { + id: "cipher-1", + name: "Cipher 1", + hasOldAttachments: true, + attachments: [{ key: null }], + } as unknown as CipherView; + + // Upgrade returns but cipher still has old attachments + const stillOldCipher = { + ...cipherWithOldAttachment, + hasOldAttachments: true, + } as unknown as CipherView; + + mockCipherService.cipherViews$.mockReturnValue(of([cipherWithOldAttachment])); + mockCipherService.upgradeOldCipherAttachments.mockResolvedValue(stillOldCipher); + + await expect( + service.transferPersonalItems(userId, organizationId, collectionId), + ).rejects.toThrow("Failed to upgrade old attachments for cipher cipher-1"); + + expect(mockLogService.error).toHaveBeenCalled(); + expect(mockCipherService.shareManyWithServer).not.toHaveBeenCalled(); + }); + + it("throws error when sanity check finds remaining old attachments after upgrade", async () => { + const cipherWithOldAttachment = { + id: "cipher-1", + name: "Cipher 1", + hasOldAttachments: true, + attachments: [{ key: null }], + } as unknown as CipherView; + + const upgradedCipher = { + ...cipherWithOldAttachment, + hasOldAttachments: false, + } as unknown as CipherView; + + // First call returns cipher with old attachment, second call (after upgrade) still returns old attachment + mockCipherService.cipherViews$ + .mockReturnValueOnce(of([cipherWithOldAttachment])) + .mockReturnValueOnce(of([cipherWithOldAttachment])); // Still has old attachments after re-fetch + mockCipherService.upgradeOldCipherAttachments.mockResolvedValue(upgradedCipher); + + await expect( + service.transferPersonalItems(userId, organizationId, collectionId), + ).rejects.toThrow( + "Failed to upgrade all old attachments. 1 ciphers still have old attachments.", + ); + + expect(mockCipherService.shareManyWithServer).not.toHaveBeenCalled(); + }); + + it("logs info when upgrading old attachments", async () => { + const cipherWithOldAttachment = { + id: "cipher-1", + name: "Cipher 1", + hasOldAttachments: true, + attachments: [{ key: null }], + } as unknown as CipherView; + + const upgradedCipher = { + ...cipherWithOldAttachment, + hasOldAttachments: false, + } as unknown as CipherView; + + mockCipherService.cipherViews$ + .mockReturnValueOnce(of([cipherWithOldAttachment])) + .mockReturnValueOnce(of([upgradedCipher])); + mockCipherService.upgradeOldCipherAttachments.mockResolvedValue(upgradedCipher); + mockCipherService.shareManyWithServer.mockResolvedValue(undefined); + + await service.transferPersonalItems(userId, organizationId, collectionId); + + expect(mockLogService.info).toHaveBeenCalledWith( + expect.stringContaining("Found 1 ciphers with old attachments needing upgrade"), + ); + expect(mockLogService.info).toHaveBeenCalledWith( + expect.stringContaining("Successfully upgraded 1 ciphers with old attachments"), + ); + }); + + it("does not upgrade when ciphers have no old attachments", async () => { + const cipherWithoutOldAttachment = { + id: "cipher-1", + name: "Cipher 1", + hasOldAttachments: false, + } as unknown as CipherView; + + mockCipherService.cipherViews$.mockReturnValue(of([cipherWithoutOldAttachment])); + mockCipherService.shareManyWithServer.mockResolvedValue(undefined); + + await service.transferPersonalItems(userId, organizationId, collectionId); + + expect(mockCipherService.upgradeOldCipherAttachments).not.toHaveBeenCalled(); + expect(mockCipherService.shareManyWithServer).toHaveBeenCalled(); + }); + }); + + describe("enforceOrganizationDataOwnership", () => { + const policy = { + organizationId: organizationId, + revisionDate: new Date("2024-01-01"), + } as Policy; + const organization = { + id: organizationId, + name: "Test Org", + } as Organization; + + function setupMocksForEnforcementScenario(options: { + featureEnabled?: boolean; + policies?: Policy[]; + organizations?: Organization[]; + ciphers?: CipherView[]; + collections?: CollectionView[]; + }): void { + mockConfigService.getFeatureFlag.mockResolvedValue(options.featureEnabled ?? true); + mockPolicyService.policiesByType$.mockReturnValue(of(options.policies ?? [])); + mockOrganizationService.organizations$.mockReturnValue(of(options.organizations ?? [])); + mockCipherService.cipherViews$.mockReturnValue(of(options.ciphers ?? [])); + mockCollectionService.decryptedCollections$.mockReturnValue(of(options.collections ?? [])); + } + + it("does nothing when feature flag is disabled", async () => { + setupMocksForEnforcementScenario({ + featureEnabled: false, + policies: [policy], + organizations: [organization], + ciphers: [{ id: "cipher-1" } as CipherView], + collections: [ + { + id: collectionId, + organizationId: organizationId, + isDefaultCollection: true, + } as CollectionView, + ], + }); + + await service.enforceOrganizationDataOwnership(userId); + + expect(mockConfigService.getFeatureFlag).toHaveBeenCalledWith( + FeatureFlag.MigrateMyVaultToMyItems, + ); + expect(mockDialogService.openSimpleDialog).not.toHaveBeenCalled(); + expect(mockCipherService.shareManyWithServer).not.toHaveBeenCalled(); + }); + + it("does nothing when no migration is required", async () => { + setupMocksForEnforcementScenario({ policies: [] }); + + await service.enforceOrganizationDataOwnership(userId); + + expect(mockDialogService.openSimpleDialog).not.toHaveBeenCalled(); + expect(mockCipherService.shareManyWithServer).not.toHaveBeenCalled(); + }); + + it("does nothing when user has no personal ciphers", async () => { + setupMocksForEnforcementScenario({ + policies: [policy], + organizations: [organization], + ciphers: [], + }); + + await service.enforceOrganizationDataOwnership(userId); + + expect(mockDialogService.openSimpleDialog).not.toHaveBeenCalled(); + expect(mockCipherService.shareManyWithServer).not.toHaveBeenCalled(); + }); + + it("logs warning and returns when default collection is missing", async () => { + setupMocksForEnforcementScenario({ + policies: [policy], + organizations: [organization], + ciphers: [{ id: "cipher-1" } as CipherView], + collections: [], + }); + + await service.enforceOrganizationDataOwnership(userId); + + expect(mockLogService.warning).toHaveBeenCalledWith( + "Default collection is missing for user during organization data ownership enforcement", + ); + expect(mockDialogService.openSimpleDialog).not.toHaveBeenCalled(); + expect(mockCipherService.shareManyWithServer).not.toHaveBeenCalled(); + }); + + it("shows confirmation dialog when migration is required", async () => { + setupMocksForEnforcementScenario({ + policies: [policy], + organizations: [organization], + ciphers: [{ id: "cipher-1" } as CipherView], + collections: [ + { + id: collectionId, + organizationId: organizationId, + isDefaultCollection: true, + } as CollectionView, + ], + }); + mockDialogService.openSimpleDialog.mockResolvedValue(false); + + await service.enforceOrganizationDataOwnership(userId); + + expect(mockDialogService.openSimpleDialog).toHaveBeenCalledWith({ + title: "Requires migration", + content: "Your vault requires migration of personal items to your organization.", + type: "warning", + }); + }); + + it("does not transfer items when user declines confirmation", async () => { + setupMocksForEnforcementScenario({ + policies: [policy], + organizations: [organization], + ciphers: [{ id: "cipher-1" } as CipherView], + collections: [ + { + id: collectionId, + organizationId: organizationId, + isDefaultCollection: true, + } as CollectionView, + ], + }); + mockDialogService.openSimpleDialog.mockResolvedValue(false); + + await service.enforceOrganizationDataOwnership(userId); + + expect(mockCipherService.shareManyWithServer).not.toHaveBeenCalled(); + }); + + it("transfers items and shows success toast when user confirms", async () => { + const personalCiphers = [{ id: "cipher-1" } as CipherView]; + setupMocksForEnforcementScenario({ + policies: [policy], + organizations: [organization], + ciphers: personalCiphers, + collections: [ + { + id: collectionId, + organizationId: organizationId, + isDefaultCollection: true, + } as CollectionView, + ], + }); + mockDialogService.openSimpleDialog.mockResolvedValue(true); + mockCipherService.shareManyWithServer.mockResolvedValue(undefined); + + await service.enforceOrganizationDataOwnership(userId); + + expect(mockCipherService.shareManyWithServer).toHaveBeenCalledWith( + personalCiphers, + organizationId, + [collectionId], + userId, + ); + expect(mockToastService.showToast).toHaveBeenCalledWith({ + variant: "success", + message: "itemsTransferred", + }); + }); + + it("shows error toast when transfer fails", async () => { + const personalCiphers = [{ id: "cipher-1" } as CipherView]; + setupMocksForEnforcementScenario({ + policies: [policy], + organizations: [organization], + ciphers: personalCiphers, + collections: [ + { + id: collectionId, + organizationId: organizationId, + isDefaultCollection: true, + } as CollectionView, + ], + }); + mockDialogService.openSimpleDialog.mockResolvedValue(true); + mockCipherService.shareManyWithServer.mockRejectedValue(new Error("Transfer failed")); + + await service.enforceOrganizationDataOwnership(userId); + + expect(mockLogService.error).toHaveBeenCalledWith( + "Error transferring personal items to organization", + expect.any(Error), + ); + expect(mockToastService.showToast).toHaveBeenCalledWith({ + variant: "error", + message: "errorOccurred", + }); + }); + }); +}); diff --git a/libs/vault/src/services/default-vault-items-transfer.service.ts b/libs/vault/src/services/default-vault-items-transfer.service.ts new file mode 100644 index 00000000000..d9c490f870e --- /dev/null +++ b/libs/vault/src/services/default-vault-items-transfer.service.ts @@ -0,0 +1,231 @@ +import { Injectable } from "@angular/core"; +import { firstValueFrom, switchMap, map, of, Observable, combineLatest } from "rxjs"; + +// eslint-disable-next-line no-restricted-imports +import { CollectionService } from "@bitwarden/admin-console/common"; +import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; +import { PolicyType } from "@bitwarden/common/admin-console/enums"; +import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { 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 { getById } from "@bitwarden/common/platform/misc"; +import { OrganizationId, CollectionId } from "@bitwarden/common/types/guid"; +import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; +import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; +import { filterOutNullish } from "@bitwarden/common/vault/utils/observable-utilities"; +import { DialogService, ToastService } from "@bitwarden/components"; +import { LogService } from "@bitwarden/logging"; +import { UserId } from "@bitwarden/user-core"; + +import { + VaultItemsTransferService, + UserMigrationInfo, +} from "../abstractions/vault-items-transfer.service"; + +@Injectable() +export class DefaultVaultItemsTransferService implements VaultItemsTransferService { + constructor( + private cipherService: CipherService, + private policyService: PolicyService, + private organizationService: OrganizationService, + private collectionService: CollectionService, + private logService: LogService, + private i18nService: I18nService, + private dialogService: DialogService, + private toastService: ToastService, + private configService: ConfigService, + ) {} + + private enforcingOrganization$(userId: UserId): Observable { + return this.policyService.policiesByType$(PolicyType.OrganizationDataOwnership, userId).pipe( + map( + (policies) => + policies.sort((a, b) => a.revisionDate.getTime() - b.revisionDate.getTime())?.[0], + ), + switchMap((policy) => { + if (policy == null) { + return of(undefined); + } + return this.organizationService.organizations$(userId).pipe(getById(policy.organizationId)); + }), + ); + } + + private personalCiphers$(userId: UserId): Observable { + return this.cipherService.cipherViews$(userId).pipe( + filterOutNullish(), + map((ciphers) => ciphers.filter((c) => c.organizationId == null)), + ); + } + + private defaultUserCollection$( + userId: UserId, + organizationId: OrganizationId, + ): Observable { + return this.collectionService.decryptedCollections$(userId).pipe( + map((collections) => { + return collections.find((c) => c.isDefaultCollection && c.organizationId === organizationId) + ?.id; + }), + ); + } + + userMigrationInfo$(userId: UserId): Observable { + return this.enforcingOrganization$(userId).pipe( + switchMap((enforcingOrganization) => { + if (enforcingOrganization == null) { + return of({ + requiresMigration: false, + }); + } + return combineLatest([ + this.personalCiphers$(userId), + this.defaultUserCollection$(userId, enforcingOrganization.id), + ]).pipe( + map(([personalCiphers, defaultCollectionId]): UserMigrationInfo => { + return { + requiresMigration: personalCiphers.length > 0, + enforcingOrganization, + defaultCollectionId, + }; + }), + ); + }), + ); + } + + async enforceOrganizationDataOwnership(userId: UserId): Promise { + const featureEnabled = await this.configService.getFeatureFlag( + FeatureFlag.MigrateMyVaultToMyItems, + ); + + if (!featureEnabled) { + return; + } + + const migrationInfo = await firstValueFrom(this.userMigrationInfo$(userId)); + + if (!migrationInfo.requiresMigration) { + return; + } + + if (migrationInfo.defaultCollectionId == null) { + // TODO: Handle creating the default collection if missing (to be handled by AC in future work) + this.logService.warning( + "Default collection is missing for user during organization data ownership enforcement", + ); + return; + } + + // Temporary confirmation dialog. Full implementation in PM-27663 + const confirmMigration = await this.dialogService.openSimpleDialog({ + title: "Requires migration", + content: "Your vault requires migration of personal items to your organization.", + type: "warning", + }); + + if (!confirmMigration) { + // TODO: Show secondary confirmation dialog in PM-27663, for now we just exit + // TODO: Revoke user from organization if they decline migration PM-29465 + return; + } + + try { + await this.transferPersonalItems( + userId, + migrationInfo.enforcingOrganization.id, + migrationInfo.defaultCollectionId, + ); + this.toastService.showToast({ + variant: "success", + message: this.i18nService.t("itemsTransferred"), + }); + } catch (error) { + this.logService.error("Error transferring personal items to organization", error); + this.toastService.showToast({ + variant: "error", + message: this.i18nService.t("errorOccurred"), + }); + } + } + + async transferPersonalItems( + userId: UserId, + organizationId: OrganizationId, + defaultCollectionId: CollectionId, + ): Promise { + let personalCiphers = await firstValueFrom(this.personalCiphers$(userId)); + + if (personalCiphers.length === 0) { + return; + } + + const oldAttachmentCiphers = personalCiphers.filter((c) => c.hasOldAttachments); + + if (oldAttachmentCiphers.length > 0) { + await this.upgradeOldAttachments(oldAttachmentCiphers, userId, organizationId); + personalCiphers = await firstValueFrom(this.personalCiphers$(userId)); + + // Sanity check to ensure all old attachments were upgraded, though upgradeOldAttachments should throw if any fail + const remainingOldAttachments = personalCiphers.filter((c) => c.hasOldAttachments); + if (remainingOldAttachments.length > 0) { + throw new Error( + `Failed to upgrade all old attachments. ${remainingOldAttachments.length} ciphers still have old attachments.`, + ); + } + } + + this.logService.info( + `Starting transfer of ${personalCiphers.length} personal ciphers to organization ${organizationId} for user ${userId}`, + ); + + await this.cipherService.shareManyWithServer( + personalCiphers, + organizationId, + [defaultCollectionId], + userId, + ); + } + + /** + * Upgrades old attachments that don't have attachment keys. + * Throws an error if any attachment fails to upgrade as it is not possible to share with an organization without a key. + */ + private async upgradeOldAttachments( + ciphers: CipherView[], + userId: UserId, + organizationId: OrganizationId, + ): Promise { + this.logService.info( + `Found ${ciphers.length} ciphers with old attachments needing upgrade during transfer to organization ${organizationId} for user ${userId}`, + ); + + for (const cipher of ciphers) { + try { + if (!cipher.hasOldAttachments) { + continue; + } + + const upgraded = await this.cipherService.upgradeOldCipherAttachments(cipher, userId); + + if (upgraded.hasOldAttachments) { + this.logService.error( + `Attachment upgrade did not complete successfully for cipher ${cipher.id} during transfer to organization ${organizationId} for user ${userId}`, + ); + throw new Error(`Failed to upgrade old attachments for cipher ${cipher.id}`); + } + } catch (e) { + this.logService.error( + `Failed to upgrade old attachments for cipher ${cipher.id} during transfer to organization ${organizationId} for user ${userId}: ${e}`, + ); + throw new Error(`Failed to upgrade old attachments for cipher ${cipher.id}`); + } + } + + this.logService.info( + `Successfully upgraded ${ciphers.length} ciphers with old attachments during transfer to organization ${organizationId} for user ${userId}`, + ); + } +} From 42c09b325c532fb5a6fc55f2633a9c0b5233c57a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 9 Dec 2025 21:45:44 -0500 Subject: [PATCH 021/188] [deps] Autofill: Update rimraf to v6.1.2 (#17295) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package-lock.json | 48 ++++++++++++++++++++++++++++++++++++++++------- package.json | 2 +- 2 files changed, 42 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index cf9b6becf2f..5321edccd18 100644 --- a/package-lock.json +++ b/package-lock.json @@ -164,7 +164,7 @@ "prettier-plugin-tailwindcss": "0.7.1", "process": "0.11.10", "remark-gfm": "4.0.1", - "rimraf": "6.0.1", + "rimraf": "6.1.2", "sass": "1.94.2", "sass-loader": "16.0.6", "storybook": "9.1.16", @@ -35750,14 +35750,14 @@ "license": "MIT" }, "node_modules/rimraf": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.0.1.tgz", - "integrity": "sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A==", + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.1.2.tgz", + "integrity": "sha512-cFCkPslJv7BAXJsYlK1dZsbP8/ZNLkCAQ0bi1hf5EKX2QHegmDFEFA6QhuYJlk7UDdc+02JjO80YSOrWPpw06g==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "glob": "^11.0.0", - "package-json-from-dist": "^1.0.0" + "glob": "^13.0.0", + "package-json-from-dist": "^1.0.1" }, "bin": { "rimraf": "dist/esm/bin.mjs" @@ -35769,6 +35769,40 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/rimraf/node_modules/glob": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.0.tgz", + "integrity": "sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "minimatch": "^10.1.1", + "minipass": "^7.1.2", + "path-scurry": "^2.0.0" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/minimatch": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.1.tgz", + "integrity": "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/brace-expansion": "^5.0.0" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/roarr": { "version": "2.15.4", "resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz", diff --git a/package.json b/package.json index 58ff0ba8ea5..c7b04c434e7 100644 --- a/package.json +++ b/package.json @@ -126,7 +126,7 @@ "prettier-plugin-tailwindcss": "0.7.1", "process": "0.11.10", "remark-gfm": "4.0.1", - "rimraf": "6.0.1", + "rimraf": "6.1.2", "sass": "1.94.2", "sass-loader": "16.0.6", "storybook": "9.1.16", From 3af19ad9340deffdcbf6df943d8b9fddf907c60c Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Wed, 10 Dec 2025 04:03:31 +0100 Subject: [PATCH 022/188] [PM-28813] Implement encryption diagnostics & recovery tool (#17673) * Implement data recovery tool * Fix tests * Move Sdkloadservice call and use bit action --- .../data-recovery.component.html | 75 ++++ .../data-recovery.component.spec.ts | 348 ++++++++++++++++++ .../data-recovery/data-recovery.component.ts | 208 +++++++++++ .../data-recovery/log-recorder.ts | 19 + .../data-recovery/steps/cipher-step.ts | 81 ++++ .../data-recovery/steps/folder-step.ts | 97 +++++ .../data-recovery/steps/index.ts | 6 + .../data-recovery/steps/private-key-step.ts | 93 +++++ .../data-recovery/steps/recovery-step.ts | 43 +++ .../data-recovery/steps/sync-step.ts | 43 +++ .../data-recovery/steps/user-info-step.ts | 49 +++ apps/web/src/app/oss-routing.module.ts | 7 + apps/web/src/locales/en/messages.json | 48 +++ libs/common/src/enums/feature-flag.enum.ts | 2 + ...ser-asymmetric-key-regeneration.service.ts | 7 + ...symmetric-key-regeneration.service.spec.ts | 49 +++ ...ser-asymmetric-key-regeneration.service.ts | 7 +- 17 files changed, 1180 insertions(+), 2 deletions(-) create mode 100644 apps/web/src/app/key-management/data-recovery/data-recovery.component.html create mode 100644 apps/web/src/app/key-management/data-recovery/data-recovery.component.spec.ts create mode 100644 apps/web/src/app/key-management/data-recovery/data-recovery.component.ts create mode 100644 apps/web/src/app/key-management/data-recovery/log-recorder.ts create mode 100644 apps/web/src/app/key-management/data-recovery/steps/cipher-step.ts create mode 100644 apps/web/src/app/key-management/data-recovery/steps/folder-step.ts create mode 100644 apps/web/src/app/key-management/data-recovery/steps/index.ts create mode 100644 apps/web/src/app/key-management/data-recovery/steps/private-key-step.ts create mode 100644 apps/web/src/app/key-management/data-recovery/steps/recovery-step.ts create mode 100644 apps/web/src/app/key-management/data-recovery/steps/sync-step.ts create mode 100644 apps/web/src/app/key-management/data-recovery/steps/user-info-step.ts diff --git a/apps/web/src/app/key-management/data-recovery/data-recovery.component.html b/apps/web/src/app/key-management/data-recovery/data-recovery.component.html new file mode 100644 index 00000000000..f357e516115 --- /dev/null +++ b/apps/web/src/app/key-management/data-recovery/data-recovery.component.html @@ -0,0 +1,75 @@ +

{{ "dataRecoveryTitle" | i18n }}

+ +
+

+ {{ "dataRecoveryDescription" | i18n }} +

+ + @if (!diagnosticsCompleted() && !recoveryCompleted()) { + + } + +
+ @for (step of steps(); track $index) { + @if ( + ($index === 0 && hasStarted()) || + ($index > 0 && + (steps()[$index - 1].status === StepStatus.Completed || + steps()[$index - 1].status === StepStatus.Failed)) + ) { +
+
+ @if (step.status === StepStatus.Failed) { + + } @else if (step.status === StepStatus.Completed) { + + } @else if (step.status === StepStatus.InProgress) { + + } @else { + + } +
+
+ + {{ step.title }} + +
+
+ } + } +
+ + @if (diagnosticsCompleted()) { +
+ @if (hasIssues() && !recoveryCompleted()) { + + } + +
+ } +
diff --git a/apps/web/src/app/key-management/data-recovery/data-recovery.component.spec.ts b/apps/web/src/app/key-management/data-recovery/data-recovery.component.spec.ts new file mode 100644 index 00000000000..1976a8dfe27 --- /dev/null +++ b/apps/web/src/app/key-management/data-recovery/data-recovery.component.spec.ts @@ -0,0 +1,348 @@ +import { ComponentFixture, TestBed } from "@angular/core/testing"; +import { mock, MockProxy } from "jest-mock-extended"; + +import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service"; +import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec"; +import { UserId } from "@bitwarden/common/types/guid"; +import { CipherEncryptionService } from "@bitwarden/common/vault/abstractions/cipher-encryption.service"; +import { FolderApiServiceAbstraction } from "@bitwarden/common/vault/abstractions/folder/folder-api.service.abstraction"; +import { DialogService } from "@bitwarden/components"; +import { KeyService, UserAsymmetricKeysRegenerationService } from "@bitwarden/key-management"; +import { LogService } from "@bitwarden/logging"; + +import { DataRecoveryComponent, StepStatus } from "./data-recovery.component"; +import { RecoveryStep, RecoveryWorkingData } from "./steps"; + +// Mock SdkLoadService +jest.mock("@bitwarden/common/platform/abstractions/sdk/sdk-load.service", () => ({ + SdkLoadService: { + Ready: Promise.resolve(), + }, +})); + +describe("DataRecoveryComponent", () => { + let component: DataRecoveryComponent; + let fixture: ComponentFixture; + + // Mock Services + let mockI18nService: MockProxy; + let mockApiService: MockProxy; + let mockAccountService: FakeAccountService; + let mockKeyService: MockProxy; + let mockFolderApiService: MockProxy; + let mockCipherEncryptService: MockProxy; + let mockDialogService: MockProxy; + let mockPrivateKeyRegenerationService: MockProxy; + let mockLogService: MockProxy; + let mockCryptoFunctionService: MockProxy; + let mockFileDownloadService: MockProxy; + + const mockUserId = "user-id" as UserId; + + beforeEach(async () => { + mockI18nService = mock(); + mockApiService = mock(); + mockAccountService = mockAccountServiceWith(mockUserId); + mockKeyService = mock(); + mockFolderApiService = mock(); + mockCipherEncryptService = mock(); + mockDialogService = mock(); + mockPrivateKeyRegenerationService = mock(); + mockLogService = mock(); + mockCryptoFunctionService = mock(); + mockFileDownloadService = mock(); + + mockI18nService.t.mockImplementation((key) => `${key}_used-i18n`); + + await TestBed.configureTestingModule({ + imports: [DataRecoveryComponent], + providers: [ + { provide: I18nService, useValue: mockI18nService }, + { provide: ApiService, useValue: mockApiService }, + { provide: AccountService, useValue: mockAccountService }, + { provide: KeyService, useValue: mockKeyService }, + { provide: FolderApiServiceAbstraction, useValue: mockFolderApiService }, + { provide: CipherEncryptionService, useValue: mockCipherEncryptService }, + { provide: DialogService, useValue: mockDialogService }, + { + provide: UserAsymmetricKeysRegenerationService, + useValue: mockPrivateKeyRegenerationService, + }, + { provide: LogService, useValue: mockLogService }, + { provide: CryptoFunctionService, useValue: mockCryptoFunctionService }, + { provide: FileDownloadService, useValue: mockFileDownloadService }, + ], + }).compileComponents(); + + fixture = TestBed.createComponent(DataRecoveryComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + describe("Component Initialization", () => { + it("should create", () => { + expect(component).toBeTruthy(); + }); + + it("should initialize with default signal values", () => { + expect(component.status()).toBe(StepStatus.NotStarted); + expect(component.hasStarted()).toBe(false); + expect(component.diagnosticsCompleted()).toBe(false); + expect(component.recoveryCompleted()).toBe(false); + expect(component.hasIssues()).toBe(false); + }); + + it("should initialize steps in correct order", () => { + const steps = component.steps(); + expect(steps.length).toBe(5); + expect(steps[0].title).toBe("recoveryStepUserInfoTitle_used-i18n"); + expect(steps[1].title).toBe("recoveryStepSyncTitle_used-i18n"); + expect(steps[2].title).toBe("recoveryStepPrivateKeyTitle_used-i18n"); + expect(steps[3].title).toBe("recoveryStepFoldersTitle_used-i18n"); + expect(steps[4].title).toBe("recoveryStepCipherTitle_used-i18n"); + }); + }); + + describe("runDiagnostics", () => { + let mockSteps: MockProxy[]; + + beforeEach(() => { + // Create mock steps + mockSteps = Array(5) + .fill(null) + .map(() => { + const mockStep = mock(); + mockStep.title = "mockStep"; + mockStep.runDiagnostics.mockResolvedValue(true); + mockStep.canRecover.mockReturnValue(false); + return mockStep; + }); + + // Replace recovery steps with mocks + component["recoverySteps"] = mockSteps; + }); + + it("should not run if already running", async () => { + component["status"].set(StepStatus.InProgress); + await component.runDiagnostics(); + + expect(mockSteps[0].runDiagnostics).not.toHaveBeenCalled(); + }); + + it("should set hasStarted, isRunning and initialize workingData", async () => { + await component.runDiagnostics(); + + expect(component.hasStarted()).toBe(true); + expect(component["workingData"]).toBeDefined(); + expect(component["workingData"]?.userId).toBeNull(); + expect(component["workingData"]?.userKey).toBeNull(); + }); + + it("should run diagnostics for all steps", async () => { + await component.runDiagnostics(); + + mockSteps.forEach((step) => { + expect(step.runDiagnostics).toHaveBeenCalledWith( + component["workingData"], + expect.anything(), + ); + }); + }); + + it("should mark steps as completed when diagnostics succeed", async () => { + await component.runDiagnostics(); + + const steps = component.steps(); + steps.forEach((step) => { + expect(step.status).toBe(StepStatus.Completed); + }); + }); + + it("should mark steps as failed when diagnostics return false", async () => { + mockSteps[2].runDiagnostics.mockResolvedValue(false); + + await component.runDiagnostics(); + + const steps = component.steps(); + expect(steps[2].status).toBe(StepStatus.Failed); + }); + + it("should mark steps as failed when diagnostics throw error", async () => { + mockSteps[3].runDiagnostics.mockRejectedValue(new Error("Test error")); + + await component.runDiagnostics(); + + const steps = component.steps(); + expect(steps[3].status).toBe(StepStatus.Failed); + expect(steps[3].message).toBe("Test error"); + }); + + it("should continue diagnostics even if a step fails", async () => { + mockSteps[1].runDiagnostics.mockRejectedValue(new Error("Step 1 failed")); + mockSteps[3].runDiagnostics.mockResolvedValue(false); + + await component.runDiagnostics(); + + // All steps should have been called despite failures + mockSteps.forEach((step) => { + expect(step.runDiagnostics).toHaveBeenCalled(); + }); + }); + + it("should set hasIssues to true when a step can recover", async () => { + mockSteps[2].runDiagnostics.mockResolvedValue(false); + mockSteps[2].canRecover.mockReturnValue(true); + + await component.runDiagnostics(); + + expect(component.hasIssues()).toBe(true); + }); + + it("should set hasIssues to false when no step can recover", async () => { + mockSteps.forEach((step) => { + step.runDiagnostics.mockResolvedValue(true); + step.canRecover.mockReturnValue(false); + }); + + await component.runDiagnostics(); + + expect(component.hasIssues()).toBe(false); + }); + + it("should set diagnosticsCompleted and status to completed when complete", async () => { + await component.runDiagnostics(); + + expect(component.diagnosticsCompleted()).toBe(true); + expect(component.status()).toBe(StepStatus.Completed); + }); + }); + + describe("runRecovery", () => { + let mockSteps: MockProxy[]; + let mockWorkingData: RecoveryWorkingData; + + beforeEach(() => { + mockWorkingData = { + userId: mockUserId, + userKey: null as any, + isPrivateKeyCorrupt: false, + encryptedPrivateKey: null, + ciphers: [], + folders: [], + }; + + mockSteps = Array(5) + .fill(null) + .map(() => { + const mockStep = mock(); + mockStep.title = "mockStep"; + mockStep.canRecover.mockReturnValue(false); + mockStep.runRecovery.mockResolvedValue(); + mockStep.runDiagnostics.mockResolvedValue(true); + return mockStep; + }); + + component["recoverySteps"] = mockSteps; + component["workingData"] = mockWorkingData; + }); + + it("should not run if already running", async () => { + component["status"].set(StepStatus.InProgress); + await component.runRecovery(); + + expect(mockSteps[0].runRecovery).not.toHaveBeenCalled(); + }); + + it("should not run if workingData is null", async () => { + component["workingData"] = null; + await component.runRecovery(); + + expect(mockSteps[0].runRecovery).not.toHaveBeenCalled(); + }); + + it("should only run recovery for steps that can recover", async () => { + mockSteps[1].canRecover.mockReturnValue(true); + mockSteps[3].canRecover.mockReturnValue(true); + + await component.runRecovery(); + + expect(mockSteps[0].runRecovery).not.toHaveBeenCalled(); + expect(mockSteps[1].runRecovery).toHaveBeenCalled(); + expect(mockSteps[2].runRecovery).not.toHaveBeenCalled(); + expect(mockSteps[3].runRecovery).toHaveBeenCalled(); + expect(mockSteps[4].runRecovery).not.toHaveBeenCalled(); + }); + + it("should set recoveryCompleted and status when successful", async () => { + mockSteps[1].canRecover.mockReturnValue(true); + + await component.runRecovery(); + + expect(component.recoveryCompleted()).toBe(true); + expect(component.status()).toBe(StepStatus.Completed); + }); + + it("should set status to failed if recovery is cancelled", async () => { + mockSteps[1].canRecover.mockReturnValue(true); + mockSteps[1].runRecovery.mockRejectedValue(new Error("User cancelled")); + + await component.runRecovery(); + + expect(component.status()).toBe(StepStatus.Failed); + expect(component.recoveryCompleted()).toBe(false); + }); + + it("should re-run diagnostics after recovery completes", async () => { + mockSteps[1].canRecover.mockReturnValue(true); + + await component.runRecovery(); + + // Diagnostics should be called twice: once for initial diagnostic scan + mockSteps.forEach((step) => { + expect(step.runDiagnostics).toHaveBeenCalledWith(mockWorkingData, expect.anything()); + }); + }); + + it("should update hasIssues after re-running diagnostics", async () => { + // Setup initial state with an issue + mockSteps[1].canRecover.mockReturnValue(true); + mockSteps[1].runDiagnostics.mockResolvedValue(false); + + // After recovery completes, the issue should be fixed + mockSteps[1].runRecovery.mockImplementation(() => { + // Simulate recovery fixing the issue + mockSteps[1].canRecover.mockReturnValue(false); + mockSteps[1].runDiagnostics.mockResolvedValue(true); + return Promise.resolve(); + }); + + await component.runRecovery(); + + // Verify hasIssues is updated after re-running diagnostics + expect(component.hasIssues()).toBe(false); + }); + }); + + describe("saveDiagnosticLogs", () => { + it("should call fileDownloadService with log content", () => { + component.saveDiagnosticLogs(); + + expect(mockFileDownloadService.download).toHaveBeenCalledWith({ + fileName: expect.stringContaining("data-recovery-logs-"), + blobData: expect.any(String), + blobOptions: { type: "text/plain" }, + }); + }); + + it("should include timestamp in filename", () => { + component.saveDiagnosticLogs(); + + const downloadCall = mockFileDownloadService.download.mock.calls[0][0]; + expect(downloadCall.fileName).toMatch(/data-recovery-logs-\d{4}-\d{2}-\d{2}T.*\.txt/); + }); + }); +}); diff --git a/apps/web/src/app/key-management/data-recovery/data-recovery.component.ts b/apps/web/src/app/key-management/data-recovery/data-recovery.component.ts new file mode 100644 index 00000000000..31179dfb062 --- /dev/null +++ b/apps/web/src/app/key-management/data-recovery/data-recovery.component.ts @@ -0,0 +1,208 @@ +import { CommonModule } from "@angular/common"; +import { ChangeDetectionStrategy, Component, inject, signal } from "@angular/core"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service"; +import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { CipherEncryptionService } from "@bitwarden/common/vault/abstractions/cipher-encryption.service"; +import { FolderApiServiceAbstraction } from "@bitwarden/common/vault/abstractions/folder/folder-api.service.abstraction"; +import { ButtonModule, DialogService } from "@bitwarden/components"; +import { KeyService, UserAsymmetricKeysRegenerationService } from "@bitwarden/key-management"; +import { LogService } from "@bitwarden/logging"; + +import { SharedModule } from "../../shared"; + +import { LogRecorder } from "./log-recorder"; +import { + SyncStep, + UserInfoStep, + RecoveryStep, + PrivateKeyStep, + RecoveryWorkingData, + FolderStep, + CipherStep, +} from "./steps"; + +export const StepStatus = Object.freeze({ + NotStarted: 0, + InProgress: 1, + Completed: 2, + Failed: 3, +} as const); +export type StepStatus = (typeof StepStatus)[keyof typeof StepStatus]; + +interface StepState { + title: string; + status: StepStatus; + message?: string; +} + +@Component({ + selector: "app-data-recovery", + templateUrl: "data-recovery.component.html", + standalone: true, + imports: [JslibModule, ButtonModule, CommonModule, SharedModule], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class DataRecoveryComponent { + protected readonly StepStatus = StepStatus; + + private i18nService = inject(I18nService); + private apiService = inject(ApiService); + private accountService = inject(AccountService); + private keyService = inject(KeyService); + private folderApiService = inject(FolderApiServiceAbstraction); + private cipherEncryptService = inject(CipherEncryptionService); + private dialogService = inject(DialogService); + private privateKeyRegenerationService = inject(UserAsymmetricKeysRegenerationService); + private cryptoFunctionService = inject(CryptoFunctionService); + private logService = inject(LogService); + private fileDownloadService = inject(FileDownloadService); + + private logger: LogRecorder = new LogRecorder(this.logService); + private recoverySteps: RecoveryStep[] = [ + new UserInfoStep(this.accountService, this.keyService), + new SyncStep(this.apiService), + new PrivateKeyStep( + this.privateKeyRegenerationService, + this.dialogService, + this.cryptoFunctionService, + ), + new FolderStep(this.folderApiService, this.dialogService), + new CipherStep(this.apiService, this.cipherEncryptService, this.dialogService), + ]; + private workingData: RecoveryWorkingData | null = null; + + readonly status = signal(StepStatus.NotStarted); + readonly hasStarted = signal(false); + readonly diagnosticsCompleted = signal(false); + readonly recoveryCompleted = signal(false); + readonly steps = signal( + this.recoverySteps.map((step) => ({ + title: this.i18nService.t(step.title), + status: StepStatus.NotStarted, + })), + ); + readonly hasIssues = signal(false); + + runDiagnostics = async () => { + if (this.status() === StepStatus.InProgress) { + return; + } + + this.hasStarted.set(true); + this.status.set(StepStatus.InProgress); + this.diagnosticsCompleted.set(false); + + this.logger.record("Starting diagnostics..."); + this.workingData = { + userId: null, + userKey: null, + isPrivateKeyCorrupt: false, + encryptedPrivateKey: null, + ciphers: [], + folders: [], + }; + + await this.runDiagnosticsInternal(); + + this.status.set(StepStatus.Completed); + this.diagnosticsCompleted.set(true); + }; + + private async runDiagnosticsInternal() { + if (!this.workingData) { + this.logger.record("No working data available"); + return; + } + + const currentSteps = this.steps(); + let hasAnyFailures = false; + + for (let i = 0; i < this.recoverySteps.length; i++) { + const step = this.recoverySteps[i]; + currentSteps[i].status = StepStatus.InProgress; + this.steps.set([...currentSteps]); + + this.logger.record(`Running diagnostics for step: ${step.title}`); + try { + const success = await step.runDiagnostics(this.workingData, this.logger); + currentSteps[i].status = success ? StepStatus.Completed : StepStatus.Failed; + if (!success) { + hasAnyFailures = true; + } + this.steps.set([...currentSteps]); + this.logger.record(`Diagnostics completed for step: ${step.title}`); + } catch (error) { + currentSteps[i].status = StepStatus.Failed; + currentSteps[i].message = (error as Error).message; + this.steps.set([...currentSteps]); + this.logger.record( + `Diagnostics failed for step: ${step.title} with error: ${(error as Error).message}`, + ); + hasAnyFailures = true; + } + } + + if (hasAnyFailures) { + this.logger.record("Diagnostics completed with errors"); + } else { + this.logger.record("Diagnostics completed successfully"); + } + + // Check if any recovery can be performed + const canRecoverAnyStep = this.recoverySteps.some((step) => step.canRecover(this.workingData!)); + this.hasIssues.set(canRecoverAnyStep); + } + + runRecovery = async () => { + if (this.status() === StepStatus.InProgress || !this.workingData) { + return; + } + + this.status.set(StepStatus.InProgress); + this.recoveryCompleted.set(false); + + this.logger.record("Starting recovery process..."); + + try { + for (let i = 0; i < this.recoverySteps.length; i++) { + const step = this.recoverySteps[i]; + if (step.canRecover(this.workingData)) { + this.logger.record(`Running recovery for step: ${step.title}`); + await step.runRecovery(this.workingData, this.logger); + } + } + + this.logger.record("Recovery process completed"); + this.recoveryCompleted.set(true); + + // Re-run diagnostics after recovery + this.logger.record("Re-running diagnostics to verify recovery..."); + await this.runDiagnosticsInternal(); + + this.status.set(StepStatus.Completed); + } catch (error) { + this.logger.record(`Recovery process cancelled or failed: ${(error as Error).message}`); + this.status.set(StepStatus.Failed); + } + }; + + saveDiagnosticLogs = () => { + const logs = this.logger.getLogs(); + const timestamp = new Date().toISOString().replace(/[:.]/g, "-"); + const filename = `data-recovery-logs-${timestamp}.txt`; + + const logContent = logs.join("\n"); + this.fileDownloadService.download({ + fileName: filename, + blobData: logContent, + blobOptions: { type: "text/plain" }, + }); + + this.logger.record("Diagnostic logs saved"); + }; +} diff --git a/apps/web/src/app/key-management/data-recovery/log-recorder.ts b/apps/web/src/app/key-management/data-recovery/log-recorder.ts new file mode 100644 index 00000000000..1bca90de48d --- /dev/null +++ b/apps/web/src/app/key-management/data-recovery/log-recorder.ts @@ -0,0 +1,19 @@ +import { LogService } from "@bitwarden/logging"; + +/** + * Record logs during the data recovery process. This only keeps them in memory and does not persist them anywhere. + */ +export class LogRecorder { + private logs: string[] = []; + + constructor(private logService: LogService) {} + + record(message: string) { + this.logs.push(message); + this.logService.info(`[DataRecovery] ${message}`); + } + + getLogs(): string[] { + return [...this.logs]; + } +} diff --git a/apps/web/src/app/key-management/data-recovery/steps/cipher-step.ts b/apps/web/src/app/key-management/data-recovery/steps/cipher-step.ts new file mode 100644 index 00000000000..34e8cbdc9f3 --- /dev/null +++ b/apps/web/src/app/key-management/data-recovery/steps/cipher-step.ts @@ -0,0 +1,81 @@ +import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { CipherEncryptionService } from "@bitwarden/common/vault/abstractions/cipher-encryption.service"; +import { DialogService } from "@bitwarden/components"; + +import { LogRecorder } from "../log-recorder"; + +import { RecoveryStep, RecoveryWorkingData } from "./recovery-step"; + +export class CipherStep implements RecoveryStep { + title = "recoveryStepCipherTitle"; + + private undecryptableCipherIds: string[] = []; + + constructor( + private apiService: ApiService, + private cipherService: CipherEncryptionService, + private dialogService: DialogService, + ) {} + + async runDiagnostics(workingData: RecoveryWorkingData, logger: LogRecorder): Promise { + if (!workingData.userId) { + logger.record("Missing user ID"); + return false; + } + + this.undecryptableCipherIds = []; + for (const cipher of workingData.ciphers) { + try { + await this.cipherService.decrypt(cipher, workingData.userId); + } catch { + logger.record(`Cipher ID ${cipher.id} was undecryptable`); + this.undecryptableCipherIds.push(cipher.id); + } + } + logger.record(`Found ${this.undecryptableCipherIds.length} undecryptable ciphers`); + + return this.undecryptableCipherIds.length == 0; + } + + canRecover(workingData: RecoveryWorkingData): boolean { + return this.undecryptableCipherIds.length > 0; + } + + async runRecovery(workingData: RecoveryWorkingData, logger: LogRecorder): Promise { + // Recovery means deleting the broken ciphers. + if (this.undecryptableCipherIds.length === 0) { + logger.record("No undecryptable ciphers to recover"); + return; + } + + logger.record(`Showing confirmation dialog for ${this.undecryptableCipherIds.length} ciphers`); + + const confirmed = await this.dialogService.openSimpleDialog({ + title: { key: "recoveryDeleteCiphersTitle" }, + content: { key: "recoveryDeleteCiphersDesc" }, + acceptButtonText: { key: "ok" }, + cancelButtonText: { key: "cancel" }, + type: "danger", + }); + + if (!confirmed) { + logger.record("User cancelled cipher deletion"); + throw new Error("Cipher recovery cancelled by user"); + } + + logger.record(`Deleting ${this.undecryptableCipherIds.length} ciphers`); + + for (const cipherId of this.undecryptableCipherIds) { + try { + await this.apiService.deleteCipher(cipherId); + logger.record(`Deleted cipher ${cipherId}`); + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + logger.record(`Failed to delete cipher ${cipherId}: ${errorMessage}`); + throw error; + } + } + + logger.record(`Successfully deleted ${this.undecryptableCipherIds.length} ciphers`); + } +} diff --git a/apps/web/src/app/key-management/data-recovery/steps/folder-step.ts b/apps/web/src/app/key-management/data-recovery/steps/folder-step.ts new file mode 100644 index 00000000000..bc0ae31efba --- /dev/null +++ b/apps/web/src/app/key-management/data-recovery/steps/folder-step.ts @@ -0,0 +1,97 @@ +import { SdkLoadService } from "@bitwarden/common/platform/abstractions/sdk/sdk-load.service"; +import { FolderApiServiceAbstraction } from "@bitwarden/common/vault/abstractions/folder/folder-api.service.abstraction"; +import { DialogService } from "@bitwarden/components"; +import { PureCrypto } from "@bitwarden/sdk-internal"; + +import { LogRecorder } from "../log-recorder"; + +import { RecoveryStep, RecoveryWorkingData } from "./recovery-step"; + +export class FolderStep implements RecoveryStep { + title = "recoveryStepFoldersTitle"; + + private undecryptableFolderIds: string[] = []; + + constructor( + private folderService: FolderApiServiceAbstraction, + private dialogService: DialogService, + ) {} + + async runDiagnostics(workingData: RecoveryWorkingData, logger: LogRecorder): Promise { + if (!workingData.userKey) { + logger.record("Missing user key"); + return false; + } + + this.undecryptableFolderIds = []; + for (const folder of workingData.folders) { + if (!folder.name?.encryptedString) { + logger.record(`Folder ID ${folder.id} has no name`); + this.undecryptableFolderIds.push(folder.id); + continue; + } + try { + await SdkLoadService.Ready; + PureCrypto.symmetric_decrypt_string( + folder.name.encryptedString, + workingData.userKey.toEncoded(), + ); + } catch { + logger.record(`Folder name for folder ID ${folder.id} was undecryptable`); + this.undecryptableFolderIds.push(folder.id); + } + } + logger.record(`Found ${this.undecryptableFolderIds.length} undecryptable folders`); + + return this.undecryptableFolderIds.length == 0; + } + + canRecover(workingData: RecoveryWorkingData): boolean { + return this.undecryptableFolderIds.length > 0; + } + + async runRecovery(workingData: RecoveryWorkingData, logger: LogRecorder): Promise { + // Recovery means deleting the broken folders. + if (this.undecryptableFolderIds.length === 0) { + logger.record("No undecryptable folders to recover"); + return; + } + + if (!workingData.userId) { + logger.record("Missing user ID"); + throw new Error("Missing user ID"); + } + + logger.record(`Showing confirmation dialog for ${this.undecryptableFolderIds.length} folders`); + + const confirmed = await this.dialogService.openSimpleDialog({ + title: { key: "recoveryDeleteFoldersTitle" }, + content: { key: "recoveryDeleteFoldersDesc" }, + acceptButtonText: { key: "ok" }, + cancelButtonText: { key: "cancel" }, + type: "danger", + }); + + if (!confirmed) { + logger.record("User cancelled folder deletion"); + throw new Error("Folder recovery cancelled by user"); + } + + logger.record(`Deleting ${this.undecryptableFolderIds.length} folders`); + + for (const folderId of this.undecryptableFolderIds) { + try { + await this.folderService.delete(folderId, workingData.userId); + logger.record(`Deleted folder ${folderId}`); + } catch (error) { + logger.record(`Failed to delete folder ${folderId}: ${error}`); + } + } + + logger.record(`Successfully deleted ${this.undecryptableFolderIds.length} folders`); + } + + getUndecryptableFolderIds(): string[] { + return this.undecryptableFolderIds; + } +} diff --git a/apps/web/src/app/key-management/data-recovery/steps/index.ts b/apps/web/src/app/key-management/data-recovery/steps/index.ts new file mode 100644 index 00000000000..caf3cdb34ef --- /dev/null +++ b/apps/web/src/app/key-management/data-recovery/steps/index.ts @@ -0,0 +1,6 @@ +export * from "./sync-step"; +export * from "./user-info-step"; +export * from "./recovery-step"; +export * from "./private-key-step"; +export * from "./folder-step"; +export * from "./cipher-step"; diff --git a/apps/web/src/app/key-management/data-recovery/steps/private-key-step.ts b/apps/web/src/app/key-management/data-recovery/steps/private-key-step.ts new file mode 100644 index 00000000000..82c20c466b8 --- /dev/null +++ b/apps/web/src/app/key-management/data-recovery/steps/private-key-step.ts @@ -0,0 +1,93 @@ +import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service"; +import { SdkLoadService } from "@bitwarden/common/platform/abstractions/sdk/sdk-load.service"; +import { EncryptionType } from "@bitwarden/common/platform/enums"; +import { DialogService } from "@bitwarden/components"; +import { UserAsymmetricKeysRegenerationService } from "@bitwarden/key-management"; +import { PureCrypto } from "@bitwarden/sdk-internal"; + +import { LogRecorder } from "../log-recorder"; + +import { RecoveryStep, RecoveryWorkingData } from "./recovery-step"; + +export class PrivateKeyStep implements RecoveryStep { + title = "recoveryStepPrivateKeyTitle"; + + constructor( + private privateKeyRegenerationService: UserAsymmetricKeysRegenerationService, + private dialogService: DialogService, + private cryptoFunctionService: CryptoFunctionService, + ) {} + + async runDiagnostics(workingData: RecoveryWorkingData, logger: LogRecorder): Promise { + if (!workingData.userId || !workingData.userKey) { + logger.record("Missing user ID or user key"); + return false; + } + + // Make sure the private key decrypts properly and is not somehow encrypted by a different user key / broken during key rotation. + const encryptedPrivateKey = workingData.encryptedPrivateKey; + if (!encryptedPrivateKey) { + logger.record("No encrypted private key found"); + return false; + } + logger.record("Private key length: " + encryptedPrivateKey.length); + let privateKey: Uint8Array; + try { + await SdkLoadService.Ready; + privateKey = PureCrypto.unwrap_decapsulation_key( + encryptedPrivateKey, + workingData.userKey.toEncoded(), + ); + } catch { + logger.record("Private key was un-decryptable"); + workingData.isPrivateKeyCorrupt = true; + return false; + } + + // Make sure the contained private key can be parsed and the public key can be derived. If not, then the private key may be corrupt / generated with an incompatible ASN.1 representation / with incompatible padding. + try { + const publicKey = await this.cryptoFunctionService.rsaExtractPublicKey(privateKey); + logger.record("Public key length: " + publicKey.length); + } catch { + logger.record("Public key could not be derived; private key is corrupt"); + workingData.isPrivateKeyCorrupt = true; + return false; + } + + return true; + } + + canRecover(workingData: RecoveryWorkingData): boolean { + // Only support recovery on V1 users. + return ( + workingData.isPrivateKeyCorrupt && + workingData.userKey !== null && + workingData.userKey.inner().type === EncryptionType.AesCbc256_HmacSha256_B64 + ); + } + + async runRecovery(workingData: RecoveryWorkingData, logger: LogRecorder): Promise { + // The recovery step is to replace the key pair. Currently, this only works if the user is not using emergency access or is part of an organization. + // This is because this will break emergency access enrollments / organization memberships / provider memberships. + logger.record("Showing confirmation dialog for private key replacement"); + + const confirmed = await this.dialogService.openSimpleDialog({ + title: { key: "recoveryReplacePrivateKeyTitle" }, + content: { key: "recoveryReplacePrivateKeyDesc" }, + acceptButtonText: { key: "ok" }, + cancelButtonText: { key: "cancel" }, + type: "danger", + }); + + if (!confirmed) { + logger.record("User cancelled private key replacement"); + throw new Error("Private key recovery cancelled by user"); + } + + logger.record("Replacing private key"); + await this.privateKeyRegenerationService.regenerateUserPublicKeyEncryptionKeyPair( + workingData.userId!, + ); + logger.record("Private key replaced successfully"); + } +} diff --git a/apps/web/src/app/key-management/data-recovery/steps/recovery-step.ts b/apps/web/src/app/key-management/data-recovery/steps/recovery-step.ts new file mode 100644 index 00000000000..265d7c68284 --- /dev/null +++ b/apps/web/src/app/key-management/data-recovery/steps/recovery-step.ts @@ -0,0 +1,43 @@ +import { WrappedPrivateKey } from "@bitwarden/common/key-management/types"; +import { UserKey } from "@bitwarden/common/types/key"; +import { Cipher } from "@bitwarden/common/vault/models/domain/cipher"; +import { Folder } from "@bitwarden/common/vault/models/domain/folder"; +import { UserId } from "@bitwarden/user-core"; + +import { LogRecorder } from "../log-recorder"; + +/** + * A recovery step performs diagnostics and recovery actions on a specific domain, such as ciphers. + */ +export abstract class RecoveryStep { + /** Title of the recovery step, as an i18n key. */ + abstract title: string; + + /** + * Runs diagnostics on the provided working data. + * Returns true if no issues were found, false otherwise. + */ + abstract runDiagnostics(workingData: RecoveryWorkingData, logger: LogRecorder): Promise; + + /** + * Returns whether recovery can be performed + */ + abstract canRecover(workingData: RecoveryWorkingData): boolean; + + /** + * Performs recovery on the provided working data. + */ + abstract runRecovery(workingData: RecoveryWorkingData, logger: LogRecorder): Promise; +} + +/** + * Data used during the recovery process, passed between steps. + */ +export type RecoveryWorkingData = { + userId: UserId | null; + userKey: UserKey | null; + encryptedPrivateKey: WrappedPrivateKey | null; + isPrivateKeyCorrupt: boolean; + ciphers: Cipher[]; + folders: Folder[]; +}; diff --git a/apps/web/src/app/key-management/data-recovery/steps/sync-step.ts b/apps/web/src/app/key-management/data-recovery/steps/sync-step.ts new file mode 100644 index 00000000000..f0adb1e0b46 --- /dev/null +++ b/apps/web/src/app/key-management/data-recovery/steps/sync-step.ts @@ -0,0 +1,43 @@ +import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { CipherData } from "@bitwarden/common/vault/models/data/cipher.data"; +import { FolderData } from "@bitwarden/common/vault/models/data/folder.data"; +import { Cipher } from "@bitwarden/common/vault/models/domain/cipher"; +import { Folder } from "@bitwarden/common/vault/models/domain/folder"; + +import { LogRecorder } from "../log-recorder"; + +import { RecoveryStep, RecoveryWorkingData } from "./recovery-step"; + +export class SyncStep implements RecoveryStep { + title = "recoveryStepSyncTitle"; + + constructor(private apiService: ApiService) {} + + async runDiagnostics(workingData: RecoveryWorkingData, logger: LogRecorder): Promise { + // The intent of this step is to fetch the latest data from the server. Diagnostics does not + // ever run on local data but only remote data that is recent. + const response = await this.apiService.getSync(); + + workingData.ciphers = response.ciphers.map((c) => new Cipher(new CipherData(c))); + logger.record(`Fetched ${workingData.ciphers.length} ciphers from server`); + + workingData.folders = response.folders.map((f) => new Folder(new FolderData(f))); + logger.record(`Fetched ${workingData.folders.length} folders from server`); + + workingData.encryptedPrivateKey = + response.profile?.accountKeys?.publicKeyEncryptionKeyPair?.wrappedPrivateKey ?? null; + logger.record( + `Fetched encrypted private key of length ${workingData.encryptedPrivateKey?.length ?? 0} from server`, + ); + + return true; + } + + canRecover(workingData: RecoveryWorkingData): boolean { + return false; + } + + runRecovery(workingData: RecoveryWorkingData, logger: LogRecorder): Promise { + return Promise.resolve(); + } +} diff --git a/apps/web/src/app/key-management/data-recovery/steps/user-info-step.ts b/apps/web/src/app/key-management/data-recovery/steps/user-info-step.ts new file mode 100644 index 00000000000..9565b1da73b --- /dev/null +++ b/apps/web/src/app/key-management/data-recovery/steps/user-info-step.ts @@ -0,0 +1,49 @@ +import { firstValueFrom } from "rxjs"; + +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { EncryptionType } from "@bitwarden/common/platform/enums"; +import { KeyService } from "@bitwarden/key-management"; + +import { LogRecorder } from "../log-recorder"; + +import { RecoveryStep, RecoveryWorkingData } from "./recovery-step"; + +export class UserInfoStep implements RecoveryStep { + title = "recoveryStepUserInfoTitle"; + + constructor( + private accountService: AccountService, + private keyService: KeyService, + ) {} + + async runDiagnostics(workingData: RecoveryWorkingData, logger: LogRecorder): Promise { + const activeAccount = await firstValueFrom(this.accountService.activeAccount$); + if (!activeAccount) { + logger.record("No active account found"); + return false; + } + const userId = activeAccount.id; + workingData.userId = userId; + logger.record(`User ID: ${userId}`); + + const userKey = await firstValueFrom(this.keyService.userKey$(userId)); + if (!userKey) { + logger.record("No user key found"); + return false; + } + workingData.userKey = userKey; + logger.record( + `User encryption type: ${userKey.inner().type === EncryptionType.AesCbc256_HmacSha256_B64 ? "V1" : userKey.inner().type === EncryptionType.CoseEncrypt0 ? "Cose" : "Unknown"}`, + ); + + return true; + } + + canRecover(workingData: RecoveryWorkingData): boolean { + return false; + } + + runRecovery(workingData: RecoveryWorkingData, logger: LogRecorder): Promise { + return Promise.resolve(); + } +} diff --git a/apps/web/src/app/oss-routing.module.ts b/apps/web/src/app/oss-routing.module.ts index b40b9143991..ac9bdc4b946 100644 --- a/apps/web/src/app/oss-routing.module.ts +++ b/apps/web/src/app/oss-routing.module.ts @@ -78,6 +78,7 @@ import { freeTrialTextResolver } from "./billing/trial-initiation/complete-trial import { EnvironmentSelectorComponent } from "./components/environment-selector/environment-selector.component"; import { RouteDataProperties } from "./core"; import { ReportsModule } from "./dirt/reports"; +import { DataRecoveryComponent } from "./key-management/data-recovery/data-recovery.component"; import { ConfirmKeyConnectorDomainComponent } from "./key-management/key-connector/confirm-key-connector-domain.component"; import { RemovePasswordComponent } from "./key-management/key-connector/remove-password.component"; import { FrontendLayoutComponent } from "./layouts/frontend-layout.component"; @@ -696,6 +697,12 @@ const routes: Routes = [ path: "security", loadChildren: () => SecurityRoutingModule, }, + { + path: "data-recovery", + component: DataRecoveryComponent, + canActivate: [canAccessFeature(FeatureFlag.DataRecoveryTool)], + data: { titleId: "dataRecovery" } satisfies RouteDataProperties, + }, { path: "domain-rules", component: DomainRulesComponent, diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index a755e4de556..c827f09d173 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -12253,6 +12253,54 @@ "userVerificationFailed": { "message": "User verification failed." }, + "recoveryDeleteCiphersTitle": { + "message": "Delete unrecoverable vault items" + }, + "recoveryDeleteCiphersDesc": { + "message": "Some of your vault items could not be recovered. Do you want to delete these unrecoverable items from your vault?" + }, + "recoveryDeleteFoldersTitle": { + "message": "Delete unrecoverable folders" + }, + "recoveryDeleteFoldersDesc": { + "message": "Some of your folders could not be recovered. Do you want to delete these unrecoverable folders from your vault?" + }, + "recoveryReplacePrivateKeyTitle": { + "message": "Replace encryption key" + }, + "recoveryReplacePrivateKeyDesc": { + "message": "Your public-key encryption key pair could not be recovered. Do you want to replace your encryption key with a new key pair? This will require you to set up existing emergency-access and organization memberships again." + }, + "recoveryStepSyncTitle": { + "message": "Synchronizing data" + }, + "recoveryStepPrivateKeyTitle": { + "message": "Verifying encryption key integrity" + }, + "recoveryStepUserInfoTitle": { + "message": "Verifying user information" + }, + "recoveryStepCipherTitle": { + "message": "Verifying vault item integrity" + }, + "recoveryStepFoldersTitle": { + "message": "Verifying folder integrity" + }, + "dataRecoveryTitle": { + "message": "Data Recovery and Diagnostics" + }, + "dataRecoveryDescription": { + "message": "Use the data recovery tool to diagnose and repair issues with your account. After running diagnostics you have the option to save diagnostic logs for support and the option to repair any detected issues." + }, + "runDiagnostics": { + "message": "Run Diagnostics" + }, + "repairIssues": { + "message": "Repair Issues" + }, + "saveDiagnosticLogs": { + "message": "Save Diagnostic Logs" + }, "sessionTimeoutSettingsManagedByOrganization": { "message": "This setting is managed by your organization." }, diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts index 1727d3da712..fb8edd8aa7d 100644 --- a/libs/common/src/enums/feature-flag.enum.ts +++ b/libs/common/src/enums/feature-flag.enum.ts @@ -43,6 +43,7 @@ export enum FeatureFlag { LinuxBiometricsV2 = "pm-26340-linux-biometrics-v2", UnlockWithMasterPasswordUnlockData = "pm-23246-unlock-with-master-password-unlock-data", NoLogoutOnKdfChange = "pm-23995-no-logout-on-kdf-change", + DataRecoveryTool = "pm-28813-data-recovery-tool", ConsolidatedSessionTimeoutComponent = "pm-26056-consolidated-session-timeout-component", /* Tools */ @@ -149,6 +150,7 @@ export const DefaultFeatureFlagValue = { [FeatureFlag.LinuxBiometricsV2]: FALSE, [FeatureFlag.UnlockWithMasterPasswordUnlockData]: FALSE, [FeatureFlag.NoLogoutOnKdfChange]: FALSE, + [FeatureFlag.DataRecoveryTool]: FALSE, [FeatureFlag.ConsolidatedSessionTimeoutComponent]: FALSE, /* Platform */ diff --git a/libs/key-management/src/user-asymmetric-key-regeneration/abstractions/user-asymmetric-key-regeneration.service.ts b/libs/key-management/src/user-asymmetric-key-regeneration/abstractions/user-asymmetric-key-regeneration.service.ts index 4703d836db7..58620f49ed1 100644 --- a/libs/key-management/src/user-asymmetric-key-regeneration/abstractions/user-asymmetric-key-regeneration.service.ts +++ b/libs/key-management/src/user-asymmetric-key-regeneration/abstractions/user-asymmetric-key-regeneration.service.ts @@ -7,4 +7,11 @@ export abstract class UserAsymmetricKeysRegenerationService { * @param userId The user id. */ abstract regenerateIfNeeded(userId: UserId): Promise; + + /** + * Performs the regeneration of the user's public/private key pair without checking any preconditions. + * This should only be used for V1 encryption accounts + * @param userId The user id. + */ + abstract regenerateUserPublicKeyEncryptionKeyPair(userId: UserId): Promise; } diff --git a/libs/key-management/src/user-asymmetric-key-regeneration/services/default-user-asymmetric-key-regeneration.service.spec.ts b/libs/key-management/src/user-asymmetric-key-regeneration/services/default-user-asymmetric-key-regeneration.service.spec.ts index e57ab74de6b..92e5240a187 100644 --- a/libs/key-management/src/user-asymmetric-key-regeneration/services/default-user-asymmetric-key-regeneration.service.spec.ts +++ b/libs/key-management/src/user-asymmetric-key-regeneration/services/default-user-asymmetric-key-regeneration.service.spec.ts @@ -370,3 +370,52 @@ describe("regenerateIfNeeded", () => { ); }); }); + +describe("regenerateUserPublicKeyEncryptionKeyPair", () => { + let sut: DefaultUserAsymmetricKeysRegenerationService; + const userId = "userId" as UserId; + + let keyService: MockProxy; + let cipherService: MockProxy; + let userAsymmetricKeysRegenerationApiService: MockProxy; + let logService: MockProxy; + let sdkService: MockSdkService; + let apiService: MockProxy; + let configService: MockProxy; + + beforeEach(() => { + keyService = mock(); + cipherService = mock(); + userAsymmetricKeysRegenerationApiService = mock(); + logService = mock(); + sdkService = new MockSdkService(); + apiService = mock(); + configService = mock(); + + sut = new DefaultUserAsymmetricKeysRegenerationService( + keyService, + cipherService, + userAsymmetricKeysRegenerationApiService, + logService, + sdkService, + apiService, + configService, + ); + }); + + afterEach(() => { + jest.resetAllMocks(); + }); + + it("should throw error when user key is not V1 encryption type", async () => { + const mockUserKey = { + keyB64: "mockKeyB64", + inner: () => ({ type: 7 }), + } as unknown as UserKey; + keyService.userKey$.mockReturnValue(of(mockUserKey)); + + await expect(sut.regenerateUserPublicKeyEncryptionKeyPair(userId)).rejects.toThrow( + "User key is not V1 encryption type", + ); + }); +}); diff --git a/libs/key-management/src/user-asymmetric-key-regeneration/services/default-user-asymmetric-key-regeneration.service.ts b/libs/key-management/src/user-asymmetric-key-regeneration/services/default-user-asymmetric-key-regeneration.service.ts index 335f45b0ce2..48fe3a1686f 100644 --- a/libs/key-management/src/user-asymmetric-key-regeneration/services/default-user-asymmetric-key-regeneration.service.ts +++ b/libs/key-management/src/user-asymmetric-key-regeneration/services/default-user-asymmetric-key-regeneration.service.ts @@ -37,7 +37,7 @@ export class DefaultUserAsymmetricKeysRegenerationService if (privateKeyRegenerationFlag) { const shouldRegenerate = await this.shouldRegenerate(userId); if (shouldRegenerate) { - await this.regenerateUserAsymmetricKeys(userId); + await this.regenerateUserPublicKeyEncryptionKeyPair(userId); } } } catch (error) { @@ -125,11 +125,14 @@ export class DefaultUserAsymmetricKeysRegenerationService return false; } - private async regenerateUserAsymmetricKeys(userId: UserId): Promise { + async regenerateUserPublicKeyEncryptionKeyPair(userId: UserId): Promise { const userKey = await firstValueFrom(this.keyService.userKey$(userId)); if (userKey == null) { throw new Error("User key not found"); } + if (userKey.inner().type !== EncryptionType.AesCbc256_HmacSha256_B64) { + throw new Error("User key is not V1 encryption type"); + } const makeKeyPairResponse = await firstValueFrom( this.sdkService.client$.pipe( map((sdk) => { From 3e9db6b472a04d94da214336add1fa4a3814a48e Mon Sep 17 00:00:00 2001 From: bmbitwarden Date: Tue, 9 Dec 2025 23:49:58 -0500 Subject: [PATCH 023/188] PM-27628 Rename Remove individual vault export policy (#17335) * PM-27628 conditions for send and export links in left navbar * PM-27628 resolved claude comment for pr * PM-27628 resolved claude comment for pr * PM-27628 reverted earlier display conditionals and changed label * PM-27628 changed out keys as well * PM-27628 revert description key change --- apps/web/src/locales/en/messages.json | 4 ++-- .../disable-personal-vault-export.component.ts | 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 c827f09d173..85159c0230c 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -6815,8 +6815,8 @@ "vaultTimeoutRangeError": { "message": "Vault timeout is not within allowed range." }, - "disablePersonalVaultExport": { - "message": "Remove individual vault export" + "disableExport": { + "message": "Remove export" }, "disablePersonalVaultExportDescription": { "message": "Do not allow members to export data from their individual vault." diff --git a/bitwarden_license/bit-web/src/app/admin-console/policies/policy-edit-definitions/disable-personal-vault-export.component.ts b/bitwarden_license/bit-web/src/app/admin-console/policies/policy-edit-definitions/disable-personal-vault-export.component.ts index 0f0fc5f358d..5a9b36912c9 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/policies/policy-edit-definitions/disable-personal-vault-export.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/policies/policy-edit-definitions/disable-personal-vault-export.component.ts @@ -8,7 +8,7 @@ import { import { SharedModule } from "@bitwarden/web-vault/app/shared"; export class DisablePersonalVaultExportPolicy extends BasePolicyEditDefinition { - name = "disablePersonalVaultExport"; + name = "disableExport"; description = "disablePersonalVaultExportDescription"; type = PolicyType.DisablePersonalVaultExport; component = DisablePersonalVaultExportPolicyComponent; From 663ef60ae5113115c092578193cf52c504f42572 Mon Sep 17 00:00:00 2001 From: Isaac Ivins Date: Wed, 10 Dec 2025 04:02:30 -0500 Subject: [PATCH 024/188] Feature/pm 27795 migrate send filters desktop migration (#17802) Created a new navigation component that renders Send type filters as sidebar navigation items. --- .../app/layout/desktop-layout.component.html | 2 +- .../layout/desktop-layout.component.spec.ts | 25 ++- .../app/layout/desktop-layout.component.ts | 11 +- .../send-v2/send-filters-nav.component.html | 25 +++ .../send-filters-nav.component.spec.ts | 204 ++++++++++++++++++ .../send-v2/send-filters-nav.component.ts | 54 +++++ .../tools/send-v2/send-v2.component.spec.ts | 34 ++- .../app/tools/send-v2/send-v2.component.ts | 44 +++- 8 files changed, 382 insertions(+), 17 deletions(-) create mode 100644 apps/desktop/src/app/tools/send-v2/send-filters-nav.component.html create mode 100644 apps/desktop/src/app/tools/send-v2/send-filters-nav.component.spec.ts create mode 100644 apps/desktop/src/app/tools/send-v2/send-filters-nav.component.ts diff --git a/apps/desktop/src/app/layout/desktop-layout.component.html b/apps/desktop/src/app/layout/desktop-layout.component.html index 7f8bd265102..1717b29acd1 100644 --- a/apps/desktop/src/app/layout/desktop-layout.component.html +++ b/apps/desktop/src/app/layout/desktop-layout.component.html @@ -3,7 +3,7 @@ - + diff --git a/apps/desktop/src/app/layout/desktop-layout.component.spec.ts b/apps/desktop/src/app/layout/desktop-layout.component.spec.ts index cc2f7e58dfb..74cddd02495 100644 --- a/apps/desktop/src/app/layout/desktop-layout.component.spec.ts +++ b/apps/desktop/src/app/layout/desktop-layout.component.spec.ts @@ -1,3 +1,4 @@ +import { ChangeDetectionStrategy, Component } from "@angular/core"; import { ComponentFixture, TestBed } from "@angular/core/testing"; import { RouterModule } from "@angular/router"; import { mock } from "jest-mock-extended"; @@ -5,8 +6,18 @@ import { mock } from "jest-mock-extended"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { NavigationModule } from "@bitwarden/components"; +import { SendFiltersNavComponent } from "../tools/send-v2/send-filters-nav.component"; + import { DesktopLayoutComponent } from "./desktop-layout.component"; +// Mock the child component to isolate DesktopLayoutComponent testing +@Component({ + selector: "app-send-filters-nav", + template: "", + changeDetection: ChangeDetectionStrategy.OnPush, +}) +class MockSendFiltersNavComponent {} + Object.defineProperty(window, "matchMedia", { writable: true, value: jest.fn().mockImplementation((query) => ({ @@ -34,7 +45,12 @@ describe("DesktopLayoutComponent", () => { useValue: mock(), }, ], - }).compileComponents(); + }) + .overrideComponent(DesktopLayoutComponent, { + remove: { imports: [SendFiltersNavComponent] }, + add: { imports: [MockSendFiltersNavComponent] }, + }) + .compileComponents(); fixture = TestBed.createComponent(DesktopLayoutComponent); component = fixture.componentInstance; @@ -58,4 +74,11 @@ describe("DesktopLayoutComponent", () => { expect(ngContent).toBeTruthy(); }); + + it("renders send filters navigation component", () => { + const compiled = fixture.nativeElement; + const sendFiltersNav = compiled.querySelector("app-send-filters-nav"); + + expect(sendFiltersNav).toBeTruthy(); + }); }); diff --git a/apps/desktop/src/app/layout/desktop-layout.component.ts b/apps/desktop/src/app/layout/desktop-layout.component.ts index 006055f475f..0ee7065fba8 100644 --- a/apps/desktop/src/app/layout/desktop-layout.component.ts +++ b/apps/desktop/src/app/layout/desktop-layout.component.ts @@ -5,13 +5,22 @@ import { PasswordManagerLogo } from "@bitwarden/assets/svg"; import { LayoutComponent, NavigationModule } from "@bitwarden/components"; import { I18nPipe } from "@bitwarden/ui-common"; +import { SendFiltersNavComponent } from "../tools/send-v2/send-filters-nav.component"; + import { DesktopSideNavComponent } from "./desktop-side-nav.component"; // FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush // eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "app-layout", - imports: [RouterModule, I18nPipe, LayoutComponent, NavigationModule, DesktopSideNavComponent], + imports: [ + RouterModule, + I18nPipe, + LayoutComponent, + NavigationModule, + DesktopSideNavComponent, + SendFiltersNavComponent, + ], templateUrl: "./desktop-layout.component.html", }) export class DesktopLayoutComponent { diff --git a/apps/desktop/src/app/tools/send-v2/send-filters-nav.component.html b/apps/desktop/src/app/tools/send-v2/send-filters-nav.component.html new file mode 100644 index 00000000000..64c52b50a49 --- /dev/null +++ b/apps/desktop/src/app/tools/send-v2/send-filters-nav.component.html @@ -0,0 +1,25 @@ + + + + + diff --git a/apps/desktop/src/app/tools/send-v2/send-filters-nav.component.spec.ts b/apps/desktop/src/app/tools/send-v2/send-filters-nav.component.spec.ts new file mode 100644 index 00000000000..95ba5c53e36 --- /dev/null +++ b/apps/desktop/src/app/tools/send-v2/send-filters-nav.component.spec.ts @@ -0,0 +1,204 @@ +import { ChangeDetectionStrategy, Component } from "@angular/core"; +import { ComponentFixture, TestBed } from "@angular/core/testing"; +import { Router, provideRouter } from "@angular/router"; +import { RouterTestingHarness } from "@angular/router/testing"; +import { BehaviorSubject } from "rxjs"; + +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { SendType } from "@bitwarden/common/tools/send/enums/send-type"; +import { NavigationModule } from "@bitwarden/components"; +import { SendListFiltersService } from "@bitwarden/send-ui"; + +import { SendFiltersNavComponent } from "./send-filters-nav.component"; + +@Component({ template: "", changeDetection: ChangeDetectionStrategy.OnPush }) +class DummyComponent {} + +Object.defineProperty(window, "matchMedia", { + writable: true, + value: jest.fn().mockImplementation((query) => ({ + matches: true, + media: query, + onchange: null, + addListener: jest.fn(), + removeListener: jest.fn(), + addEventListener: jest.fn(), + removeEventListener: jest.fn(), + dispatchEvent: jest.fn(), + })), +}); + +describe("SendFiltersNavComponent", () => { + let component: SendFiltersNavComponent; + let fixture: ComponentFixture; + let harness: RouterTestingHarness; + let filterFormValueSubject: BehaviorSubject<{ sendType: SendType | null }>; + let mockSendListFiltersService: Partial; + + beforeEach(async () => { + filterFormValueSubject = new BehaviorSubject<{ sendType: SendType | null }>({ + sendType: null, + }); + + mockSendListFiltersService = { + filterForm: { + value: { sendType: null }, + valueChanges: filterFormValueSubject.asObservable(), + patchValue: jest.fn((value) => { + mockSendListFiltersService.filterForm.value = { + ...mockSendListFiltersService.filterForm.value, + ...value, + }; + filterFormValueSubject.next(mockSendListFiltersService.filterForm.value); + }), + } as any, + filters$: filterFormValueSubject.asObservable(), + }; + + await TestBed.configureTestingModule({ + imports: [SendFiltersNavComponent, NavigationModule], + providers: [ + provideRouter([ + { path: "vault", component: DummyComponent }, + { path: "new-sends", component: DummyComponent }, + ]), + { + provide: SendListFiltersService, + useValue: mockSendListFiltersService, + }, + { + provide: I18nService, + useValue: { + t: jest.fn((key) => key), + }, + }, + ], + }).compileComponents(); + + // Create harness and navigate to initial route + harness = await RouterTestingHarness.create("/vault"); + + // Create the component fixture separately (not a routed component) + fixture = TestBed.createComponent(SendFiltersNavComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it("creates component", () => { + expect(component).toBeTruthy(); + }); + + it("renders bit-nav-group with Send icon and text", () => { + const compiled = fixture.nativeElement; + const navGroup = compiled.querySelector("bit-nav-group"); + + expect(navGroup).toBeTruthy(); + expect(navGroup.getAttribute("icon")).toBe("bwi-send"); + }); + + it("component exposes SendType enum for template", () => { + expect(component["SendType"]).toBe(SendType); + }); + + describe("isSendRouteActive", () => { + it("returns true when on /new-sends route", async () => { + await harness.navigateByUrl("/new-sends"); + fixture.detectChanges(); + + expect(component["isSendRouteActive"]()).toBe(true); + }); + + it("returns false when not on /new-sends route", () => { + expect(component["isSendRouteActive"]()).toBe(false); + }); + }); + + describe("activeSendType", () => { + it("returns the active send type when on send route and filter type is set", async () => { + await harness.navigateByUrl("/new-sends"); + mockSendListFiltersService.filterForm.value = { sendType: SendType.Text }; + filterFormValueSubject.next({ sendType: SendType.Text }); + fixture.detectChanges(); + + expect(component["activeSendType"]()).toBe(SendType.Text); + }); + + it("returns undefined when not on send route", () => { + mockSendListFiltersService.filterForm.value = { sendType: SendType.Text }; + filterFormValueSubject.next({ sendType: SendType.Text }); + fixture.detectChanges(); + + expect(component["activeSendType"]()).toBeUndefined(); + }); + + it("returns null when on send route but no type is selected", async () => { + await harness.navigateByUrl("/new-sends"); + mockSendListFiltersService.filterForm.value = { sendType: null }; + filterFormValueSubject.next({ sendType: null }); + fixture.detectChanges(); + + expect(component["activeSendType"]()).toBeNull(); + }); + }); + + describe("selectTypeAndNavigate", () => { + it("clears the sendType filter when called with no parameter", async () => { + await component["selectTypeAndNavigate"](); + + expect(mockSendListFiltersService.filterForm.patchValue).toHaveBeenCalledWith({ + sendType: null, + }); + }); + + it("updates filter form with Text type", async () => { + await component["selectTypeAndNavigate"](SendType.Text); + + expect(mockSendListFiltersService.filterForm.patchValue).toHaveBeenCalledWith({ + sendType: SendType.Text, + }); + }); + + it("updates filter form with File type", async () => { + await component["selectTypeAndNavigate"](SendType.File); + + expect(mockSendListFiltersService.filterForm.patchValue).toHaveBeenCalledWith({ + sendType: SendType.File, + }); + }); + + it("navigates to /new-sends when not on send route", async () => { + expect(harness.routeNativeElement?.textContent).toBeDefined(); + + await component["selectTypeAndNavigate"](SendType.Text); + + const currentUrl = TestBed.inject(Router).url; + expect(currentUrl).toBe("/new-sends"); + expect(mockSendListFiltersService.filterForm.patchValue).toHaveBeenCalledWith({ + sendType: SendType.Text, + }); + }); + + it("does not navigate when already on send route (component is reactive)", async () => { + await harness.navigateByUrl("/new-sends"); + const router = TestBed.inject(Router); + const navigateSpy = jest.spyOn(router, "navigate"); + + await component["selectTypeAndNavigate"](SendType.Text); + + expect(navigateSpy).not.toHaveBeenCalled(); + expect(mockSendListFiltersService.filterForm.patchValue).toHaveBeenCalledWith({ + sendType: SendType.Text, + }); + }); + + it("navigates when clearing filter from different route", async () => { + await component["selectTypeAndNavigate"](); // No parameter = clear filter + + const currentUrl = TestBed.inject(Router).url; + expect(currentUrl).toBe("/new-sends"); + expect(mockSendListFiltersService.filterForm.patchValue).toHaveBeenCalledWith({ + sendType: null, + }); + }); + }); +}); diff --git a/apps/desktop/src/app/tools/send-v2/send-filters-nav.component.ts b/apps/desktop/src/app/tools/send-v2/send-filters-nav.component.ts new file mode 100644 index 00000000000..28004f475e5 --- /dev/null +++ b/apps/desktop/src/app/tools/send-v2/send-filters-nav.component.ts @@ -0,0 +1,54 @@ +import { CommonModule } from "@angular/common"; +import { ChangeDetectionStrategy, Component, computed, inject } from "@angular/core"; +import { toSignal } from "@angular/core/rxjs-interop"; +import { NavigationEnd, Router } from "@angular/router"; +import { filter, map, startWith } from "rxjs"; + +import { SendType } from "@bitwarden/common/tools/send/enums/send-type"; +import { NavigationModule } from "@bitwarden/components"; +import { SendListFiltersService } from "@bitwarden/send-ui"; +import { I18nPipe } from "@bitwarden/ui-common"; + +/** + * Navigation component that renders Send filter options in the sidebar. + * Fully reactive using signals - no manual subscriptions or method-based computed values. + * - Parent "Send" nav-group clears filter (shows all sends) + * - Child "Text"/"File" items set filter to specific type + * - Active states computed reactively from filter signal + route signal + */ +@Component({ + selector: "app-send-filters-nav", + templateUrl: "./send-filters-nav.component.html", + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [CommonModule, NavigationModule, I18nPipe], +}) +export class SendFiltersNavComponent { + protected readonly SendType = SendType; + private readonly filtersService = inject(SendListFiltersService); + private readonly router = inject(Router); + private readonly currentFilter = toSignal(this.filtersService.filters$); + + // Track whether current route is the send route + private readonly isSendRouteActive = toSignal( + this.router.events.pipe( + filter((event) => event instanceof NavigationEnd), + map((event) => (event as NavigationEnd).urlAfterRedirects.includes("/new-sends")), + startWith(this.router.url.includes("/new-sends")), + ), + { initialValue: this.router.url.includes("/new-sends") }, + ); + + // Computed: Active send type (null when on send route with no filter, undefined when not on send route) + protected readonly activeSendType = computed(() => { + return this.isSendRouteActive() ? this.currentFilter()?.sendType : undefined; + }); + + // Update send filter and navigate to /new-sends (only if not already there - send-v2 component reacts to filter changes) + protected async selectTypeAndNavigate(type?: SendType): Promise { + this.filtersService.filterForm.patchValue({ sendType: type !== undefined ? type : null }); + + if (!this.router.url.includes("/new-sends")) { + await this.router.navigate(["/new-sends"]); + } + } +} diff --git a/apps/desktop/src/app/tools/send-v2/send-v2.component.spec.ts b/apps/desktop/src/app/tools/send-v2/send-v2.component.spec.ts index 5798df0989d..8657f3e375e 100644 --- a/apps/desktop/src/app/tools/send-v2/send-v2.component.spec.ts +++ b/apps/desktop/src/app/tools/send-v2/send-v2.component.spec.ts @@ -1,4 +1,8 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore +import { ChangeDetectorRef } from "@angular/core"; import { ComponentFixture, TestBed } from "@angular/core/testing"; +import { FormBuilder } from "@angular/forms"; import { mock, MockProxy } from "jest-mock-extended"; import { BehaviorSubject, of } from "rxjs"; @@ -15,6 +19,7 @@ import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.s import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction"; import { SearchService } from "@bitwarden/common/vault/abstractions/search.service"; import { DialogService, ToastService } from "@bitwarden/components"; +import { SendListFiltersService } from "@bitwarden/send-ui"; import * as utils from "../../../utils"; import { SearchBarService } from "../../layout/search/search-bar.service"; @@ -35,6 +40,8 @@ describe("SendV2Component", () => { let broadcasterService: MockProxy; let accountService: MockProxy; let policyService: MockProxy; + let sendListFiltersService: SendListFiltersService; + let changeDetectorRef: MockProxy; beforeEach(async () => { sendService = mock(); @@ -42,6 +49,13 @@ describe("SendV2Component", () => { broadcasterService = mock(); accountService = mock(); policyService = mock(); + changeDetectorRef = mock(); + + // Create real SendListFiltersService with mocked dependencies + const formBuilder = new FormBuilder(); + const i18nService = mock(); + i18nService.t.mockImplementation((key: string) => key); + sendListFiltersService = new SendListFiltersService(i18nService, formBuilder); // Mock sendViews$ observable sendService.sendViews$ = of([]); @@ -51,6 +65,10 @@ describe("SendV2Component", () => { accountService.activeAccount$ = of({ id: "test-user-id" } as any); policyService.policyAppliesToUser$ = jest.fn().mockReturnValue(of(false)); + // Mock SearchService methods needed by base component + const mockSearchService = mock(); + mockSearchService.isSearchable.mockResolvedValue(false); + await TestBed.configureTestingModule({ imports: [SendV2Component], providers: [ @@ -59,7 +77,7 @@ describe("SendV2Component", () => { { provide: PlatformUtilsService, useValue: mock() }, { provide: EnvironmentService, useValue: mock() }, { provide: BroadcasterService, useValue: broadcasterService }, - { provide: SearchService, useValue: mock() }, + { provide: SearchService, useValue: mockSearchService }, { provide: PolicyService, useValue: policyService }, { provide: SearchBarService, useValue: searchBarService }, { provide: LogService, useValue: mock() }, @@ -67,6 +85,8 @@ describe("SendV2Component", () => { { provide: DialogService, useValue: mock() }, { provide: ToastService, useValue: mock() }, { provide: AccountService, useValue: accountService }, + { provide: SendListFiltersService, useValue: sendListFiltersService }, + { provide: ChangeDetectorRef, useValue: changeDetectorRef }, ], }).compileComponents(); @@ -331,7 +351,6 @@ describe("SendV2Component", () => { describe("load", () => { it("sets loading states correctly", async () => { jest.spyOn(component, "search").mockResolvedValue(); - jest.spyOn(component, "selectAll"); expect(component.loaded).toBeFalsy(); @@ -341,14 +360,17 @@ describe("SendV2Component", () => { expect(component.loaded).toBe(true); }); - it("calls selectAll when onSuccessfulLoad is not set", async () => { + it("sets up sendViews$ subscription", async () => { + const mockSends = [new SendView(), new SendView()]; + sendService.sendViews$ = of(mockSends); jest.spyOn(component, "search").mockResolvedValue(); - jest.spyOn(component, "selectAll"); - component.onSuccessfulLoad = null; await component.load(); - expect(component.selectAll).toHaveBeenCalled(); + // Give observable time to emit + await new Promise((resolve) => setTimeout(resolve, 10)); + + expect(component.sends).toEqual(mockSends); }); it("calls onSuccessfulLoad when it is set", async () => { diff --git a/apps/desktop/src/app/tools/send-v2/send-v2.component.ts b/apps/desktop/src/app/tools/send-v2/send-v2.component.ts index 4afe02d9f98..eb0856b76af 100644 --- a/apps/desktop/src/app/tools/send-v2/send-v2.component.ts +++ b/apps/desktop/src/app/tools/send-v2/send-v2.component.ts @@ -2,8 +2,9 @@ // @ts-strict-ignore import { CommonModule } from "@angular/common"; import { Component, OnInit, OnDestroy, ViewChild, NgZone, ChangeDetectorRef } from "@angular/core"; +import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { FormsModule } from "@angular/forms"; -import { mergeMap } from "rxjs"; +import { mergeMap, Subscription } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { SendComponent as BaseSendComponent } from "@bitwarden/angular/tools/send/send.component"; @@ -14,11 +15,13 @@ import { EnvironmentService } from "@bitwarden/common/platform/abstractions/envi 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 { SendType } from "@bitwarden/common/tools/send/enums/send-type"; import { SendView } from "@bitwarden/common/tools/send/models/view/send.view"; import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction"; import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction"; import { SearchService } from "@bitwarden/common/vault/abstractions/search.service"; import { DialogService, ToastService } from "@bitwarden/components"; +import { SendListFiltersService } from "@bitwarden/send-ui"; import { invokeMenu, RendererMenuItem } from "../../../utils"; import { SearchBarService } from "../../layout/search/search-bar.service"; @@ -55,6 +58,9 @@ export class SendV2Component extends BaseSendComponent implements OnInit, OnDest // Tracks the current UI state: viewing list (None), adding new Send (Add), or editing existing Send (Edit) action: Action = Action.None; + // Subscription for sendViews$ cleanup + private sendViewsSubscription: Subscription; + constructor( sendService: SendService, i18nService: I18nService, @@ -71,6 +77,7 @@ export class SendV2Component extends BaseSendComponent implements OnInit, OnDest toastService: ToastService, accountService: AccountService, private cdr: ChangeDetectorRef, + private sendListFiltersService: SendListFiltersService, ) { super( sendService, @@ -88,12 +95,17 @@ export class SendV2Component extends BaseSendComponent implements OnInit, OnDest ); // Listen to search bar changes and update the Send list filter - // eslint-disable-next-line rxjs-angular/prefer-takeuntil - this.searchBarService.searchText$.subscribe((searchText) => { + this.searchBarService.searchText$.pipe(takeUntilDestroyed()).subscribe((searchText) => { this.searchText = searchText; this.searchTextChanged(); - setTimeout(() => this.cdr.detectChanges(), 250); }); + + // Listen to filter changes from sidebar navigation + this.sendListFiltersService.filterForm.valueChanges + .pipe(takeUntilDestroyed()) + .subscribe((filters) => { + this.applySendTypeFilter(filters); + }); } // Initialize the component: enable search bar, subscribe to sync events, and load Send items @@ -103,6 +115,10 @@ export class SendV2Component extends BaseSendComponent implements OnInit, OnDest await super.ngOnInit(); + // Read current filter synchronously to avoid race condition on navigation + const currentFilter = this.sendListFiltersService.filterForm.value; + this.applySendTypeFilter(currentFilter); + // Listen for sync completion events to refresh the Send list 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. @@ -118,8 +134,18 @@ export class SendV2Component extends BaseSendComponent implements OnInit, OnDest await this.load(); } + // Apply send type filter to display: centralized logic for initial load and filter changes + private applySendTypeFilter(filters: Partial<{ sendType: SendType | null }>): void { + if (filters.sendType === null || filters.sendType === undefined) { + this.selectAll(); + } else { + this.selectType(filters.sendType); + } + } + // Clean up subscriptions and disable search bar when component is destroyed ngOnDestroy() { + this.sendViewsSubscription?.unsubscribe(); this.broadcasterService.unsubscribe(BroadcasterSubscriptionId); this.searchBarService.setEnabled(false); } @@ -130,7 +156,12 @@ export class SendV2Component extends BaseSendComponent implements OnInit, OnDest // Note: The filter parameter is ignored in this implementation for desktop-specific behavior. async load(filter: (send: SendView) => boolean = null) { this.loading = true; - this.sendService.sendViews$ + + // Recreate subscription on each load (required for sync refresh) + // Manual cleanup in ngOnDestroy is intentional - load() is called multiple times + this.sendViewsSubscription?.unsubscribe(); + + this.sendViewsSubscription = this.sendService.sendViews$ .pipe( mergeMap(async (sends) => { this.sends = sends; @@ -143,9 +174,6 @@ export class SendV2Component extends BaseSendComponent implements OnInit, OnDest .subscribe(); if (this.onSuccessfulLoad != null) { await this.onSuccessfulLoad(); - } else { - // Default action - this.selectAll(); } this.loading = false; this.loaded = true; From 6e383ecbc6cfe68f73959d38c49227425b064226 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 10 Dec 2025 13:23:04 +0100 Subject: [PATCH 025/188] [deps]: Update peter-evans/repository-dispatch action to v4.0.1 (#17891) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/test-browser-interactions.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-browser-interactions.yml b/.github/workflows/test-browser-interactions.yml index dfc0f28b9c6..c8f4c959c52 100644 --- a/.github/workflows/test-browser-interactions.yml +++ b/.github/workflows/test-browser-interactions.yml @@ -75,7 +75,7 @@ jobs: - name: Trigger test-all workflow in browser-interactions-testing if: steps.changed-files.outputs.monitored == 'true' - uses: peter-evans/repository-dispatch@5fc4efd1a4797ddb68ffd0714a238564e4cc0e6f # v4.0.0 + uses: peter-evans/repository-dispatch@28959ce8df70de7be546dd1250a005dd32156697 # v4.0.1 with: token: ${{ steps.app-token.outputs.token }} repository: "bitwarden/browser-interactions-testing" From 0301e9d1d7ad1c42127451372c66a99ff7bdff49 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 10 Dec 2025 07:57:58 -0600 Subject: [PATCH 026/188] [deps]: Update Rust crate tokio-util to v0.7.17 (#17575) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- apps/desktop/desktop_native/Cargo.lock | 4 ++-- apps/desktop/desktop_native/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/desktop/desktop_native/Cargo.lock b/apps/desktop/desktop_native/Cargo.lock index cf946f7f204..1b98e677ac7 100644 --- a/apps/desktop/desktop_native/Cargo.lock +++ b/apps/desktop/desktop_native/Cargo.lock @@ -3329,9 +3329,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.13" +version = "0.7.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" +checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594" dependencies = [ "bytes", "futures-core", diff --git a/apps/desktop/desktop_native/Cargo.toml b/apps/desktop/desktop_native/Cargo.toml index 59df7ba57fb..b492fc62e2a 100644 --- a/apps/desktop/desktop_native/Cargo.toml +++ b/apps/desktop/desktop_native/Cargo.toml @@ -63,7 +63,7 @@ ssh-key = { version = "=0.6.7", default-features = false } sysinfo = "=0.37.2" thiserror = "=2.0.17" tokio = "=1.45.0" -tokio-util = "=0.7.13" +tokio-util = "=0.7.17" tracing = "=0.1.41" tracing-subscriber = { version = "=0.3.20", features = [ "fmt", From 151c2d97f07e47cbce92593c83ae03c6193bd2a9 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 10 Dec 2025 14:13:24 +0000 Subject: [PATCH 027/188] [deps]: Update Rust crate tokio to v1.48.0 (#15700) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- apps/desktop/desktop_native/Cargo.lock | 77 +++----------------------- apps/desktop/desktop_native/Cargo.toml | 2 +- 2 files changed, 9 insertions(+), 70 deletions(-) diff --git a/apps/desktop/desktop_native/Cargo.lock b/apps/desktop/desktop_native/Cargo.lock index 1b98e677ac7..692ebfa29e9 100644 --- a/apps/desktop/desktop_native/Cargo.lock +++ b/apps/desktop/desktop_native/Cargo.lock @@ -2,21 +2,6 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "addr2line" -version = "0.24.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler2" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" - [[package]] name = "aead" version = "0.5.2" @@ -351,21 +336,6 @@ dependencies = [ "windows-core", ] -[[package]] -name = "backtrace" -version = "0.3.75" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" -dependencies = [ - "addr2line", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", - "windows-targets 0.52.6", -] - [[package]] name = "base16ct" version = "0.2.0" @@ -1415,12 +1385,6 @@ dependencies = [ "polyval", ] -[[package]] -name = "gimli" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" - [[package]] name = "glob" version = "0.3.3" @@ -1857,15 +1821,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" -[[package]] -name = "miniz_oxide" -version = "0.8.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" -dependencies = [ - "adler2", -] - [[package]] name = "mio" version = "1.0.3" @@ -2177,15 +2132,6 @@ dependencies = [ "objc2-core-foundation", ] -[[package]] -name = "object" -version = "0.36.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" -dependencies = [ - "memchr", -] - [[package]] name = "once_cell" version = "1.21.3" @@ -2751,12 +2697,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "rustc-demangle" -version = "0.1.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" - [[package]] name = "rustc_version" version = "0.4.1" @@ -3078,12 +3018,12 @@ checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" [[package]] name = "socket2" -version = "0.5.9" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef" +checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.60.2", ] [[package]] @@ -3299,11 +3239,10 @@ dependencies = [ [[package]] name = "tokio" -version = "1.45.0" +version = "1.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2513ca694ef9ede0fb23fe71a4ee4107cb102b9dc1930f6d0fd77aae068ae165" +checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" dependencies = [ - "backtrace", "bytes", "libc", "mio", @@ -3313,14 +3252,14 @@ dependencies = [ "socket2", "tokio-macros", "tracing", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] name = "tokio-macros" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", diff --git a/apps/desktop/desktop_native/Cargo.toml b/apps/desktop/desktop_native/Cargo.toml index b492fc62e2a..2d5b1e31175 100644 --- a/apps/desktop/desktop_native/Cargo.toml +++ b/apps/desktop/desktop_native/Cargo.toml @@ -62,7 +62,7 @@ ssh-encoding = "=0.2.0" ssh-key = { version = "=0.6.7", default-features = false } sysinfo = "=0.37.2" thiserror = "=2.0.17" -tokio = "=1.45.0" +tokio = "=1.48.0" tokio-util = "=0.7.17" tracing = "=0.1.41" tracing-subscriber = { version = "=0.3.20", features = [ From 892f5548d21f63e64fedac03c94d540c83b9ef6d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 10 Dec 2025 15:14:18 +0100 Subject: [PATCH 028/188] [deps] Platform: Update Rust crate bytes to v1.11.0 (#17618) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- apps/desktop/desktop_native/Cargo.lock | 4 ++-- apps/desktop/desktop_native/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/desktop/desktop_native/Cargo.lock b/apps/desktop/desktop_native/Cargo.lock index 692ebfa29e9..a2b653a929e 100644 --- a/apps/desktop/desktop_native/Cargo.lock +++ b/apps/desktop/desktop_native/Cargo.lock @@ -485,9 +485,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.10.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" [[package]] name = "camino" diff --git a/apps/desktop/desktop_native/Cargo.toml b/apps/desktop/desktop_native/Cargo.toml index 2d5b1e31175..58ce3758ae1 100644 --- a/apps/desktop/desktop_native/Cargo.toml +++ b/apps/desktop/desktop_native/Cargo.toml @@ -27,7 +27,7 @@ ashpd = "=0.11.0" base64 = "=0.22.1" bitwarden-russh = { git = "https://github.com/bitwarden/bitwarden-russh.git", rev = "a641316227227f8777fdf56ac9fa2d6b5f7fe662" } byteorder = "=1.5.0" -bytes = "=1.10.1" +bytes = "=1.11.0" cbc = "=0.1.2" chacha20poly1305 = "=0.10.1" core-foundation = "=0.10.1" From 44384d51c9d2b2017603be94a54329b2df6cd8b3 Mon Sep 17 00:00:00 2001 From: Bryan Cunningham Date: Wed, 10 Dec 2025 09:28:36 -0500 Subject: [PATCH 029/188] fix padding when nested. remove ng style and class (#17874) * fix padding when nested. remove ng style and class * add expanded group to story to cover bug fix * use class binding for async classes * remove unnecessary x padding to improve truncation * simplify padding logic * fix padding end in closed state * add back some padding in tree view * add back padding to avoid weird spacing scenarios --- .../src/navigation/nav-group.stories.ts | 5 ++++ .../src/navigation/nav-item.component.html | 27 ++++++++----------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/libs/components/src/navigation/nav-group.stories.ts b/libs/components/src/navigation/nav-group.stories.ts index d5c381ac3e3..e3033e4b40a 100644 --- a/libs/components/src/navigation/nav-group.stories.ts +++ b/libs/components/src/navigation/nav-group.stories.ts @@ -84,6 +84,11 @@ export const Default: StoryObj = { + + + + + `, }), diff --git a/libs/components/src/navigation/nav-item.component.html b/libs/components/src/navigation/nav-item.component.html index 1de8a9bd167..8a59d474d94 100644 --- a/libs/components/src/navigation/nav-item.component.html +++ b/libs/components/src/navigation/nav-item.component.html @@ -2,16 +2,12 @@ @let open = sideNavService.open$ | async; @if (open || icon()) {
@if (open) { @@ -26,13 +22,12 @@
@if (icon()) { From 852248d5fa38b43824011d99072a97179492d884 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 10 Dec 2025 09:43:51 -0500 Subject: [PATCH 030/188] [deps] Platform: Update napi to v3 (major) (#16053) * [deps] Platform: Update napi to v3 * fix: upgrade required dependencies * fix: deprecated syntax in package.json * fix: TS code after napi changes * fix: lint * fix: floating promise * fix: libsqlite musl compilation * feat: remove support for musl * fix: sorting lint * fix: logging not working * fix: pre-emptive fix for passkey autofill * fix: rust lint * fix: package-lock * fix: linux type error * fix: windows type error --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Andreas Coroiu Co-authored-by: Andreas Coroiu --- .github/workflows/build-desktop.yml | 14 +- apps/desktop/desktop_native/Cargo.lock | 71 +- apps/desktop/desktop_native/Cargo.toml | 6 +- apps/desktop/desktop_native/build.js | 4 +- .../chromium_importer/src/metadata.rs | 23 +- apps/desktop/desktop_native/napi/index.d.ts | 431 ++--- apps/desktop/desktop_native/napi/index.js | 12 +- apps/desktop/desktop_native/napi/package.json | 26 +- apps/desktop/desktop_native/napi/src/lib.rs | 136 +- .../autofill/main/main-ssh-agent.service.ts | 2 +- .../desktop/src/main/native-messaging.main.ts | 4 +- .../main/autofill/native-autofill.main.ts | 18 +- package-lock.json | 1479 ++++++++++++++++- 13 files changed, 1833 insertions(+), 393 deletions(-) diff --git a/.github/workflows/build-desktop.yml b/.github/workflows/build-desktop.yml index c99d2183d71..6978edd8b3c 100644 --- a/.github/workflows/build-desktop.yml +++ b/.github/workflows/build-desktop.yml @@ -209,7 +209,7 @@ jobs: - name: Set up environment run: | sudo apt-get update - sudo apt-get -y install pkg-config libxss-dev rpm musl-dev musl-tools flatpak flatpak-builder + sudo apt-get -y install pkg-config libxss-dev rpm flatpak flatpak-builder - name: Set up Snap run: sudo snap install snapcraft --classic @@ -262,12 +262,10 @@ jobs: env: PKG_CONFIG_ALLOW_CROSS: true PKG_CONFIG_ALL_STATIC: true - TARGET: musl # Note: It is important that we use the release build because some compute heavy - # operations such as key derivation for oo7 on linux are too slow in debug mode + # operations such as key derivation for oo7 on linux are too slow in debug mode run: | - rustup target add x86_64-unknown-linux-musl - node build.js --target=x86_64-unknown-linux-musl --release + node build.js --release - name: Build application run: npm run dist:lin @@ -367,7 +365,7 @@ jobs: - name: Set up environment run: | sudo apt-get update - sudo apt-get -y install pkg-config libxss-dev rpm musl-dev musl-tools flatpak flatpak-builder squashfs-tools ruby ruby-dev rubygems build-essential + sudo apt-get -y install pkg-config libxss-dev rpm flatpak flatpak-builder squashfs-tools ruby ruby-dev rubygems build-essential sudo gem install --no-document fpm - name: Set up Snap @@ -427,12 +425,10 @@ jobs: env: PKG_CONFIG_ALLOW_CROSS: true PKG_CONFIG_ALL_STATIC: true - TARGET: musl # Note: It is important that we use the release build because some compute heavy # operations such as key derivation for oo7 on linux are too slow in debug mode run: | - rustup target add aarch64-unknown-linux-musl - node build.js --target=aarch64-unknown-linux-musl --release + node build.js --release - name: Check index.d.ts generated if: github.event_name == 'pull_request' && steps.cache.outputs.cache-hit != 'true' diff --git a/apps/desktop/desktop_native/Cargo.lock b/apps/desktop/desktop_native/Cargo.lock index a2b653a929e..7aeeefb2d0d 100644 --- a/apps/desktop/desktop_native/Cargo.lock +++ b/apps/desktop/desktop_native/Cargo.lock @@ -685,9 +685,9 @@ checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] name = "convert_case" -version = "0.6.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +checksum = "baaaa0ecca5b51987b9423ccdc971514dd8b0bb7b4060b983d3664dad3f1f89f" dependencies = [ "unicode-segmentation", ] @@ -746,16 +746,6 @@ dependencies = [ "typenum", ] -[[package]] -name = "ctor" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" -dependencies = [ - "quote", - "syn", -] - [[package]] name = "ctor" version = "0.5.0" @@ -1860,32 +1850,33 @@ dependencies = [ [[package]] name = "napi" -version = "2.16.17" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55740c4ae1d8696773c78fdafd5d0e5fe9bc9f1b071c7ba493ba5c413a9184f3" +checksum = "f1b74e3dce5230795bb4d2821b941706dee733c7308752507254b0497f39cad7" dependencies = [ "bitflags", - "ctor 0.2.9", - "napi-derive", + "ctor", + "napi-build", "napi-sys", - "once_cell", + "nohash-hasher", + "rustc-hash", "tokio", ] [[package]] name = "napi-build" -version = "2.2.0" +version = "2.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03acbfa4f156a32188bfa09b86dc11a431b5725253fc1fc6f6df5bed273382c4" +checksum = "dcae8ad5609d14afb3a3b91dee88c757016261b151e9dcecabf1b2a31a6cab14" [[package]] name = "napi-derive" -version = "2.16.13" +version = "3.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cbe2585d8ac223f7d34f13701434b9d5f4eb9c332cccce8dee57ea18ab8ab0c" +checksum = "7552d5a579b834614bbd496db5109f1b9f1c758f08224b0dee1e408333adf0d0" dependencies = [ - "cfg-if", "convert_case", + "ctor", "napi-derive-backend", "proc-macro2", "quote", @@ -1894,24 +1885,22 @@ dependencies = [ [[package]] name = "napi-derive-backend" -version = "1.0.75" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1639aaa9eeb76e91c6ae66da8ce3e89e921cd3885e99ec85f4abacae72fc91bf" +checksum = "5f6a81ac7486b70f2532a289603340862c06eea5a1e650c1ffeda2ce1238516a" dependencies = [ "convert_case", - "once_cell", "proc-macro2", "quote", - "regex", "semver", "syn", ] [[package]] name = "napi-sys" -version = "2.4.0" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "427802e8ec3a734331fec1035594a210ce1ff4dc5bc1950530920ab717964ea3" +checksum = "3e4e7135a8f97aa0f1509cce21a8a1f9dcec1b50d8dee006b48a5adb69a9d64d" dependencies = [ "libloading", ] @@ -1929,6 +1918,12 @@ dependencies = [ "memoffset", ] +[[package]] +name = "nohash-hasher" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" + [[package]] name = "nom" version = "7.1.3" @@ -2498,7 +2493,7 @@ dependencies = [ name = "process_isolation" version = "0.0.0" dependencies = [ - "ctor 0.5.0", + "ctor", "desktop_core", "libc", "tracing", @@ -2613,18 +2608,6 @@ dependencies = [ "thiserror 2.0.17", ] -[[package]] -name = "regex" -version = "1.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - [[package]] name = "regex-automata" version = "0.4.9" @@ -2697,6 +2680,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + [[package]] name = "rustc_version" version = "0.4.1" diff --git a/apps/desktop/desktop_native/Cargo.toml b/apps/desktop/desktop_native/Cargo.toml index 58ce3758ae1..2eff1af41b5 100644 --- a/apps/desktop/desktop_native/Cargo.toml +++ b/apps/desktop/desktop_native/Cargo.toml @@ -42,9 +42,9 @@ interprocess = "=2.2.1" libc = "=0.2.178" linux-keyutils = "=0.2.4" memsec = "=0.7.0" -napi = "=2.16.17" -napi-build = "=2.2.0" -napi-derive = "=2.16.13" +napi = "=3.3.0" +napi-build = "=2.2.3" +napi-derive = "=3.2.5" oo7 = "=0.4.3" pin-project = "=1.1.10" pkcs8 = "=0.10.2" diff --git a/apps/desktop/desktop_native/build.js b/apps/desktop/desktop_native/build.js index a7ed89a9c17..e267e28a08c 100644 --- a/apps/desktop/desktop_native/build.js +++ b/apps/desktop/desktop_native/build.js @@ -11,8 +11,8 @@ const rustTargetsMap = { "aarch64-pc-windows-msvc": { nodeArch: 'arm64', platform: 'win32' }, "x86_64-apple-darwin": { nodeArch: 'x64', platform: 'darwin' }, "aarch64-apple-darwin": { nodeArch: 'arm64', platform: 'darwin' }, - 'x86_64-unknown-linux-musl': { nodeArch: 'x64', platform: 'linux' }, - 'aarch64-unknown-linux-musl': { nodeArch: 'arm64', platform: 'linux' }, + 'x86_64-unknown-linux-gnu': { nodeArch: 'x64', platform: 'linux' }, + 'aarch64-unknown-linux-gnu': { nodeArch: 'arm64', platform: 'linux' }, } // Ensure the dist directory exists diff --git a/apps/desktop/desktop_native/chromium_importer/src/metadata.rs b/apps/desktop/desktop_native/chromium_importer/src/metadata.rs index 51a181f7f49..9aa2cea6e5e 100644 --- a/apps/desktop/desktop_native/chromium_importer/src/metadata.rs +++ b/apps/desktop/desktop_native/chromium_importer/src/metadata.rs @@ -7,9 +7,9 @@ pub struct NativeImporterMetadata { /// Identifies the importer pub id: String, /// Describes the strategies used to obtain imported data - pub loaders: Vec<&'static str>, + pub loaders: Vec, /// Identifies the instructions for the importer - pub instructions: &'static str, + pub instructions: String, } /// Returns a map of supported importers based on the current platform. @@ -36,9 +36,9 @@ pub fn get_supported_importers( PLATFORM_SUPPORTED_BROWSERS.iter().map(|b| b.name).collect(); for (id, browser_name) in IMPORTERS { - let mut loaders: Vec<&'static str> = vec!["file"]; + let mut loaders: Vec = vec!["file".to_string()]; if supported.contains(browser_name) { - loaders.push("chromium"); + loaders.push("chromium".to_string()); } if installed_browsers.contains(&browser_name.to_string()) { @@ -47,7 +47,7 @@ pub fn get_supported_importers( NativeImporterMetadata { id: id.to_string(), loaders, - instructions: "chromium", + instructions: "chromium".to_string(), }, ); } @@ -79,12 +79,9 @@ mod tests { map.keys().cloned().collect() } - fn get_loaders( - map: &HashMap, - id: &str, - ) -> HashSet<&'static str> { + fn get_loaders(map: &HashMap, id: &str) -> HashSet { map.get(id) - .map(|m| m.loaders.iter().copied().collect::>()) + .map(|m| m.loaders.iter().cloned().collect::>()) .unwrap_or_default() } @@ -107,7 +104,7 @@ mod tests { for (key, meta) in map.iter() { assert_eq!(&meta.id, key); assert_eq!(meta.instructions, "chromium"); - assert!(meta.loaders.contains(&"file")); + assert!(meta.loaders.contains(&"file".to_owned())); } } @@ -147,7 +144,7 @@ mod tests { for (key, meta) in map.iter() { assert_eq!(&meta.id, key); assert_eq!(meta.instructions, "chromium"); - assert!(meta.loaders.contains(&"file")); + assert!(meta.loaders.contains(&"file".to_owned())); } } @@ -183,7 +180,7 @@ mod tests { for (key, meta) in map.iter() { assert_eq!(&meta.id, key); assert_eq!(meta.instructions, "chromium"); - assert!(meta.loaders.contains(&"file")); + assert!(meta.loaders.contains(&"file".to_owned())); } } diff --git a/apps/desktop/desktop_native/napi/index.d.ts b/apps/desktop/desktop_native/napi/index.d.ts index 0db29c9a05d..375c65edb8d 100644 --- a/apps/desktop/desktop_native/napi/index.d.ts +++ b/apps/desktop/desktop_native/napi/index.d.ts @@ -1,125 +1,7 @@ -/* tslint:disable */ -/* eslint-disable */ - /* auto-generated by NAPI-RS */ - -export declare namespace passwords { - /** The error message returned when a password is not found during retrieval or deletion. */ - export const PASSWORD_NOT_FOUND: string - /** - * Fetch the stored password from the keychain. - * Throws {@link Error} with message {@link PASSWORD_NOT_FOUND} if the password does not exist. - */ - export function getPassword(service: string, account: string): Promise - /** - * Save the password to the keychain. Adds an entry if none exists otherwise updates the - * existing entry. - */ - export function setPassword(service: string, account: string, password: string): Promise - /** - * Delete the stored password from the keychain. - * Throws {@link Error} with message {@link PASSWORD_NOT_FOUND} if the password does not exist. - */ - export function deletePassword(service: string, account: string): Promise - /** Checks if the os secure storage is available */ - export function isAvailable(): Promise -} -export declare namespace biometrics { - export function prompt(hwnd: Buffer, message: string): Promise - export function available(): Promise - export function setBiometricSecret(service: string, account: string, secret: string, keyMaterial: KeyMaterial | undefined | null, ivB64: string): Promise - /** - * Retrieves the biometric secret for the given service and account. - * Throws Error with message [`passwords::PASSWORD_NOT_FOUND`] if the secret does not exist. - */ - export function getBiometricSecret(service: string, account: string, keyMaterial?: KeyMaterial | undefined | null): Promise - /** - * Derives key material from biometric data. Returns a string encoded with a - * base64 encoded key and the base64 encoded challenge used to create it - * separated by a `|` character. - * - * If the iv is provided, it will be used as the challenge. Otherwise a random challenge will - * be generated. - * - * `format!("|")` - */ - export function deriveKeyMaterial(iv?: string | undefined | null): Promise - export interface KeyMaterial { - osKeyPartB64: string - clientKeyPartB64?: string - } - export interface OsDerivedKey { - keyB64: string - ivB64: string - } -} -export declare namespace biometrics_v2 { - export function initBiometricSystem(): BiometricLockSystem - export function authenticate(biometricLockSystem: BiometricLockSystem, hwnd: Buffer, message: string): Promise - export function authenticateAvailable(biometricLockSystem: BiometricLockSystem): Promise - export function enrollPersistent(biometricLockSystem: BiometricLockSystem, userId: string, key: Buffer): Promise - export function provideKey(biometricLockSystem: BiometricLockSystem, userId: string, key: Buffer): Promise - export function unlock(biometricLockSystem: BiometricLockSystem, userId: string, hwnd: Buffer): Promise - export function unlockAvailable(biometricLockSystem: BiometricLockSystem, userId: string): Promise - export function hasPersistent(biometricLockSystem: BiometricLockSystem, userId: string): Promise - export function unenroll(biometricLockSystem: BiometricLockSystem, userId: string): Promise - export class BiometricLockSystem { } -} -export declare namespace clipboards { - export function read(): Promise - export function write(text: string, password: boolean): Promise -} -export declare namespace sshagent { - export interface PrivateKey { - privateKey: string - name: string - cipherId: string - } - export interface SshKey { - privateKey: string - publicKey: string - keyFingerprint: string - } - export interface SshUiRequest { - cipherId?: string - isList: boolean - processName: string - isForwarding: boolean - namespace?: string - } - export function serve(callback: (err: Error | null, arg: SshUiRequest) => any): Promise - export function stop(agentState: SshAgentState): void - export function isRunning(agentState: SshAgentState): boolean - export function setKeys(agentState: SshAgentState, newKeys: Array): void - export function lock(agentState: SshAgentState): void - export function clearKeys(agentState: SshAgentState): void - export class SshAgentState { } -} -export declare namespace processisolations { - export function disableCoredumps(): Promise - export function isCoreDumpingDisabled(): Promise - export function isolateProcess(): Promise -} -export declare namespace powermonitors { - export function onLock(callback: (err: Error | null, ) => any): Promise - export function isLockMonitorAvailable(): Promise -} -export declare namespace windows_registry { - export function createKey(key: string, subkey: string, value: string): Promise - export function deleteKey(key: string, subkey: string): Promise -} -export declare namespace ipc { - export interface IpcMessage { - clientId: number - kind: IpcMessageType - message?: string - } - export const enum IpcMessageType { - Connected = 0, - Disconnected = 1, - Message = 2 - } - export class IpcServer { +/* eslint-disable */ +export declare namespace autofill { + export class AutofillIpcServer { /** * Create and start the IPC server without blocking. * @@ -127,34 +9,43 @@ export declare namespace ipc { * connection and must be the same for both the server and client. @param callback * This function will be called whenever a message is received from a client. */ - static listen(name: string, callback: (error: null | Error, message: IpcMessage) => void): Promise + static listen(name: string, registrationCallback: (error: null | Error, clientId: number, sequenceNumber: number, message: PasskeyRegistrationRequest) => void, assertionCallback: (error: null | Error, clientId: number, sequenceNumber: number, message: PasskeyAssertionRequest) => void, assertionWithoutUserInterfaceCallback: (error: null | Error, clientId: number, sequenceNumber: number, message: PasskeyAssertionWithoutUserInterfaceRequest) => void, nativeStatusCallback: (error: null | Error, clientId: number, sequenceNumber: number, message: NativeStatus) => void): Promise /** Return the path to the IPC server. */ getPath(): string /** Stop the IPC server. */ stop(): void - /** - * Send a message over the IPC server to all the connected clients - * - * @return The number of clients that the message was sent to. Note that the number of - * messages actually received may be less, as some clients could disconnect before - * receiving the message. - */ - send(message: string): number + completeRegistration(clientId: number, sequenceNumber: number, response: PasskeyRegistrationResponse): number + completeAssertion(clientId: number, sequenceNumber: number, response: PasskeyAssertionResponse): number + completeError(clientId: number, sequenceNumber: number, error: string): number } -} -export declare namespace autostart { - export function setAutostart(autostart: boolean, params: Array): Promise -} -export declare namespace autofill { - export function runCommand(value: string): Promise - export const enum UserVerification { - Preferred = 'preferred', - Required = 'required', - Discouraged = 'discouraged' + export interface NativeStatus { + key: string + value: string } - export interface Position { - x: number - y: number + export interface PasskeyAssertionRequest { + rpId: string + clientDataHash: Array + userVerification: UserVerification + allowedCredentials: Array> + windowXy: Position + } + export interface PasskeyAssertionResponse { + rpId: string + userHandle: Array + signature: Array + clientDataHash: Array + authenticatorData: Array + credentialId: Array + } + export interface PasskeyAssertionWithoutUserInterfaceRequest { + rpId: string + credentialId: Array + userName: string + userHandle: Array + recordIdentifier?: string + clientDataHash: Array + userVerification: UserVerification + windowXy: Position } export interface PasskeyRegistrationRequest { rpId: string @@ -172,71 +63,77 @@ export declare namespace autofill { credentialId: Array attestationObject: Array } - export interface PasskeyAssertionRequest { - rpId: string - clientDataHash: Array - userVerification: UserVerification - allowedCredentials: Array> - windowXy: Position + export interface Position { + x: number + y: number } - export interface PasskeyAssertionWithoutUserInterfaceRequest { - rpId: string - credentialId: Array - userName: string - userHandle: Array - recordIdentifier?: string - clientDataHash: Array - userVerification: UserVerification - windowXy: Position - } - export interface NativeStatus { - key: string - value: string - } - export interface PasskeyAssertionResponse { - rpId: string - userHandle: Array - signature: Array - clientDataHash: Array - authenticatorData: Array - credentialId: Array - } - export class IpcServer { - /** - * Create and start the IPC server without blocking. - * - * @param name The endpoint name to listen on. This name uniquely identifies the IPC - * connection and must be the same for both the server and client. @param callback - * This function will be called whenever a message is received from a client. - */ - static listen(name: string, registrationCallback: (error: null | Error, clientId: number, sequenceNumber: number, message: PasskeyRegistrationRequest) => void, assertionCallback: (error: null | Error, clientId: number, sequenceNumber: number, message: PasskeyAssertionRequest) => void, assertionWithoutUserInterfaceCallback: (error: null | Error, clientId: number, sequenceNumber: number, message: PasskeyAssertionWithoutUserInterfaceRequest) => void, nativeStatusCallback: (error: null | Error, clientId: number, sequenceNumber: number, message: NativeStatus) => void): Promise - /** Return the path to the IPC server. */ - getPath(): string - /** Stop the IPC server. */ - stop(): void - completeRegistration(clientId: number, sequenceNumber: number, response: PasskeyRegistrationResponse): number - completeAssertion(clientId: number, sequenceNumber: number, response: PasskeyAssertionResponse): number - completeError(clientId: number, sequenceNumber: number, error: string): number + export function runCommand(value: string): Promise + export const enum UserVerification { + Preferred = 'preferred', + Required = 'required', + Discouraged = 'discouraged' } } -export declare namespace passkey_authenticator { - export function register(): void + +export declare namespace autostart { + export function setAutostart(autostart: boolean, params: Array): Promise } -export declare namespace logging { - export const enum LogLevel { - Trace = 0, - Debug = 1, - Info = 2, - Warn = 3, - Error = 4 + +export declare namespace autotype { + export function getForegroundWindowTitle(): string + export function typeInput(input: Array, keyboardShortcut: Array): void +} + +export declare namespace biometrics { + export function available(): Promise + /** + * Derives key material from biometric data. Returns a string encoded with a + * base64 encoded key and the base64 encoded challenge used to create it + * separated by a `|` character. + * + * If the iv is provided, it will be used as the challenge. Otherwise a random challenge will + * be generated. + * + * `format!("|")` + */ + export function deriveKeyMaterial(iv?: string | undefined | null): Promise + /** + * Retrieves the biometric secret for the given service and account. + * Throws Error with message [`passwords::PASSWORD_NOT_FOUND`] if the secret does not exist. + */ + export function getBiometricSecret(service: string, account: string, keyMaterial?: KeyMaterial | undefined | null): Promise + export interface KeyMaterial { + osKeyPartB64: string + clientKeyPartB64?: string } - export function initNapiLog(jsLogFn: (err: Error | null, arg0: LogLevel, arg1: string) => any): void + export interface OsDerivedKey { + keyB64: string + ivB64: string + } + export function prompt(hwnd: Buffer, message: string): Promise + export function setBiometricSecret(service: string, account: string, secret: string, keyMaterial: KeyMaterial | undefined | null, ivB64: string): Promise } + +export declare namespace biometrics_v2 { + export class BiometricLockSystem { + + } + export function authenticate(biometricLockSystem: BiometricLockSystem, hwnd: Buffer, message: string): Promise + export function authenticateAvailable(biometricLockSystem: BiometricLockSystem): Promise + export function enrollPersistent(biometricLockSystem: BiometricLockSystem, userId: string, key: Buffer): Promise + export function hasPersistent(biometricLockSystem: BiometricLockSystem, userId: string): Promise + export function initBiometricSystem(): BiometricLockSystem + export function provideKey(biometricLockSystem: BiometricLockSystem, userId: string, key: Buffer): Promise + export function unenroll(biometricLockSystem: BiometricLockSystem, userId: string): Promise + export function unlock(biometricLockSystem: BiometricLockSystem, userId: string, hwnd: Buffer): Promise + export function unlockAvailable(biometricLockSystem: BiometricLockSystem, userId: string): Promise +} + export declare namespace chromium_importer { - export interface ProfileInfo { - id: string - name: string - } + export function getAvailableProfiles(browser: string): Array + /** Returns OS aware metadata describing supported Chromium based importers as a JSON string. */ + export function getMetadata(): Record + export function importLogins(browser: string, profileId: string): Promise> export interface Login { url: string username: string @@ -257,12 +154,130 @@ export declare namespace chromium_importer { loaders: Array instructions: string } - /** Returns OS aware metadata describing supported Chromium based importers as a JSON string. */ - export function getMetadata(): Record - export function getAvailableProfiles(browser: string): Array - export function importLogins(browser: string, profileId: string): Promise> + export interface ProfileInfo { + id: string + name: string + } } -export declare namespace autotype { - export function getForegroundWindowTitle(): string - export function typeInput(input: Array, keyboardShortcut: Array): void + +export declare namespace clipboards { + export function read(): Promise + export function write(text: string, password: boolean): Promise +} + +export declare namespace ipc { + export class NativeIpcServer { + /** + * Create and start the IPC server without blocking. + * + * @param name The endpoint name to listen on. This name uniquely identifies the IPC + * connection and must be the same for both the server and client. @param callback + * This function will be called whenever a message is received from a client. + */ + static listen(name: string, callback: (error: null | Error, message: IpcMessage) => void): Promise + /** Return the path to the IPC server. */ + getPath(): string + /** Stop the IPC server. */ + stop(): void + /** + * Send a message over the IPC server to all the connected clients + * + * @return The number of clients that the message was sent to. Note that the number of + * messages actually received may be less, as some clients could disconnect before + * receiving the message. + */ + send(message: string): number + } + export interface IpcMessage { + clientId: number + kind: IpcMessageType + message?: string + } + export const enum IpcMessageType { + Connected = 0, + Disconnected = 1, + Message = 2 + } +} + +export declare namespace logging { + export function initNapiLog(jsLogFn: ((err: Error | null, arg0: LogLevel, arg1: string) => any)): void + export const enum LogLevel { + Trace = 0, + Debug = 1, + Info = 2, + Warn = 3, + Error = 4 + } +} + +export declare namespace passkey_authenticator { + export function register(): void +} + +export declare namespace passwords { + /** + * Delete the stored password from the keychain. + * Throws {@link Error} with message {@link PASSWORD_NOT_FOUND} if the password does not exist. + */ + export function deletePassword(service: string, account: string): Promise + /** + * Fetch the stored password from the keychain. + * Throws {@link Error} with message {@link PASSWORD_NOT_FOUND} if the password does not exist. + */ + export function getPassword(service: string, account: string): Promise + /** Checks if the os secure storage is available */ + export function isAvailable(): Promise + /** The error message returned when a password is not found during retrieval or deletion. */ + export const PASSWORD_NOT_FOUND: string + /** + * Save the password to the keychain. Adds an entry if none exists otherwise updates the + * existing entry. + */ + export function setPassword(service: string, account: string, password: string): Promise +} + +export declare namespace powermonitors { + export function isLockMonitorAvailable(): Promise + export function onLock(callback: ((err: Error | null, ) => any)): Promise +} + +export declare namespace processisolations { + export function disableCoredumps(): Promise + export function isCoreDumpingDisabled(): Promise + export function isolateProcess(): Promise +} + +export declare namespace sshagent { + export class SshAgentState { + + } + export function clearKeys(agentState: SshAgentState): void + export function isRunning(agentState: SshAgentState): boolean + export function lock(agentState: SshAgentState): void + export interface PrivateKey { + privateKey: string + name: string + cipherId: string + } + export function serve(callback: ((err: Error | null, arg: SshUiRequest) => Promise)): Promise + export function setKeys(agentState: SshAgentState, newKeys: Array): void + export interface SshKey { + privateKey: string + publicKey: string + keyFingerprint: string + } + export interface SshUiRequest { + cipherId?: string + isList: boolean + processName: string + isForwarding: boolean + namespace?: string + } + export function stop(agentState: SshAgentState): void +} + +export declare namespace windows_registry { + export function createKey(key: string, subkey: string, value: string): Promise + export function deleteKey(key: string, subkey: string): Promise } diff --git a/apps/desktop/desktop_native/napi/index.js b/apps/desktop/desktop_native/napi/index.js index 64819be4405..0362d9ee2bb 100644 --- a/apps/desktop/desktop_native/napi/index.js +++ b/apps/desktop/desktop_native/napi/index.js @@ -82,20 +82,20 @@ switch (platform) { switch (arch) { case "x64": nativeBinding = loadFirstAvailable( - ["desktop_napi.linux-x64-musl.node", "desktop_napi.linux-x64-gnu.node"], - "@bitwarden/desktop-napi-linux-x64-musl", + ["desktop_napi.linux-x64-gnu.node"], + "@bitwarden/desktop-napi-linux-x64-gnu", ); break; case "arm64": nativeBinding = loadFirstAvailable( - ["desktop_napi.linux-arm64-musl.node", "desktop_napi.linux-arm64-gnu.node"], - "@bitwarden/desktop-napi-linux-arm64-musl", + ["desktop_napi.linux-arm64-gnu.node"], + "@bitwarden/desktop-napi-linux-arm64-gnu", ); break; case "arm": nativeBinding = loadFirstAvailable( - ["desktop_napi.linux-arm-musl.node", "desktop_napi.linux-arm-gnu.node"], - "@bitwarden/desktop-napi-linux-arm-musl", + ["desktop_napi.linux-arm-gnu.node"], + "@bitwarden/desktop-napi-linux-arm-gnu", ); localFileExisted = existsSync(join(__dirname, "desktop_napi.linux-arm-gnueabihf.node")); try { diff --git a/apps/desktop/desktop_native/napi/package.json b/apps/desktop/desktop_native/napi/package.json index d557ccfd259..5401207c252 100644 --- a/apps/desktop/desktop_native/napi/package.json +++ b/apps/desktop/desktop_native/napi/package.json @@ -3,27 +3,23 @@ "version": "0.1.0", "description": "", "scripts": { - "build": "napi build --platform --js false", + "build": "napi build --platform --no-js", "test": "cargo test" }, "author": "", "license": "GPL-3.0", "devDependencies": { - "@napi-rs/cli": "2.18.4" + "@napi-rs/cli": "3.2.0" }, "napi": { - "name": "desktop_napi", - "triples": { - "defaults": true, - "additional": [ - "x86_64-unknown-linux-musl", - "aarch64-unknown-linux-gnu", - "i686-pc-windows-msvc", - "armv7-unknown-linux-gnueabihf", - "aarch64-apple-darwin", - "aarch64-unknown-linux-musl", - "aarch64-pc-windows-msvc" - ] - } + "binaryName": "desktop_napi", + "targets": [ + "aarch64-apple-darwin", + "aarch64-pc-windows-msvc", + "aarch64-unknown-linux-gnu", + "armv7-unknown-linux-gnueabihf", + "i686-pc-windows-msvc", + "x86_64-unknown-linux-gnu" + ] } } diff --git a/apps/desktop/desktop_native/napi/src/lib.rs b/apps/desktop/desktop_native/napi/src/lib.rs index 7f63001c221..25dfdd08336 100644 --- a/apps/desktop/desktop_native/napi/src/lib.rs +++ b/apps/desktop/desktop_native/napi/src/lib.rs @@ -290,7 +290,7 @@ pub mod sshagent { use napi::{ bindgen_prelude::Promise, - threadsafe_function::{ErrorStrategy::CalleeHandled, ThreadsafeFunction}, + threadsafe_function::{ThreadsafeFunction, ThreadsafeFunctionCallMode}, }; use tokio::{self, sync::Mutex}; use tracing::error; @@ -326,13 +326,15 @@ pub mod sshagent { #[allow(clippy::unused_async)] // FIXME: Remove unused async! #[napi] pub async fn serve( - callback: ThreadsafeFunction, + callback: ThreadsafeFunction>, ) -> napi::Result { let (auth_request_tx, mut auth_request_rx) = tokio::sync::mpsc::channel::(32); let (auth_response_tx, auth_response_rx) = tokio::sync::broadcast::channel::<(u32, bool)>(32); let auth_response_tx_arc = Arc::new(Mutex::new(auth_response_tx)); + // Wrap callback in Arc so it can be shared across spawned tasks + let callback = Arc::new(callback); tokio::spawn(async move { let _ = auth_response_rx; @@ -342,42 +344,50 @@ pub mod sshagent { tokio::spawn(async move { let auth_response_tx_arc = cloned_response_tx_arc; let callback = cloned_callback; - let promise_result: Result, napi::Error> = callback - .call_async(Ok(SshUIRequest { + // In NAPI v3, obtain the JS callback return as a Promise and await it + // in Rust + let (tx, rx) = std::sync::mpsc::channel::>(); + let status = callback.call_with_return_value( + Ok(SshUIRequest { cipher_id: request.cipher_id, is_list: request.is_list, process_name: request.process_name, is_forwarding: request.is_forwarding, namespace: request.namespace, - })) - .await; - match promise_result { - Ok(promise_result) => match promise_result.await { - Ok(result) => { - let _ = auth_response_tx_arc - .lock() - .await - .send((request.request_id, result)) - .expect("should be able to send auth response to agent"); - } - Err(e) => { - error!(error = %e, "Calling UI callback promise was rejected"); - let _ = auth_response_tx_arc - .lock() - .await - .send((request.request_id, false)) - .expect("should be able to send auth response to agent"); + }), + ThreadsafeFunctionCallMode::Blocking, + move |ret: Result, napi::Error>, _env| { + if let Ok(p) = ret { + let _ = tx.send(p); } + Ok(()) }, - Err(e) => { - error!(error = %e, "Calling UI callback could not create promise"); - let _ = auth_response_tx_arc - .lock() - .await - .send((request.request_id, false)) - .expect("should be able to send auth response to agent"); + ); + + let result = if status == napi::Status::Ok { + match rx.recv() { + Ok(promise) => match promise.await { + Ok(v) => v, + Err(e) => { + error!(error = %e, "UI callback promise rejected"); + false + } + }, + Err(e) => { + error!(error = %e, "Failed to receive UI callback promise"); + false + } } - } + } else { + error!(error = ?status, "Calling UI callback failed"); + false + }; + + let _ = auth_response_tx_arc + .lock() + .await + .send((request.request_id, result)) + .expect("should be able to send auth response to agent"); }); } }); @@ -465,14 +475,12 @@ pub mod processisolations { #[napi] pub mod powermonitors { use napi::{ - threadsafe_function::{ - ErrorStrategy::CalleeHandled, ThreadsafeFunction, ThreadsafeFunctionCallMode, - }, + threadsafe_function::{ThreadsafeFunction, ThreadsafeFunctionCallMode}, tokio, }; #[napi] - pub async fn on_lock(callback: ThreadsafeFunction<(), CalleeHandled>) -> napi::Result<()> { + pub async fn on_lock(callback: ThreadsafeFunction<()>) -> napi::Result<()> { let (tx, mut rx) = tokio::sync::mpsc::channel::<()>(32); desktop_core::powermonitor::on_lock(tx) .await @@ -511,9 +519,7 @@ pub mod windows_registry { #[napi] pub mod ipc { use desktop_core::ipc::server::{Message, MessageType}; - use napi::threadsafe_function::{ - ErrorStrategy, ThreadsafeFunction, ThreadsafeFunctionCallMode, - }; + use napi::threadsafe_function::{ThreadsafeFunction, ThreadsafeFunctionCallMode}; #[napi(object)] pub struct IpcMessage { @@ -550,12 +556,12 @@ pub mod ipc { } #[napi] - pub struct IpcServer { + pub struct NativeIpcServer { server: desktop_core::ipc::server::Server, } #[napi] - impl IpcServer { + impl NativeIpcServer { /// Create and start the IPC server without blocking. /// /// @param name The endpoint name to listen on. This name uniquely identifies the IPC @@ -566,7 +572,7 @@ pub mod ipc { pub async fn listen( name: String, #[napi(ts_arg_type = "(error: null | Error, message: IpcMessage) => void")] - callback: ThreadsafeFunction, + callback: ThreadsafeFunction, ) -> napi::Result { let (send, mut recv) = tokio::sync::mpsc::channel::(32); tokio::spawn(async move { @@ -583,7 +589,7 @@ pub mod ipc { )) })?; - Ok(IpcServer { server }) + Ok(NativeIpcServer { server }) } /// Return the path to the IPC server. @@ -630,8 +636,9 @@ pub mod autostart { #[napi] pub mod autofill { use desktop_core::ipc::server::{Message, MessageType}; - use napi::threadsafe_function::{ - ErrorStrategy, ThreadsafeFunction, ThreadsafeFunctionCallMode, + use napi::{ + bindgen_prelude::FnArgs, + threadsafe_function::{ThreadsafeFunction, ThreadsafeFunctionCallMode}, }; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use tracing::error; @@ -746,14 +753,14 @@ pub mod autofill { } #[napi] - pub struct IpcServer { + pub struct AutofillIpcServer { server: desktop_core::ipc::server::Server, } // FIXME: Remove unwraps! They panic and terminate the whole application. #[allow(clippy::unwrap_used)] #[napi] - impl IpcServer { + impl AutofillIpcServer { /// Create and start the IPC server without blocking. /// /// @param name The endpoint name to listen on. This name uniquely identifies the IPC @@ -769,30 +776,24 @@ pub mod autofill { ts_arg_type = "(error: null | Error, clientId: number, sequenceNumber: number, message: PasskeyRegistrationRequest) => void" )] registration_callback: ThreadsafeFunction< - (u32, u32, PasskeyRegistrationRequest), - ErrorStrategy::CalleeHandled, + FnArgs<(u32, u32, PasskeyRegistrationRequest)>, >, #[napi( ts_arg_type = "(error: null | Error, clientId: number, sequenceNumber: number, message: PasskeyAssertionRequest) => void" )] assertion_callback: ThreadsafeFunction< - (u32, u32, PasskeyAssertionRequest), - ErrorStrategy::CalleeHandled, + FnArgs<(u32, u32, PasskeyAssertionRequest)>, >, #[napi( ts_arg_type = "(error: null | Error, clientId: number, sequenceNumber: number, message: PasskeyAssertionWithoutUserInterfaceRequest) => void" )] assertion_without_user_interface_callback: ThreadsafeFunction< - (u32, u32, PasskeyAssertionWithoutUserInterfaceRequest), - ErrorStrategy::CalleeHandled, + FnArgs<(u32, u32, PasskeyAssertionWithoutUserInterfaceRequest)>, >, #[napi( ts_arg_type = "(error: null | Error, clientId: number, sequenceNumber: number, message: NativeStatus) => void" )] - native_status_callback: ThreadsafeFunction< - (u32, u32, NativeStatus), - ErrorStrategy::CalleeHandled, - >, + native_status_callback: ThreadsafeFunction<(u32, u32, NativeStatus)>, ) -> napi::Result { let (send, mut recv) = tokio::sync::mpsc::channel::(32); tokio::spawn(async move { @@ -817,7 +818,7 @@ pub mod autofill { Ok(msg) => { let value = msg .value - .map(|value| (client_id, msg.sequence_number, value)) + .map(|value| (client_id, msg.sequence_number, value).into()) .map_err(|e| napi::Error::from_reason(format!("{e:?}"))); assertion_callback @@ -836,7 +837,7 @@ pub mod autofill { Ok(msg) => { let value = msg .value - .map(|value| (client_id, msg.sequence_number, value)) + .map(|value| (client_id, msg.sequence_number, value).into()) .map_err(|e| napi::Error::from_reason(format!("{e:?}"))); assertion_without_user_interface_callback @@ -854,7 +855,7 @@ pub mod autofill { Ok(msg) => { let value = msg .value - .map(|value| (client_id, msg.sequence_number, value)) + .map(|value| (client_id, msg.sequence_number, value).into()) .map_err(|e| napi::Error::from_reason(format!("{e:?}"))); registration_callback .call(value, ThreadsafeFunctionCallMode::NonBlocking); @@ -894,7 +895,7 @@ pub mod autofill { )) })?; - Ok(IpcServer { server }) + Ok(AutofillIpcServer { server }) } /// Return the path to the IPC server. @@ -987,8 +988,9 @@ pub mod logging { use std::{fmt::Write, sync::OnceLock}; - use napi::threadsafe_function::{ - ErrorStrategy::CalleeHandled, ThreadsafeFunction, ThreadsafeFunctionCallMode, + use napi::{ + bindgen_prelude::FnArgs, + threadsafe_function::{ThreadsafeFunction, ThreadsafeFunctionCallMode}, }; use tracing::Level; use tracing_subscriber::{ @@ -999,7 +1001,7 @@ pub mod logging { Layer, }; - struct JsLogger(OnceLock>); + struct JsLogger(OnceLock>>); static JS_LOGGER: JsLogger = JsLogger(OnceLock::new()); #[napi] @@ -1071,13 +1073,13 @@ pub mod logging { let msg = (event.metadata().level().into(), buffer); if let Some(logger) = JS_LOGGER.0.get() { - let _ = logger.call(Ok(msg), ThreadsafeFunctionCallMode::NonBlocking); + let _ = logger.call(Ok(msg.into()), ThreadsafeFunctionCallMode::NonBlocking); }; } } #[napi] - pub fn init_napi_log(js_log_fn: ThreadsafeFunction<(LogLevel, String), CalleeHandled>) { + pub fn init_napi_log(js_log_fn: ThreadsafeFunction>) { let _ = JS_LOGGER.0.set(js_log_fn); let filter = EnvFilter::builder() @@ -1140,8 +1142,8 @@ pub mod chromium_importer { #[napi(object)] pub struct NativeImporterMetadata { pub id: String, - pub loaders: Vec<&'static str>, - pub instructions: &'static str, + pub loaders: Vec, + pub instructions: String, } impl From<_LoginImportResult> for LoginImportResult { @@ -1218,7 +1220,7 @@ pub mod chromium_importer { #[napi] pub mod autotype { #[napi] - pub fn get_foreground_window_title() -> napi::Result { + pub fn get_foreground_window_title() -> napi::Result { autotype::get_foreground_window_title().map_err(|_| { napi::Error::from_reason( "Autotype Error: failed to get foreground window title".to_string(), diff --git a/apps/desktop/src/autofill/main/main-ssh-agent.service.ts b/apps/desktop/src/autofill/main/main-ssh-agent.service.ts index 595ef778bcf..31196e4cf98 100644 --- a/apps/desktop/src/autofill/main/main-ssh-agent.service.ts +++ b/apps/desktop/src/autofill/main/main-ssh-agent.service.ts @@ -37,7 +37,7 @@ export class MainSshAgentService { init() { // handle sign request passing to UI sshagent - .serve(async (err: Error, sshUiRequest: sshagent.SshUiRequest) => { + .serve(async (err: Error | null, sshUiRequest: sshagent.SshUiRequest): Promise => { // clear all old (> SIGN_TIMEOUT) requests this.requestResponses = this.requestResponses.filter( (response) => response.timestamp > new Date(Date.now() - this.SIGN_TIMEOUT), diff --git a/apps/desktop/src/main/native-messaging.main.ts b/apps/desktop/src/main/native-messaging.main.ts index 23d2e038635..a0c17a115e0 100644 --- a/apps/desktop/src/main/native-messaging.main.ts +++ b/apps/desktop/src/main/native-messaging.main.ts @@ -14,7 +14,7 @@ import { isDev } from "../utils"; import { WindowMain } from "./window.main"; export class NativeMessagingMain { - private ipcServer: ipc.IpcServer | null; + private ipcServer: ipc.NativeIpcServer | null; private connected: number[] = []; constructor( @@ -78,7 +78,7 @@ export class NativeMessagingMain { this.ipcServer.stop(); } - this.ipcServer = await ipc.IpcServer.listen("bw", (error, msg) => { + this.ipcServer = await ipc.NativeIpcServer.listen("bw", (error, msg) => { switch (msg.kind) { case ipc.IpcMessageType.Connected: { this.connected.push(msg.clientId); diff --git a/apps/desktop/src/platform/main/autofill/native-autofill.main.ts b/apps/desktop/src/platform/main/autofill/native-autofill.main.ts index 7ecd7c2e9e5..c0d860d74db 100644 --- a/apps/desktop/src/platform/main/autofill/native-autofill.main.ts +++ b/apps/desktop/src/platform/main/autofill/native-autofill.main.ts @@ -21,7 +21,7 @@ export type RunCommandParams = { export type RunCommandResult = C["output"]; export class NativeAutofillMain { - private ipcServer: autofill.IpcServer | null; + private ipcServer?: autofill.AutofillIpcServer; private messageBuffer: BufferedMessage[] = []; private listenerReady = false; @@ -70,13 +70,13 @@ export class NativeAutofillMain { }, ); - this.ipcServer = await autofill.IpcServer.listen( + this.ipcServer = await autofill.AutofillIpcServer.listen( "af", // RegistrationCallback (error, clientId, sequenceNumber, request) => { if (error) { this.logService.error("autofill.IpcServer.registration", error); - this.ipcServer.completeError(clientId, sequenceNumber, String(error)); + this.ipcServer?.completeError(clientId, sequenceNumber, String(error)); return; } this.safeSend("autofill.passkeyRegistration", { @@ -89,7 +89,7 @@ export class NativeAutofillMain { (error, clientId, sequenceNumber, request) => { if (error) { this.logService.error("autofill.IpcServer.assertion", error); - this.ipcServer.completeError(clientId, sequenceNumber, String(error)); + this.ipcServer?.completeError(clientId, sequenceNumber, String(error)); return; } this.safeSend("autofill.passkeyAssertion", { @@ -102,7 +102,7 @@ export class NativeAutofillMain { (error, clientId, sequenceNumber, request) => { if (error) { this.logService.error("autofill.IpcServer.assertion", error); - this.ipcServer.completeError(clientId, sequenceNumber, String(error)); + this.ipcServer?.completeError(clientId, sequenceNumber, String(error)); return; } this.safeSend("autofill.passkeyAssertionWithoutUserInterface", { @@ -115,7 +115,7 @@ export class NativeAutofillMain { (error, clientId, sequenceNumber, status) => { if (error) { this.logService.error("autofill.IpcServer.nativeStatus", error); - this.ipcServer.completeError(clientId, sequenceNumber, String(error)); + this.ipcServer?.completeError(clientId, sequenceNumber, String(error)); return; } this.safeSend("autofill.nativeStatus", { @@ -137,19 +137,19 @@ export class NativeAutofillMain { ipcMain.on("autofill.completePasskeyRegistration", (event, data) => { this.logService.debug("autofill.completePasskeyRegistration", data); const { clientId, sequenceNumber, response } = data; - this.ipcServer.completeRegistration(clientId, sequenceNumber, response); + this.ipcServer?.completeRegistration(clientId, sequenceNumber, response); }); ipcMain.on("autofill.completePasskeyAssertion", (event, data) => { this.logService.debug("autofill.completePasskeyAssertion", data); const { clientId, sequenceNumber, response } = data; - this.ipcServer.completeAssertion(clientId, sequenceNumber, response); + this.ipcServer?.completeAssertion(clientId, sequenceNumber, response); }); ipcMain.on("autofill.completeError", (event, data) => { this.logService.debug("autofill.completeError", data); const { clientId, sequenceNumber, error } = data; - this.ipcServer.completeError(clientId, sequenceNumber, String(error)); + this.ipcServer?.completeError(clientId, sequenceNumber, String(error)); }); } diff --git a/package-lock.json b/package-lock.json index 5321edccd18..82b7e805a70 100644 --- a/package-lock.json +++ b/package-lock.json @@ -287,7 +287,206 @@ "version": "0.1.0", "license": "GPL-3.0", "devDependencies": { - "@napi-rs/cli": "2.18.4" + "@napi-rs/cli": "3.2.0" + } + }, + "apps/desktop/node_modules/@inquirer/confirm": { + "version": "5.1.21", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.21.tgz", + "integrity": "sha512-KR8edRkIsUayMXV+o3Gv+q4jlhENF9nMYUZs9PA2HzrXeHI8M5uDag70U7RJn9yyiMZSbtF5/UexBtAVtZGSbQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "apps/desktop/node_modules/@inquirer/prompts": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-7.10.1.tgz", + "integrity": "sha512-Dx/y9bCQcXLI5ooQ5KyvA4FTgeo2jYj/7plWfV5Ak5wDPKQZgudKez2ixyfz7tKXzcJciTxqLeK7R9HItwiByg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/checkbox": "^4.3.2", + "@inquirer/confirm": "^5.1.21", + "@inquirer/editor": "^4.2.23", + "@inquirer/expand": "^4.0.23", + "@inquirer/input": "^4.3.1", + "@inquirer/number": "^3.0.23", + "@inquirer/password": "^4.0.23", + "@inquirer/rawlist": "^4.1.11", + "@inquirer/search": "^3.2.2", + "@inquirer/select": "^4.4.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "apps/desktop/node_modules/@napi-rs/cli": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@napi-rs/cli/-/cli-3.2.0.tgz", + "integrity": "sha512-heyXt/9OBPv/WrTFW2+PxIMzH6MCeqP9ZsvOg0LN6pLngBnszcxFsdhCAh5I6sddzQsvru53zj59GUzvmpWk8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/prompts": "^7.8.4", + "@napi-rs/cross-toolchain": "^1.0.3", + "@napi-rs/wasm-tools": "^1.0.1", + "@octokit/rest": "^22.0.0", + "clipanion": "^4.0.0-rc.4", + "colorette": "^2.0.20", + "debug": "^4.4.1", + "emnapi": "^1.5.0", + "es-toolkit": "^1.39.10", + "find-up": "^7.0.0", + "js-yaml": "^4.1.0", + "semver": "^7.7.2", + "typanion": "^3.14.0" + }, + "bin": { + "napi": "dist/cli.js", + "napi-raw": "cli.mjs" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/runtime": "^1.1.0", + "emnapi": "^1.1.0" + }, + "peerDependenciesMeta": { + "@emnapi/runtime": { + "optional": true + }, + "emnapi": { + "optional": true + } + } + }, + "apps/desktop/node_modules/find-up": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-7.0.0.tgz", + "integrity": "sha512-YyZM99iHrqLKjmt4LJDj58KI+fYyufRLBSYcqycxf//KpBk9FoewoGX0450m9nB44qrZnovzC2oeP5hUibxc/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^7.2.0", + "path-exists": "^5.0.0", + "unicorn-magic": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "apps/desktop/node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "apps/desktop/node_modules/locate-path": { + "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" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "apps/desktop/node_modules/p-limit": { + "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" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "apps/desktop/node_modules/p-locate": { + "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" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "apps/desktop/node_modules/path-exists": { + "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" + } + }, + "apps/desktop/node_modules/yocto-queue": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.2.tgz", + "integrity": "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "apps/web": { @@ -6123,28 +6322,28 @@ } }, "node_modules/@emnapi/core": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.3.tgz", - "integrity": "sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g==", + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.7.1.tgz", + "integrity": "sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg==", "license": "MIT", "dependencies": { - "@emnapi/wasi-threads": "1.0.2", + "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" } }, "node_modules/@emnapi/runtime": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.3.tgz", - "integrity": "sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ==", + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.7.1.tgz", + "integrity": "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==", "license": "MIT", "dependencies": { "tslib": "^2.4.0" } }, "node_modules/@emnapi/wasi-threads": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.0.2.tgz", - "integrity": "sha512-5n3nTJblwRi8LlXkJ9eBzu+kZR8Yxcc7ubakyQTFzPMtIhFpUBRbsnc2Dv88IZDIbCDlBiWrknhB4Lsz7mg6BA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz", + "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==", "license": "MIT", "dependencies": { "tslib": "^2.4.0" @@ -8792,21 +8991,411 @@ "win32" ] }, - "node_modules/@napi-rs/cli": { - "version": "2.18.4", - "resolved": "https://registry.npmjs.org/@napi-rs/cli/-/cli-2.18.4.tgz", - "integrity": "sha512-SgJeA4df9DE2iAEpr3M2H0OKl/yjtg1BnRI5/JyowS71tUWhrfSu2LT0V3vlHET+g1hBVlrO60PmEXwUEKp8Mg==", + "node_modules/@napi-rs/cross-toolchain": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@napi-rs/cross-toolchain/-/cross-toolchain-1.0.3.tgz", + "integrity": "sha512-ENPfLe4937bsKVTDA6zdABx4pq9w0tHqRrJHyaGxgaPq03a2Bd1unD5XSKjXJjebsABJ+MjAv1A2OvCgK9yehg==", "dev": true, "license": "MIT", - "bin": { - "napi": "scripts/index.js" + "workspaces": [ + ".", + "arm64/*", + "x64/*" + ], + "dependencies": { + "@napi-rs/lzma": "^1.4.5", + "@napi-rs/tar": "^1.1.0", + "debug": "^4.4.1" }, + "peerDependencies": { + "@napi-rs/cross-toolchain-arm64-target-aarch64": "^1.0.3", + "@napi-rs/cross-toolchain-arm64-target-armv7": "^1.0.3", + "@napi-rs/cross-toolchain-arm64-target-ppc64le": "^1.0.3", + "@napi-rs/cross-toolchain-arm64-target-s390x": "^1.0.3", + "@napi-rs/cross-toolchain-arm64-target-x86_64": "^1.0.3", + "@napi-rs/cross-toolchain-x64-target-aarch64": "^1.0.3", + "@napi-rs/cross-toolchain-x64-target-armv7": "^1.0.3", + "@napi-rs/cross-toolchain-x64-target-ppc64le": "^1.0.3", + "@napi-rs/cross-toolchain-x64-target-s390x": "^1.0.3", + "@napi-rs/cross-toolchain-x64-target-x86_64": "^1.0.3" + }, + "peerDependenciesMeta": { + "@napi-rs/cross-toolchain-arm64-target-aarch64": { + "optional": true + }, + "@napi-rs/cross-toolchain-arm64-target-armv7": { + "optional": true + }, + "@napi-rs/cross-toolchain-arm64-target-ppc64le": { + "optional": true + }, + "@napi-rs/cross-toolchain-arm64-target-s390x": { + "optional": true + }, + "@napi-rs/cross-toolchain-arm64-target-x86_64": { + "optional": true + }, + "@napi-rs/cross-toolchain-x64-target-aarch64": { + "optional": true + }, + "@napi-rs/cross-toolchain-x64-target-armv7": { + "optional": true + }, + "@napi-rs/cross-toolchain-x64-target-ppc64le": { + "optional": true + }, + "@napi-rs/cross-toolchain-x64-target-s390x": { + "optional": true + }, + "@napi-rs/cross-toolchain-x64-target-x86_64": { + "optional": true + } + } + }, + "node_modules/@napi-rs/lzma": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma/-/lzma-1.4.5.tgz", + "integrity": "sha512-zS5LuN1OBPAyZpda2ZZgYOEDC+xecUdAGnrvbYzjnLXkrq/OBC3B9qcRvlxbDR3k5H/gVfvef1/jyUqPknqjbg==", + "dev": true, + "license": "MIT", "engines": { "node": ">= 10" }, "funding": { "type": "github", "url": "https://github.com/sponsors/Brooooooklyn" + }, + "optionalDependencies": { + "@napi-rs/lzma-android-arm-eabi": "1.4.5", + "@napi-rs/lzma-android-arm64": "1.4.5", + "@napi-rs/lzma-darwin-arm64": "1.4.5", + "@napi-rs/lzma-darwin-x64": "1.4.5", + "@napi-rs/lzma-freebsd-x64": "1.4.5", + "@napi-rs/lzma-linux-arm-gnueabihf": "1.4.5", + "@napi-rs/lzma-linux-arm64-gnu": "1.4.5", + "@napi-rs/lzma-linux-arm64-musl": "1.4.5", + "@napi-rs/lzma-linux-ppc64-gnu": "1.4.5", + "@napi-rs/lzma-linux-riscv64-gnu": "1.4.5", + "@napi-rs/lzma-linux-s390x-gnu": "1.4.5", + "@napi-rs/lzma-linux-x64-gnu": "1.4.5", + "@napi-rs/lzma-linux-x64-musl": "1.4.5", + "@napi-rs/lzma-wasm32-wasi": "1.4.5", + "@napi-rs/lzma-win32-arm64-msvc": "1.4.5", + "@napi-rs/lzma-win32-ia32-msvc": "1.4.5", + "@napi-rs/lzma-win32-x64-msvc": "1.4.5" + } + }, + "node_modules/@napi-rs/lzma-android-arm-eabi": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-android-arm-eabi/-/lzma-android-arm-eabi-1.4.5.tgz", + "integrity": "sha512-Up4gpyw2SacmyKWWEib06GhiDdF+H+CCU0LAV8pnM4aJIDqKKd5LHSlBht83Jut6frkB0vwEPmAkv4NjQ5u//Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/lzma-android-arm64": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-android-arm64/-/lzma-android-arm64-1.4.5.tgz", + "integrity": "sha512-uwa8sLlWEzkAM0MWyoZJg0JTD3BkPknvejAFG2acUA1raXM8jLrqujWCdOStisXhqQjZ2nDMp3FV6cs//zjfuQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/lzma-darwin-arm64": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-darwin-arm64/-/lzma-darwin-arm64-1.4.5.tgz", + "integrity": "sha512-0Y0TQLQ2xAjVabrMDem1NhIssOZzF/y/dqetc6OT8mD3xMTDtF8u5BqZoX3MyPc9FzpsZw4ksol+w7DsxHrpMA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/lzma-darwin-x64": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-darwin-x64/-/lzma-darwin-x64-1.4.5.tgz", + "integrity": "sha512-vR2IUyJY3En+V1wJkwmbGWcYiT8pHloTAWdW4pG24+51GIq+intst6Uf6D/r46citObGZrlX0QvMarOkQeHWpw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/lzma-freebsd-x64": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-freebsd-x64/-/lzma-freebsd-x64-1.4.5.tgz", + "integrity": "sha512-XpnYQC5SVovO35tF0xGkbHYjsS6kqyNCjuaLQ2dbEblFRr5cAZVvsJ/9h7zj/5FluJPJRDojVNxGyRhTp4z2lw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/lzma-linux-arm-gnueabihf": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-arm-gnueabihf/-/lzma-linux-arm-gnueabihf-1.4.5.tgz", + "integrity": "sha512-ic1ZZMoRfRMwtSwxkyw4zIlbDZGC6davC9r+2oX6x9QiF247BRqqT94qGeL5ZP4Vtz0Hyy7TEViWhx5j6Bpzvw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/lzma-linux-arm64-gnu": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-arm64-gnu/-/lzma-linux-arm64-gnu-1.4.5.tgz", + "integrity": "sha512-asEp7FPd7C1Yi6DQb45a3KPHKOFBSfGuJWXcAd4/bL2Fjetb2n/KK2z14yfW8YC/Fv6x3rBM0VAZKmJuz4tysg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/lzma-linux-arm64-musl": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-arm64-musl/-/lzma-linux-arm64-musl-1.4.5.tgz", + "integrity": "sha512-yWjcPDgJ2nIL3KNvi4536dlT/CcCWO0DUyEOlBs/SacG7BeD6IjGh6yYzd3/X1Y3JItCbZoDoLUH8iB1lTXo3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/lzma-linux-ppc64-gnu": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-ppc64-gnu/-/lzma-linux-ppc64-gnu-1.4.5.tgz", + "integrity": "sha512-0XRhKuIU/9ZjT4WDIG/qnX7Xz7mSQHYZo9Gb3MP2gcvBgr6BA4zywQ9k3gmQaPn9ECE+CZg2V7DV7kT+x2pUMQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/lzma-linux-riscv64-gnu": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-riscv64-gnu/-/lzma-linux-riscv64-gnu-1.4.5.tgz", + "integrity": "sha512-QrqDIPEUUB23GCpyQj/QFyMlr8SGxxyExeZz9OWFnHfb70kXdTLWrHS/hEI1Ru+lSbQ/6xRqeoGyQ4Aqdg+/RA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/lzma-linux-s390x-gnu": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-s390x-gnu/-/lzma-linux-s390x-gnu-1.4.5.tgz", + "integrity": "sha512-k8RVM5aMhW86E9H0QXdquwojew4H3SwPxbRVbl49/COJQWCUjGi79X6mYruMnMPEznZinUiT1jgKbFo2A00NdA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/lzma-linux-x64-gnu": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.4.5.tgz", + "integrity": "sha512-6rMtBgnIq2Wcl1rQdZsnM+rtCcVCbws1nF8S2NzaUsVaZv8bjrPiAa0lwg4Eqnn1d9lgwqT+cZgm5m+//K08Kw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/lzma-linux-x64-musl": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-musl/-/lzma-linux-x64-musl-1.4.5.tgz", + "integrity": "sha512-eiadGBKi7Vd0bCArBUOO/qqRYPHt/VQVvGyYvDFt6C2ZSIjlD+HuOl+2oS1sjf4CFjK4eDIog6EdXnL0NE6iyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/lzma-wasm32-wasi": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-wasm32-wasi/-/lzma-wasm32-wasi-1.4.5.tgz", + "integrity": "sha512-+VyHHlr68dvey6fXc2hehw9gHVFIW3TtGF1XkcbAu65qVXsA9D/T+uuoRVqhE+JCyFHFrO0ixRbZDRK1XJt1sA==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^1.0.3" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@napi-rs/lzma-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.0.tgz", + "integrity": "sha512-Fq6DJW+Bb5jaWE69/qOE0D1TUN9+6uWhCeZpdnSBk14pjLcCWR7Q8n49PTSPHazM37JqrsdpEthXy2xn6jWWiA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1", + "@tybys/wasm-util": "^0.10.1" + } + }, + "node_modules/@napi-rs/lzma-wasm32-wasi/node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@napi-rs/lzma-win32-arm64-msvc": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-win32-arm64-msvc/-/lzma-win32-arm64-msvc-1.4.5.tgz", + "integrity": "sha512-eewnqvIyyhHi3KaZtBOJXohLvwwN27gfS2G/YDWdfHlbz1jrmfeHAmzMsP5qv8vGB+T80TMHNkro4kYjeh6Deg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/lzma-win32-ia32-msvc": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-win32-ia32-msvc/-/lzma-win32-ia32-msvc-1.4.5.tgz", + "integrity": "sha512-OeacFVRCJOKNU/a0ephUfYZ2Yt+NvaHze/4TgOwJ0J0P4P7X1mHzN+ig9Iyd74aQDXYqc7kaCXA2dpAOcH87Cg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/lzma-win32-x64-msvc": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-win32-x64-msvc/-/lzma-win32-x64-msvc-1.4.5.tgz", + "integrity": "sha512-T4I1SamdSmtyZgDXGAGP+y5LEK5vxHUFwe8mz6D4R7Sa5/WCxTcCIgPJ9BD7RkpO17lzhlaM2vmVvMy96Lvk9Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" } }, "node_modules/@napi-rs/nice": { @@ -9132,6 +9721,330 @@ "node": ">= 10" } }, + "node_modules/@napi-rs/tar": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@napi-rs/tar/-/tar-1.1.0.tgz", + "integrity": "sha512-7cmzIu+Vbupriudo7UudoMRH2OA3cTw67vva8MxeoAe5S7vPFI7z0vp0pMXiA25S8IUJefImQ90FeJjl8fjEaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@napi-rs/tar-android-arm-eabi": "1.1.0", + "@napi-rs/tar-android-arm64": "1.1.0", + "@napi-rs/tar-darwin-arm64": "1.1.0", + "@napi-rs/tar-darwin-x64": "1.1.0", + "@napi-rs/tar-freebsd-x64": "1.1.0", + "@napi-rs/tar-linux-arm-gnueabihf": "1.1.0", + "@napi-rs/tar-linux-arm64-gnu": "1.1.0", + "@napi-rs/tar-linux-arm64-musl": "1.1.0", + "@napi-rs/tar-linux-ppc64-gnu": "1.1.0", + "@napi-rs/tar-linux-s390x-gnu": "1.1.0", + "@napi-rs/tar-linux-x64-gnu": "1.1.0", + "@napi-rs/tar-linux-x64-musl": "1.1.0", + "@napi-rs/tar-wasm32-wasi": "1.1.0", + "@napi-rs/tar-win32-arm64-msvc": "1.1.0", + "@napi-rs/tar-win32-ia32-msvc": "1.1.0", + "@napi-rs/tar-win32-x64-msvc": "1.1.0" + } + }, + "node_modules/@napi-rs/tar-android-arm-eabi": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@napi-rs/tar-android-arm-eabi/-/tar-android-arm-eabi-1.1.0.tgz", + "integrity": "sha512-h2Ryndraj/YiKgMV/r5by1cDusluYIRT0CaE0/PekQ4u+Wpy2iUVqvzVU98ZPnhXaNeYxEvVJHNGafpOfaD0TA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/tar-android-arm64": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@napi-rs/tar-android-arm64/-/tar-android-arm64-1.1.0.tgz", + "integrity": "sha512-DJFyQHr1ZxNZorm/gzc1qBNLF/FcKzcH0V0Vwan5P+o0aE2keQIGEjJ09FudkF9v6uOuJjHCVDdK6S6uHtShAw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/tar-darwin-arm64": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@napi-rs/tar-darwin-arm64/-/tar-darwin-arm64-1.1.0.tgz", + "integrity": "sha512-Zz2sXRzjIX4e532zD6xm2SjXEym6MkvfCvL2RMpG2+UwNVDVscHNcz3d47Pf3sysP2e2af7fBB3TIoK2f6trPw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/tar-darwin-x64": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@napi-rs/tar-darwin-x64/-/tar-darwin-x64-1.1.0.tgz", + "integrity": "sha512-EI+CptIMNweT0ms9S3mkP/q+J6FNZ1Q6pvpJOEcWglRfyfQpLqjlC0O+dptruTPE8VamKYuqdjxfqD8hifZDOA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/tar-freebsd-x64": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@napi-rs/tar-freebsd-x64/-/tar-freebsd-x64-1.1.0.tgz", + "integrity": "sha512-J0PIqX+pl6lBIAckL/c87gpodLbjZB1OtIK+RDscKC9NLdpVv6VGOxzUV/fYev/hctcE8EfkLbgFOfpmVQPg2g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/tar-linux-arm-gnueabihf": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@napi-rs/tar-linux-arm-gnueabihf/-/tar-linux-arm-gnueabihf-1.1.0.tgz", + "integrity": "sha512-SLgIQo3f3EjkZ82ZwvrEgFvMdDAhsxCYjyoSuWfHCz0U16qx3SuGCp8+FYOPYCECHN3ZlGjXnoAIt9ERd0dEUg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/tar-linux-arm64-gnu": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@napi-rs/tar-linux-arm64-gnu/-/tar-linux-arm64-gnu-1.1.0.tgz", + "integrity": "sha512-d014cdle52EGaH6GpYTQOP9Py7glMO1zz/+ynJPjjzYFSxvdYx0byrjumZk2UQdIyGZiJO2MEFpCkEEKFSgPYA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/tar-linux-arm64-musl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@napi-rs/tar-linux-arm64-musl/-/tar-linux-arm64-musl-1.1.0.tgz", + "integrity": "sha512-L/y1/26q9L/uBqiW/JdOb/Dc94egFvNALUZV2WCGKQXc6UByPBMgdiEyW2dtoYxYYYYc+AKD+jr+wQPcvX2vrQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/tar-linux-ppc64-gnu": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@napi-rs/tar-linux-ppc64-gnu/-/tar-linux-ppc64-gnu-1.1.0.tgz", + "integrity": "sha512-EPE1K/80RQvPbLRJDJs1QmCIcH+7WRi0F73+oTe1582y9RtfGRuzAkzeBuAGRXAQEjRQw/RjtNqr6UTJ+8UuWQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/tar-linux-s390x-gnu": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@napi-rs/tar-linux-s390x-gnu/-/tar-linux-s390x-gnu-1.1.0.tgz", + "integrity": "sha512-B2jhWiB1ffw1nQBqLUP1h4+J1ovAxBOoe5N2IqDMOc63fsPZKNqF1PvO/dIem8z7LL4U4bsfmhy3gBfu547oNQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/tar-linux-x64-gnu": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@napi-rs/tar-linux-x64-gnu/-/tar-linux-x64-gnu-1.1.0.tgz", + "integrity": "sha512-tbZDHnb9617lTnsDMGo/eAMZxnsQFnaRe+MszRqHguKfMwkisc9CCJnks/r1o84u5fECI+J/HOrKXgczq/3Oww==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/tar-linux-x64-musl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@napi-rs/tar-linux-x64-musl/-/tar-linux-x64-musl-1.1.0.tgz", + "integrity": "sha512-dV6cODlzbO8u6Anmv2N/ilQHq/AWz0xyltuXoLU3yUyXbZcnWYZuB2rL8OBGPmqNcD+x9NdScBNXh7vWN0naSQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/tar-wasm32-wasi": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@napi-rs/tar-wasm32-wasi/-/tar-wasm32-wasi-1.1.0.tgz", + "integrity": "sha512-jIa9nb2HzOrfH0F8QQ9g3WE4aMH5vSI5/1NYVNm9ysCmNjCCtMXCAhlI3WKCdm/DwHf0zLqdrrtDFXODcNaqMw==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^1.0.3" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@napi-rs/tar-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.0.tgz", + "integrity": "sha512-Fq6DJW+Bb5jaWE69/qOE0D1TUN9+6uWhCeZpdnSBk14pjLcCWR7Q8n49PTSPHazM37JqrsdpEthXy2xn6jWWiA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1", + "@tybys/wasm-util": "^0.10.1" + } + }, + "node_modules/@napi-rs/tar-wasm32-wasi/node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@napi-rs/tar-win32-arm64-msvc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@napi-rs/tar-win32-arm64-msvc/-/tar-win32-arm64-msvc-1.1.0.tgz", + "integrity": "sha512-vfpG71OB0ijtjemp3WTdmBKJm9R70KM8vsSExMsIQtV0lVzP07oM1CW6JbNRPXNLhRoue9ofYLiUDk8bE0Hckg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/tar-win32-ia32-msvc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@napi-rs/tar-win32-ia32-msvc/-/tar-win32-ia32-msvc-1.1.0.tgz", + "integrity": "sha512-hGPyPW60YSpOSgzfy68DLBHgi6HxkAM+L59ZZZPMQ0TOXjQg+p2EW87+TjZfJOkSpbYiEkULwa/f4a2hcVjsqQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/tar-win32-x64-msvc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@napi-rs/tar-win32-x64-msvc/-/tar-win32-x64-msvc-1.1.0.tgz", + "integrity": "sha512-L6Ed1DxXK9YSCMyvpR8MiNAyKNkQLjsHsHK9E0qnHa8NzLFqzDKhvs5LfnWxM2kJ+F7m/e5n9zPm24kHb3LsVw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/@napi-rs/wasm-runtime": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.4.tgz", @@ -9143,6 +10056,276 @@ "@tybys/wasm-util": "^0.9.0" } }, + "node_modules/@napi-rs/wasm-tools": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-tools/-/wasm-tools-1.0.1.tgz", + "integrity": "sha512-enkZYyuCdo+9jneCPE/0fjIta4wWnvVN9hBo2HuiMpRF0q3lzv1J6b/cl7i0mxZUKhBrV3aCKDBQnCOhwKbPmQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@napi-rs/wasm-tools-android-arm-eabi": "1.0.1", + "@napi-rs/wasm-tools-android-arm64": "1.0.1", + "@napi-rs/wasm-tools-darwin-arm64": "1.0.1", + "@napi-rs/wasm-tools-darwin-x64": "1.0.1", + "@napi-rs/wasm-tools-freebsd-x64": "1.0.1", + "@napi-rs/wasm-tools-linux-arm64-gnu": "1.0.1", + "@napi-rs/wasm-tools-linux-arm64-musl": "1.0.1", + "@napi-rs/wasm-tools-linux-x64-gnu": "1.0.1", + "@napi-rs/wasm-tools-linux-x64-musl": "1.0.1", + "@napi-rs/wasm-tools-wasm32-wasi": "1.0.1", + "@napi-rs/wasm-tools-win32-arm64-msvc": "1.0.1", + "@napi-rs/wasm-tools-win32-ia32-msvc": "1.0.1", + "@napi-rs/wasm-tools-win32-x64-msvc": "1.0.1" + } + }, + "node_modules/@napi-rs/wasm-tools-android-arm-eabi": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-tools-android-arm-eabi/-/wasm-tools-android-arm-eabi-1.0.1.tgz", + "integrity": "sha512-lr07E/l571Gft5v4aA1dI8koJEmF1F0UigBbsqg9OWNzg80H3lDPO+auv85y3T/NHE3GirDk7x/D3sLO57vayw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/wasm-tools-android-arm64": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-tools-android-arm64/-/wasm-tools-android-arm64-1.0.1.tgz", + "integrity": "sha512-WDR7S+aRLV6LtBJAg5fmjKkTZIdrEnnQxgdsb7Cf8pYiMWBHLU+LC49OUVppQ2YSPY0+GeYm9yuZWW3kLjJ7Bg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/wasm-tools-darwin-arm64": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-tools-darwin-arm64/-/wasm-tools-darwin-arm64-1.0.1.tgz", + "integrity": "sha512-qWTI+EEkiN0oIn/N2gQo7+TVYil+AJ20jjuzD2vATS6uIjVz+Updeqmszi7zq7rdFTLp6Ea3/z4kDKIfZwmR9g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/wasm-tools-darwin-x64": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-tools-darwin-x64/-/wasm-tools-darwin-x64-1.0.1.tgz", + "integrity": "sha512-bA6hubqtHROR5UI3tToAF/c6TDmaAgF0SWgo4rADHtQ4wdn0JeogvOk50gs2TYVhKPE2ZD2+qqt7oBKB+sxW3A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/wasm-tools-freebsd-x64": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-tools-freebsd-x64/-/wasm-tools-freebsd-x64-1.0.1.tgz", + "integrity": "sha512-90+KLBkD9hZEjPQW1MDfwSt5J1L46EUKacpCZWyRuL6iIEO5CgWU0V/JnEgFsDOGyyYtiTvHc5bUdUTWd4I9Vg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/wasm-tools-linux-arm64-gnu": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-tools-linux-arm64-gnu/-/wasm-tools-linux-arm64-gnu-1.0.1.tgz", + "integrity": "sha512-rG0QlS65x9K/u3HrKafDf8cFKj5wV2JHGfl8abWgKew0GVPyp6vfsDweOwHbWAjcHtp2LHi6JHoW80/MTHm52Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/wasm-tools-linux-arm64-musl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-tools-linux-arm64-musl/-/wasm-tools-linux-arm64-musl-1.0.1.tgz", + "integrity": "sha512-jAasbIvjZXCgX0TCuEFQr+4D6Lla/3AAVx2LmDuMjgG4xoIXzjKWl7c4chuaD+TI+prWT0X6LJcdzFT+ROKGHQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/wasm-tools-linux-x64-gnu": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-tools-linux-x64-gnu/-/wasm-tools-linux-x64-gnu-1.0.1.tgz", + "integrity": "sha512-Plgk5rPqqK2nocBGajkMVbGm010Z7dnUgq0wtnYRZbzWWxwWcXfZMPa8EYxrK4eE8SzpI7VlZP1tdVsdjgGwMw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/wasm-tools-linux-x64-musl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-tools-linux-x64-musl/-/wasm-tools-linux-x64-musl-1.0.1.tgz", + "integrity": "sha512-GW7AzGuWxtQkyHknHWYFdR0CHmW6is8rG2Rf4V6GNmMpmwtXt/ItWYWtBe4zqJWycMNazpfZKSw/BpT7/MVCXQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/wasm-tools-wasm32-wasi": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-tools-wasm32-wasi/-/wasm-tools-wasm32-wasi-1.0.1.tgz", + "integrity": "sha512-/nQVSTrqSsn7YdAc2R7Ips/tnw5SPUcl3D7QrXCNGPqjbatIspnaexvaOYNyKMU6xPu+pc0BTnKVmqhlJJCPLA==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^1.0.3" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@napi-rs/wasm-tools-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.0.tgz", + "integrity": "sha512-Fq6DJW+Bb5jaWE69/qOE0D1TUN9+6uWhCeZpdnSBk14pjLcCWR7Q8n49PTSPHazM37JqrsdpEthXy2xn6jWWiA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1", + "@tybys/wasm-util": "^0.10.1" + } + }, + "node_modules/@napi-rs/wasm-tools-wasm32-wasi/node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@napi-rs/wasm-tools-win32-arm64-msvc": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-tools-win32-arm64-msvc/-/wasm-tools-win32-arm64-msvc-1.0.1.tgz", + "integrity": "sha512-PFi7oJIBu5w7Qzh3dwFea3sHRO3pojMsaEnUIy22QvsW+UJfNQwJCryVrpoUt8m4QyZXI+saEq/0r4GwdoHYFQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/wasm-tools-win32-ia32-msvc": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-tools-win32-ia32-msvc/-/wasm-tools-win32-ia32-msvc-1.0.1.tgz", + "integrity": "sha512-gXkuYzxQsgkj05Zaq+KQTkHIN83dFAwMcTKa2aQcpYPRImFm2AQzEyLtpXmyCWzJ0F9ZYAOmbSyrNew8/us6bw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/wasm-tools-win32-x64-msvc": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-tools-win32-x64-msvc/-/wasm-tools-win32-x64-msvc-1.0.1.tgz", + "integrity": "sha512-rEAf05nol3e3eei2sRButmgXP+6ATgm0/38MKhz9Isne82T4rPIMYsCIFj0kOisaGeVwoi2fnm7O9oWp5YVnYQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/@ng-select/ng-select": { "version": "20.7.0", "resolved": "https://registry.npmjs.org/@ng-select/ng-select/-/ng-select-20.7.0.tgz", @@ -11411,6 +12594,172 @@ "yargs-parser": "21.1.1" } }, + "node_modules/@octokit/auth-token": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-6.0.0.tgz", + "integrity": "sha512-P4YJBPdPSpWTQ1NU4XYdvHvXJJDxM6YwpS0FZHRgP7YFkdVxsWcpWGy/NVqlAA7PcPCnMacXlRm1y2PFZRWL/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/core": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-7.0.6.tgz", + "integrity": "sha512-DhGl4xMVFGVIyMwswXeyzdL4uXD5OGILGX5N8Y+f6W7LhC1Ze2poSNrkF/fedpVDHEEZ+PHFW0vL14I+mm8K3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/auth-token": "^6.0.0", + "@octokit/graphql": "^9.0.3", + "@octokit/request": "^10.0.6", + "@octokit/request-error": "^7.0.2", + "@octokit/types": "^16.0.0", + "before-after-hook": "^4.0.0", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/endpoint": { + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-11.0.2.tgz", + "integrity": "sha512-4zCpzP1fWc7QlqunZ5bSEjxc6yLAlRTnDwKtgXfcI/FxxGoqedDG8V2+xJ60bV2kODqcGB+nATdtap/XYq2NZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/types": "^16.0.0", + "universal-user-agent": "^7.0.2" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/graphql": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-9.0.3.tgz", + "integrity": "sha512-grAEuupr/C1rALFnXTv6ZQhFuL1D8G5y8CN04RgrO4FIPMrtm+mcZzFG7dcBm+nq+1ppNixu+Jd78aeJOYxlGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/request": "^10.0.6", + "@octokit/types": "^16.0.0", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/openapi-types": { + "version": "27.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-27.0.0.tgz", + "integrity": "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@octokit/plugin-paginate-rest": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-14.0.0.tgz", + "integrity": "sha512-fNVRE7ufJiAA3XUrha2omTA39M6IXIc6GIZLvlbsm8QOQCYvpq/LkMNGyFlB1d8hTDzsAXa3OKtybdMAYsV/fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/types": "^16.0.0" + }, + "engines": { + "node": ">= 20" + }, + "peerDependencies": { + "@octokit/core": ">=6" + } + }, + "node_modules/@octokit/plugin-request-log": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-6.0.0.tgz", + "integrity": "sha512-UkOzeEN3W91/eBq9sPZNQ7sUBvYCqYbrrD8gTbBuGtHEuycE4/awMXcYvx6sVYo7LypPhmQwwpUe4Yyu4QZN5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "peerDependencies": { + "@octokit/core": ">=6" + } + }, + "node_modules/@octokit/plugin-rest-endpoint-methods": { + "version": "17.0.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-17.0.0.tgz", + "integrity": "sha512-B5yCyIlOJFPqUUeiD0cnBJwWJO8lkJs5d8+ze9QDP6SvfiXSz1BF+91+0MeI1d2yxgOhU/O+CvtiZ9jSkHhFAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/types": "^16.0.0" + }, + "engines": { + "node": ">= 20" + }, + "peerDependencies": { + "@octokit/core": ">=6" + } + }, + "node_modules/@octokit/request": { + "version": "10.0.7", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-10.0.7.tgz", + "integrity": "sha512-v93h0i1yu4idj8qFPZwjehoJx4j3Ntn+JhXsdJrG9pYaX6j/XRz2RmasMUHtNgQD39nrv/VwTWSqK0RNXR8upA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/endpoint": "^11.0.2", + "@octokit/request-error": "^7.0.2", + "@octokit/types": "^16.0.0", + "fast-content-type-parse": "^3.0.0", + "universal-user-agent": "^7.0.2" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/request-error": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-7.1.0.tgz", + "integrity": "sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/types": "^16.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/rest": { + "version": "22.0.1", + "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-22.0.1.tgz", + "integrity": "sha512-Jzbhzl3CEexhnivb1iQ0KJ7s5vvjMWcmRtq5aUsKmKDrRW6z3r84ngmiFKFvpZjpiU/9/S6ITPFRpn5s/3uQJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/core": "^7.0.6", + "@octokit/plugin-paginate-rest": "^14.0.0", + "@octokit/plugin-request-log": "^6.0.0", + "@octokit/plugin-rest-endpoint-methods": "^17.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/types": { + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-16.0.0.tgz", + "integrity": "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^27.0.0" + } + }, "node_modules/@parcel/watcher": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz", @@ -17108,6 +18457,13 @@ "url": "https://github.com/sponsors/fb55" } }, + "node_modules/before-after-hook": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-4.0.0.tgz", + "integrity": "sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/bent": { "version": "7.3.12", "resolved": "https://registry.npmjs.org/bent/-/bent-7.3.12.tgz", @@ -18323,6 +19679,22 @@ "node": ">= 12" } }, + "node_modules/clipanion": { + "version": "4.0.0-rc.4", + "resolved": "https://registry.npmjs.org/clipanion/-/clipanion-4.0.0-rc.4.tgz", + "integrity": "sha512-CXkMQxU6s9GklO/1f714dkKBMu1lopS1WFF0B8o4AxPykR1hpozxSiUZ5ZUeBjfPgCWqbcNOtZVFhB8Lkfp1+Q==", + "dev": true, + "license": "MIT", + "workspaces": [ + "website" + ], + "dependencies": { + "typanion": "^3.8.0" + }, + "peerDependencies": { + "typanion": "*" + } + }, "node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -20856,6 +22228,21 @@ "url": "https://github.com/sindresorhus/emittery?sponsor=1" } }, + "node_modules/emnapi": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/emnapi/-/emnapi-1.7.1.tgz", + "integrity": "sha512-wlLK2xFq+T+rCBlY6+lPlFVDEyE93b7hSn9dMrfWBIcPf4ArwUvymvvMnN9M5WWuiryYQe9M+UJrkqw4trdyRA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "node-addon-api": ">= 6.1.0" + }, + "peerDependenciesMeta": { + "node-addon-api": { + "optional": true + } + } + }, "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -21179,6 +22566,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es-toolkit": { + "version": "1.42.0", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.42.0.tgz", + "integrity": "sha512-SLHIyY7VfDJBM8clz4+T2oquwTQxEzu263AyhVK4jREOAwJ+8eebaa4wM3nlvnAqhDrMm2EsA6hWHaQsMPQ1nA==", + "dev": true, + "license": "MIT", + "workspaces": [ + "docs", + "benchmarks" + ] + }, "node_modules/es6-error": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", @@ -22182,6 +23580,23 @@ "node": ">=10.13.0" } }, + "node_modules/fast-content-type-parse": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-3.0.0.tgz", + "integrity": "sha512-ZvLdcY8P+N8mGQJahJV5G4U88CSvT1rP8ApL6uETe88MBXrBHAkZlSEySdUlyztF7ccb+Znos3TFqaepHxdhBg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -39707,6 +41122,16 @@ "node": "*" } }, + "node_modules/typanion": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/typanion/-/typanion-3.14.0.tgz", + "integrity": "sha512-ZW/lVMRabETuYCd9O9ZvMhAh8GslSqaUjxmK/JLPCh6l73CvLBiuXswj/+7LdnWOgYsQ130FqLzFz5aGT4I3Ug==", + "dev": true, + "license": "MIT", + "workspaces": [ + "website" + ] + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -40112,6 +41537,19 @@ "node": ">=4" } }, + "node_modules/unicorn-magic": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", + "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/unified": { "version": "11.0.5", "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", @@ -40217,6 +41655,13 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/universal-user-agent": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.3.tgz", + "integrity": "sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==", + "dev": true, + "license": "ISC" + }, "node_modules/universalify": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", From 0e277a411d3c37fcb4141cb652e6dddfd44ba37d Mon Sep 17 00:00:00 2001 From: Ike <137194738+ike-kottlowski@users.noreply.github.com> Date: Wed, 10 Dec 2025 10:31:28 -0500 Subject: [PATCH 031/188] [PM-1632] Redirect on SSO required response from `connect/token` (#17637) * feat: add Identity Sso Required Response type as possible response from token endpoint. * feat: consume sso organization identifier to redirect user * feat: add get requiresSso to AuthResult for more ergonomic code. * feat: sso-redirect on sso-required for CLI and Desktop * chore: fixing type errors * test: fix and add tests for new sso method * docs: fix misspelling * fix: get email from AuthResult instead of the FormGroup * fix:claude: when email is not available for SSO login show error toast. * fix:claude: add null safety check --- .../extension-login-component.service.spec.ts | 30 +++++++ .../extension-login-component.service.ts | 2 + apps/cli/src/auth/commands/login.command.ts | 80 ++++++++++++++++--- .../desktop-login-component.service.spec.ts | 52 ++++++++++++ .../login/desktop-login-component.service.ts | 12 ++- apps/desktop/src/platform/preload.ts | 9 ++- .../sso-localhost-callback.service.ts | 33 +++++--- .../login/web-login-component.service.ts | 5 +- apps/web/src/locales/en/messages.json | 3 + .../login/default-login-component.service.ts | 36 ++++++--- .../angular/login/login-component.service.ts | 8 ++ .../auth/src/angular/login/login.component.ts | 18 +++++ .../common/login-strategies/login.strategy.ts | 21 ++++- .../password-login.strategy.ts | 11 ++- .../services/sso-redirect/sso-url.service.ts | 4 +- libs/common/src/abstractions/api.service.ts | 6 +- .../src/auth/models/domain/auth-result.ts | 8 ++ .../identity-sso-required.response.ts | 10 +++ libs/common/src/services/api.service.ts | 8 +- 19 files changed, 308 insertions(+), 48 deletions(-) create mode 100644 libs/common/src/auth/models/response/identity-sso-required.response.ts diff --git a/apps/browser/src/auth/popup/login/extension-login-component.service.spec.ts b/apps/browser/src/auth/popup/login/extension-login-component.service.spec.ts index bd85ff9293e..dc1a7b4bb6b 100644 --- a/apps/browser/src/auth/popup/login/extension-login-component.service.spec.ts +++ b/apps/browser/src/auth/popup/login/extension-login-component.service.spec.ts @@ -102,6 +102,36 @@ describe("ExtensionLoginComponentService", () => { }); }); + describe("redirectToSsoLoginWithOrganizationSsoIdentifier", () => { + it("launches SSO browser window with correct Url", async () => { + const email = "test@bitwarden.com"; + const state = "testState"; + const expectedState = "testState:clientId=browser"; + const codeVerifier = "testCodeVerifier"; + const codeChallenge = "testCodeChallenge"; + const orgSsoIdentifier = "org-sso-identifier"; + + passwordGenerationService.generatePassword.mockResolvedValueOnce(state); + passwordGenerationService.generatePassword.mockResolvedValueOnce(codeVerifier); + jest.spyOn(Utils, "fromBufferToUrlB64").mockReturnValue(codeChallenge); + + await service.redirectToSsoLoginWithOrganizationSsoIdentifier(email, orgSsoIdentifier); + + expect(ssoUrlService.buildSsoUrl).toHaveBeenCalledWith( + expect.any(String), + expect.any(String), + expect.any(String), + expect.any(String), + expect.any(String), + email, + orgSsoIdentifier, + ); + expect(ssoLoginService.setSsoState).toHaveBeenCalledWith(expectedState); + expect(ssoLoginService.setCodeVerifier).toHaveBeenCalledWith(codeVerifier); + expect(platformUtilsService.launchUri).toHaveBeenCalled(); + }); + }); + describe("showBackButton", () => { it("sets showBackButton in extensionAnonLayoutWrapperDataService", () => { service.showBackButton(true); 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 621c7d74876..cfaf6e04d10 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 @@ -47,6 +47,7 @@ export class ExtensionLoginComponentService email: string, state: string, codeChallenge: string, + orgSsoIdentifier?: string, ): Promise { const env = await firstValueFrom(this.environmentService.environment$); const webVaultUrl = env.getWebVaultUrl(); @@ -60,6 +61,7 @@ export class ExtensionLoginComponentService state, codeChallenge, email, + orgSsoIdentifier, ); this.platformUtilsService.launchUri(webAppSsoUrl); diff --git a/apps/cli/src/auth/commands/login.command.ts b/apps/cli/src/auth/commands/login.command.ts index 661e052fb72..d8859318b52 100644 --- a/apps/cli/src/auth/commands/login.command.ts +++ b/apps/cli/src/auth/commands/login.command.ts @@ -113,20 +113,14 @@ export class LoginCommand { } else if (options.sso != null && this.canInteract) { // If the optional Org SSO Identifier isn't provided, the option value is `true`. const orgSsoIdentifier = options.sso === true ? null : options.sso; - const passwordOptions: any = { - type: "password", - length: 64, - uppercase: true, - lowercase: true, - numbers: true, - special: false, - }; - const state = await this.passwordGenerationService.generatePassword(passwordOptions); - ssoCodeVerifier = await this.passwordGenerationService.generatePassword(passwordOptions); - const codeVerifierHash = await this.cryptoFunctionService.hash(ssoCodeVerifier, "sha256"); - const codeChallenge = Utils.fromBufferToUrlB64(codeVerifierHash); + const ssoPromptData = await this.makeSsoPromptData(); + ssoCodeVerifier = ssoPromptData.ssoCodeVerifier; try { - const ssoParams = await this.openSsoPrompt(codeChallenge, state, orgSsoIdentifier); + const ssoParams = await this.openSsoPrompt( + ssoPromptData.codeChallenge, + ssoPromptData.state, + orgSsoIdentifier, + ); ssoCode = ssoParams.ssoCode; orgIdentifier = ssoParams.orgIdentifier; } catch { @@ -231,9 +225,43 @@ export class LoginCommand { new PasswordLoginCredentials(email, password, twoFactor), ); } + + // Begin Acting on initial AuthResult + if (response.requiresEncryptionKeyMigration) { return Response.error(this.i18nService.t("legacyEncryptionUnsupported")); } + + // Opting for not checking feature flag since the server will not respond with + // SsoOrganizationIdentifier if the feature flag is not enabled. + if (response.requiresSso && this.canInteract) { + const ssoPromptData = await this.makeSsoPromptData(); + ssoCodeVerifier = ssoPromptData.ssoCodeVerifier; + try { + const ssoParams = await this.openSsoPrompt( + ssoPromptData.codeChallenge, + ssoPromptData.state, + response.ssoOrganizationIdentifier, + ); + ssoCode = ssoParams.ssoCode; + orgIdentifier = ssoParams.orgIdentifier; + if (ssoCode != null && ssoCodeVerifier != null) { + response = await this.loginStrategyService.logIn( + new SsoLoginCredentials( + ssoCode, + ssoCodeVerifier, + this.ssoRedirectUri, + orgIdentifier, + undefined, // email to look up 2FA token not required as CLI can't remember 2FA token + twoFactor, + ), + ); + } + } catch { + return Response.badRequest("Something went wrong. Try again."); + } + } + if (response.requiresTwoFactor) { const twoFactorProviders = await this.twoFactorService.getSupportedProviders(null); if (twoFactorProviders.length === 0) { @@ -279,6 +307,10 @@ export class LoginCommand { if (twoFactorToken == null && selectedProvider.type === TwoFactorProviderType.Email) { const emailReq = new TwoFactorEmailRequest(); emailReq.email = await this.loginStrategyService.getEmail(); + // if the user was logging in with SSO, we need to include the SSO session token + if (response.ssoEmail2FaSessionToken != null) { + emailReq.ssoEmail2FaSessionToken = response.ssoEmail2FaSessionToken; + } emailReq.masterPasswordHash = await this.loginStrategyService.getMasterPasswordHash(); await this.twoFactorApiService.postTwoFactorEmail(emailReq); } @@ -324,6 +356,7 @@ export class LoginCommand { response = await this.loginStrategyService.logInNewDeviceVerification(newDeviceToken); } + // We check response two factor again here since MFA could fail based on the logic on ln 226 if (response.requiresTwoFactor) { return Response.error("Login failed."); } @@ -692,6 +725,27 @@ export class LoginCommand { }; } + /// Generate SSO prompt data: code verifier, code challenge, and state + private async makeSsoPromptData(): Promise<{ + ssoCodeVerifier: string; + codeChallenge: string; + state: string; + }> { + const passwordOptions: any = { + type: "password", + length: 64, + uppercase: true, + lowercase: true, + numbers: true, + special: false, + }; + const state = await this.passwordGenerationService.generatePassword(passwordOptions); + const ssoCodeVerifier = await this.passwordGenerationService.generatePassword(passwordOptions); + const codeVerifierHash = await this.cryptoFunctionService.hash(ssoCodeVerifier, "sha256"); + const codeChallenge = Utils.fromBufferToUrlB64(codeVerifierHash); + return { ssoCodeVerifier, codeChallenge, state }; + } + private async openSsoPrompt( codeChallenge: string, state: string, diff --git a/apps/desktop/src/auth/login/desktop-login-component.service.spec.ts b/apps/desktop/src/auth/login/desktop-login-component.service.spec.ts index c88627250c9..414bbaca56f 100644 --- a/apps/desktop/src/auth/login/desktop-login-component.service.spec.ts +++ b/apps/desktop/src/auth/login/desktop-login-component.service.spec.ts @@ -136,6 +136,7 @@ describe("DesktopLoginComponentService", () => { codeChallenge, state, email, + undefined, ); } else { expect(ssoLoginService.setSsoState).toHaveBeenCalledWith(state); @@ -145,4 +146,55 @@ describe("DesktopLoginComponentService", () => { }); }); }); + + describe("redirectToSsoLoginWithOrganizationSsoIdentifier", () => { + // Array of all permutations of isAppImage and isDev + const permutations = [ + [true, false], // Case 1: isAppImage true + [false, true], // Case 2: isDev true + [true, true], // Case 3: all true + [false, false], // Case 4: all false + ]; + + permutations.forEach(([isAppImage, isDev]) => { + it("calls redirectToSso with orgSsoIdentifier", async () => { + (global as any).ipc.platform.isAppImage = isAppImage; + (global as any).ipc.platform.isDev = isDev; + + const email = "test@bitwarden.com"; + const state = "testState"; + const codeVerifier = "testCodeVerifier"; + const codeChallenge = "testCodeChallenge"; + const orgSsoIdentifier = "orgSsoId"; + + passwordGenerationService.generatePassword.mockResolvedValueOnce(state); + passwordGenerationService.generatePassword.mockResolvedValueOnce(codeVerifier); + jest.spyOn(Utils, "fromBufferToUrlB64").mockReturnValue(codeChallenge); + + await service.redirectToSsoLoginWithOrganizationSsoIdentifier(email, orgSsoIdentifier); + + if (isAppImage || isDev) { + expect(ipc.platform.localhostCallbackService.openSsoPrompt).toHaveBeenCalledWith( + codeChallenge, + state, + email, + orgSsoIdentifier, + ); + } else { + expect(ssoUrlService.buildSsoUrl).toHaveBeenCalledWith( + expect.any(String), + expect.any(String), + expect.any(String), + expect.any(String), + expect.any(String), + email, + orgSsoIdentifier, + ); + expect(ssoLoginService.setSsoState).toHaveBeenCalledWith(state); + expect(ssoLoginService.setCodeVerifier).toHaveBeenCalledWith(codeVerifier); + expect(platformUtilsService.launchUri).toHaveBeenCalled(); + } + }); + }); + }); }); diff --git a/apps/desktop/src/auth/login/desktop-login-component.service.ts b/apps/desktop/src/auth/login/desktop-login-component.service.ts index d7e7ba0178b..6ef39eaa018 100644 --- a/apps/desktop/src/auth/login/desktop-login-component.service.ts +++ b/apps/desktop/src/auth/login/desktop-login-component.service.ts @@ -48,11 +48,12 @@ export class DesktopLoginComponentService email: string, state: string, codeChallenge: string, + orgSsoIdentifier?: string, ): Promise { // For platforms that cannot support a protocol-based (e.g. bitwarden://) callback, we use a localhost callback // Otherwise, we launch the SSO component in a browser window and wait for the callback if (ipc.platform.isAppImage || ipc.platform.isDev) { - await this.initiateSsoThroughLocalhostCallback(email, state, codeChallenge); + await this.initiateSsoThroughLocalhostCallback(email, state, codeChallenge, orgSsoIdentifier); } else { const env = await firstValueFrom(this.environmentService.environment$); const webVaultUrl = env.getWebVaultUrl(); @@ -66,6 +67,7 @@ export class DesktopLoginComponentService state, codeChallenge, email, + orgSsoIdentifier, ); this.platformUtilsService.launchUri(ssoWebAppUrl); @@ -76,9 +78,15 @@ export class DesktopLoginComponentService email: string, state: string, challenge: string, + orgSsoIdentifier?: string, ): Promise { try { - await ipc.platform.localhostCallbackService.openSsoPrompt(challenge, state, email); + await ipc.platform.localhostCallbackService.openSsoPrompt( + challenge, + state, + email, + orgSsoIdentifier, + ); // FIXME: Remove when updating file. Eslint update // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (err) { diff --git a/apps/desktop/src/platform/preload.ts b/apps/desktop/src/platform/preload.ts index 5af2fa571ec..a45ac753b3f 100644 --- a/apps/desktop/src/platform/preload.ts +++ b/apps/desktop/src/platform/preload.ts @@ -108,8 +108,13 @@ const ephemeralStore = { }; const localhostCallbackService = { - openSsoPrompt: (codeChallenge: string, state: string, email: string): Promise => { - return ipcRenderer.invoke("openSsoPrompt", { codeChallenge, state, email }); + openSsoPrompt: ( + codeChallenge: string, + state: string, + email: string, + orgSsoIdentifier?: string, + ): Promise => { + return ipcRenderer.invoke("openSsoPrompt", { codeChallenge, state, email, orgSsoIdentifier }); }, }; diff --git a/apps/desktop/src/platform/services/sso-localhost-callback.service.ts b/apps/desktop/src/platform/services/sso-localhost-callback.service.ts index 75a84919b07..fdd9bc29237 100644 --- a/apps/desktop/src/platform/services/sso-localhost-callback.service.ts +++ b/apps/desktop/src/platform/services/sso-localhost-callback.service.ts @@ -25,20 +25,25 @@ export class SSOLocalhostCallbackService { private messagingService: MessageSender, private ssoUrlService: SsoUrlService, ) { - ipcMain.handle("openSsoPrompt", async (event, { codeChallenge, state, email }) => { - // Close any existing server before starting new one - if (this.currentServer) { - await this.closeCurrentServer(); - } + ipcMain.handle( + "openSsoPrompt", + async (event, { codeChallenge, state, email, orgSsoIdentifier }) => { + // Close any existing server before starting new one + if (this.currentServer) { + await this.closeCurrentServer(); + } - return this.openSsoPrompt(codeChallenge, state, email).then(({ ssoCode, recvState }) => { - this.messagingService.send("ssoCallback", { - code: ssoCode, - state: recvState, - redirectUri: this.ssoRedirectUri, - }); - }); - }); + return this.openSsoPrompt(codeChallenge, state, email, orgSsoIdentifier).then( + ({ ssoCode, recvState }) => { + this.messagingService.send("ssoCallback", { + code: ssoCode, + state: recvState, + redirectUri: this.ssoRedirectUri, + }); + }, + ); + }, + ); } private async closeCurrentServer(): Promise { @@ -58,6 +63,7 @@ export class SSOLocalhostCallbackService { codeChallenge: string, state: string, email: string, + orgSsoIdentifier?: string, ): Promise<{ ssoCode: string; recvState: string }> { const env = await firstValueFrom(this.environmentService.environment$); @@ -121,6 +127,7 @@ export class SSOLocalhostCallbackService { state, codeChallenge, email, + orgSsoIdentifier, ); // Set up error handler before attempting to listen diff --git a/apps/web/src/app/auth/core/services/login/web-login-component.service.ts b/apps/web/src/app/auth/core/services/login/web-login-component.service.ts index 5bea0908b0a..8c1bc4bd080 100644 --- a/apps/web/src/app/auth/core/services/login/web-login-component.service.ts +++ b/apps/web/src/app/auth/core/services/login/web-login-component.service.ts @@ -61,8 +61,11 @@ export class WebLoginComponentService email: string, state: string, codeChallenge: string, + orgSsoIdentifier?: string, ): Promise { - await this.router.navigate(["/sso"]); + await this.router.navigate(["/sso"], { + queryParams: { identifier: orgSsoIdentifier }, + }); return; } diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 85159c0230c..be2f72e34b0 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -9490,6 +9490,9 @@ "ssoLoginIsRequired": { "message": "SSO login is required" }, + "emailRequiredForSsoLogin": { + "message": "Email is required for SSO" + }, "selectedRegionFlag": { "message": "Selected region flag" }, diff --git a/libs/auth/src/angular/login/default-login-component.service.ts b/libs/auth/src/angular/login/default-login-component.service.ts index 7f98040d9c2..2d50a0ffeb7 100644 --- a/libs/auth/src/angular/login/default-login-component.service.ts +++ b/libs/auth/src/angular/login/default-login-component.service.ts @@ -33,19 +33,27 @@ export class DefaultLoginComponentService implements LoginComponentService { */ async redirectToSsoLogin(email: string): Promise { // Set the state that we'll need to verify the SSO login when we get the code back - const [state, codeChallenge] = await this.setSsoPreLoginState(); - - // Set the email address in state. This is used in 2 places: - // 1. On the web client, on the SSO component we need the email address to look up - // the org SSO identifier. The email address is passed via query param for the other clients. - // 2. On all clients, after authentication on the originating client the SSO component - // will need to look up 2FA Remember token by email. - await this.ssoLoginService.setSsoEmail(email); + const [state, codeChallenge] = await this.setSsoPreLoginState(email); // Finally, we redirect to the SSO login page. This will be handled by each client implementation of this service. await this.redirectToSso(email, state, codeChallenge); } + /** + * Redirects the user to the SSO login page, either via route or in a new browser window. + * @param email The email address of the user attempting to log in + */ + async redirectToSsoLoginWithOrganizationSsoIdentifier( + email: string, + orgSsoIdentifier: string, + ): Promise { + // Set the state that we'll need to verify the SSO login when we get the code back + const [state, codeChallenge] = await this.setSsoPreLoginState(email); + + // Finally, we redirect to the SSO login page. This will be handled by each client implementation of this service. + await this.redirectToSso(email, state, codeChallenge, orgSsoIdentifier); + } + /** * No-op implementation of redirectToSso */ @@ -53,6 +61,7 @@ export class DefaultLoginComponentService implements LoginComponentService { email: string, state: string, codeChallenge: string, + orgSsoIdentifier?: string, ): Promise { return; } @@ -65,9 +74,9 @@ export class DefaultLoginComponentService implements LoginComponentService { } /** - * Sets the state required for verifying SSO login after completion + * Set the state that we'll need to verify the SSO login when we get the authorization code back */ - private async setSsoPreLoginState(): Promise<[string, string]> { + private async setSsoPreLoginState(email: string): Promise<[string, string]> { // Generate SSO params const passwordOptions: any = { type: "password", @@ -93,6 +102,13 @@ export class DefaultLoginComponentService implements LoginComponentService { await this.ssoLoginService.setSsoState(state); await this.ssoLoginService.setCodeVerifier(codeVerifier); + // Set the email address in state. This is used in 2 places: + // 1. On the web client, on the SSO component we need the email address to look up + // the org SSO identifier. The email address is passed via query param for the other clients. + // 2. On all clients, after authentication on the originating client the SSO component + // will need to look up 2FA Remember token by email. + await this.ssoLoginService.setSsoEmail(email); + return [state, codeChallenge]; } } diff --git a/libs/auth/src/angular/login/login-component.service.ts b/libs/auth/src/angular/login/login-component.service.ts index 5ca83c97c5f..b7c2b16ce24 100644 --- a/libs/auth/src/angular/login/login-component.service.ts +++ b/libs/auth/src/angular/login/login-component.service.ts @@ -35,6 +35,14 @@ export abstract class LoginComponentService { */ redirectToSsoLogin: (email: string) => Promise; + /** + * Redirects the user to the SSO login page with organization SSO identifier, either via route or in a new browser window. + */ + redirectToSsoLoginWithOrganizationSsoIdentifier: ( + email: string, + orgSsoIdentifier: string | null | undefined, + ) => Promise; + /** * Shows the back button. */ diff --git a/libs/auth/src/angular/login/login.component.ts b/libs/auth/src/angular/login/login.component.ts index 91ca2b614d1..8e688f3f830 100644 --- a/libs/auth/src/angular/login/login.component.ts +++ b/libs/auth/src/angular/login/login.component.ts @@ -381,6 +381,24 @@ export class LoginComponent implements OnInit, OnDestroy { return; } + // redirect to SSO if ssoOrganizationIdentifier is present in token response + if (authResult.requiresSso) { + const email = this.formGroup?.value?.email; + if (!email) { + this.toastService.showToast({ + variant: "error", + title: this.i18nService.t("errorOccurred"), + message: this.i18nService.t("emailRequiredForSsoLogin"), + }); + return; + } + await this.loginComponentService.redirectToSsoLoginWithOrganizationSsoIdentifier( + email, + authResult.ssoOrganizationIdentifier, + ); + return; + } + // User logged in successfully so execute side effects await this.loginSuccessHandlerService.run(authResult.userId, authResult.masterPassword); diff --git a/libs/auth/src/common/login-strategies/login.strategy.ts b/libs/auth/src/common/login-strategies/login.strategy.ts index 08d5ae6246f..ae375c8b2f5 100644 --- a/libs/auth/src/common/login-strategies/login.strategy.ts +++ b/libs/auth/src/common/login-strategies/login.strategy.ts @@ -13,6 +13,7 @@ import { TokenTwoFactorRequest } from "@bitwarden/common/auth/models/request/ide import { UserApiTokenRequest } from "@bitwarden/common/auth/models/request/identity-token/user-api-token.request"; import { WebAuthnLoginTokenRequest } from "@bitwarden/common/auth/models/request/identity-token/webauthn-login-token.request"; import { IdentityDeviceVerificationResponse } from "@bitwarden/common/auth/models/response/identity-device-verification.response"; +import { IdentitySsoRequiredResponse } from "@bitwarden/common/auth/models/response/identity-sso-required.response"; import { IdentityTokenResponse } from "@bitwarden/common/auth/models/response/identity-token.response"; import { IdentityTwoFactorResponse } from "@bitwarden/common/auth/models/response/identity-two-factor.response"; import { TwoFactorService } from "@bitwarden/common/auth/two-factor"; @@ -49,7 +50,8 @@ import { CacheData } from "../services/login-strategies/login-strategy.state"; type IdentityResponse = | IdentityTokenResponse | IdentityTwoFactorResponse - | IdentityDeviceVerificationResponse; + | IdentityDeviceVerificationResponse + | IdentitySsoRequiredResponse; export abstract class LoginStrategyData { tokenRequest: @@ -128,6 +130,8 @@ export abstract class LoginStrategy { return [await this.processTokenResponse(response), response]; } else if (response instanceof IdentityDeviceVerificationResponse) { return [await this.processDeviceVerificationResponse(response), response]; + } else if (response instanceof IdentitySsoRequiredResponse) { + return [await this.processSsoRequiredResponse(response), response]; } throw new Error("Invalid response object."); @@ -398,4 +402,19 @@ export abstract class LoginStrategy { result.requiresDeviceVerification = true; return result; } + + /** + * Handles the response from the server when a SSO Authentication is required. + * It hydrates the AuthResult with the SSO organization identifier. + * + * @param {IdentitySsoRequiredResponse} response - The response from the server indicating that SSO is required. + * @returns {Promise} - A promise that resolves to an AuthResult object + */ + protected async processSsoRequiredResponse( + response: IdentitySsoRequiredResponse, + ): Promise { + const result = new AuthResult(); + result.ssoOrganizationIdentifier = response.ssoOrganizationIdentifier; + return result; + } } diff --git a/libs/auth/src/common/login-strategies/password-login.strategy.ts b/libs/auth/src/common/login-strategies/password-login.strategy.ts index ad49567b2ff..842a48e28cd 100644 --- a/libs/auth/src/common/login-strategies/password-login.strategy.ts +++ b/libs/auth/src/common/login-strategies/password-login.strategy.ts @@ -10,6 +10,7 @@ import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/for import { PasswordTokenRequest } from "@bitwarden/common/auth/models/request/identity-token/password-token.request"; import { TokenTwoFactorRequest } from "@bitwarden/common/auth/models/request/identity-token/token-two-factor.request"; import { IdentityDeviceVerificationResponse } from "@bitwarden/common/auth/models/response/identity-device-verification.response"; +import { IdentitySsoRequiredResponse } from "@bitwarden/common/auth/models/response/identity-sso-required.response"; import { IdentityTokenResponse } from "@bitwarden/common/auth/models/response/identity-token.response"; import { IdentityTwoFactorResponse } from "@bitwarden/common/auth/models/response/identity-two-factor.response"; import { HashPurpose } from "@bitwarden/common/platform/enums"; @@ -165,14 +166,20 @@ export class PasswordLoginStrategy extends LoginStrategy { identityResponse: | IdentityTokenResponse | IdentityTwoFactorResponse - | IdentityDeviceVerificationResponse, + | IdentityDeviceVerificationResponse + | IdentitySsoRequiredResponse, credentials: PasswordLoginCredentials, authResult: AuthResult, ): Promise { // TODO: PM-21084 - investigate if we should be sending down masterPasswordPolicy on the // IdentityDeviceVerificationResponse like we do for the IdentityTwoFactorResponse // If the response is a device verification response, we don't need to evaluate the password - if (identityResponse instanceof IdentityDeviceVerificationResponse) { + // If SSO is required, we also do not evaluate the password here, since the user needs to first + // authenticate with their SSO IdP Provider + if ( + identityResponse instanceof IdentityDeviceVerificationResponse || + identityResponse instanceof IdentitySsoRequiredResponse + ) { return; } diff --git a/libs/auth/src/common/services/sso-redirect/sso-url.service.ts b/libs/auth/src/common/services/sso-redirect/sso-url.service.ts index b2d6231db7c..22404c5b1f7 100644 --- a/libs/auth/src/common/services/sso-redirect/sso-url.service.ts +++ b/libs/auth/src/common/services/sso-redirect/sso-url.service.ts @@ -8,9 +8,9 @@ export class SsoUrlService { * @param webAppUrl The URL of the web app * @param clientType The client type that is initiating SSO, which will drive how the response is handled * @param redirectUri The redirect URI or callback that will receive the SSO code after authentication - * @param state A state value that will be peristed through the SSO flow + * @param state A state value that will be persisted through the SSO flow * @param codeChallenge A challenge value that will be used to verify the SSO code after authentication - * @param email The optional email adddress of the user initiating SSO, which will be used to look up the org SSO identifier + * @param email The optional email address of the user initiating SSO, which will be used to look up the org SSO identifier * @param orgSsoIdentifier The optional SSO identifier of the org that is initiating SSO * @returns The URL for redirecting users to the web app SSO component */ diff --git a/libs/common/src/abstractions/api.service.ts b/libs/common/src/abstractions/api.service.ts index f7ca1964b76..72a17f0fa87 100644 --- a/libs/common/src/abstractions/api.service.ts +++ b/libs/common/src/abstractions/api.service.ts @@ -50,6 +50,7 @@ import { UpdateProfileRequest } from "../auth/models/request/update-profile.requ import { ApiKeyResponse } from "../auth/models/response/api-key.response"; import { AuthRequestResponse } from "../auth/models/response/auth-request.response"; import { IdentityDeviceVerificationResponse } from "../auth/models/response/identity-device-verification.response"; +import { IdentitySsoRequiredResponse } from "../auth/models/response/identity-sso-required.response"; import { IdentityTokenResponse } from "../auth/models/response/identity-token.response"; import { IdentityTwoFactorResponse } from "../auth/models/response/identity-two-factor.response"; import { KeyConnectorUserKeyResponse } from "../auth/models/response/key-connector-user-key.response"; @@ -140,7 +141,10 @@ export abstract class ApiService { | UserApiTokenRequest | WebAuthnLoginTokenRequest, ): Promise< - IdentityTokenResponse | IdentityTwoFactorResponse | IdentityDeviceVerificationResponse + | IdentityTokenResponse + | IdentityTwoFactorResponse + | IdentityDeviceVerificationResponse + | IdentitySsoRequiredResponse >; abstract refreshIdentityToken(userId?: UserId): Promise; diff --git a/libs/common/src/auth/models/domain/auth-result.ts b/libs/common/src/auth/models/domain/auth-result.ts index ae3e9bdeda6..178866901d3 100644 --- a/libs/common/src/auth/models/domain/auth-result.ts +++ b/libs/common/src/auth/models/domain/auth-result.ts @@ -1,5 +1,7 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore +import { Utils } from "@bitwarden/common/platform/misc/utils"; + import { UserId } from "../../../types/guid"; import { TwoFactorProviderType } from "../../enums/two-factor-provider-type"; @@ -18,10 +20,16 @@ export class AuthResult { email: string; requiresEncryptionKeyMigration: boolean; requiresDeviceVerification: boolean; + ssoOrganizationIdentifier?: string | null; // The master-password used in the authentication process masterPassword: string | null; get requiresTwoFactor() { return this.twoFactorProviders != null; } + + // This is not as extensible as an object-based approach. In the future we may need to adjust to an object based approach. + get requiresSso() { + return !Utils.isNullOrWhitespace(this.ssoOrganizationIdentifier); + } } diff --git a/libs/common/src/auth/models/response/identity-sso-required.response.ts b/libs/common/src/auth/models/response/identity-sso-required.response.ts new file mode 100644 index 00000000000..b1b6df6fd08 --- /dev/null +++ b/libs/common/src/auth/models/response/identity-sso-required.response.ts @@ -0,0 +1,10 @@ +import { BaseResponse } from "@bitwarden/common/models/response/base.response"; + +export class IdentitySsoRequiredResponse extends BaseResponse { + ssoOrganizationIdentifier: string | null; + + constructor(response: any) { + super(response); + this.ssoOrganizationIdentifier = this.getResponseProperty("SsoOrganizationIdentifier"); + } +} diff --git a/libs/common/src/services/api.service.ts b/libs/common/src/services/api.service.ts index 5f4d3de11b5..c60f6c5e907 100644 --- a/libs/common/src/services/api.service.ts +++ b/libs/common/src/services/api.service.ts @@ -63,6 +63,7 @@ import { UpdateProfileRequest } from "../auth/models/request/update-profile.requ import { ApiKeyResponse } from "../auth/models/response/api-key.response"; import { AuthRequestResponse } from "../auth/models/response/auth-request.response"; import { IdentityDeviceVerificationResponse } from "../auth/models/response/identity-device-verification.response"; +import { IdentitySsoRequiredResponse } from "../auth/models/response/identity-sso-required.response"; import { IdentityTokenResponse } from "../auth/models/response/identity-token.response"; import { IdentityTwoFactorResponse } from "../auth/models/response/identity-two-factor.response"; import { KeyConnectorUserKeyResponse } from "../auth/models/response/key-connector-user-key.response"; @@ -165,7 +166,10 @@ export class ApiService implements ApiServiceAbstraction { | SsoTokenRequest | WebAuthnLoginTokenRequest, ): Promise< - IdentityTokenResponse | IdentityTwoFactorResponse | IdentityDeviceVerificationResponse + | IdentityTokenResponse + | IdentityTwoFactorResponse + | IdentityDeviceVerificationResponse + | IdentitySsoRequiredResponse > { const headers = new Headers({ "Content-Type": "application/x-www-form-urlencoded; charset=utf-8", @@ -212,6 +216,8 @@ export class ApiService implements ApiServiceAbstraction { responseJson?.ErrorModel?.Message === ApiService.NEW_DEVICE_VERIFICATION_REQUIRED_MESSAGE ) { return new IdentityDeviceVerificationResponse(responseJson); + } else if (response.status === 400 && responseJson?.SsoOrganizationIdentifier) { + return new IdentitySsoRequiredResponse(responseJson); } } From 38ca88be1fd9fc29738af99f67c9f8bca0076395 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 10 Dec 2025 10:08:08 -0600 Subject: [PATCH 032/188] [deps] Platform: Update parse5 to v7.3.0 (#14924) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package-lock.json | 20 ++++++++++++++++---- package.json | 2 +- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 82b7e805a70..b6e8d19af59 100644 --- a/package-lock.json +++ b/package-lock.json @@ -34605,12 +34605,12 @@ } }, "node_modules/parse5": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz", - "integrity": "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==", + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", "license": "MIT", "dependencies": { - "entities": "^4.5.0" + "entities": "^6.0.0" }, "funding": { "url": "https://github.com/inikulin/parse5?sponsor=1" @@ -34684,6 +34684,18 @@ "url": "https://github.com/inikulin/parse5?sponsor=1" } }, + "node_modules/parse5/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", diff --git a/package.json b/package.json index c7b04c434e7..2358422258f 100644 --- a/package.json +++ b/package.json @@ -217,7 +217,7 @@ "eslint": "$eslint" }, "tailwindcss": "$tailwindcss", - "parse5": "7.2.1", + "parse5": "7.3.0", "react": "18.3.1", "react-dom": "18.3.1", "@types/react": "18.3.27" From 0439f60a3bf20f4b61c54bd2d95fef7162840d04 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 10 Dec 2025 10:22:35 -0600 Subject: [PATCH 033/188] [deps] Platform: Update Rust crate oo7 to v0.5.0 (#16416) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- apps/desktop/desktop_native/Cargo.lock | 25 +++++++++++++++++++++---- apps/desktop/desktop_native/Cargo.toml | 2 +- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/apps/desktop/desktop_native/Cargo.lock b/apps/desktop/desktop_native/Cargo.lock index 7aeeefb2d0d..4a5985de8f9 100644 --- a/apps/desktop/desktop_native/Cargo.lock +++ b/apps/desktop/desktop_native/Cargo.lock @@ -138,6 +138,23 @@ dependencies = [ "zbus", ] +[[package]] +name = "ashpd" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0986d5b4f0802160191ad75f8d33ada000558757db3defb70299ca95d9fcbd" +dependencies = [ + "enumflags2", + "futures-channel", + "futures-util", + "rand 0.9.1", + "serde", + "serde_repr", + "tokio", + "url", + "zbus", +] + [[package]] name = "askama" version = "0.12.1" @@ -816,7 +833,7 @@ dependencies = [ "aes", "anyhow", "arboard", - "ashpd", + "ashpd 0.11.0", "base64", "bitwarden-russh", "bytes", @@ -2135,12 +2152,12 @@ checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "oo7" -version = "0.4.3" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cb23d3ec3527d65a83be1c1795cb883c52cfa57147d42acc797127df56fc489" +checksum = "e3299dd401feaf1d45afd8fd1c0586f10fcfb22f244bb9afa942cec73503b89d" dependencies = [ "aes", - "ashpd", + "ashpd 0.12.0", "cbc", "cipher", "digest", diff --git a/apps/desktop/desktop_native/Cargo.toml b/apps/desktop/desktop_native/Cargo.toml index 2eff1af41b5..2926aac7f06 100644 --- a/apps/desktop/desktop_native/Cargo.toml +++ b/apps/desktop/desktop_native/Cargo.toml @@ -45,7 +45,7 @@ memsec = "=0.7.0" napi = "=3.3.0" napi-build = "=2.2.3" napi-derive = "=3.2.5" -oo7 = "=0.4.3" +oo7 = "=0.5.0" pin-project = "=1.1.10" pkcs8 = "=0.10.2" rand = "=0.9.1" From 8b5cb48519cbcb282dabd540bd5268a16acb1431 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 10 Dec 2025 10:37:09 -0600 Subject: [PATCH 034/188] [deps] Platform: Update sass to v1.95.1 (#17887) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index b6e8d19af59..a86f728f21d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -165,7 +165,7 @@ "process": "0.11.10", "remark-gfm": "4.0.1", "rimraf": "6.1.2", - "sass": "1.94.2", + "sass": "1.95.1", "sass-loader": "16.0.6", "storybook": "9.1.16", "style-loader": "4.0.0", @@ -37529,9 +37529,9 @@ } }, "node_modules/sass": { - "version": "1.94.2", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.94.2.tgz", - "integrity": "sha512-N+7WK20/wOr7CzA2snJcUSSNTCzeCGUTFY3OgeQP3mZ1aj9NMQ0mSTXwlrnd89j33zzQJGqIN52GIOmYrfq46A==", + "version": "1.95.1", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.95.1.tgz", + "integrity": "sha512-uPoDh5NIEZV4Dp5GBodkmNY9tSQfXY02pmCcUo+FR1P+x953HGkpw+vV28D4IqYB6f8webZtwoSaZaiPtpTeMg==", "license": "MIT", "dependencies": { "chokidar": "^4.0.0", diff --git a/package.json b/package.json index 2358422258f..9cb65a6e1db 100644 --- a/package.json +++ b/package.json @@ -127,7 +127,7 @@ "process": "0.11.10", "remark-gfm": "4.0.1", "rimraf": "6.1.2", - "sass": "1.94.2", + "sass": "1.95.1", "sass-loader": "16.0.6", "storybook": "9.1.16", "style-loader": "4.0.0", From 3c6b6eaa56fdf565eb56e8189c18d6bd96d632e9 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 10 Dec 2025 10:51:02 -0600 Subject: [PATCH 035/188] [deps] Platform: Update Rust crate anyhow to v1.0.100 (#17546) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- apps/desktop/desktop_native/Cargo.lock | 4 ++-- apps/desktop/desktop_native/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/desktop/desktop_native/Cargo.lock b/apps/desktop/desktop_native/Cargo.lock index 4a5985de8f9..6536ccaa7af 100644 --- a/apps/desktop/desktop_native/Cargo.lock +++ b/apps/desktop/desktop_native/Cargo.lock @@ -99,9 +99,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.94" +version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" [[package]] name = "arboard" diff --git a/apps/desktop/desktop_native/Cargo.toml b/apps/desktop/desktop_native/Cargo.toml index 2926aac7f06..37584dca235 100644 --- a/apps/desktop/desktop_native/Cargo.toml +++ b/apps/desktop/desktop_native/Cargo.toml @@ -21,7 +21,7 @@ publish = false [workspace.dependencies] aes = "=0.8.4" aes-gcm = "=0.10.3" -anyhow = "=1.0.94" +anyhow = "=1.0.100" arboard = { version = "=3.6.1", default-features = false } ashpd = "=0.11.0" base64 = "=0.22.1" From 7f892cf26afd3183a7aab94929148999152f4552 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 10 Dec 2025 10:57:36 -0600 Subject: [PATCH 036/188] [deps] Autofill: Update prettier to v3.7.3 (#17853) * [deps] Autofill: Update prettier to v3.6.2 * fix: [PM-23425] Fix prettier issues related to dependency updte Signed-off-by: Ben Brooks * [deps] Autofill: Update prettier to v3.6.2 * [deps] Autofill: Update prettier to v3.7.3 * [PM-29379] Fix prettier issues found with the updated Prettier 3.7.3 Signed-off-by: Ben Brooks --------- Signed-off-by: Ben Brooks Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Ben Brooks --- .../extension-device-management-component.service.ts | 4 +--- .../services/browser-fido2-user-interface.service.ts | 4 +--- .../content/overlay-notifications-content.service.ts | 4 +--- .../inline-menu-field-qualification.service.ts | 4 +--- .../services/desktop-fido2-user-interface.service.ts | 4 +--- .../organization-user-reset-password.service.ts | 11 ++++------- .../webauthn-login/webauthn-login-admin.service.ts | 4 +--- .../services/emergency-access.service.ts | 11 ++++------- .../default-device-management-component.service.ts | 4 +--- ...default-login-approval-dialog-component.service.ts | 4 +--- .../encrypted-migrations-scheduler.service.ts | 4 +--- ...fault-new-device-verification-component.service.ts | 4 +--- ...ault-two-factor-auth-webauthn-component.service.ts | 4 +--- .../user-decryption-options.service.ts | 4 +--- ...ult-organization-management-preferences.service.ts | 4 +--- ...assword-reset-enrollment.service.implementation.ts | 4 +--- .../organization-sponsorship-api.service.ts | 4 +--- libs/common/src/platform/ipc/ipc-message.ts | 6 ++++-- .../services/fido2/fido2-authenticator.service.ts | 6 +++--- .../platform/services/fido2/fido2-client.service.ts | 6 +++--- libs/common/src/tools/state/secret-classifier.ts | 8 +++++--- libs/common/src/tools/state/secret-state.ts | 10 +++++++--- libs/common/src/tools/state/user-state-subject.ts | 10 +++++----- libs/components/src/dialog/dialog.service.ts | 7 ++++--- ...lt-user-asymmetric-key-regeneration-api.service.ts | 4 +--- ...efault-user-asymmetric-key-regeneration.service.ts | 4 +--- libs/state-internal/src/default-derived-state.ts | 8 +++++--- libs/state-internal/src/inline-derived-state.ts | 8 +++++--- libs/state-test-utils/src/fake-state.ts | 8 +++++--- .../core/src/engine/rpc/create-forwarding-address.ts | 3 +-- .../generator/core/src/engine/rpc/get-account-id.ts | 3 +-- .../core/src/policies/default-policy-evaluator.ts | 7 ++++--- .../policies/dynamic-password-policy-constraints.ts | 4 +--- .../src/strategies/catchall-generator-strategy.ts | 7 ++++--- .../src/strategies/eff-username-generator-strategy.ts | 7 ++++--- .../core/src/strategies/options-classifier.ts | 3 +-- .../src/strategies/passphrase-generator-strategy.ts | 7 ++++--- .../src/strategies/password-generator-strategy.ts | 7 ++++--- .../src/strategies/subaddress-generator-strategy.ts | 7 ++++--- .../navigation/src/generator-navigation-evaluator.ts | 7 ++++--- package-lock.json | 8 ++++---- package.json | 2 +- 42 files changed, 108 insertions(+), 131 deletions(-) diff --git a/apps/browser/src/auth/services/extension-device-management-component.service.ts b/apps/browser/src/auth/services/extension-device-management-component.service.ts index 2585ba3198c..eb7ea4be37b 100644 --- a/apps/browser/src/auth/services/extension-device-management-component.service.ts +++ b/apps/browser/src/auth/services/extension-device-management-component.service.ts @@ -3,9 +3,7 @@ import { DeviceManagementComponentServiceAbstraction } from "@bitwarden/angular/ /** * Browser extension implementation of the device management component service */ -export class ExtensionDeviceManagementComponentService - implements DeviceManagementComponentServiceAbstraction -{ +export class ExtensionDeviceManagementComponentService implements DeviceManagementComponentServiceAbstraction { /** * Don't show header information in browser extension client */ diff --git a/apps/browser/src/autofill/fido2/services/browser-fido2-user-interface.service.ts b/apps/browser/src/autofill/fido2/services/browser-fido2-user-interface.service.ts index 5818bbf8d82..e0ab45e9f84 100644 --- a/apps/browser/src/autofill/fido2/services/browser-fido2-user-interface.service.ts +++ b/apps/browser/src/autofill/fido2/services/browser-fido2-user-interface.service.ts @@ -120,9 +120,7 @@ export type BrowserFido2ParentWindowReference = chrome.tabs.Tab; * Browser implementation of the {@link Fido2UserInterfaceService}. * The user interface is implemented as a popout and the service uses the browser's messaging API to communicate with it. */ -export class BrowserFido2UserInterfaceService - implements Fido2UserInterfaceServiceAbstraction -{ +export class BrowserFido2UserInterfaceService implements Fido2UserInterfaceServiceAbstraction { constructor(private authService: AuthService) {} async newSession( diff --git a/apps/browser/src/autofill/overlay/notifications/content/overlay-notifications-content.service.ts b/apps/browser/src/autofill/overlay/notifications/content/overlay-notifications-content.service.ts index 8dc08169468..ab3b8144426 100644 --- a/apps/browser/src/autofill/overlay/notifications/content/overlay-notifications-content.service.ts +++ b/apps/browser/src/autofill/overlay/notifications/content/overlay-notifications-content.service.ts @@ -15,9 +15,7 @@ import { OverlayNotificationsExtensionMessageHandlers, } from "../abstractions/overlay-notifications-content.service"; -export class OverlayNotificationsContentService - implements OverlayNotificationsContentServiceInterface -{ +export class OverlayNotificationsContentService implements OverlayNotificationsContentServiceInterface { private notificationBarRootElement: HTMLElement | null = null; private notificationBarElement: HTMLElement | null = null; private notificationBarIframeElement: HTMLIFrameElement | null = null; diff --git a/apps/browser/src/autofill/services/inline-menu-field-qualification.service.ts b/apps/browser/src/autofill/services/inline-menu-field-qualification.service.ts index f6afaae202f..65f9eee1ecb 100644 --- a/apps/browser/src/autofill/services/inline-menu-field-qualification.service.ts +++ b/apps/browser/src/autofill/services/inline-menu-field-qualification.service.ts @@ -16,9 +16,7 @@ import { } from "./autofill-constants"; import AutofillService from "./autofill.service"; -export class InlineMenuFieldQualificationService - implements InlineMenuFieldQualificationServiceInterface -{ +export class InlineMenuFieldQualificationService implements InlineMenuFieldQualificationServiceInterface { private searchFieldNamesSet = new Set(AutoFillConstants.SearchFieldNames); private excludedAutofillFieldTypesSet = new Set(AutoFillConstants.ExcludedAutofillLoginTypes); private usernameFieldTypes = new Set(["text", "email", "number", "tel"]); diff --git a/apps/desktop/src/autofill/services/desktop-fido2-user-interface.service.ts b/apps/desktop/src/autofill/services/desktop-fido2-user-interface.service.ts index 19946ab590c..cf29370840d 100644 --- a/apps/desktop/src/autofill/services/desktop-fido2-user-interface.service.ts +++ b/apps/desktop/src/autofill/services/desktop-fido2-user-interface.service.ts @@ -43,9 +43,7 @@ export type NativeWindowObject = { windowXy?: { x: number; y: number }; }; -export class DesktopFido2UserInterfaceService - implements Fido2UserInterfaceServiceAbstraction -{ +export class DesktopFido2UserInterfaceService implements Fido2UserInterfaceServiceAbstraction { constructor( private authService: AuthService, private cipherService: CipherService, diff --git a/apps/web/src/app/admin-console/organizations/members/services/organization-user-reset-password/organization-user-reset-password.service.ts b/apps/web/src/app/admin-console/organizations/members/services/organization-user-reset-password/organization-user-reset-password.service.ts index df5e7e8a25c..88797f86650 100644 --- a/apps/web/src/app/admin-console/organizations/members/services/organization-user-reset-password/organization-user-reset-password.service.ts +++ b/apps/web/src/app/admin-console/organizations/members/services/organization-user-reset-password/organization-user-reset-password.service.ts @@ -35,13 +35,10 @@ import { OrganizationUserResetPasswordEntry } from "./organization-user-reset-pa @Injectable({ providedIn: "root", }) -export class OrganizationUserResetPasswordService - implements - UserKeyRotationKeyRecoveryProvider< - OrganizationUserResetPasswordWithIdRequest, - OrganizationUserResetPasswordEntry - > -{ +export class OrganizationUserResetPasswordService implements UserKeyRotationKeyRecoveryProvider< + OrganizationUserResetPasswordWithIdRequest, + OrganizationUserResetPasswordEntry +> { constructor( private keyService: KeyService, private encryptService: EncryptService, diff --git a/apps/web/src/app/auth/core/services/webauthn-login/webauthn-login-admin.service.ts b/apps/web/src/app/auth/core/services/webauthn-login/webauthn-login-admin.service.ts index 7765d01f75c..3fc57e1a22c 100644 --- a/apps/web/src/app/auth/core/services/webauthn-login/webauthn-login-admin.service.ts +++ b/apps/web/src/app/auth/core/services/webauthn-login/webauthn-login-admin.service.ts @@ -39,9 +39,7 @@ import { WebAuthnLoginAdminApiService } from "./webauthn-login-admin-api.service /** * Service for managing WebAuthnLogin credentials. */ -export class WebauthnLoginAdminService - implements UserKeyRotationDataProvider -{ +export class WebauthnLoginAdminService implements UserKeyRotationDataProvider { static readonly MaxCredentialCount = 5; private navigatorCredentials: CredentialsContainer; diff --git a/apps/web/src/app/auth/emergency-access/services/emergency-access.service.ts b/apps/web/src/app/auth/emergency-access/services/emergency-access.service.ts index b91bc932e83..80b1b27116b 100644 --- a/apps/web/src/app/auth/emergency-access/services/emergency-access.service.ts +++ b/apps/web/src/app/auth/emergency-access/services/emergency-access.service.ts @@ -45,13 +45,10 @@ import { EmergencyAccessGranteeDetailsResponse } from "../response/emergency-acc import { EmergencyAccessApiService } from "./emergency-access-api.service"; @Injectable() -export class EmergencyAccessService - implements - UserKeyRotationKeyRecoveryProvider< - EmergencyAccessWithIdRequest, - GranteeEmergencyAccessWithPublicKey - > -{ +export class EmergencyAccessService implements UserKeyRotationKeyRecoveryProvider< + EmergencyAccessWithIdRequest, + GranteeEmergencyAccessWithPublicKey +> { constructor( private emergencyAccessApiService: EmergencyAccessApiService, private apiService: ApiService, diff --git a/libs/angular/src/auth/device-management/default-device-management-component.service.ts b/libs/angular/src/auth/device-management/default-device-management-component.service.ts index 5089ba259a5..169ee6ce7b6 100644 --- a/libs/angular/src/auth/device-management/default-device-management-component.service.ts +++ b/libs/angular/src/auth/device-management/default-device-management-component.service.ts @@ -3,9 +3,7 @@ import { DeviceManagementComponentServiceAbstraction } from "./device-management /** * Default implementation of the device management component service */ -export class DefaultDeviceManagementComponentService - implements DeviceManagementComponentServiceAbstraction -{ +export class DefaultDeviceManagementComponentService implements DeviceManagementComponentServiceAbstraction { /** * Show header information in web client */ diff --git a/libs/angular/src/auth/login-approval/default-login-approval-dialog-component.service.ts b/libs/angular/src/auth/login-approval/default-login-approval-dialog-component.service.ts index 5fefd3c3abb..4a9a37fd0de 100644 --- a/libs/angular/src/auth/login-approval/default-login-approval-dialog-component.service.ts +++ b/libs/angular/src/auth/login-approval/default-login-approval-dialog-component.service.ts @@ -3,9 +3,7 @@ import { LoginApprovalDialogComponentServiceAbstraction } from "./login-approval /** * Default implementation of the LoginApprovalDialogComponentServiceAbstraction. */ -export class DefaultLoginApprovalDialogComponentService - implements LoginApprovalDialogComponentServiceAbstraction -{ +export class DefaultLoginApprovalDialogComponentService implements LoginApprovalDialogComponentServiceAbstraction { /** * No-op implementation of the showLoginRequestedAlertIfWindowNotVisible method. * @returns diff --git a/libs/angular/src/key-management/encrypted-migration/encrypted-migrations-scheduler.service.ts b/libs/angular/src/key-management/encrypted-migration/encrypted-migrations-scheduler.service.ts index 1c50919d1cb..cf79c65e998 100644 --- a/libs/angular/src/key-management/encrypted-migration/encrypted-migrations-scheduler.service.ts +++ b/libs/angular/src/key-management/encrypted-migration/encrypted-migrations-scheduler.service.ts @@ -45,9 +45,7 @@ const VAULT_ROUTE = "/vault"; * if it is required by showing a UI prompt. It is only one means of triggering migrations, in case the user stays unlocked for a while, * or regularly logs in without a master-password, when the migrations do require a master-password to run. */ -export class DefaultEncryptedMigrationsSchedulerService - implements EncryptedMigrationsSchedulerService -{ +export class DefaultEncryptedMigrationsSchedulerService implements EncryptedMigrationsSchedulerService { isMigrating = false; url$: Observable; 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 index 88ea652bc4b..4e7731a412b 100644 --- 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 @@ -1,8 +1,6 @@ import { NewDeviceVerificationComponentService } from "./new-device-verification-component.service"; -export class DefaultNewDeviceVerificationComponentService - implements NewDeviceVerificationComponentService -{ +export class DefaultNewDeviceVerificationComponentService implements NewDeviceVerificationComponentService { showBackButton() { return true; } diff --git a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/default-two-factor-auth-webauthn-component.service.ts b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/default-two-factor-auth-webauthn-component.service.ts index 3d3578c656e..545c57f2946 100644 --- a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/default-two-factor-auth-webauthn-component.service.ts +++ b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/default-two-factor-auth-webauthn-component.service.ts @@ -1,8 +1,6 @@ import { TwoFactorAuthWebAuthnComponentService } from "./two-factor-auth-webauthn-component.service"; -export class DefaultTwoFactorAuthWebAuthnComponentService - implements TwoFactorAuthWebAuthnComponentService -{ +export class DefaultTwoFactorAuthWebAuthnComponentService implements TwoFactorAuthWebAuthnComponentService { /** * Default implementation is to not open in a new tab. */ diff --git a/libs/auth/src/common/services/user-decryption-options/user-decryption-options.service.ts b/libs/auth/src/common/services/user-decryption-options/user-decryption-options.service.ts index a0075d1987b..09d73f0224d 100644 --- a/libs/auth/src/common/services/user-decryption-options/user-decryption-options.service.ts +++ b/libs/auth/src/common/services/user-decryption-options/user-decryption-options.service.ts @@ -19,9 +19,7 @@ export const USER_DECRYPTION_OPTIONS = new UserKeyDefinition { diff --git a/libs/common/src/admin-console/services/organization-management-preferences/default-organization-management-preferences.service.ts b/libs/common/src/admin-console/services/organization-management-preferences/default-organization-management-preferences.service.ts index e257b691638..55af4b0bf29 100644 --- a/libs/common/src/admin-console/services/organization-management-preferences/default-organization-management-preferences.service.ts +++ b/libs/common/src/admin-console/services/organization-management-preferences/default-organization-management-preferences.service.ts @@ -27,9 +27,7 @@ function buildKeyDefinition(key: string): UserKeyDefinition { export const AUTO_CONFIRM_FINGERPRINTS = buildKeyDefinition("autoConfirmFingerPrints"); -export class DefaultOrganizationManagementPreferencesService - implements OrganizationManagementPreferencesService -{ +export class DefaultOrganizationManagementPreferencesService implements OrganizationManagementPreferencesService { constructor(private stateProvider: StateProvider) {} autoConfirmFingerPrints = this.buildOrganizationManagementPreference( diff --git a/libs/common/src/auth/services/password-reset-enrollment.service.implementation.ts b/libs/common/src/auth/services/password-reset-enrollment.service.implementation.ts index 55644009f16..a1464ed9c9b 100644 --- a/libs/common/src/auth/services/password-reset-enrollment.service.implementation.ts +++ b/libs/common/src/auth/services/password-reset-enrollment.service.implementation.ts @@ -22,9 +22,7 @@ import { UserKey } from "../../types/key"; import { AccountService } from "../abstractions/account.service"; import { PasswordResetEnrollmentServiceAbstraction } from "../abstractions/password-reset-enrollment.service.abstraction"; -export class PasswordResetEnrollmentServiceImplementation - implements PasswordResetEnrollmentServiceAbstraction -{ +export class PasswordResetEnrollmentServiceImplementation implements PasswordResetEnrollmentServiceAbstraction { constructor( protected organizationApiService: OrganizationApiServiceAbstraction, protected accountService: AccountService, diff --git a/libs/common/src/billing/services/organization/organization-sponsorship-api.service.ts b/libs/common/src/billing/services/organization/organization-sponsorship-api.service.ts index de0ff302737..dea3979ac2d 100644 --- a/libs/common/src/billing/services/organization/organization-sponsorship-api.service.ts +++ b/libs/common/src/billing/services/organization/organization-sponsorship-api.service.ts @@ -4,9 +4,7 @@ import { PlatformUtilsService } from "../../../platform/abstractions/platform-ut import { OrganizationSponsorshipApiServiceAbstraction } from "../../abstractions/organizations/organization-sponsorship-api.service.abstraction"; import { OrganizationSponsorshipInvitesResponse } from "../../models/response/organization-sponsorship-invites.response"; -export class OrganizationSponsorshipApiService - implements OrganizationSponsorshipApiServiceAbstraction -{ +export class OrganizationSponsorshipApiService implements OrganizationSponsorshipApiServiceAbstraction { constructor( private apiService: ApiService, private platformUtilsService: PlatformUtilsService, diff --git a/libs/common/src/platform/ipc/ipc-message.ts b/libs/common/src/platform/ipc/ipc-message.ts index c3ac6360597..ede68e170c5 100644 --- a/libs/common/src/platform/ipc/ipc-message.ts +++ b/libs/common/src/platform/ipc/ipc-message.ts @@ -5,8 +5,10 @@ export interface IpcMessage { message: SerializedOutgoingMessage; } -export interface SerializedOutgoingMessage - extends Omit { +export interface SerializedOutgoingMessage extends Omit< + OutgoingMessage, + typeof Symbol.dispose | "free" | "payload" +> { payload: number[]; } diff --git a/libs/common/src/platform/services/fido2/fido2-authenticator.service.ts b/libs/common/src/platform/services/fido2/fido2-authenticator.service.ts index e560a77cc2e..d1081e9f7b2 100644 --- a/libs/common/src/platform/services/fido2/fido2-authenticator.service.ts +++ b/libs/common/src/platform/services/fido2/fido2-authenticator.service.ts @@ -46,9 +46,9 @@ const KeyUsages: KeyUsage[] = ["sign"]; * * It is highly recommended that the W3C specification is used a reference when reading this code. */ -export class Fido2AuthenticatorService - implements Fido2AuthenticatorServiceAbstraction -{ +export class Fido2AuthenticatorService< + ParentWindowReference, +> implements Fido2AuthenticatorServiceAbstraction { constructor( private cipherService: CipherService, private userInterface: Fido2UserInterfaceService, diff --git a/libs/common/src/platform/services/fido2/fido2-client.service.ts b/libs/common/src/platform/services/fido2/fido2-client.service.ts index 08c0a265100..503ffef8241 100644 --- a/libs/common/src/platform/services/fido2/fido2-client.service.ts +++ b/libs/common/src/platform/services/fido2/fido2-client.service.ts @@ -47,9 +47,9 @@ import { guidToRawFormat } from "./guid-utils"; * * It is highly recommended that the W3C specification is used a reference when reading this code. */ -export class Fido2ClientService - implements Fido2ClientServiceAbstraction -{ +export class Fido2ClientService< + ParentWindowReference, +> implements Fido2ClientServiceAbstraction { private timeoutAbortController: AbortController; private readonly TIMEOUTS = { NO_VERIFICATION: { diff --git a/libs/common/src/tools/state/secret-classifier.ts b/libs/common/src/tools/state/secret-classifier.ts index e961ffcd20e..ddb4bc2c63a 100644 --- a/libs/common/src/tools/state/secret-classifier.ts +++ b/libs/common/src/tools/state/secret-classifier.ts @@ -14,9 +14,11 @@ import { Classifier } from "./classifier"; * Data that cannot be serialized by JSON.stringify() should * be excluded. */ -export class SecretClassifier - implements Classifier<Plaintext, Disclosed, Secret> -{ +export class SecretClassifier<Plaintext extends object, Disclosed, Secret> implements Classifier< + Plaintext, + Disclosed, + Secret +> { private constructor( disclosed: readonly (keyof Jsonify<Disclosed> & keyof Jsonify<Plaintext>)[], excluded: readonly (keyof Plaintext)[], diff --git a/libs/common/src/tools/state/secret-state.ts b/libs/common/src/tools/state/secret-state.ts index 91f45a51211..0aa808d2cb7 100644 --- a/libs/common/src/tools/state/secret-state.ts +++ b/libs/common/src/tools/state/secret-state.ts @@ -25,9 +25,13 @@ const ONE_MINUTE = 1000 * 60; * * DO NOT USE THIS for synchronized data. */ -export class SecretState<Outer, Id, Plaintext extends object, Disclosed, Secret> - implements SingleUserState<Outer> -{ +export class SecretState< + Outer, + Id, + Plaintext extends object, + Disclosed, + Secret, +> implements SingleUserState<Outer> { // The constructor is private to avoid creating a circular dependency when // wiring the derived and secret states together. private constructor( diff --git a/libs/common/src/tools/state/user-state-subject.ts b/libs/common/src/tools/state/user-state-subject.ts index e6b66d8f699..f9cdf87ea04 100644 --- a/libs/common/src/tools/state/user-state-subject.ts +++ b/libs/common/src/tools/state/user-state-subject.ts @@ -79,11 +79,11 @@ const DEFAULT_FRAME_SIZE = 32; * @template Dependencies use-specific dependencies provided by the user. */ export class UserStateSubject< - State extends object, - Secret = State, - Disclosed = Record<string, never>, - Dependencies = null, - > + State extends object, + Secret = State, + Disclosed = Record<string, never>, + Dependencies = null, +> extends Observable<State> implements SubjectLike<State> { diff --git a/libs/components/src/dialog/dialog.service.ts b/libs/components/src/dialog/dialog.service.ts index 1fc452418e1..804b650beab 100644 --- a/libs/components/src/dialog/dialog.service.ts +++ b/libs/components/src/dialog/dialog.service.ts @@ -43,9 +43,10 @@ class CustomBlockScrollStrategy implements ScrollStrategy { detach() {} } -export abstract class DialogRef<R = unknown, C = unknown> - implements Pick<CdkDialogRef<R, C>, "close" | "closed" | "disableClose" | "componentInstance"> -{ +export abstract class DialogRef<R = unknown, C = unknown> implements Pick< + CdkDialogRef<R, C>, + "close" | "closed" | "disableClose" | "componentInstance" +> { abstract readonly isDrawer?: boolean; // --- From CdkDialogRef --- diff --git a/libs/key-management/src/user-asymmetric-key-regeneration/services/default-user-asymmetric-key-regeneration-api.service.ts b/libs/key-management/src/user-asymmetric-key-regeneration/services/default-user-asymmetric-key-regeneration-api.service.ts index a79bcfd0ef3..37410b9fffb 100644 --- a/libs/key-management/src/user-asymmetric-key-regeneration/services/default-user-asymmetric-key-regeneration-api.service.ts +++ b/libs/key-management/src/user-asymmetric-key-regeneration/services/default-user-asymmetric-key-regeneration-api.service.ts @@ -4,9 +4,7 @@ import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-st import { UserAsymmetricKeysRegenerationApiService } from "../abstractions/user-asymmetric-key-regeneration-api.service"; import { KeyRegenerationRequest } from "../models/requests/key-regeneration.request"; -export class DefaultUserAsymmetricKeysRegenerationApiService - implements UserAsymmetricKeysRegenerationApiService -{ +export class DefaultUserAsymmetricKeysRegenerationApiService implements UserAsymmetricKeysRegenerationApiService { constructor(private apiService: ApiService) {} async regenerateUserAsymmetricKeys( diff --git a/libs/key-management/src/user-asymmetric-key-regeneration/services/default-user-asymmetric-key-regeneration.service.ts b/libs/key-management/src/user-asymmetric-key-regeneration/services/default-user-asymmetric-key-regeneration.service.ts index 48fe3a1686f..36bf9c8a421 100644 --- a/libs/key-management/src/user-asymmetric-key-regeneration/services/default-user-asymmetric-key-regeneration.service.ts +++ b/libs/key-management/src/user-asymmetric-key-regeneration/services/default-user-asymmetric-key-regeneration.service.ts @@ -15,9 +15,7 @@ import { KeyService } from "../../abstractions/key.service"; import { UserAsymmetricKeysRegenerationApiService } from "../abstractions/user-asymmetric-key-regeneration-api.service"; import { UserAsymmetricKeysRegenerationService } from "../abstractions/user-asymmetric-key-regeneration.service"; -export class DefaultUserAsymmetricKeysRegenerationService - implements UserAsymmetricKeysRegenerationService -{ +export class DefaultUserAsymmetricKeysRegenerationService implements UserAsymmetricKeysRegenerationService { constructor( private keyService: KeyService, private cipherService: CipherService, diff --git a/libs/state-internal/src/default-derived-state.ts b/libs/state-internal/src/default-derived-state.ts index ce84be93f92..60355dc033b 100644 --- a/libs/state-internal/src/default-derived-state.ts +++ b/libs/state-internal/src/default-derived-state.ts @@ -5,9 +5,11 @@ import { DeriveDefinition, DerivedState, DerivedStateDependencies } from "@bitwa /** * Default derived state */ -export class DefaultDerivedState<TFrom, TTo, TDeps extends DerivedStateDependencies> - implements DerivedState<TTo> -{ +export class DefaultDerivedState< + TFrom, + TTo, + TDeps extends DerivedStateDependencies, +> implements DerivedState<TTo> { private readonly storageKey: string; private forcedValueSubject = new Subject<TTo>(); diff --git a/libs/state-internal/src/inline-derived-state.ts b/libs/state-internal/src/inline-derived-state.ts index 2c03443d42c..cdbc329e050 100644 --- a/libs/state-internal/src/inline-derived-state.ts +++ b/libs/state-internal/src/inline-derived-state.ts @@ -17,9 +17,11 @@ export class InlineDerivedStateProvider implements DerivedStateProvider { } } -export class InlineDerivedState<TFrom, TTo, TDeps extends DerivedStateDependencies> - implements DerivedState<TTo> -{ +export class InlineDerivedState< + TFrom, + TTo, + TDeps extends DerivedStateDependencies, +> implements DerivedState<TTo> { constructor( parentState$: Observable<TFrom>, deriveDefinition: DeriveDefinition<TFrom, TTo, TDeps>, diff --git a/libs/state-test-utils/src/fake-state.ts b/libs/state-test-utils/src/fake-state.ts index 25aabcd9933..21cb5e7aa73 100644 --- a/libs/state-test-utils/src/fake-state.ts +++ b/libs/state-test-utils/src/fake-state.ts @@ -236,9 +236,11 @@ export class FakeActiveUserState<T> implements ActiveUserState<T> { } } -export class FakeDerivedState<TFrom, TTo, TDeps extends DerivedStateDependencies> - implements DerivedState<TTo> -{ +export class FakeDerivedState< + TFrom, + TTo, + TDeps extends DerivedStateDependencies, +> implements DerivedState<TTo> { // eslint-disable-next-line rxjs/no-exposed-subjects -- exposed for testing setup stateSubject = new ReplaySubject<TTo>(1); diff --git a/libs/tools/generator/core/src/engine/rpc/create-forwarding-address.ts b/libs/tools/generator/core/src/engine/rpc/create-forwarding-address.ts index 4d4a5ba2ded..3321d48b345 100644 --- a/libs/tools/generator/core/src/engine/rpc/create-forwarding-address.ts +++ b/libs/tools/generator/core/src/engine/rpc/create-forwarding-address.ts @@ -7,8 +7,7 @@ import { ForwarderContext } from "../forwarder-context"; export class CreateForwardingAddressRpc< Settings extends ApiSettings, Req extends IntegrationRequest = IntegrationRequest, -> implements JsonRpc<Req, string> -{ +> implements JsonRpc<Req, string> { constructor( readonly requestor: ForwarderConfiguration<Settings>, readonly context: ForwarderContext<Settings>, diff --git a/libs/tools/generator/core/src/engine/rpc/get-account-id.ts b/libs/tools/generator/core/src/engine/rpc/get-account-id.ts index 751220fc216..19550967835 100644 --- a/libs/tools/generator/core/src/engine/rpc/get-account-id.ts +++ b/libs/tools/generator/core/src/engine/rpc/get-account-id.ts @@ -9,8 +9,7 @@ import { ForwarderContext } from "../forwarder-context"; export class GetAccountIdRpc< Settings extends ApiSettings, Req extends IntegrationRequest = IntegrationRequest, -> implements JsonRpc<Req, string> -{ +> implements JsonRpc<Req, string> { constructor( readonly requestor: ForwarderConfiguration<Settings>, readonly context: ForwarderContext<Settings>, diff --git a/libs/tools/generator/core/src/policies/default-policy-evaluator.ts b/libs/tools/generator/core/src/policies/default-policy-evaluator.ts index 384b3bc1aeb..2d2ce48aec8 100644 --- a/libs/tools/generator/core/src/policies/default-policy-evaluator.ts +++ b/libs/tools/generator/core/src/policies/default-policy-evaluator.ts @@ -2,9 +2,10 @@ import { PolicyEvaluator } from "../abstractions"; import { NoPolicy } from "../types"; /** A policy evaluator that does not apply any policy */ -export class DefaultPolicyEvaluator<PolicyTarget> - implements PolicyEvaluator<NoPolicy, PolicyTarget> -{ +export class DefaultPolicyEvaluator<PolicyTarget> implements PolicyEvaluator< + NoPolicy, + PolicyTarget +> { /** {@link PolicyEvaluator.policy} */ get policy() { return {}; diff --git a/libs/tools/generator/core/src/policies/dynamic-password-policy-constraints.ts b/libs/tools/generator/core/src/policies/dynamic-password-policy-constraints.ts index b1e582637e4..32a6099d6cc 100644 --- a/libs/tools/generator/core/src/policies/dynamic-password-policy-constraints.ts +++ b/libs/tools/generator/core/src/policies/dynamic-password-policy-constraints.ts @@ -13,9 +13,7 @@ import { atLeast, atLeastSum, maybe, readonlyTrueWhen, AtLeastOne, Zero } from " import { PasswordPolicyConstraints } from "./password-policy-constraints"; /** Creates state constraints by blending policy and password settings. */ -export class DynamicPasswordPolicyConstraints - implements DynamicStateConstraints<PasswordGeneratorSettings> -{ +export class DynamicPasswordPolicyConstraints implements DynamicStateConstraints<PasswordGeneratorSettings> { /** Instantiates the object. * @param policy the password policy to enforce. This cannot be * `null` or `undefined`. diff --git a/libs/tools/generator/core/src/strategies/catchall-generator-strategy.ts b/libs/tools/generator/core/src/strategies/catchall-generator-strategy.ts index 36365fc338f..a093aa164f1 100644 --- a/libs/tools/generator/core/src/strategies/catchall-generator-strategy.ts +++ b/libs/tools/generator/core/src/strategies/catchall-generator-strategy.ts @@ -13,9 +13,10 @@ import { observe$PerUserId, sharedStateByUserId } from "../util"; import { CATCHALL_SETTINGS } from "./storage"; /** Strategy for creating usernames using a catchall email address */ -export class CatchallGeneratorStrategy - implements GeneratorStrategy<CatchallGenerationOptions, NoPolicy> -{ +export class CatchallGeneratorStrategy implements GeneratorStrategy< + CatchallGenerationOptions, + NoPolicy +> { /** Instantiates the generation strategy * @param usernameService generates a catchall address for a domain */ diff --git a/libs/tools/generator/core/src/strategies/eff-username-generator-strategy.ts b/libs/tools/generator/core/src/strategies/eff-username-generator-strategy.ts index ebeacef81e8..8c75d2d2a34 100644 --- a/libs/tools/generator/core/src/strategies/eff-username-generator-strategy.ts +++ b/libs/tools/generator/core/src/strategies/eff-username-generator-strategy.ts @@ -16,9 +16,10 @@ const UsernameDigits = Object.freeze({ }); /** Strategy for creating usernames from the EFF wordlist */ -export class EffUsernameGeneratorStrategy - implements GeneratorStrategy<EffUsernameGenerationOptions, NoPolicy> -{ +export class EffUsernameGeneratorStrategy implements GeneratorStrategy< + EffUsernameGenerationOptions, + NoPolicy +> { /** Instantiates the generation strategy * @param usernameService generates a username from EFF word list */ diff --git a/libs/tools/generator/core/src/strategies/options-classifier.ts b/libs/tools/generator/core/src/strategies/options-classifier.ts index 672b5e5cc65..9ed98d71c96 100644 --- a/libs/tools/generator/core/src/strategies/options-classifier.ts +++ b/libs/tools/generator/core/src/strategies/options-classifier.ts @@ -10,8 +10,7 @@ import { Classifier } from "@bitwarden/common/tools/state/classifier"; export class OptionsClassifier< Settings, Options extends IntegrationRequest & Settings = IntegrationRequest & Settings, -> implements Classifier<Options, Record<string, never>, Settings> -{ +> implements Classifier<Options, Record<string, never>, Settings> { /** Partitions `secret` into its disclosed properties and secret properties. * @param value The object to partition * @returns an object that classifies secrets. diff --git a/libs/tools/generator/core/src/strategies/passphrase-generator-strategy.ts b/libs/tools/generator/core/src/strategies/passphrase-generator-strategy.ts index 374df84a5bd..be2984e09e1 100644 --- a/libs/tools/generator/core/src/strategies/passphrase-generator-strategy.ts +++ b/libs/tools/generator/core/src/strategies/passphrase-generator-strategy.ts @@ -14,9 +14,10 @@ import { observe$PerUserId, optionsToEffWordListRequest, sharedStateByUserId } f import { PASSPHRASE_SETTINGS } from "./storage"; /** Generates passphrases composed of random words */ -export class PassphraseGeneratorStrategy - implements GeneratorStrategy<PassphraseGenerationOptions, PassphraseGeneratorPolicy> -{ +export class PassphraseGeneratorStrategy implements GeneratorStrategy< + PassphraseGenerationOptions, + PassphraseGeneratorPolicy +> { /** instantiates the password generator strategy. * @param legacy generates the passphrase * @param stateProvider provides durable state diff --git a/libs/tools/generator/core/src/strategies/password-generator-strategy.ts b/libs/tools/generator/core/src/strategies/password-generator-strategy.ts index 1a5070901c2..ab8a04cf79b 100644 --- a/libs/tools/generator/core/src/strategies/password-generator-strategy.ts +++ b/libs/tools/generator/core/src/strategies/password-generator-strategy.ts @@ -12,9 +12,10 @@ import { observe$PerUserId, optionsToRandomAsciiRequest, sharedStateByUserId } f import { PASSWORD_SETTINGS } from "./storage"; /** Generates passwords composed of random characters */ -export class PasswordGeneratorStrategy - implements GeneratorStrategy<PasswordGenerationOptions, PasswordGeneratorPolicy> -{ +export class PasswordGeneratorStrategy implements GeneratorStrategy< + PasswordGenerationOptions, + PasswordGeneratorPolicy +> { /** instantiates the password generator strategy. * @param legacy generates the password */ diff --git a/libs/tools/generator/core/src/strategies/subaddress-generator-strategy.ts b/libs/tools/generator/core/src/strategies/subaddress-generator-strategy.ts index 86df7f1c667..f0c7c482060 100644 --- a/libs/tools/generator/core/src/strategies/subaddress-generator-strategy.ts +++ b/libs/tools/generator/core/src/strategies/subaddress-generator-strategy.ts @@ -17,9 +17,10 @@ import { SUBADDRESS_SETTINGS } from "./storage"; * For example, if the email address is `jd+xyz@domain.io`, * the subaddress is `xyz`. */ -export class SubaddressGeneratorStrategy - implements GeneratorStrategy<SubaddressGenerationOptions, NoPolicy> -{ +export class SubaddressGeneratorStrategy implements GeneratorStrategy< + SubaddressGenerationOptions, + NoPolicy +> { /** Instantiates the generation strategy * @param usernameService generates an email subaddress from an email address */ diff --git a/libs/tools/generator/extensions/navigation/src/generator-navigation-evaluator.ts b/libs/tools/generator/extensions/navigation/src/generator-navigation-evaluator.ts index 5446c1f26ad..e5b1ab87817 100644 --- a/libs/tools/generator/extensions/navigation/src/generator-navigation-evaluator.ts +++ b/libs/tools/generator/extensions/navigation/src/generator-navigation-evaluator.ts @@ -8,9 +8,10 @@ import { GeneratorNavigationPolicy } from "./generator-navigation-policy"; /** Enforces policy for generator navigation options. */ -export class GeneratorNavigationEvaluator - implements PolicyEvaluator<GeneratorNavigationPolicy, GeneratorNavigation> -{ +export class GeneratorNavigationEvaluator implements PolicyEvaluator< + GeneratorNavigationPolicy, + GeneratorNavigation +> { /** Instantiates the evaluator. * @param policy The policy applied by the evaluator. When this conflicts with * the defaults, the policy takes precedence. diff --git a/package-lock.json b/package-lock.json index a86f728f21d..9e4b320a6f7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -160,7 +160,7 @@ "path-browserify": "1.0.1", "postcss": "8.5.6", "postcss-loader": "8.2.0", - "prettier": "3.6.2", + "prettier": "3.7.3", "prettier-plugin-tailwindcss": "0.7.1", "process": "0.11.10", "remark-gfm": "4.0.1", @@ -35983,9 +35983,9 @@ } }, "node_modules/prettier": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", - "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", + "version": "3.7.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.7.3.tgz", + "integrity": "sha512-QgODejq9K3OzoBbuyobZlUhznP5SKwPqp+6Q6xw6o8gnhr4O85L2U915iM2IDcfF2NPXVaM9zlo9tdwipnYwzg==", "dev": true, "license": "MIT", "bin": { diff --git a/package.json b/package.json index 9cb65a6e1db..380d76727e0 100644 --- a/package.json +++ b/package.json @@ -122,7 +122,7 @@ "path-browserify": "1.0.1", "postcss": "8.5.6", "postcss-loader": "8.2.0", - "prettier": "3.6.2", + "prettier": "3.7.3", "prettier-plugin-tailwindcss": "0.7.1", "process": "0.11.10", "remark-gfm": "4.0.1", From cdeacf2a77ea5ce924d0fac8be6d82a179be1ef5 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 10 Dec 2025 17:14:12 +0000 Subject: [PATCH 037/188] [deps]: Update Rust crate ashpd to v0.12.0 (#16420) 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/desktop/desktop_native/Cargo.lock | 23 +++-------------------- apps/desktop/desktop_native/Cargo.toml | 2 +- 2 files changed, 4 insertions(+), 21 deletions(-) diff --git a/apps/desktop/desktop_native/Cargo.lock b/apps/desktop/desktop_native/Cargo.lock index 6536ccaa7af..53eee09d9b8 100644 --- a/apps/desktop/desktop_native/Cargo.lock +++ b/apps/desktop/desktop_native/Cargo.lock @@ -121,23 +121,6 @@ dependencies = [ "x11rb", ] -[[package]] -name = "ashpd" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cbdf310d77fd3aaee6ea2093db7011dc2d35d2eb3481e5607f1f8d942ed99df" -dependencies = [ - "enumflags2", - "futures-channel", - "futures-util", - "rand 0.9.1", - "serde", - "serde_repr", - "tokio", - "url", - "zbus", -] - [[package]] name = "ashpd" version = "0.12.0" @@ -833,7 +816,7 @@ dependencies = [ "aes", "anyhow", "arboard", - "ashpd 0.11.0", + "ashpd", "base64", "bitwarden-russh", "bytes", @@ -1672,7 +1655,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a793df0d7afeac54f95b471d3af7f0d4fb975699f972341a4b76988d49cdf0c" dependencies = [ "cfg-if", - "windows-targets 0.53.3", + "windows-targets 0.48.5", ] [[package]] @@ -2157,7 +2140,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3299dd401feaf1d45afd8fd1c0586f10fcfb22f244bb9afa942cec73503b89d" dependencies = [ "aes", - "ashpd 0.12.0", + "ashpd", "cbc", "cipher", "digest", diff --git a/apps/desktop/desktop_native/Cargo.toml b/apps/desktop/desktop_native/Cargo.toml index 37584dca235..74a1b6bb1da 100644 --- a/apps/desktop/desktop_native/Cargo.toml +++ b/apps/desktop/desktop_native/Cargo.toml @@ -23,7 +23,7 @@ aes = "=0.8.4" aes-gcm = "=0.10.3" anyhow = "=1.0.100" arboard = { version = "=3.6.1", default-features = false } -ashpd = "=0.11.0" +ashpd = "=0.12.0" base64 = "=0.22.1" bitwarden-russh = { git = "https://github.com/bitwarden/bitwarden-russh.git", rev = "a641316227227f8777fdf56ac9fa2d6b5f7fe662" } byteorder = "=1.5.0" From 6828b9374ac7b826e0b9320f7a517d657e4c38d0 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann <mail@quexten.com> Date: Wed, 10 Dec 2025 19:04:38 +0100 Subject: [PATCH 038/188] Fix cipher key decryption in TS code (#17907) --- libs/common/src/vault/models/domain/cipher.ts | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/libs/common/src/vault/models/domain/cipher.ts b/libs/common/src/vault/models/domain/cipher.ts index 599b1c765e4..850952364ca 100644 --- a/libs/common/src/vault/models/domain/cipher.ts +++ b/libs/common/src/vault/models/domain/cipher.ts @@ -155,7 +155,7 @@ export class Cipher extends Domain implements Decryptable<CipherView> { if (this.login != null) { model.login = await this.login.decrypt( bypassValidation, - userKeyOrOrgKey, + cipherDecryptionKey, `Cipher Id: ${this.id}`, ); } @@ -167,17 +167,20 @@ export class Cipher extends Domain implements Decryptable<CipherView> { break; case CipherType.Card: if (this.card != null) { - model.card = await this.card.decrypt(userKeyOrOrgKey, `Cipher Id: ${this.id}`); + model.card = await this.card.decrypt(cipherDecryptionKey, `Cipher Id: ${this.id}`); } break; case CipherType.Identity: if (this.identity != null) { - model.identity = await this.identity.decrypt(userKeyOrOrgKey, `Cipher Id: ${this.id}`); + model.identity = await this.identity.decrypt( + cipherDecryptionKey, + `Cipher Id: ${this.id}`, + ); } break; case CipherType.SshKey: if (this.sshKey != null) { - model.sshKey = await this.sshKey.decrypt(userKeyOrOrgKey, `Cipher Id: ${this.id}`); + model.sshKey = await this.sshKey.decrypt(cipherDecryptionKey, `Cipher Id: ${this.id}`); } break; default: @@ -188,7 +191,7 @@ export class Cipher extends Domain implements Decryptable<CipherView> { const attachments: AttachmentView[] = []; for (const attachment of this.attachments) { const decryptedAttachment = await attachment.decrypt( - userKeyOrOrgKey, + cipherDecryptionKey, `Cipher Id: ${this.id}`, ); attachments.push(decryptedAttachment); @@ -199,7 +202,7 @@ export class Cipher extends Domain implements Decryptable<CipherView> { if (this.fields != null && this.fields.length > 0) { const fields: FieldView[] = []; for (const field of this.fields) { - const decryptedField = await field.decrypt(userKeyOrOrgKey); + const decryptedField = await field.decrypt(cipherDecryptionKey); fields.push(decryptedField); } model.fields = fields; @@ -208,7 +211,7 @@ export class Cipher extends Domain implements Decryptable<CipherView> { if (this.passwordHistory != null && this.passwordHistory.length > 0) { const passwordHistory: PasswordHistoryView[] = []; for (const ph of this.passwordHistory) { - const decryptedPh = await ph.decrypt(userKeyOrOrgKey); + const decryptedPh = await ph.decrypt(cipherDecryptionKey); passwordHistory.push(decryptedPh); } model.passwordHistory = passwordHistory; From 48941a36504e9cefd79a95fb33ad279846d0e057 Mon Sep 17 00:00:00 2001 From: Vince Grassia <593223+vgrassia@users.noreply.github.com> Date: Wed, 10 Dec 2025 15:03:28 -0500 Subject: [PATCH 039/188] Fix Publish Web workflow (#17897) --- .github/workflows/publish-web.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/publish-web.yml b/.github/workflows/publish-web.yml index 62d9342cf61..fb1de5a1bc5 100644 --- a/.github/workflows/publish-web.yml +++ b/.github/workflows/publish-web.yml @@ -187,6 +187,8 @@ jobs: with: app-id: ${{ steps.get-kv-secrets.outputs.BW-GHAPP-ID }} private-key: ${{ steps.get-kv-secrets.outputs.BW-GHAPP-KEY }} + owner: ${{ github.repository_owner }} + repositories: self-host - name: Trigger Bitwarden lite build uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 From d205580701fe1f8970ee353232fcd6156b53ab1d Mon Sep 17 00:00:00 2001 From: Alex Morask <144709477+amorask-bitwarden@users.noreply.github.com> Date: Wed, 10 Dec 2025 14:45:06 -0600 Subject: [PATCH 040/188] Only show free for 1 year for SM standalone (#17914) --- .../organization-subscription-cloud.component.html | 9 ++------- .../organization-subscription-cloud.component.ts | 8 +++++--- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.html b/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.html index 0666cca2c4b..8b9b98dc390 100644 --- a/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.html +++ b/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.html @@ -38,12 +38,7 @@ {{ i.amount | currency: "$" }} </td> <td bitCell class="tw-text-right"> - <ng-container - *ngIf=" - sub?.customerDiscount?.appliesTo?.includes(i.productId); - else calculateElse - " - > + <ng-container *ngIf="isSecretsManagerTrial(); else calculateElse"> {{ "freeForOneYear" | i18n }} </ng-container> <ng-template #calculateElse> @@ -52,7 +47,7 @@ {{ i.quantity * i.amount | currency: "$" }} /{{ i.interval | i18n }} </span> <span - *ngIf="customerDiscount?.percentOff && !isSecretsManagerTrial()" + *ngIf="customerDiscount?.percentOff" class="tw-line-through !tw-text-muted" >{{ calculateTotalAppliedDiscount(i.quantity * i.amount) | currency: "$" diff --git a/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.ts b/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.ts index e0c1a12a80f..de5d71cce5e 100644 --- a/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.ts +++ b/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.ts @@ -403,11 +403,13 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy } isSecretsManagerTrial(): boolean { - return ( + const isSmStandalone = this.sub?.customerDiscount?.id === "sm-standalone"; + const appliesToProduct = this.sub?.subscription?.items?.some((item) => this.sub?.customerDiscount?.appliesTo?.includes(item.productId), - ) ?? false - ); + ) ?? false; + + return isSmStandalone && appliesToProduct; } closeChangePlan() { From 93640e65e3cf2236d17102ead53e17372fac9830 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 10 Dec 2025 15:56:09 -0500 Subject: [PATCH 041/188] [deps] Autofill: Update @lit-labs/signals to v0.1.3 (#17539) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9e4b320a6f7..d3908e71266 100644 --- a/package-lock.json +++ b/package-lock.json @@ -86,7 +86,7 @@ "@electron/notarize": "3.0.1", "@electron/rebuild": "4.0.1", "@eslint/compat": "2.0.0", - "@lit-labs/signals": "0.1.2", + "@lit-labs/signals": "0.1.3", "@ngtools/webpack": "20.3.12", "@storybook/addon-a11y": "9.1.16", "@storybook/addon-designs": "9.0.0-next.3", @@ -8586,9 +8586,9 @@ "license": "BSD-3-Clause" }, "node_modules/@lit-labs/signals": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/@lit-labs/signals/-/signals-0.1.2.tgz", - "integrity": "sha512-hkOL0ua4ILeHlaJ8IqFKS+Y+dpYznWaDhdikzwt3zJ1/LPz3Etft4OPIMoltzbBJS5pyXPRseD/uWRlET3ImEA==", + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@lit-labs/signals/-/signals-0.1.3.tgz", + "integrity": "sha512-P0yWgH5blwVyEwBg+WFspLzeu1i0ypJP1QB0l1Omr9qZLIPsUu0p4Fy2jshOg7oQyha5n163K3GJGeUhQQ682Q==", "dev": true, "license": "BSD-3-Clause", "dependencies": { diff --git a/package.json b/package.json index 380d76727e0..128b413704b 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,7 @@ "@electron/notarize": "3.0.1", "@electron/rebuild": "4.0.1", "@eslint/compat": "2.0.0", - "@lit-labs/signals": "0.1.2", + "@lit-labs/signals": "0.1.3", "@ngtools/webpack": "20.3.12", "@storybook/addon-a11y": "9.1.16", "@storybook/addon-designs": "9.0.0-next.3", From fe4895d97e64ba54efb4972399b9583dc774bd3b Mon Sep 17 00:00:00 2001 From: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com> Date: Wed, 10 Dec 2025 15:24:20 -0600 Subject: [PATCH 042/188] [PM-28264] Consolidate and update the UI for key connector migration/confirmation (#17642) * Consolidate the RemovePasswordComponent * Add getting confirmation details for confirm key connector * Add missing message --- apps/browser/src/_locales/en/messages.json | 42 +++++++++- .../remove-password.component.html | 51 ------------ .../remove-password.component.ts | 14 ---- apps/browser/src/popup/app-routing.module.ts | 24 +++++- apps/browser/src/popup/app.module.ts | 9 +-- apps/desktop/src/app/app-routing.module.ts | 25 ++++-- apps/desktop/src/app/app.module.ts | 2 - .../remove-password.component.html | 20 ----- .../remove-password.component.ts | 12 --- apps/desktop/src/locales/en/messages.json | 42 +++++++++- .../remove-password.component.html | 37 --------- .../remove-password.component.ts | 12 --- apps/web/src/app/oss-routing.module.ts | 11 ++- .../src/app/shared/loose-components.module.ts | 3 - apps/web/src/locales/en/messages.json | 42 +++++++++- .../src/services/jslib-services.module.ts | 7 ++ .../two-factor-auth.component.spec.ts | 1 + .../abstractions/key-connector-api.service.ts | 7 ++ .../key-connector-domain-confirmation.ts | 1 + ...connector-confirmation-details.response.ts | 10 +++ .../default-key-connector-api.service.spec.ts | 54 +++++++++++++ .../default-key-connector-api.service.ts | 20 +++++ .../services/key-connector.service.spec.ts | 5 +- .../services/key-connector.service.ts | 13 ++- ...onfirm-key-connector-domain.component.html | 29 +++++-- ...irm-key-connector-domain.component.spec.ts | 79 ++++++++++++++++++- .../confirm-key-connector-domain.component.ts | 61 +++++++++++++- .../remove-password.component.html | 35 ++++++++ .../remove-password.component.spec.ts | 2 + .../remove-password.component.ts | 32 ++++++-- 30 files changed, 496 insertions(+), 206 deletions(-) delete mode 100644 apps/browser/src/key-management/key-connector/remove-password.component.html delete mode 100644 apps/browser/src/key-management/key-connector/remove-password.component.ts delete mode 100644 apps/desktop/src/key-management/key-connector/remove-password.component.html delete mode 100644 apps/desktop/src/key-management/key-connector/remove-password.component.ts delete mode 100644 apps/web/src/app/key-management/key-connector/remove-password.component.html delete mode 100644 apps/web/src/app/key-management/key-connector/remove-password.component.ts create mode 100644 libs/common/src/key-management/key-connector/abstractions/key-connector-api.service.ts create mode 100644 libs/common/src/key-management/key-connector/models/response/key-connector-confirmation-details.response.ts create mode 100644 libs/common/src/key-management/key-connector/services/default-key-connector-api.service.spec.ts create mode 100644 libs/common/src/key-management/key-connector/services/default-key-connector-api.service.ts create mode 100644 libs/key-management-ui/src/key-connector/remove-password.component.html diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index a90fbcbf332..09ea964823c 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -3252,9 +3252,6 @@ "copyCustomFieldNameNotUnique": { "message": "No unique identifier found." }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "organizationName": { "message": "Organization name" }, @@ -5891,6 +5888,45 @@ "cardNumberLabel": { "message": "Card number" }, + "removeMasterPasswordForOrgUserKeyConnector":{ + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified":{ + "message": "Organization verified" + }, + "domainVerified":{ + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { "message": "Timeout action" }, diff --git a/apps/browser/src/key-management/key-connector/remove-password.component.html b/apps/browser/src/key-management/key-connector/remove-password.component.html deleted file mode 100644 index 427065e83f3..00000000000 --- a/apps/browser/src/key-management/key-connector/remove-password.component.html +++ /dev/null @@ -1,51 +0,0 @@ -<popup-page> - <popup-header slot="header" pageTitle="{{ 'removeMasterPassword' | i18n }}"> - <ng-container slot="end"> - <app-pop-out></app-pop-out> - </ng-container> - </popup-header> - - @if (loading) { - <div class="tw-text-center"> - <i - class="bwi bwi-spinner bwi-spin bwi-2x tw-text-muted" - title="{{ 'loading' | i18n }}" - aria-hidden="true" - ></i> - <span class="tw-sr-only">{{ "loading" | i18n }}</span> - </div> - } @else { - <p>{{ "removeMasterPasswordForOrganizationUserKeyConnector" | i18n }}</p> - <p class="tw-mb-0">{{ "organizationName" | i18n }}:</p> - <p class="tw-text-muted tw-mb-6">{{ organization.name }}</p> - <p class="tw-mb-0">{{ "keyConnectorDomain" | i18n }}:</p> - <p class="tw-text-muted tw-mb-6">{{ organization.keyConnectorUrl }}</p> - <button - type="button" - bitButton - buttonType="primary" - block - (click)="convert()" - [disabled]="action" - class="tw-mb-2" - > - <i - class="bwi bwi-spinner bwi-spin" - title="{{ 'loading' | i18n }}" - aria-hidden="true" - *ngIf="continuing" - ></i> - {{ "removeMasterPassword" | i18n }} - </button> - - <button type="button" bitButton block (click)="leave()" [disabled]="action"> - <i - class="bwi bwi-spinner bwi-spin" - title="{{ 'loading' | i18n }}" - aria-hidden="true" - *ngIf="leaving" - ></i> - {{ "leaveOrganization" | i18n }} - </button> - } -</popup-page> diff --git a/apps/browser/src/key-management/key-connector/remove-password.component.ts b/apps/browser/src/key-management/key-connector/remove-password.component.ts deleted file mode 100644 index c4077a1eca9..00000000000 --- a/apps/browser/src/key-management/key-connector/remove-password.component.ts +++ /dev/null @@ -1,14 +0,0 @@ -// FIXME (PM-22628): angular imports are forbidden in background -// eslint-disable-next-line no-restricted-imports -import { Component } from "@angular/core"; - -import { RemovePasswordComponent as BaseRemovePasswordComponent } from "@bitwarden/key-management-ui"; - -// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush -// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection -@Component({ - selector: "app-remove-password", - templateUrl: "remove-password.component.html", - standalone: false, -}) -export class RemovePasswordComponent extends BaseRemovePasswordComponent {} diff --git a/apps/browser/src/popup/app-routing.module.ts b/apps/browser/src/popup/app-routing.module.ts index 48f06147cdf..eb64c076192 100644 --- a/apps/browser/src/popup/app-routing.module.ts +++ b/apps/browser/src/popup/app-routing.module.ts @@ -43,7 +43,11 @@ import { TwoFactorAuthGuard, } from "@bitwarden/auth/angular"; import { AnonLayoutWrapperComponent, AnonLayoutWrapperData } from "@bitwarden/components"; -import { LockComponent, ConfirmKeyConnectorDomainComponent } from "@bitwarden/key-management-ui"; +import { + LockComponent, + ConfirmKeyConnectorDomainComponent, + RemovePasswordComponent, +} from "@bitwarden/key-management-ui"; import { AccountSwitcherComponent } from "../auth/popup/account-switching/account-switcher.component"; import { AuthExtensionRoute } from "../auth/popup/constants/auth-extension-route.constant"; @@ -59,7 +63,6 @@ import { NotificationsSettingsComponent } from "../autofill/popup/settings/notif import { PremiumV2Component } from "../billing/popup/settings/premium-v2.component"; import { PhishingWarning } from "../dirt/phishing-detection/popup/phishing-warning.component"; import { ProtectedByComponent } from "../dirt/phishing-detection/popup/protected-by-component"; -import { RemovePasswordComponent } from "../key-management/key-connector/remove-password.component"; import BrowserPopupUtils from "../platform/browser/browser-popup-utils"; import { popupRouterCacheGuard } from "../platform/popup/view-cache/popup-router-cache.service"; import { RouteCacheOptions } from "../platform/services/popup-view-cache-background.service"; @@ -188,9 +191,22 @@ const routes: Routes = [ }, { path: "remove-password", - component: RemovePasswordComponent, + component: ExtensionAnonLayoutWrapperComponent, canActivate: [authGuard], data: { elevation: 1 } satisfies RouteDataProperties, + children: [ + { + path: "", + component: RemovePasswordComponent, + data: { + pageTitle: { + key: "verifyYourOrganization", + }, + showBackButton: false, + pageIcon: LockIcon, + } satisfies ExtensionAnonLayoutWrapperData, + }, + ], }, { path: "view-cipher", @@ -646,7 +662,7 @@ const routes: Routes = [ component: ConfirmKeyConnectorDomainComponent, data: { pageTitle: { - key: "confirmKeyConnectorDomain", + key: "verifyYourOrganization", }, showBackButton: true, pageIcon: DomainIcon, diff --git a/apps/browser/src/popup/app.module.ts b/apps/browser/src/popup/app.module.ts index 71846cc6444..d178cee2fc3 100644 --- a/apps/browser/src/popup/app.module.ts +++ b/apps/browser/src/popup/app.module.ts @@ -28,7 +28,6 @@ import { CurrentAccountComponent } from "../auth/popup/account-switching/current import { AccountSecurityComponent } from "../auth/popup/settings/account-security.component"; import { AutofillComponent } from "../autofill/popup/settings/autofill.component"; import { NotificationsSettingsComponent } from "../autofill/popup/settings/notifications.component"; -import { RemovePasswordComponent } from "../key-management/key-connector/remove-password.component"; import { PopOutComponent } from "../platform/popup/components/pop-out.component"; import { PopupFooterComponent } from "../platform/popup/layout/popup-footer.component"; import { PopupHeaderComponent } from "../platform/popup/layout/popup-header.component"; @@ -85,13 +84,7 @@ import "../platform/popup/locales"; CalloutModule, LinkModule, ], - declarations: [ - AppComponent, - ColorPasswordPipe, - ColorPasswordCountPipe, - TabsV2Component, - RemovePasswordComponent, - ], + declarations: [AppComponent, ColorPasswordPipe, ColorPasswordCountPipe, TabsV2Component], exports: [CalloutModule], providers: [CurrencyPipe, DatePipe], bootstrap: [AppComponent], diff --git a/apps/desktop/src/app/app-routing.module.ts b/apps/desktop/src/app/app-routing.module.ts index fdc421153e1..6077afa8c12 100644 --- a/apps/desktop/src/app/app-routing.module.ts +++ b/apps/desktop/src/app/app-routing.module.ts @@ -42,14 +42,17 @@ import { } from "@bitwarden/auth/angular"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { AnonLayoutWrapperComponent, AnonLayoutWrapperData } from "@bitwarden/components"; -import { LockComponent, ConfirmKeyConnectorDomainComponent } from "@bitwarden/key-management-ui"; +import { + LockComponent, + ConfirmKeyConnectorDomainComponent, + RemovePasswordComponent, +} from "@bitwarden/key-management-ui"; import { maxAccountsGuardFn } from "../auth/guards/max-accounts.guard"; import { reactiveUnlockVaultGuard } from "../autofill/guards/reactive-vault-guard"; import { Fido2CreateComponent } from "../autofill/modal/credentials/fido2-create.component"; import { Fido2ExcludedCiphersComponent } from "../autofill/modal/credentials/fido2-excluded-ciphers.component"; import { Fido2VaultComponent } from "../autofill/modal/credentials/fido2-vault.component"; -import { RemovePasswordComponent } from "../key-management/key-connector/remove-password.component"; import { VaultV2Component } from "../vault/app/vault/vault-v2.component"; import { VaultComponent } from "../vault/app/vault-v3/vault.component"; @@ -117,11 +120,6 @@ const routes: Routes = [ component: SendComponent, canActivate: [authGuard], }, - { - path: "remove-password", - component: RemovePasswordComponent, - canActivate: [authGuard], - }, { path: "fido2-assertion", component: Fido2VaultComponent, @@ -327,13 +325,24 @@ const routes: Routes = [ pageIcon: LockIcon, } satisfies AnonLayoutWrapperData, }, + { + path: "remove-password", + component: RemovePasswordComponent, + canActivate: [authGuard], + data: { + pageTitle: { + key: "verifyYourOrganization", + }, + pageIcon: LockIcon, + } satisfies RouteDataProperties & AnonLayoutWrapperData, + }, { path: "confirm-key-connector-domain", component: ConfirmKeyConnectorDomainComponent, canActivate: [], data: { pageTitle: { - key: "confirmKeyConnectorDomain", + key: "verifyYourOrganization", }, pageIcon: DomainIcon, } satisfies RouteDataProperties & AnonLayoutWrapperData, diff --git a/apps/desktop/src/app/app.module.ts b/apps/desktop/src/app/app.module.ts index 4f53e587994..31131c6202a 100644 --- a/apps/desktop/src/app/app.module.ts +++ b/apps/desktop/src/app/app.module.ts @@ -15,7 +15,6 @@ import { DeleteAccountComponent } from "../auth/delete-account.component"; import { LoginModule } from "../auth/login/login.module"; import { SshAgentService } from "../autofill/services/ssh-agent.service"; import { PremiumComponent } from "../billing/app/accounts/premium.component"; -import { RemovePasswordComponent } from "../key-management/key-connector/remove-password.component"; import { VaultFilterModule } from "../vault/app/vault/vault-filter/vault-filter.module"; import { VaultV2Component } from "../vault/app/vault/vault-v2.component"; @@ -50,7 +49,6 @@ import { SharedModule } from "./shared/shared.module"; ColorPasswordCountPipe, HeaderComponent, PremiumComponent, - RemovePasswordComponent, SearchComponent, ], providers: [SshAgentService], diff --git a/apps/desktop/src/key-management/key-connector/remove-password.component.html b/apps/desktop/src/key-management/key-connector/remove-password.component.html deleted file mode 100644 index 5276e00c531..00000000000 --- a/apps/desktop/src/key-management/key-connector/remove-password.component.html +++ /dev/null @@ -1,20 +0,0 @@ -<div id="remove-password-page" *ngIf="!loading"> - <div class="content"> - <h1>{{ "removeMasterPassword" | i18n }}</h1> - <p>{{ "removeMasterPasswordForOrganizationUserKeyConnector" | i18n }}</p> - <p class="tw-mb-0">{{ "organizationName" | i18n }}:</p> - <p class="tw-text-muted tw-mb-6">{{ organization.name }}</p> - <p class="tw-mb-0">{{ "keyConnectorDomain" | i18n }}:</p> - <p class="tw-text-muted tw-mb-6">{{ organization.keyConnectorUrl }}</p> - <div class="buttons"> - <button type="submit" class="btn primary block" [disabled]="action" (click)="convert()"> - <b [hidden]="continuing">{{ "removeMasterPassword" | i18n }}</b> - <i class="bwi bwi-spinner bwi-spin" [hidden]="!continuing" aria-hidden="true"></i> - </button> - <button type="button" class="btn secondary block" [disabled]="action" (click)="leave()"> - <b [hidden]="leaving">{{ "leaveOrganization" | i18n }}</b> - <i class="bwi bwi-spinner bwi-spin" [hidden]="!leaving" aria-hidden="true"></i> - </button> - </div> - </div> -</div> diff --git a/apps/desktop/src/key-management/key-connector/remove-password.component.ts b/apps/desktop/src/key-management/key-connector/remove-password.component.ts deleted file mode 100644 index d9fea9409f8..00000000000 --- a/apps/desktop/src/key-management/key-connector/remove-password.component.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Component } from "@angular/core"; - -import { RemovePasswordComponent as BaseRemovePasswordComponent } from "@bitwarden/key-management-ui"; - -// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush -// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection -@Component({ - selector: "app-remove-password", - templateUrl: "remove-password.component.html", - standalone: false, -}) -export class RemovePasswordComponent extends BaseRemovePasswordComponent {} diff --git a/apps/desktop/src/locales/en/messages.json b/apps/desktop/src/locales/en/messages.json index 7a3abe528e8..48e346d9c68 100644 --- a/apps/desktop/src/locales/en/messages.json +++ b/apps/desktop/src/locales/en/messages.json @@ -2637,9 +2637,6 @@ "removedMasterPassword": { "message": "Master password removed" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "organizationName": { "message": "Organization name" }, @@ -4337,6 +4334,45 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, + "removeMasterPasswordForOrgUserKeyConnector":{ + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified":{ + "message": "Organization verified" + }, + "domainVerified":{ + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { "message": "Timeout action" }, diff --git a/apps/web/src/app/key-management/key-connector/remove-password.component.html b/apps/web/src/app/key-management/key-connector/remove-password.component.html deleted file mode 100644 index aae660ce504..00000000000 --- a/apps/web/src/app/key-management/key-connector/remove-password.component.html +++ /dev/null @@ -1,37 +0,0 @@ -<div *ngIf="loading" class="tw-text-center"> - <i - class="bwi bwi-spinner bwi-spin bwi-2x tw-text-muted" - title="{{ 'loading' | i18n }}" - aria-hidden="true" - ></i> - <span class="tw-sr-only">{{ "loading" | i18n }}</span> -</div> - -<div *ngIf="!loading"> - <p>{{ "removeMasterPasswordForOrganizationUserKeyConnector" | i18n }}</p> - <p class="tw-mb-0">{{ "organizationName" | i18n }}:</p> - <p class="tw-text-muted tw-mb-6">{{ organization.name }}</p> - <p class="tw-mb-0">{{ "keyConnectorDomain" | i18n }}:</p> - <p class="tw-text-muted tw-mb-6">{{ organization.keyConnectorUrl }}</p> - - <button - bitButton - type="button" - buttonType="primary" - class="tw-w-full tw-mb-2" - [bitAction]="convert" - [block]="true" - > - {{ "removeMasterPassword" | i18n }} - </button> - <button - bitButton - type="button" - buttonType="secondary" - class="tw-w-full" - [bitAction]="leave" - [block]="true" - > - {{ "leaveOrganization" | i18n }} - </button> -</div> diff --git a/apps/web/src/app/key-management/key-connector/remove-password.component.ts b/apps/web/src/app/key-management/key-connector/remove-password.component.ts deleted file mode 100644 index d9fea9409f8..00000000000 --- a/apps/web/src/app/key-management/key-connector/remove-password.component.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Component } from "@angular/core"; - -import { RemovePasswordComponent as BaseRemovePasswordComponent } from "@bitwarden/key-management-ui"; - -// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush -// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection -@Component({ - selector: "app-remove-password", - templateUrl: "remove-password.component.html", - standalone: false, -}) -export class RemovePasswordComponent extends BaseRemovePasswordComponent {} diff --git a/apps/web/src/app/oss-routing.module.ts b/apps/web/src/app/oss-routing.module.ts index ac9bdc4b946..e3c9da635f9 100644 --- a/apps/web/src/app/oss-routing.module.ts +++ b/apps/web/src/app/oss-routing.module.ts @@ -51,7 +51,7 @@ import { import { canAccessEmergencyAccess } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { AnonLayoutWrapperComponent, AnonLayoutWrapperData } from "@bitwarden/components"; -import { LockComponent } from "@bitwarden/key-management-ui"; +import { LockComponent, RemovePasswordComponent } from "@bitwarden/key-management-ui"; import { premiumInterestRedirectGuard } from "@bitwarden/web-vault/app/vault/guards/premium-interest-redirect/premium-interest-redirect.guard"; import { flagEnabled, Flags } from "../utils/flags"; @@ -80,7 +80,6 @@ import { RouteDataProperties } from "./core"; import { ReportsModule } from "./dirt/reports"; import { DataRecoveryComponent } from "./key-management/data-recovery/data-recovery.component"; import { ConfirmKeyConnectorDomainComponent } from "./key-management/key-connector/confirm-key-connector-domain.component"; -import { RemovePasswordComponent } from "./key-management/key-connector/remove-password.component"; import { FrontendLayoutComponent } from "./layouts/frontend-layout.component"; import { UserLayoutComponent } from "./layouts/user-layout.component"; import { RequestSMAccessComponent } from "./secrets-manager/secrets-manager-landing/request-sm-access.component"; @@ -545,9 +544,9 @@ const routes: Routes = [ canActivate: [authGuard], data: { pageTitle: { - key: "removeMasterPassword", + key: "verifyYourOrganization", }, - titleId: "removeMasterPassword", + titleId: "verifyYourOrganization", pageIcon: LockIcon, } satisfies RouteDataProperties & AnonLayoutWrapperData, }, @@ -557,9 +556,9 @@ const routes: Routes = [ canActivate: [], data: { pageTitle: { - key: "confirmKeyConnectorDomain", + key: "verifyYourOrganization", }, - titleId: "confirmKeyConnectorDomain", + titleId: "verifyYourOrganization", pageIcon: DomainIcon, } satisfies RouteDataProperties & AnonLayoutWrapperData, }, diff --git a/apps/web/src/app/shared/loose-components.module.ts b/apps/web/src/app/shared/loose-components.module.ts index 0fff13f428c..f096ef6a292 100644 --- a/apps/web/src/app/shared/loose-components.module.ts +++ b/apps/web/src/app/shared/loose-components.module.ts @@ -7,7 +7,6 @@ import { VerifyRecoverDeleteComponent } from "../auth/verify-recover-delete.comp import { FreeBitwardenFamiliesComponent } from "../billing/members/free-bitwarden-families.component"; import { SponsoredFamiliesComponent } from "../billing/settings/sponsored-families.component"; import { SponsoringOrgRowComponent } from "../billing/settings/sponsoring-org-row.component"; -import { RemovePasswordComponent } from "../key-management/key-connector/remove-password.component"; import { HeaderModule } from "../layouts/header/header.module"; import { OrganizationBadgeModule } from "../vault/individual-vault/organization-badge/organization-badge.module"; import { PipesModule } from "../vault/individual-vault/pipes/pipes.module"; @@ -21,7 +20,6 @@ import { SharedModule } from "./shared.module"; declarations: [ RecoverDeleteComponent, RecoverTwoFactorComponent, - RemovePasswordComponent, SponsoredFamiliesComponent, FreeBitwardenFamiliesComponent, SponsoringOrgRowComponent, @@ -31,7 +29,6 @@ import { SharedModule } from "./shared.module"; exports: [ RecoverDeleteComponent, RecoverTwoFactorComponent, - RemovePasswordComponent, SponsoredFamiliesComponent, VerifyEmailTokenComponent, VerifyRecoverDeleteComponent, diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index be2f72e34b0..3b0554547c5 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -7136,9 +7136,6 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "keyConnectorDomain": { "message": "Key Connector domain" }, @@ -12247,6 +12244,45 @@ } } }, + "removeMasterPasswordForOrgUserKeyConnector":{ + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified":{ + "message": "Organization verified" + }, + "domainVerified":{ + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "confirmNoSelectedCriticalApplicationsTitle": { "message": "No critical applications are selected" }, diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index 6164c4e05d3..b26db7e9056 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -184,7 +184,9 @@ import { DefaultChangeKdfApiService } from "@bitwarden/common/key-management/kdf import { ChangeKdfApiService } from "@bitwarden/common/key-management/kdf/change-kdf-api.service.abstraction"; import { DefaultChangeKdfService } from "@bitwarden/common/key-management/kdf/change-kdf.service"; import { ChangeKdfService } from "@bitwarden/common/key-management/kdf/change-kdf.service.abstraction"; +import { KeyConnectorApiService } from "@bitwarden/common/key-management/key-connector/abstractions/key-connector-api.service"; import { KeyConnectorService as KeyConnectorServiceAbstraction } from "@bitwarden/common/key-management/key-connector/abstractions/key-connector.service"; +import { DefaultKeyConnectorApiService } from "@bitwarden/common/key-management/key-connector/services/default-key-connector-api.service"; import { KeyConnectorService } from "@bitwarden/common/key-management/key-connector/services/key-connector.service"; import { KeyApiService } from "@bitwarden/common/key-management/keys/services/abstractions/key-api-service.abstraction"; import { RotateableKeySetService } from "@bitwarden/common/key-management/keys/services/abstractions/rotateable-key-set.service"; @@ -1835,6 +1837,11 @@ const safeProviders: SafeProvider[] = [ useClass: IpcSessionRepository, deps: [StateProvider], }), + safeProvider({ + provide: KeyConnectorApiService, + useClass: DefaultKeyConnectorApiService, + deps: [ApiServiceAbstraction], + }), safeProvider({ provide: PremiumInterestStateService, useClass: NoopPremiumInterestStateService, diff --git a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.spec.ts b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.spec.ts index 8c12060168b..9d7acd7d26e 100644 --- a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.spec.ts +++ b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.spec.ts @@ -421,6 +421,7 @@ describe("TwoFactorAuthComponent", () => { keyConnectorUrl: mockUserDecryptionOpts.noMasterPasswordWithKeyConnector.keyConnectorOption! .keyConnectorUrl, + organizationSsoIdentifier: "test-sso-id", }), ); const authResult = new AuthResult(); diff --git a/libs/common/src/key-management/key-connector/abstractions/key-connector-api.service.ts b/libs/common/src/key-management/key-connector/abstractions/key-connector-api.service.ts new file mode 100644 index 00000000000..10d55bfc3fb --- /dev/null +++ b/libs/common/src/key-management/key-connector/abstractions/key-connector-api.service.ts @@ -0,0 +1,7 @@ +import { KeyConnectorConfirmationDetailsResponse } from "../models/response/key-connector-confirmation-details.response"; + +export abstract class KeyConnectorApiService { + abstract getConfirmationDetails( + orgSsoIdentifier: string, + ): Promise<KeyConnectorConfirmationDetailsResponse>; +} diff --git a/libs/common/src/key-management/key-connector/models/key-connector-domain-confirmation.ts b/libs/common/src/key-management/key-connector/models/key-connector-domain-confirmation.ts index 277057485c1..aa3596c1c99 100644 --- a/libs/common/src/key-management/key-connector/models/key-connector-domain-confirmation.ts +++ b/libs/common/src/key-management/key-connector/models/key-connector-domain-confirmation.ts @@ -1,3 +1,4 @@ export interface KeyConnectorDomainConfirmation { keyConnectorUrl: string; + organizationSsoIdentifier: string; } diff --git a/libs/common/src/key-management/key-connector/models/response/key-connector-confirmation-details.response.ts b/libs/common/src/key-management/key-connector/models/response/key-connector-confirmation-details.response.ts new file mode 100644 index 00000000000..bd6ce14194d --- /dev/null +++ b/libs/common/src/key-management/key-connector/models/response/key-connector-confirmation-details.response.ts @@ -0,0 +1,10 @@ +import { BaseResponse } from "../../../../models/response/base.response"; + +export class KeyConnectorConfirmationDetailsResponse extends BaseResponse { + organizationName: string; + + constructor(response: any) { + super(response); + this.organizationName = this.getResponseProperty("OrganizationName"); + } +} diff --git a/libs/common/src/key-management/key-connector/services/default-key-connector-api.service.spec.ts b/libs/common/src/key-management/key-connector/services/default-key-connector-api.service.spec.ts new file mode 100644 index 00000000000..553ce7a3ba0 --- /dev/null +++ b/libs/common/src/key-management/key-connector/services/default-key-connector-api.service.spec.ts @@ -0,0 +1,54 @@ +import { mock, MockProxy } from "jest-mock-extended"; + +import { ApiService } from "../../../abstractions/api.service"; +import { KeyConnectorConfirmationDetailsResponse } from "../models/response/key-connector-confirmation-details.response"; + +import { DefaultKeyConnectorApiService } from "./default-key-connector-api.service"; + +describe("DefaultKeyConnectorApiService", () => { + let apiService: MockProxy<ApiService>; + let sut: DefaultKeyConnectorApiService; + + beforeEach(() => { + apiService = mock<ApiService>(); + sut = new DefaultKeyConnectorApiService(apiService); + }); + + describe("getConfirmationDetails", () => { + it("encodes orgSsoIdentifier in URL", async () => { + const orgSsoIdentifier = "test org/with special@chars"; + const expectedEncodedIdentifier = encodeURIComponent(orgSsoIdentifier); + const mockResponse = {}; + apiService.send.mockResolvedValue(mockResponse); + + await sut.getConfirmationDetails(orgSsoIdentifier); + + expect(apiService.send).toHaveBeenCalledWith( + "GET", + `/accounts/key-connector/confirmation-details/${expectedEncodedIdentifier}`, + null, + true, + true, + ); + }); + + it("returns expected response", async () => { + const orgSsoIdentifier = "test-org"; + const expectedOrgName = "example"; + const mockResponse = { OrganizationName: expectedOrgName }; + apiService.send.mockResolvedValue(mockResponse); + + const result = await sut.getConfirmationDetails(orgSsoIdentifier); + + expect(result).toBeInstanceOf(KeyConnectorConfirmationDetailsResponse); + expect(result.organizationName).toBe(expectedOrgName); + expect(apiService.send).toHaveBeenCalledWith( + "GET", + "/accounts/key-connector/confirmation-details/test-org", + null, + true, + true, + ); + }); + }); +}); diff --git a/libs/common/src/key-management/key-connector/services/default-key-connector-api.service.ts b/libs/common/src/key-management/key-connector/services/default-key-connector-api.service.ts new file mode 100644 index 00000000000..8bf0cdfed16 --- /dev/null +++ b/libs/common/src/key-management/key-connector/services/default-key-connector-api.service.ts @@ -0,0 +1,20 @@ +import { ApiService } from "../../../abstractions/api.service"; +import { KeyConnectorApiService } from "../abstractions/key-connector-api.service"; +import { KeyConnectorConfirmationDetailsResponse } from "../models/response/key-connector-confirmation-details.response"; + +export class DefaultKeyConnectorApiService implements KeyConnectorApiService { + constructor(private apiService: ApiService) {} + + async getConfirmationDetails( + orgSsoIdentifier: string, + ): Promise<KeyConnectorConfirmationDetailsResponse> { + const r = await this.apiService.send( + "GET", + "/accounts/key-connector/confirmation-details/" + encodeURIComponent(orgSsoIdentifier), + null, + true, + true, + ); + return new KeyConnectorConfirmationDetailsResponse(r); + } +} diff --git a/libs/common/src/key-management/key-connector/services/key-connector.service.spec.ts b/libs/common/src/key-management/key-connector/services/key-connector.service.spec.ts index bb458ff49f4..45b4f5e4ac6 100644 --- a/libs/common/src/key-management/key-connector/services/key-connector.service.spec.ts +++ b/libs/common/src/key-management/key-connector/services/key-connector.service.spec.ts @@ -603,7 +603,10 @@ describe("KeyConnectorService", () => { const data$ = keyConnectorService.requiresDomainConfirmation$(mockUserId); const data = await firstValueFrom(data$); - expect(data).toEqual({ keyConnectorUrl: conversion.keyConnectorUrl }); + expect(data).toEqual({ + keyConnectorUrl: conversion.keyConnectorUrl, + organizationSsoIdentifier: conversion.organizationId, + }); }); it("should return observable of null value when no data is set", async () => { diff --git a/libs/common/src/key-management/key-connector/services/key-connector.service.ts b/libs/common/src/key-management/key-connector/services/key-connector.service.ts index f6730cf8870..8a75034cae1 100644 --- a/libs/common/src/key-management/key-connector/services/key-connector.service.ts +++ b/libs/common/src/key-management/key-connector/services/key-connector.service.ts @@ -202,9 +202,16 @@ export class KeyConnectorService implements KeyConnectorServiceAbstraction { } requiresDomainConfirmation$(userId: UserId): Observable<KeyConnectorDomainConfirmation | null> { - return this.stateProvider - .getUserState$(NEW_SSO_USER_KEY_CONNECTOR_CONVERSION, userId) - .pipe(map((data) => (data != null ? { keyConnectorUrl: data.keyConnectorUrl } : null))); + return this.stateProvider.getUserState$(NEW_SSO_USER_KEY_CONNECTOR_CONVERSION, userId).pipe( + map((data) => + data != null + ? { + keyConnectorUrl: data.keyConnectorUrl, + organizationSsoIdentifier: data.organizationId, + } + : null, + ), + ); } private handleKeyConnectorError(e: any) { diff --git a/libs/key-management-ui/src/key-connector/confirm-key-connector-domain.component.html b/libs/key-management-ui/src/key-connector/confirm-key-connector-domain.component.html index 11b34a8409f..b3bed15c698 100644 --- a/libs/key-management-ui/src/key-connector/confirm-key-connector-domain.component.html +++ b/libs/key-management-ui/src/key-connector/confirm-key-connector-domain.component.html @@ -8,17 +8,34 @@ <span class="tw-sr-only">{{ "loading" | i18n }}</span> </div> } @else { - <div class="tw-mb-4"> - <p class="tw-mb-1 tw-text-sm tw-font-medium">{{ "keyConnectorDomain" | i18n }}:</p> - <p class="tw-text-muted tw-break-all">{{ keyConnectorUrl }}</p> - </div> + @if (organizationName) { + <p>{{ "confirmKeyConnectorOrganizationUserDescription" | i18n }}</p> + + <p class="tw-mb-0 tw-font-bold">{{ "organization" | i18n }}</p> + <p class="tw-mb-6">{{ organizationName }}</p> + } @else { + <p>{{ "verifyYourDomainDescription" | i18n }}</p> + } + + <p class="tw-mb-0 tw-font-bold tw-inline-flex tw-items-center"> + {{ "domain" | i18n }} + <button + type="button" + [label]="'keyConnectorDomainTooltip' | i18n" + tooltipPosition="above-center" + bitIconButton="bwi-info-circle" + size="small" + ></button> + </p> + <p class="tw-mb-6 tw-font-mono">{{ keyConnectorHostName }}</p> <div class="tw-flex tw-flex-col tw-gap-2"> <button bitButton type="button" buttonType="primary" [bitAction]="confirm" [block]="true"> - {{ "confirm" | i18n }} + {{ "continueWithLogIn" | i18n }} </button> + <button bitButton type="button" buttonType="secondary" [bitAction]="cancel" [block]="true"> - {{ "cancel" | i18n }} + {{ "doNotContinue" | i18n }} </button> </div> } diff --git a/libs/key-management-ui/src/key-connector/confirm-key-connector-domain.component.spec.ts b/libs/key-management-ui/src/key-connector/confirm-key-connector-domain.component.spec.ts index b53b0a196f5..ad0b783eee3 100644 --- a/libs/key-management-ui/src/key-connector/confirm-key-connector-domain.component.spec.ts +++ b/libs/key-management-ui/src/key-connector/confirm-key-connector-domain.component.spec.ts @@ -2,13 +2,17 @@ import { Router } from "@angular/router"; import { mock } from "jest-mock-extended"; import { of } from "rxjs"; +import { KeyConnectorApiService } from "@bitwarden/common/key-management/key-connector/abstractions/key-connector-api.service"; import { KeyConnectorService } from "@bitwarden/common/key-management/key-connector/abstractions/key-connector.service"; import { KeyConnectorDomainConfirmation } from "@bitwarden/common/key-management/key-connector/models/key-connector-domain-confirmation"; +import { KeyConnectorConfirmationDetailsResponse } from "@bitwarden/common/key-management/key-connector/models/response/key-connector-confirmation-details.response"; +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 { SyncService } from "@bitwarden/common/platform/sync"; import { mockAccountServiceWith } from "@bitwarden/common/spec"; import { UserId } from "@bitwarden/common/types/guid"; +import { AnonLayoutWrapperDataService, ToastService } from "@bitwarden/components"; import { ConfirmKeyConnectorDomainComponent } from "./confirm-key-connector-domain.component"; @@ -16,8 +20,10 @@ describe("ConfirmKeyConnectorDomainComponent", () => { let component: ConfirmKeyConnectorDomainComponent; const userId = "test-user-id" as UserId; + const expectedHostName = "key-connector-url.com"; const confirmation: KeyConnectorDomainConfirmation = { keyConnectorUrl: "https://key-connector-url.com", + organizationSsoIdentifier: "org-sso-identifier", }; const mockRouter = mock<Router>(); @@ -25,6 +31,10 @@ describe("ConfirmKeyConnectorDomainComponent", () => { const mockKeyConnectorService = mock<KeyConnectorService>(); const mockLogService = mock<LogService>(); const mockMessagingService = mock<MessagingService>(); + const mockKeyConnectorApiService = mock<KeyConnectorApiService>(); + const mockToastService = mock<ToastService>(); + const mockI18nService = mock<I18nService>(); + const mockAnonLayoutWrapperDataService = mock<AnonLayoutWrapperDataService>(); let mockAccountService = mockAccountServiceWith(userId); const onBeforeNavigation = jest.fn(); @@ -33,6 +43,8 @@ describe("ConfirmKeyConnectorDomainComponent", () => { mockAccountService = mockAccountServiceWith(userId); + mockI18nService.t.mockImplementation((key) => `${key}-used-i18n`); + component = new ConfirmKeyConnectorDomainComponent( mockRouter, mockLogService, @@ -40,6 +52,10 @@ describe("ConfirmKeyConnectorDomainComponent", () => { mockMessagingService, mockSyncService, mockAccountService, + mockKeyConnectorApiService, + mockToastService, + mockI18nService, + mockAnonLayoutWrapperDataService, ); jest.spyOn(component, "onBeforeNavigation").mockImplementation(onBeforeNavigation); @@ -67,17 +83,41 @@ describe("ConfirmKeyConnectorDomainComponent", () => { expect(component.loading).toEqual(true); }); + it("sets organization name to undefined when getOrganizationName throws error", async () => { + mockKeyConnectorApiService.getConfirmationDetails.mockRejectedValue(new Error("API error")); + + await component.ngOnInit(); + + expect(component.organizationName).toBeUndefined(); + expect(component.userId).toEqual(userId); + expect(component.keyConnectorUrl).toEqual(confirmation.keyConnectorUrl); + expect(component.keyConnectorHostName).toEqual(expectedHostName); + expect(component.loading).toEqual(false); + expect(mockAnonLayoutWrapperDataService.setAnonLayoutWrapperData).toHaveBeenCalledWith({ + pageTitle: { key: "verifyYourDomainToLogin" }, + }); + }); + it("should set component properties correctly", async () => { + const expectedOrgName = "Test Organization"; + mockKeyConnectorApiService.getConfirmationDetails.mockResolvedValue({ + organizationName: expectedOrgName, + } as KeyConnectorConfirmationDetailsResponse); + await component.ngOnInit(); expect(component.userId).toEqual(userId); + expect(component.organizationName).toEqual(expectedOrgName); expect(component.keyConnectorUrl).toEqual(confirmation.keyConnectorUrl); + expect(component.keyConnectorHostName).toEqual(expectedHostName); expect(component.loading).toEqual(false); }); }); describe("confirm", () => { - it("should call keyConnectorService.convertNewSsoUserToKeyConnector with full sync and navigation to home page", async () => { + it("calls domain verified toast when organization name is not set", async () => { + mockKeyConnectorApiService.getConfirmationDetails.mockRejectedValue(new Error("API error")); + await component.ngOnInit(); await component.confirm(); @@ -94,6 +134,43 @@ describe("ConfirmKeyConnectorDomainComponent", () => { expect(mockSyncService.fullSync.mock.invocationCallOrder[0]).toBeLessThan( mockMessagingService.send.mock.invocationCallOrder[0], ); + expect(mockToastService.showToast).toHaveBeenCalledWith({ + variant: "success", + message: "domainVerified-used-i18n", + }); + expect(mockMessagingService.send.mock.invocationCallOrder[0]).toBeLessThan( + onBeforeNavigation.mock.invocationCallOrder[0], + ); + expect(onBeforeNavigation.mock.invocationCallOrder[0]).toBeLessThan( + mockRouter.navigate.mock.invocationCallOrder[0], + ); + }); + + it("should call keyConnectorService.convertNewSsoUserToKeyConnector with full sync and navigation to home page", async () => { + mockKeyConnectorApiService.getConfirmationDetails.mockResolvedValue({ + organizationName: "Test Org Name", + } as KeyConnectorConfirmationDetailsResponse); + + await component.ngOnInit(); + + await component.confirm(); + + expect(mockKeyConnectorService.convertNewSsoUserToKeyConnector).toHaveBeenCalledWith(userId); + expect(mockSyncService.fullSync).toHaveBeenCalledWith(true); + expect(mockRouter.navigate).toHaveBeenCalledWith(["/"]); + expect(mockMessagingService.send).toHaveBeenCalledWith("loggedIn"); + expect(onBeforeNavigation).toHaveBeenCalled(); + + expect( + mockKeyConnectorService.convertNewSsoUserToKeyConnector.mock.invocationCallOrder[0], + ).toBeLessThan(mockSyncService.fullSync.mock.invocationCallOrder[0]); + expect(mockSyncService.fullSync.mock.invocationCallOrder[0]).toBeLessThan( + mockMessagingService.send.mock.invocationCallOrder[0], + ); + expect(mockToastService.showToast).toHaveBeenCalledWith({ + variant: "success", + message: "organizationVerified-used-i18n", + }); expect(mockMessagingService.send.mock.invocationCallOrder[0]).toBeLessThan( onBeforeNavigation.mock.invocationCallOrder[0], ); diff --git a/libs/key-management-ui/src/key-connector/confirm-key-connector-domain.component.ts b/libs/key-management-ui/src/key-connector/confirm-key-connector-domain.component.ts index fe96e4620ad..aa65f4c43f9 100644 --- a/libs/key-management-ui/src/key-connector/confirm-key-connector-domain.component.ts +++ b/libs/key-management-ui/src/key-connector/confirm-key-connector-domain.component.ts @@ -5,12 +5,21 @@ import { firstValueFrom } from "rxjs"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { getUserId } from "@bitwarden/common/auth/services/account.service"; +import { KeyConnectorApiService } from "@bitwarden/common/key-management/key-connector/abstractions/key-connector-api.service"; import { KeyConnectorService } from "@bitwarden/common/key-management/key-connector/abstractions/key-connector.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 { Utils } from "@bitwarden/common/platform/misc/utils"; import { SyncService } from "@bitwarden/common/platform/sync"; import { UserId } from "@bitwarden/common/types/guid"; -import { BitActionDirective, ButtonModule } from "@bitwarden/components"; +import { + AnonLayoutWrapperDataService, + BitActionDirective, + ButtonModule, + IconButtonModule, + ToastService, +} from "@bitwarden/components"; import { I18nPipe } from "@bitwarden/ui-common"; // FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush @@ -19,11 +28,13 @@ import { I18nPipe } from "@bitwarden/ui-common"; selector: "confirm-key-connector-domain", templateUrl: "confirm-key-connector-domain.component.html", standalone: true, - imports: [CommonModule, ButtonModule, I18nPipe, BitActionDirective], + imports: [CommonModule, ButtonModule, I18nPipe, BitActionDirective, IconButtonModule], }) export class ConfirmKeyConnectorDomainComponent implements OnInit { loading = true; keyConnectorUrl!: string; + keyConnectorHostName!: string; + organizationName: string | undefined; userId!: UserId; // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals @@ -37,6 +48,10 @@ export class ConfirmKeyConnectorDomainComponent implements OnInit { private messagingService: MessagingService, private syncService: SyncService, private accountService: AccountService, + private keyConnectorApiService: KeyConnectorApiService, + private toastService: ToastService, + private i18nService: I18nService, + private anonLayoutWrapperDataService: AnonLayoutWrapperDataService, ) {} async ngOnInit() { @@ -57,14 +72,36 @@ export class ConfirmKeyConnectorDomainComponent implements OnInit { return; } - this.keyConnectorUrl = confirmation.keyConnectorUrl; + this.organizationName = await this.getOrganizationName(confirmation.organizationSsoIdentifier); + // PM-29133 Remove during cleanup. + if (this.organizationName == undefined) { + this.anonLayoutWrapperDataService.setAnonLayoutWrapperData({ + pageTitle: { key: "verifyYourDomainToLogin" }, + }); + } + + this.keyConnectorUrl = confirmation.keyConnectorUrl; + this.keyConnectorHostName = Utils.getHostname(confirmation.keyConnectorUrl); this.loading = false; } confirm = async () => { await this.keyConnectorService.convertNewSsoUserToKeyConnector(this.userId); + if (this.organizationName) { + this.toastService.showToast({ + variant: "success", + message: this.i18nService.t("organizationVerified"), + }); + } else { + // PM-29133 Remove during cleanup. + this.toastService.showToast({ + variant: "success", + message: this.i18nService.t("domainVerified"), + }); + } + await this.syncService.fullSync(true); this.messagingService.send("loggedIn"); @@ -77,4 +114,22 @@ export class ConfirmKeyConnectorDomainComponent implements OnInit { cancel = async () => { this.messagingService.send("logout"); }; + + private async getOrganizationName( + organizationSsoIdentifier: string, + ): Promise<string | undefined> { + try { + const details = + await this.keyConnectorApiService.getConfirmationDetails(organizationSsoIdentifier); + return details.organizationName; + } catch (error) { + // PM-29133 Remove during cleanup. + // Old self hosted servers may not have this endpoint yet. On error log a warning and continue without organization name. + this.logService.warning( + `[ConfirmKeyConnectorDomainComponent] Unable to get key connector confirmation details for organizationSsoIdentifier ${organizationSsoIdentifier}:`, + error, + ); + return undefined; + } + } } diff --git a/libs/key-management-ui/src/key-connector/remove-password.component.html b/libs/key-management-ui/src/key-connector/remove-password.component.html new file mode 100644 index 00000000000..42cee2fe168 --- /dev/null +++ b/libs/key-management-ui/src/key-connector/remove-password.component.html @@ -0,0 +1,35 @@ +@if (loading) { + <div class="tw-text-center"> + <i + class="bwi bwi-spinner bwi-spin bwi-2x tw-text-muted" + title="{{ 'loading' | i18n }}" + aria-hidden="true" + ></i> + <span class="tw-sr-only">{{ "loading" | i18n }}</span> + </div> +} @else { + <p>{{ "removeMasterPasswordForOrgUserKeyConnector" | i18n }}</p> + <p class="tw-mb-0 tw-font-bold">{{ "organization" | i18n }}</p> + <p class="tw-mb-6">{{ organization.name }}</p> + <p class="tw-mb-0 tw-font-bold tw-inline-flex tw-items-center"> + {{ "domain" | i18n }} + <button + type="button" + [label]="'keyConnectorDomainTooltip' | i18n" + tooltipPosition="above-center" + bitIconButton="bwi-info-circle" + size="small" + ></button> + </p> + <p class="tw-mb-6 tw-font-mono">{{ keyConnectorHostName }}</p> + + <div class="tw-flex tw-flex-col tw-gap-2"> + <button bitButton type="button" buttonType="primary" [block]="true" [bitAction]="convert"> + {{ "continueWithLogIn" | i18n }} + </button> + + <button bitButton type="button" buttonType="secondary" [block]="true" [bitAction]="leave"> + {{ "doNotContinue" | i18n }} + </button> + </div> +} diff --git a/libs/key-management-ui/src/key-connector/remove-password.component.spec.ts b/libs/key-management-ui/src/key-connector/remove-password.component.spec.ts index eb11932d931..240cddee2f9 100644 --- a/libs/key-management-ui/src/key-connector/remove-password.component.spec.ts +++ b/libs/key-management-ui/src/key-connector/remove-password.component.spec.ts @@ -19,6 +19,7 @@ describe("RemovePasswordComponent", () => { let component: RemovePasswordComponent; const userId = "test-user-id" as UserId; + const expectedHostName = "key-connector-url.com"; const organization = { id: "test-organization-id", name: "test-organization-name", @@ -62,6 +63,7 @@ describe("RemovePasswordComponent", () => { expect(component["activeUserId"]).toBe("test-user-id"); expect(component.organization).toEqual(organization); expect(component.loading).toEqual(false); + expect(component.keyConnectorHostName).toBe(expectedHostName); expect(mockKeyConnectorService.getManagingOrganization).toHaveBeenCalledWith(userId); expect(mockSyncService.fullSync).toHaveBeenCalledWith(false); diff --git a/libs/key-management-ui/src/key-connector/remove-password.component.ts b/libs/key-management-ui/src/key-connector/remove-password.component.ts index a31989ffc49..bb17475230d 100644 --- a/libs/key-management-ui/src/key-connector/remove-password.component.ts +++ b/libs/key-management-ui/src/key-connector/remove-password.component.ts @@ -1,4 +1,5 @@ -import { Directive, OnInit } from "@angular/core"; +import { CommonModule } from "@angular/common"; +import { Component, OnInit } from "@angular/core"; import { Router } from "@angular/router"; import { firstValueFrom } from "rxjs"; @@ -9,17 +10,33 @@ import { KeyConnectorService } from "@bitwarden/common/key-management/key-connec 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 { Utils } from "@bitwarden/common/platform/misc/utils"; import { UserId } from "@bitwarden/common/types/guid"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; -import { DialogService, ToastService } from "@bitwarden/components"; +import { + DialogService, + ToastService, + ButtonModule, + BitActionDirective, + IconButtonModule, +} from "@bitwarden/components"; +import { I18nPipe } from "@bitwarden/ui-common"; -@Directive() +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection +@Component({ + selector: "km-ui-remove-password", + templateUrl: "remove-password.component.html", + standalone: true, + imports: [CommonModule, ButtonModule, I18nPipe, BitActionDirective, IconButtonModule], +}) export class RemovePasswordComponent implements OnInit { continuing = false; leaving = false; loading = true; organization!: Organization; + keyConnectorHostName!: string; private activeUserId!: UserId; constructor( @@ -55,6 +72,7 @@ export class RemovePasswordComponent implements OnInit { await this.router.navigate(["/"]); return; } + this.keyConnectorHostName = Utils.getHostname(this.organization.keyConnectorUrl); this.loading = false; } @@ -73,7 +91,7 @@ export class RemovePasswordComponent implements OnInit { this.toastService.showToast({ variant: "success", - message: this.i18nService.t("removedMasterPassword"), + message: this.i18nService.t("organizationVerified"), }); await this.router.navigate(["/"]); @@ -86,9 +104,11 @@ export class RemovePasswordComponent implements OnInit { leave = async () => { const confirmed = await this.dialogService.openSimpleDialog({ - title: this.organization.name, - content: { key: "leaveOrganizationConfirmation" }, + title: { key: "leaveOrganization" }, + content: { key: "leaveOrganizationContent" }, type: "warning", + acceptButtonText: { key: "leaveNow" }, + cancelButtonText: { key: "cancel" }, }); if (!confirmed) { From f5cdee3fa6d747cf53c6c29571d9fa82dfe52c25 Mon Sep 17 00:00:00 2001 From: bmbitwarden <bmcferren@bitwarden.com> Date: Wed, 10 Dec 2025 20:03:40 -0500 Subject: [PATCH 043/188] PM-28180 responsively hide deletion date column in sends table (#17652) * PM-28180 responsively hide options in sends table * PM-28180 resolved pr comments * PM-28180 revert named container change * PM-28180 resolved pr comment re naming container * PM-28180 resolved double class issue --- apps/web/src/app/tools/send/send.component.html | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/apps/web/src/app/tools/send/send.component.html b/apps/web/src/app/tools/send/send.component.html index b8538606aec..e593f5c1176 100644 --- a/apps/web/src/app/tools/send/send.component.html +++ b/apps/web/src/app/tools/send/send.component.html @@ -80,13 +80,15 @@ </div> </div> </div> - <div class="tw-col-span-9"> + <div class="tw-col-span-9 tw-@container/send-table"> <!--Listing Table--> <bit-table [dataSource]="dataSource" *ngIf="filteredSends && filteredSends.length"> <ng-container header> <tr> <th bitCell bitSortable="name" default>{{ "name" | i18n }}</th> - <th bitCell bitSortable="deletionDate">{{ "deletionDate" | i18n }}</th> + <th bitCell bitSortable="deletionDate" class="@lg/send-table:tw-table-cell tw-hidden"> + {{ "deletionDate" | i18n }} + </th> <th bitCell>{{ "options" | i18n }}</th> </tr> </ng-container> @@ -148,8 +150,14 @@ </ng-container> </div> </td> - <td bitCell class="tw-text-muted" (click)="editSend(s)" class="tw-cursor-pointer"> - <small bitTypography="body2" appStopProp>{{ s.deletionDate | date: "medium" }}</small> + <td + bitCell + (click)="editSend(s)" + class="tw-text-muted tw-cursor-pointer @lg/send-table:tw-table-cell tw-hidden" + > + <small bitTypography="body2" appStopProp> + {{ s.deletionDate | date: "medium" }} + </small> </td> <td bitCell class="tw-w-0 tw-text-right"> <button From 7183d77f7b2ed1bd21c0d5d8855c78afc6dc5854 Mon Sep 17 00:00:00 2001 From: Derek Nance <dnance@bitwarden.com> Date: Thu, 11 Dec 2025 02:03:49 -0600 Subject: [PATCH 044/188] Remove parse5 override (#17916) * chore(deps): Remove parse5 from Platform-owned deps This reverts commit 8182f6fa02a49d26fbc6dea0948b86dc044447a5. * chore(deps): Remove parse5 override This commit reverts c8eae5897e15f23ea330aa827f44c609ddd4d2ab. --- .github/renovate.json5 | 1 - package-lock.json | 69 ++++++++++++++++++++++++++++++++++++++++++ package.json | 1 - 3 files changed, 69 insertions(+), 2 deletions(-) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 997812735de..858dcccc094 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -197,7 +197,6 @@ "nx", "oo7", "oslog", - "parse5", "pin-project", "pkg", "postcss", diff --git a/package-lock.json b/package-lock.json index d3908e71266..dc8694f77b6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2416,6 +2416,30 @@ "rxjs": "^6.5.3 || ^7.4.0" } }, + "node_modules/@angular/cdk/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/@angular/cdk/node_modules/parse5": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.0.tgz", + "integrity": "sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==", + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, "node_modules/@angular/cli": { "version": "20.3.12", "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-20.3.12.tgz", @@ -12489,6 +12513,12 @@ "node": "*" } }, + "node_modules/@nx/webpack/node_modules/parse5": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-4.0.0.tgz", + "integrity": "sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA==", + "license": "MIT" + }, "node_modules/@nx/webpack/node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", @@ -34644,6 +34674,19 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/parse5-html-rewriting-stream/node_modules/parse5": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.0.tgz", + "integrity": "sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, "node_modules/parse5-htmlparser2-tree-adapter": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz", @@ -34684,6 +34727,32 @@ "url": "https://github.com/inikulin/parse5?sponsor=1" } }, + "node_modules/parse5-sax-parser/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/parse5-sax-parser/node_modules/parse5": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.0.tgz", + "integrity": "sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, "node_modules/parse5/node_modules/entities": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", diff --git a/package.json b/package.json index 128b413704b..cd1262ac373 100644 --- a/package.json +++ b/package.json @@ -217,7 +217,6 @@ "eslint": "$eslint" }, "tailwindcss": "$tailwindcss", - "parse5": "7.3.0", "react": "18.3.1", "react-dom": "18.3.1", "@types/react": "18.3.27" From 267e4883903f3a91ce3f644c95e55dda5176e76f Mon Sep 17 00:00:00 2001 From: Oscar Hinton <Hinton@users.noreply.github.com> Date: Thu, 11 Dec 2025 12:04:15 +0100 Subject: [PATCH 045/188] [BEEEP] [PM-28239] Migrate generators to standalone (#17386) * Migrate generators to use standalone and control flow * Resolve feedback * Add variable for account * Fix generators --- .../src/popup/services/services.module.ts | 3 +- .../src/app/services/services.module.ts | 3 +- apps/web/src/app/core/core.module.ts | 3 +- .../src/catchall-settings.component.ts | 7 +- ...al-generator-history-dialog.component.html | 7 +- ...redential-generator-history.component.html | 46 +++--- .../credential-generator-history.component.ts | 4 +- .../src/credential-generator.component.html | 151 ++++++++++-------- .../src/credential-generator.component.ts | 57 ++++++- .../src/forwarder-settings.component.html | 58 ++++--- .../src/forwarder-settings.component.ts | 20 ++- .../components/src/generator.module.ts | 58 +------ libs/tools/generator/components/src/index.ts | 10 ++ .../nudge-generator-spotlight.component.html | 34 ++-- .../src/passphrase-settings.component.html | 12 +- .../src/passphrase-settings.component.ts | 28 +++- .../src/password-generator.component.html | 57 ++++--- .../src/password-generator.component.ts | 34 +++- .../src/password-settings.component.html | 18 ++- .../src/password-settings.component.ts | 28 +++- .../src/subaddress-settings.component.ts | 7 +- .../src/username-generator.component.html | 67 ++++---- .../src/username-generator.component.ts | 50 +++++- .../src/username-settings.component.ts | 7 +- 24 files changed, 481 insertions(+), 288 deletions(-) diff --git a/apps/browser/src/popup/services/services.module.ts b/apps/browser/src/popup/services/services.module.ts index 0a82a07b722..bb89eff1147 100644 --- a/apps/browser/src/popup/services/services.module.ts +++ b/apps/browser/src/popup/services/services.module.ts @@ -136,6 +136,7 @@ import { DialogService, ToastService, } from "@bitwarden/components"; +import { GeneratorServicesModule } from "@bitwarden/generator-components"; import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy"; import { BiometricsService, @@ -743,7 +744,7 @@ const safeProviders: SafeProvider[] = [ ]; @NgModule({ - imports: [JslibServicesModule], + imports: [JslibServicesModule, GeneratorServicesModule], declarations: [], // Do not register your dependency here! Add it to the typesafeProviders array using the helper function providers: safeProviders, diff --git a/apps/desktop/src/app/services/services.module.ts b/apps/desktop/src/app/services/services.module.ts index 5e20b2fa921..1b373f08881 100644 --- a/apps/desktop/src/app/services/services.module.ts +++ b/apps/desktop/src/app/services/services.module.ts @@ -102,6 +102,7 @@ import { GlobalStateProvider, StateProvider } from "@bitwarden/common/platform/s import { SyncService } from "@bitwarden/common/platform/sync"; import { CipherService as CipherServiceAbstraction } from "@bitwarden/common/vault/abstractions/cipher.service"; import { DialogService, ToastService } from "@bitwarden/components"; +import { GeneratorServicesModule } from "@bitwarden/generator-components"; import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy"; import { KdfConfigService, @@ -499,7 +500,7 @@ const safeProviders: SafeProvider[] = [ ]; @NgModule({ - imports: [JslibServicesModule], + imports: [JslibServicesModule, GeneratorServicesModule], declarations: [], // Do not register your dependency here! Add it to the typesafeProviders array using the helper function providers: safeProviders, diff --git a/apps/web/src/app/core/core.module.ts b/apps/web/src/app/core/core.module.ts index ab8f06dcf32..fb42e19f863 100644 --- a/apps/web/src/app/core/core.module.ts +++ b/apps/web/src/app/core/core.module.ts @@ -112,6 +112,7 @@ import { } from "@bitwarden/common/platform/theming/theme-state.service"; import { PremiumUpgradePromptService } from "@bitwarden/common/vault/abstractions/premium-upgrade-prompt.service"; import { DialogService, ToastService } from "@bitwarden/components"; +import { GeneratorServicesModule } from "@bitwarden/generator-components"; import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy"; import { KdfConfigService, @@ -484,7 +485,7 @@ const safeProviders: SafeProvider[] = [ @NgModule({ declarations: [], - imports: [CommonModule, JslibServicesModule], + imports: [CommonModule, JslibServicesModule, GeneratorServicesModule], // Do not register your dependency here! Add it to the typesafeProviders array using the helper function providers: safeProviders, }) diff --git a/libs/tools/generator/components/src/catchall-settings.component.ts b/libs/tools/generator/components/src/catchall-settings.component.ts index 0fb953b86dc..a30a1c65b8c 100644 --- a/libs/tools/generator/components/src/catchall-settings.component.ts +++ b/libs/tools/generator/components/src/catchall-settings.component.ts @@ -8,15 +8,18 @@ import { Output, SimpleChanges, } from "@angular/core"; -import { FormBuilder } from "@angular/forms"; +import { FormBuilder, ReactiveFormsModule } from "@angular/forms"; import { map, ReplaySubject, skip, Subject, takeUntil, withLatestFrom } from "rxjs"; +import { JslibModule } from "@bitwarden/angular/jslib.module"; import { Account } from "@bitwarden/common/auth/abstractions/account.service"; +import { FormFieldModule } from "@bitwarden/components"; import { CatchallGenerationOptions, CredentialGeneratorService, BuiltIn, } from "@bitwarden/generator-core"; +import { I18nPipe } from "@bitwarden/ui-common"; /** Options group for catchall emails */ // FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush @@ -24,7 +27,7 @@ import { @Component({ selector: "tools-catchall-settings", templateUrl: "catchall-settings.component.html", - standalone: false, + imports: [ReactiveFormsModule, FormFieldModule, JslibModule, I18nPipe], }) export class CatchallSettingsComponent implements OnInit, OnDestroy, OnChanges { /** Instantiates the component diff --git a/libs/tools/generator/components/src/credential-generator-history-dialog.component.html b/libs/tools/generator/components/src/credential-generator-history-dialog.component.html index e107e33ca50..c392b323a6b 100644 --- a/libs/tools/generator/components/src/credential-generator-history-dialog.component.html +++ b/libs/tools/generator/components/src/credential-generator-history-dialog.component.html @@ -1,8 +1,11 @@ <bit-dialog #dialog background="alt"> <span bitDialogTitle>{{ "generatorHistory" | i18n }}</span> <ng-container bitDialogContent> - <bit-empty-credential-history *ngIf="!(hasHistory$ | async)" style="display: contents" /> - <bit-credential-generator-history [account]="account$ | async" *ngIf="hasHistory$ | async" /> + @if (hasHistory$ | async) { + <bit-credential-generator-history [account]="account$ | async" /> + } @else { + <bit-empty-credential-history style="display: contents" /> + } </ng-container> <ng-container bitDialogFooter> <button diff --git a/libs/tools/generator/components/src/credential-generator-history.component.html b/libs/tools/generator/components/src/credential-generator-history.component.html index 1f2f3d99e00..c00f9760cea 100644 --- a/libs/tools/generator/components/src/credential-generator-history.component.html +++ b/libs/tools/generator/components/src/credential-generator-history.component.html @@ -1,22 +1,24 @@ -<bit-item *ngFor="let credential of credentials$ | async"> - <bit-item-content> - <bit-color-password class="tw-font-mono" [password]="credential.credential" /> - <div slot="secondary"> - {{ credential.generationDate | date: "medium" }} - </div> - </bit-item-content> - <ng-container slot="end"> - <bit-item-action> - <button - type="button" - bitIconButton="bwi-clone" - [appCopyClick]="credential.credential" - [valueLabel]="getGeneratedValueText(credential)" - [label]="getCopyText(credential)" - showToast - > - {{ getCopyText(credential) }} - </button> - </bit-item-action> - </ng-container> -</bit-item> +@for (credential of credentials$ | async; track credential) { + <bit-item> + <bit-item-content> + <bit-color-password class="tw-font-mono" [password]="credential.credential" /> + <div slot="secondary"> + {{ credential.generationDate | date: "medium" }} + </div> + </bit-item-content> + <ng-container slot="end"> + <bit-item-action> + <button + type="button" + bitIconButton="bwi-clone" + [appCopyClick]="credential.credential" + [valueLabel]="getGeneratedValueText(credential)" + [label]="getCopyText(credential)" + showToast + > + {{ getCopyText(credential) }} + </button> + </bit-item-action> + </ng-container> + </bit-item> +} diff --git a/libs/tools/generator/components/src/credential-generator-history.component.ts b/libs/tools/generator/components/src/credential-generator-history.component.ts index a09a82c74b8..e732aa0198e 100644 --- a/libs/tools/generator/components/src/credential-generator-history.component.ts +++ b/libs/tools/generator/components/src/credential-generator-history.component.ts @@ -23,7 +23,6 @@ import { import { AlgorithmsByType, CredentialGeneratorService } from "@bitwarden/generator-core"; import { GeneratedCredential, GeneratorHistoryService } from "@bitwarden/generator-history"; -import { GeneratorModule } from "./generator.module"; import { translate } from "./util"; // FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush @@ -32,13 +31,12 @@ import { translate } from "./util"; selector: "bit-credential-generator-history", templateUrl: "credential-generator-history.component.html", imports: [ - ColorPasswordModule, CommonModule, + ColorPasswordModule, IconButtonModule, NoItemsModule, JslibModule, ItemModule, - GeneratorModule, ], }) export class CredentialGeneratorHistoryComponent implements OnChanges, OnInit, OnDestroy { diff --git a/libs/tools/generator/components/src/credential-generator.component.html b/libs/tools/generator/components/src/credential-generator.component.html index 124de1e3c45..f97e1500b33 100644 --- a/libs/tools/generator/components/src/credential-generator.component.html +++ b/libs/tools/generator/components/src/credential-generator.component.html @@ -6,9 +6,11 @@ (selectedChange)="onRootChanged({ nav: $event })" attr.aria-label="{{ 'type' | i18n }}" > - <bit-toggle *ngFor="let option of rootOptions$ | async" [value]="option.value"> - {{ option.label }} - </bit-toggle> + @for (option of rootOptions$ | async; track option) { + <bit-toggle [value]="option.value"> + {{ option.label }} + </bit-toggle> + } </bit-toggle-group> <nudge-generator-spotlight></nudge-generator-spotlight> @@ -40,69 +42,80 @@ ></button> </div> </bit-card> -<tools-password-settings - class="tw-mt-6" - *ngIf="(showAlgorithm$ | async)?.id === Algorithm.password" - [account]="account$ | async" - (onUpdated)="generate('password settings')" -/> -<tools-passphrase-settings - class="tw-mt-6" - *ngIf="(showAlgorithm$ | async)?.id === Algorithm.passphrase" - [account]="account$ | async" - (onUpdated)="generate('passphrase settings')" -/> -<bit-section *ngIf="(category$ | async) !== 'password'"> - <bit-section-header> - <h2 bitTypography="h6">{{ "options" | i18n }}</h2> - </bit-section-header> - <div class="tw-mb-4"> - <bit-card> - <form [formGroup]="username" class="tw-container"> - <bit-form-field> - <bit-label>{{ "type" | i18n }}</bit-label> - <bit-select - [items]="usernameOptions$ | async" - formControlName="nav" - data-testid="username-type" - > - </bit-select> - <bit-hint *ngIf="!!(credentialTypeHint$ | async)">{{ - credentialTypeHint$ | async - }}</bit-hint> - </bit-form-field> - </form> - <form *ngIf="showForwarder$ | async" [formGroup]="forwarder" class="tw-container"> - <bit-form-field> - <bit-label>{{ "service" | i18n }}</bit-label> - <bit-select - [items]="forwarderOptions$ | async" - formControlName="nav" - data-testid="email-forwarding-service" - > - </bit-select> - </bit-form-field> - </form> - <tools-catchall-settings - *ngIf="(showAlgorithm$ | async)?.id === Algorithm.catchall" - [account]="account$ | async" - (onUpdated)="generate('catchall settings')" - /> - <tools-forwarder-settings - *ngIf="!!(forwarderId$ | async)" - [account]="account$ | async" - [forwarder]="forwarderId$ | async" - /> - <tools-subaddress-settings - *ngIf="(showAlgorithm$ | async)?.id === Algorithm.plusAddress" - [account]="account$ | async" - (onUpdated)="generate('subaddress settings')" - /> - <tools-username-settings - *ngIf="(showAlgorithm$ | async)?.id === Algorithm.username" - [account]="account$ | async" - (onUpdated)="generate('username settings')" - /> - </bit-card> - </div> -</bit-section> +@let showAlgorithm = showAlgorithm$ | async; +@let account = account$ | async; +@switch (showAlgorithm?.id) { + @case (Algorithm.password) { + <tools-password-settings + class="tw-mt-6" + [account]="account" + (onUpdated)="generate('password settings')" + /> + } + @case (Algorithm.passphrase) { + <tools-passphrase-settings + class="tw-mt-6" + [account]="account" + (onUpdated)="generate('passphrase settings')" + /> + } +} +@if ((category$ | async) !== "password") { + <bit-section> + <bit-section-header> + <h2 bitTypography="h6">{{ "options" | i18n }}</h2> + </bit-section-header> + <div class="tw-mb-4"> + <bit-card> + <form [formGroup]="username" class="tw-container"> + <bit-form-field> + <bit-label>{{ "type" | i18n }}</bit-label> + <bit-select + [items]="usernameOptions$ | async" + formControlName="nav" + data-testid="username-type" + > + </bit-select> + @if (credentialTypeHint$ | async) { + <bit-hint>{{ credentialTypeHint$ | async }}</bit-hint> + } + </bit-form-field> + </form> + @if (showForwarder$ | async) { + <form [formGroup]="forwarder" class="tw-container"> + <bit-form-field> + <bit-label>{{ "service" | i18n }}</bit-label> + <bit-select + [items]="forwarderOptions$ | async" + formControlName="nav" + data-testid="email-forwarding-service" + > + </bit-select> + </bit-form-field> + </form> + } + @if (showAlgorithm?.id === Algorithm.catchall) { + <tools-catchall-settings + [account]="account" + (onUpdated)="generate('catchall settings')" + /> + } + @if (forwarderId$ | async; as forwarderId) { + <tools-forwarder-settings [account]="account" [forwarder]="forwarderId" /> + } + @if (showAlgorithm?.id === Algorithm.plusAddress) { + <tools-subaddress-settings + [account]="account" + (onUpdated)="generate('subaddress settings')" + /> + } + @if (showAlgorithm?.id === Algorithm.username) { + <tools-username-settings + [account]="account" + (onUpdated)="generate('username settings')" + /> + } + </bit-card> + </div> + </bit-section> +} diff --git a/libs/tools/generator/components/src/credential-generator.component.ts b/libs/tools/generator/components/src/credential-generator.component.ts index f48180d93bd..af6791f673b 100644 --- a/libs/tools/generator/components/src/credential-generator.component.ts +++ b/libs/tools/generator/components/src/credential-generator.component.ts @@ -1,4 +1,5 @@ import { LiveAnnouncer } from "@angular/cdk/a11y"; +import { AsyncPipe } from "@angular/common"; import { Component, EventEmitter, @@ -10,7 +11,7 @@ import { Output, SimpleChanges, } from "@angular/core"; -import { FormBuilder } from "@angular/forms"; +import { FormBuilder, ReactiveFormsModule } from "@angular/forms"; import { BehaviorSubject, catchError, @@ -27,6 +28,7 @@ import { withLatestFrom, } from "rxjs"; +import { JslibModule } from "@bitwarden/angular/jslib.module"; import { Account, AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; @@ -37,7 +39,23 @@ import { ifEnabledSemanticLoggerProvider, } from "@bitwarden/common/tools/log"; import { UserId } from "@bitwarden/common/types/guid"; -import { ToastService, Option } from "@bitwarden/components"; +import { + ToastService, + Option, + BaseCardDirective, + CardComponent, + ColorPasswordComponent, + AriaDisableDirective, + TooltipDirective, + BitIconButtonComponent, + CopyClickDirective, + SectionComponent, + SectionHeaderComponent, + ToggleGroupModule, + TypographyModule, + FormFieldModule, + SelectModule, +} from "@bitwarden/components"; import { CredentialType, CredentialGeneratorService, @@ -55,7 +73,15 @@ import { Type, } from "@bitwarden/generator-core"; import { GeneratorHistoryService } from "@bitwarden/generator-history"; +import { I18nPipe } from "@bitwarden/ui-common"; +import { CatchallSettingsComponent } from "./catchall-settings.component"; +import { ForwarderSettingsComponent } from "./forwarder-settings.component"; +import { NudgeGeneratorSpotlightComponent } from "./nudge-generator-spotlight.component"; +import { PassphraseSettingsComponent } from "./passphrase-settings.component"; +import { PasswordSettingsComponent } from "./password-settings.component"; +import { SubaddressSettingsComponent } from "./subaddress-settings.component"; +import { UsernameSettingsComponent } from "./username-settings.component"; import { translate } from "./util"; // constants used to identify navigation selections that are not @@ -69,7 +95,32 @@ const NONE_SELECTED = "none"; @Component({ selector: "tools-credential-generator", templateUrl: "credential-generator.component.html", - standalone: false, + imports: [ + ToggleGroupModule, + NudgeGeneratorSpotlightComponent, + BaseCardDirective, + CardComponent, + ColorPasswordComponent, + AriaDisableDirective, + TooltipDirective, + BitIconButtonComponent, + CopyClickDirective, + PasswordSettingsComponent, + PassphraseSettingsComponent, + SectionComponent, + SectionHeaderComponent, + TypographyModule, + ReactiveFormsModule, + FormFieldModule, + SelectModule, + CatchallSettingsComponent, + ForwarderSettingsComponent, + SubaddressSettingsComponent, + UsernameSettingsComponent, + AsyncPipe, + JslibModule, + I18nPipe, + ], }) export class CredentialGeneratorComponent implements OnInit, OnChanges, OnDestroy { private readonly destroyed = new Subject<void>(); diff --git a/libs/tools/generator/components/src/forwarder-settings.component.html b/libs/tools/generator/components/src/forwarder-settings.component.html index 8ad6eab1acf..0d213179c2b 100644 --- a/libs/tools/generator/components/src/forwarder-settings.component.html +++ b/libs/tools/generator/components/src/forwarder-settings.component.html @@ -1,28 +1,34 @@ <form [formGroup]="settings" class="tw-container"> - <bit-form-field *ngIf="displayDomain"> - <bit-label>{{ "forwarderDomainName" | i18n }}</bit-label> - <input - bitInput - formControlName="domain" - type="text" - placeholder="example.com" - (change)="save('domain')" - /> - <bit-hint>{{ "forwarderDomainNameHint" | i18n }}</bit-hint> - </bit-form-field> - <bit-form-field *ngIf="displayToken"> - <bit-label>{{ "apiKey" | i18n }}</bit-label> - <input bitInput formControlName="token" type="password" (change)="save('password')" /> - <button - type="button" - bitIconButton - bitSuffix - bitPasswordInputToggle - (change)="save('token')" - ></button> - </bit-form-field> - <bit-form-field *ngIf="displayBaseUrl" disableMargin> - <bit-label>{{ "selfHostBaseUrl" | i18n }}</bit-label> - <input bitInput formControlName="baseUrl" type="text" (change)="save('baseUrl')" /> - </bit-form-field> + @if (displayDomain) { + <bit-form-field> + <bit-label>{{ "forwarderDomainName" | i18n }}</bit-label> + <input + bitInput + formControlName="domain" + type="text" + placeholder="example.com" + (change)="save('domain')" + /> + <bit-hint>{{ "forwarderDomainNameHint" | i18n }}</bit-hint> + </bit-form-field> + } + @if (displayToken) { + <bit-form-field> + <bit-label>{{ "apiKey" | i18n }}</bit-label> + <input bitInput formControlName="token" type="password" (change)="save('password')" /> + <button + type="button" + bitIconButton + bitSuffix + bitPasswordInputToggle + (change)="save('token')" + ></button> + </bit-form-field> + } + @if (displayBaseUrl) { + <bit-form-field disableMargin> + <bit-label>{{ "selfHostBaseUrl" | i18n }}</bit-label> + <input bitInput formControlName="baseUrl" type="text" (change)="save('baseUrl')" /> + </bit-form-field> + } </form> diff --git a/libs/tools/generator/components/src/forwarder-settings.component.ts b/libs/tools/generator/components/src/forwarder-settings.component.ts index 32fa3effdf6..0f1c863b2f6 100644 --- a/libs/tools/generator/components/src/forwarder-settings.component.ts +++ b/libs/tools/generator/components/src/forwarder-settings.component.ts @@ -8,16 +8,24 @@ import { Output, SimpleChanges, } from "@angular/core"; -import { FormBuilder } from "@angular/forms"; +import { FormBuilder, ReactiveFormsModule } from "@angular/forms"; import { map, ReplaySubject, skip, Subject, switchAll, takeUntil, withLatestFrom } from "rxjs"; +import { JslibModule } from "@bitwarden/angular/jslib.module"; import { Account } from "@bitwarden/common/auth/abstractions/account.service"; import { VendorId } from "@bitwarden/common/tools/extension"; +import { + FormFieldModule, + AriaDisableDirective, + TooltipDirective, + BitIconButtonComponent, +} from "@bitwarden/components"; import { CredentialGeneratorService, ForwarderOptions, GeneratorMetadata, } from "@bitwarden/generator-core"; +import { I18nPipe } from "@bitwarden/ui-common"; const Controls = Object.freeze({ domain: "domain", @@ -31,7 +39,15 @@ const Controls = Object.freeze({ @Component({ selector: "tools-forwarder-settings", templateUrl: "forwarder-settings.component.html", - standalone: false, + imports: [ + ReactiveFormsModule, + FormFieldModule, + AriaDisableDirective, + TooltipDirective, + BitIconButtonComponent, + JslibModule, + I18nPipe, + ], }) export class ForwarderSettingsComponent implements OnInit, OnChanges, OnDestroy { /** Instantiates the component diff --git a/libs/tools/generator/components/src/generator.module.ts b/libs/tools/generator/components/src/generator.module.ts index d710f368106..795a5ffe972 100644 --- a/libs/tools/generator/components/src/generator.module.ts +++ b/libs/tools/generator/components/src/generator.module.ts @@ -1,67 +1,13 @@ -import { CommonModule } from "@angular/common"; import { NgModule } from "@angular/core"; -import { ReactiveFormsModule } from "@angular/forms"; -import { JslibModule } from "@bitwarden/angular/jslib.module"; -import { - CardComponent, - ColorPasswordModule, - CheckboxModule, - FormFieldModule, - IconButtonModule, - InputModule, - ItemModule, - SectionComponent, - SectionHeaderComponent, - SelectModule, - ToggleGroupModule, - TypographyModule, -} from "@bitwarden/components"; - -import { CatchallSettingsComponent } from "./catchall-settings.component"; import { CredentialGeneratorComponent } from "./credential-generator.component"; -import { ForwarderSettingsComponent } from "./forwarder-settings.component"; -import { GeneratorServicesModule } from "./generator-services.module"; -import { NudgeGeneratorSpotlightComponent } from "./nudge-generator-spotlight.component"; -import { PassphraseSettingsComponent } from "./passphrase-settings.component"; import { PasswordGeneratorComponent } from "./password-generator.component"; -import { PasswordSettingsComponent } from "./password-settings.component"; -import { SubaddressSettingsComponent } from "./subaddress-settings.component"; import { UsernameGeneratorComponent } from "./username-generator.component"; -import { UsernameSettingsComponent } from "./username-settings.component"; /** Shared module containing generator component dependencies */ +/** @deprecated Use individual components instead. */ @NgModule({ - imports: [ - CardComponent, - ColorPasswordModule, - CheckboxModule, - CommonModule, - FormFieldModule, - GeneratorServicesModule, - IconButtonModule, - InputModule, - ItemModule, - JslibModule, - ReactiveFormsModule, - SectionComponent, - SectionHeaderComponent, - SelectModule, - ToggleGroupModule, - TypographyModule, - NudgeGeneratorSpotlightComponent, - ], - declarations: [ - CatchallSettingsComponent, - CredentialGeneratorComponent, - ForwarderSettingsComponent, - SubaddressSettingsComponent, - PasswordGeneratorComponent, - PassphraseSettingsComponent, - PasswordSettingsComponent, - UsernameGeneratorComponent, - UsernameSettingsComponent, - ], + imports: [CredentialGeneratorComponent, PasswordGeneratorComponent, UsernameGeneratorComponent], exports: [CredentialGeneratorComponent, PasswordGeneratorComponent, UsernameGeneratorComponent], }) export class GeneratorModule { diff --git a/libs/tools/generator/components/src/index.ts b/libs/tools/generator/components/src/index.ts index 4ec32032de0..bfb19b07b05 100644 --- a/libs/tools/generator/components/src/index.ts +++ b/libs/tools/generator/components/src/index.ts @@ -1,5 +1,15 @@ +/** + * This file contains the public interface for the generator components library. + * + * Be mindful of what you export here, as those components should be considered stable + * and part of the public API contract. + */ + +export { CredentialGeneratorComponent } from "./credential-generator.component"; export { CredentialGeneratorHistoryComponent } from "./credential-generator-history.component"; export { CredentialGeneratorHistoryDialogComponent } from "./credential-generator-history-dialog.component"; export { EmptyCredentialHistoryComponent } from "./empty-credential-history.component"; export { GeneratorModule } from "./generator.module"; export { GeneratorServicesModule, SYSTEM_SERVICE_PROVIDER } from "./generator-services.module"; +export { PasswordGeneratorComponent } from "./password-generator.component"; +export { UsernameGeneratorComponent } from "./username-generator.component"; diff --git a/libs/tools/generator/components/src/nudge-generator-spotlight.component.html b/libs/tools/generator/components/src/nudge-generator-spotlight.component.html index 581825936be..74fa5eb484c 100644 --- a/libs/tools/generator/components/src/nudge-generator-spotlight.component.html +++ b/libs/tools/generator/components/src/nudge-generator-spotlight.component.html @@ -1,16 +1,18 @@ -<div class="tw-mb-4" *ngIf="showGeneratorSpotlight$ | async"> - <bit-spotlight - [title]="'generatorNudgeTitle' | i18n" - (onDismiss)="dismissGeneratorSpotlight(NudgeType.GeneratorNudgeStatus)" - > - <p class="tw-text-main tw-mb-0" bitTypography="body2"> - <span class="tw-sr-only"> - {{ "generatorNudgeBodyAria" | i18n }} - </span> - <span aria-hidden="true"> - {{ "generatorNudgeBodyOne" | i18n }} <i class="bwi bwi-generate"></i> - {{ "generatorNudgeBodyTwo" | i18n }} - </span> - </p> - </bit-spotlight> -</div> +@if (showGeneratorSpotlight$ | async) { + <div class="tw-mb-4"> + <bit-spotlight + [title]="'generatorNudgeTitle' | i18n" + (onDismiss)="dismissGeneratorSpotlight(NudgeType.GeneratorNudgeStatus)" + > + <p class="tw-text-main tw-mb-0" bitTypography="body2"> + <span class="tw-sr-only"> + {{ "generatorNudgeBodyAria" | i18n }} + </span> + <span aria-hidden="true"> + {{ "generatorNudgeBodyOne" | i18n }} <i class="bwi bwi-generate"></i> + {{ "generatorNudgeBodyTwo" | i18n }} + </span> + </p> + </bit-spotlight> + </div> +} diff --git a/libs/tools/generator/components/src/passphrase-settings.component.html b/libs/tools/generator/components/src/passphrase-settings.component.html index 0af27f7fdcb..d40e91f7013 100644 --- a/libs/tools/generator/components/src/passphrase-settings.component.html +++ b/libs/tools/generator/components/src/passphrase-settings.component.html @@ -1,7 +1,9 @@ <bit-section [disableMargin]="disableMargin"> - <bit-section-header *ngIf="showHeader"> - <h6 bitTypography="h6">{{ "options" | i18n }}</h6> - </bit-section-header> + @if (showHeader) { + <bit-section-header> + <h6 bitTypography="h6">{{ "options" | i18n }}</h6> + </bit-section-header> + } <form [formGroup]="settings" class="tw-container"> <div class="tw-mb-4"> <bit-card> @@ -51,7 +53,9 @@ /> <bit-label>{{ "includeNumber" | i18n }}</bit-label> </bit-form-control> - <p *ngIf="policyInEffect" bitTypography="helper">{{ "generatorPolicyInEffect" | i18n }}</p> + @if (policyInEffect) { + <p bitTypography="helper">{{ "generatorPolicyInEffect" | i18n }}</p> + } </bit-card> </div> </form> diff --git a/libs/tools/generator/components/src/passphrase-settings.component.ts b/libs/tools/generator/components/src/passphrase-settings.component.ts index 7e4ae8b5af9..6ab3020c688 100644 --- a/libs/tools/generator/components/src/passphrase-settings.component.ts +++ b/libs/tools/generator/components/src/passphrase-settings.component.ts @@ -1,4 +1,5 @@ import { coerceBooleanProperty } from "@angular/cdk/coercion"; +import { AsyncPipe } from "@angular/common"; import { OnInit, Input, @@ -9,9 +10,10 @@ import { SimpleChanges, OnChanges, } from "@angular/core"; -import { FormBuilder } from "@angular/forms"; +import { FormBuilder, ReactiveFormsModule } from "@angular/forms"; import { skip, takeUntil, Subject, map, withLatestFrom, ReplaySubject, tap } from "rxjs"; +import { JslibModule } from "@bitwarden/angular/jslib.module"; import { Account } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; @@ -20,11 +22,21 @@ import { disabledSemanticLoggerProvider, ifEnabledSemanticLoggerProvider, } from "@bitwarden/common/tools/log"; +import { + SectionComponent, + SectionHeaderComponent, + BaseCardDirective, + CardComponent, + TypographyModule, + FormFieldModule, + CheckboxModule, +} from "@bitwarden/components"; import { CredentialGeneratorService, PassphraseGenerationOptions, BuiltIn, } from "@bitwarden/generator-core"; +import { I18nPipe } from "@bitwarden/ui-common"; const Controls = Object.freeze({ numWords: "numWords", @@ -39,7 +51,19 @@ const Controls = Object.freeze({ @Component({ selector: "tools-passphrase-settings", templateUrl: "passphrase-settings.component.html", - standalone: false, + imports: [ + SectionComponent, + SectionHeaderComponent, + TypographyModule, + ReactiveFormsModule, + BaseCardDirective, + CardComponent, + FormFieldModule, + CheckboxModule, + AsyncPipe, + JslibModule, + I18nPipe, + ], }) export class PassphraseSettingsComponent implements OnInit, OnChanges, OnDestroy { /** Instantiates the component diff --git a/libs/tools/generator/components/src/password-generator.component.html b/libs/tools/generator/components/src/password-generator.component.html index 9995613685b..0088628dff0 100644 --- a/libs/tools/generator/components/src/password-generator.component.html +++ b/libs/tools/generator/components/src/password-generator.component.html @@ -1,15 +1,18 @@ -<bit-toggle-group - fullWidth - class="tw-mb-4" - [selected]="credentialType$ | async" - (selectedChange)="onCredentialTypeChanged($event)" - *ngIf="showCredentialTypes$ | async" - attr.aria-label="{{ 'type' | i18n }}" -> - <bit-toggle *ngFor="let option of passwordOptions$ | async" [value]="option.value"> - {{ option.label }} - </bit-toggle> -</bit-toggle-group> +@if (showCredentialTypes$ | async) { + <bit-toggle-group + fullWidth + class="tw-mb-4" + [selected]="credentialType$ | async" + (selectedChange)="onCredentialTypeChanged($event)" + attr.aria-label="{{ 'type' | i18n }}" + > + @for (option of passwordOptions$ | async; track option) { + <bit-toggle [value]="option.value"> + {{ option.label }} + </bit-toggle> + } + </bit-toggle-group> +} <bit-card class="tw-flex tw-justify-between tw-mb-4"> <div class="tw-grow tw-flex tw-items-center tw-min-w-0"> <bit-color-password class="tw-font-mono" [password]="value$ | async"></bit-color-password> @@ -37,17 +40,19 @@ ></button> </div> </bit-card> -<tools-password-settings - class="tw-mt-6" - *ngIf="(algorithm$ | async)?.id === Algorithm.password" - [account]="account$ | async" - [disableMargin]="disableMargin" - (onUpdated)="generate('password settings')" -/> -<tools-passphrase-settings - class="tw-mt-6" - *ngIf="(algorithm$ | async)?.id === Algorithm.passphrase" - [account]="account$ | async" - (onUpdated)="generate('passphrase settings')" - [disableMargin]="disableMargin" -/> +@if ((algorithm$ | async)?.id === Algorithm.password) { + <tools-password-settings + class="tw-mt-6" + [account]="account$ | async" + [disableMargin]="disableMargin" + (onUpdated)="generate('password settings')" + /> +} +@if ((algorithm$ | async)?.id === Algorithm.passphrase) { + <tools-passphrase-settings + class="tw-mt-6" + [account]="account$ | async" + (onUpdated)="generate('passphrase settings')" + [disableMargin]="disableMargin" + /> +} diff --git a/libs/tools/generator/components/src/password-generator.component.ts b/libs/tools/generator/components/src/password-generator.component.ts index 2b1d5044651..52cf902293d 100644 --- a/libs/tools/generator/components/src/password-generator.component.ts +++ b/libs/tools/generator/components/src/password-generator.component.ts @@ -1,5 +1,6 @@ import { LiveAnnouncer } from "@angular/cdk/a11y"; import { coerceBooleanProperty } from "@angular/cdk/coercion"; +import { AsyncPipe } from "@angular/common"; import { Component, EventEmitter, @@ -24,6 +25,7 @@ import { withLatestFrom, } from "rxjs"; +import { JslibModule } from "@bitwarden/angular/jslib.module"; import { Account, AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; @@ -33,7 +35,18 @@ import { ifEnabledSemanticLoggerProvider, } from "@bitwarden/common/tools/log"; import { UserId } from "@bitwarden/common/types/guid"; -import { ToastService, Option } from "@bitwarden/components"; +import { + ToastService, + Option, + BaseCardDirective, + CardComponent, + ColorPasswordComponent, + AriaDisableDirective, + TooltipDirective, + BitIconButtonComponent, + CopyClickDirective, + ToggleGroupModule, +} from "@bitwarden/components"; import { CredentialGeneratorService, GeneratedCredential, @@ -49,7 +62,10 @@ import { Profile, } from "@bitwarden/generator-core"; import { GeneratorHistoryService } from "@bitwarden/generator-history"; +import { I18nPipe } from "@bitwarden/ui-common"; +import { PassphraseSettingsComponent } from "./passphrase-settings.component"; +import { PasswordSettingsComponent } from "./password-settings.component"; import { toAlgorithmInfo, translate } from "./util"; /** Options group for passwords */ @@ -58,7 +74,21 @@ import { toAlgorithmInfo, translate } from "./util"; @Component({ selector: "tools-password-generator", templateUrl: "password-generator.component.html", - standalone: false, + imports: [ + ToggleGroupModule, + BaseCardDirective, + CardComponent, + ColorPasswordComponent, + AriaDisableDirective, + TooltipDirective, + BitIconButtonComponent, + CopyClickDirective, + PasswordSettingsComponent, + PassphraseSettingsComponent, + AsyncPipe, + JslibModule, + I18nPipe, + ], }) export class PasswordGeneratorComponent implements OnInit, OnChanges, OnDestroy { constructor( diff --git a/libs/tools/generator/components/src/password-settings.component.html b/libs/tools/generator/components/src/password-settings.component.html index 13bf6822462..b435a13fe4c 100644 --- a/libs/tools/generator/components/src/password-settings.component.html +++ b/libs/tools/generator/components/src/password-settings.component.html @@ -1,7 +1,9 @@ <bit-section [disableMargin]="disableMargin"> - <bit-section-header *ngIf="showHeader"> - <h2 bitTypography="h6">{{ "options" | i18n }}</h2> - </bit-section-header> + @if (showHeader) { + <bit-section-header> + <h2 bitTypography="h6">{{ "options" | i18n }}</h2> + </bit-section-header> + } <form [formGroup]="settings" class="tw-container"> <div class="tw-mb-4"> <bit-card> @@ -62,9 +64,9 @@ (change)="save('special')" /> <!-- hard-coded the special characters string because `$` indicates an i18n interpolation, - and is handled inconsistently across browsers. Angular template syntax is used to - ensure special characters are entity-encoded. - --> + and is handled inconsistently across browsers. Angular template syntax is used to + ensure special characters are entity-encoded. + --> <bit-label>{{ "!@#$%^&*" }}</bit-label> </bit-form-control> </div> @@ -97,7 +99,9 @@ /> <bit-label>{{ "avoidAmbiguous" | i18n }}</bit-label> </bit-form-control> - <p *ngIf="policyInEffect" bitTypography="helper">{{ "generatorPolicyInEffect" | i18n }}</p> + @if (policyInEffect) { + <p bitTypography="helper">{{ "generatorPolicyInEffect" | i18n }}</p> + } </bit-card> </div> </form> diff --git a/libs/tools/generator/components/src/password-settings.component.ts b/libs/tools/generator/components/src/password-settings.component.ts index 5d5980edf1b..9445e655310 100644 --- a/libs/tools/generator/components/src/password-settings.component.ts +++ b/libs/tools/generator/components/src/password-settings.component.ts @@ -1,4 +1,5 @@ import { coerceBooleanProperty } from "@angular/cdk/coercion"; +import { AsyncPipe } from "@angular/common"; import { OnInit, Input, @@ -9,16 +10,27 @@ import { SimpleChanges, OnChanges, } from "@angular/core"; -import { FormBuilder } from "@angular/forms"; +import { FormBuilder, ReactiveFormsModule } from "@angular/forms"; import { takeUntil, Subject, map, filter, tap, skip, ReplaySubject, withLatestFrom } from "rxjs"; +import { JslibModule } from "@bitwarden/angular/jslib.module"; import { Account } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { + SectionComponent, + SectionHeaderComponent, + BaseCardDirective, + CardComponent, + FormFieldModule, + TypographyModule, + CheckboxModule, +} from "@bitwarden/components"; import { CredentialGeneratorService, PasswordGenerationOptions, BuiltIn, } from "@bitwarden/generator-core"; +import { I18nPipe } from "@bitwarden/ui-common"; import { hasRangeOfValues } from "./util"; @@ -39,7 +51,19 @@ const Controls = Object.freeze({ @Component({ selector: "tools-password-settings", templateUrl: "password-settings.component.html", - standalone: false, + imports: [ + SectionComponent, + SectionHeaderComponent, + TypographyModule, + ReactiveFormsModule, + BaseCardDirective, + CardComponent, + FormFieldModule, + CheckboxModule, + AsyncPipe, + JslibModule, + I18nPipe, + ], }) export class PasswordSettingsComponent implements OnInit, OnChanges, OnDestroy { /** Instantiates the component diff --git a/libs/tools/generator/components/src/subaddress-settings.component.ts b/libs/tools/generator/components/src/subaddress-settings.component.ts index f9cef2341ba..068511e29e5 100644 --- a/libs/tools/generator/components/src/subaddress-settings.component.ts +++ b/libs/tools/generator/components/src/subaddress-settings.component.ts @@ -8,15 +8,18 @@ import { Output, SimpleChanges, } from "@angular/core"; -import { FormBuilder } from "@angular/forms"; +import { FormBuilder, ReactiveFormsModule } from "@angular/forms"; import { map, ReplaySubject, skip, Subject, takeUntil, withLatestFrom } from "rxjs"; +import { JslibModule } from "@bitwarden/angular/jslib.module"; import { Account } from "@bitwarden/common/auth/abstractions/account.service"; +import { FormFieldModule } from "@bitwarden/components"; import { CredentialGeneratorService, BuiltIn, SubaddressGenerationOptions, } from "@bitwarden/generator-core"; +import { I18nPipe } from "@bitwarden/ui-common"; /** Options group for plus-addressed emails */ // FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush @@ -24,7 +27,7 @@ import { @Component({ selector: "tools-subaddress-settings", templateUrl: "subaddress-settings.component.html", - standalone: false, + imports: [ReactiveFormsModule, FormFieldModule, JslibModule, I18nPipe], }) export class SubaddressSettingsComponent implements OnInit, OnChanges, OnDestroy { /** Instantiates the component diff --git a/libs/tools/generator/components/src/username-generator.component.html b/libs/tools/generator/components/src/username-generator.component.html index 0f3182118a1..9f74f5d1fea 100644 --- a/libs/tools/generator/components/src/username-generator.component.html +++ b/libs/tools/generator/components/src/username-generator.component.html @@ -42,42 +42,41 @@ data-testid="username-type" > </bit-select> - <bit-hint *ngIf="!!(credentialTypeHint$ | async)">{{ - credentialTypeHint$ | async - }}</bit-hint> + @if (credentialTypeHint$ | async) { + <bit-hint>{{ credentialTypeHint$ | async }}</bit-hint> + } </bit-form-field> </form> - <form *ngIf="showForwarder$ | async" [formGroup]="forwarder" class="tw-container"> - <bit-form-field> - <bit-label>{{ "service" | i18n }}</bit-label> - <bit-select - [items]="forwarderOptions$ | async" - formControlName="nav" - data-testid="email-forwarding-service" - > - </bit-select> - </bit-form-field> - </form> - <tools-catchall-settings - *ngIf="(showAlgorithm$ | async)?.id === Algorithm.catchall" - [account]="account$ | async" - (onUpdated)="generate('catchall settings')" - /> - <tools-forwarder-settings - *ngIf="!!(forwarderId$ | async)" - [forwarder]="forwarderId$ | async" - [account]="account$ | async" - /> - <tools-subaddress-settings - *ngIf="(showAlgorithm$ | async)?.id === Algorithm.plusAddress" - [account]="account$ | async" - (onUpdated)="generate('subaddress settings')" - /> - <tools-username-settings - *ngIf="(showAlgorithm$ | async)?.id === Algorithm.username" - [account]="account$ | async" - (onUpdated)="generate('username settings')" - /> + @if (showForwarder$ | async) { + <form [formGroup]="forwarder" class="tw-container"> + <bit-form-field> + <bit-label>{{ "service" | i18n }}</bit-label> + <bit-select + [items]="forwarderOptions$ | async" + formControlName="nav" + data-testid="email-forwarding-service" + > + </bit-select> + </bit-form-field> + </form> + } + @let showAlgorithm = showAlgorithm$ | async; + @let account = account$ | async; + @if (showAlgorithm?.id === Algorithm.catchall) { + <tools-catchall-settings [account]="account" (onUpdated)="generate('catchall settings')" /> + } + @if (forwarderId$ | async; as forwarderId) { + <tools-forwarder-settings [forwarder]="forwarderId" [account]="account" /> + } + @if (showAlgorithm?.id === Algorithm.plusAddress) { + <tools-subaddress-settings + [account]="account" + (onUpdated)="generate('subaddress settings')" + /> + } + @if (showAlgorithm?.id === Algorithm.username) { + <tools-username-settings [account]="account" (onUpdated)="generate('username settings')" /> + } </bit-card> </div> </bit-section> diff --git a/libs/tools/generator/components/src/username-generator.component.ts b/libs/tools/generator/components/src/username-generator.component.ts index dc4b8d26e7e..faf9c4dde2c 100644 --- a/libs/tools/generator/components/src/username-generator.component.ts +++ b/libs/tools/generator/components/src/username-generator.component.ts @@ -1,5 +1,6 @@ import { LiveAnnouncer } from "@angular/cdk/a11y"; import { coerceBooleanProperty } from "@angular/cdk/coercion"; +import { NgClass, AsyncPipe } from "@angular/common"; import { Component, EventEmitter, @@ -11,7 +12,7 @@ import { Output, SimpleChanges, } from "@angular/core"; -import { FormBuilder } from "@angular/forms"; +import { FormBuilder, ReactiveFormsModule } from "@angular/forms"; import { BehaviorSubject, catchError, @@ -28,6 +29,7 @@ import { withLatestFrom, } from "rxjs"; +import { JslibModule } from "@bitwarden/angular/jslib.module"; import { Account, AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; @@ -38,7 +40,22 @@ import { ifEnabledSemanticLoggerProvider, } from "@bitwarden/common/tools/log"; import { UserId } from "@bitwarden/common/types/guid"; -import { ToastService, Option } from "@bitwarden/components"; +import { + ToastService, + Option, + AriaDisableDirective, + BaseCardDirective, + CardComponent, + ColorPasswordComponent, + CopyClickDirective, + BitIconButtonComponent, + TooltipDirective, + SectionComponent, + SectionHeaderComponent, + SelectComponent, + TypographyModule, + FormFieldModule, +} from "@bitwarden/components"; import { AlgorithmInfo, CredentialGeneratorService, @@ -55,7 +72,12 @@ import { Algorithm, } from "@bitwarden/generator-core"; import { GeneratorHistoryService } from "@bitwarden/generator-history"; +import { I18nPipe } from "@bitwarden/ui-common"; +import { CatchallSettingsComponent } from "./catchall-settings.component"; +import { ForwarderSettingsComponent } from "./forwarder-settings.component"; +import { SubaddressSettingsComponent } from "./subaddress-settings.component"; +import { UsernameSettingsComponent } from "./username-settings.component"; import { toAlgorithmInfo, translate } from "./util"; // constants used to identify navigation selections that are not @@ -69,7 +91,29 @@ const NONE_SELECTED = "none"; @Component({ selector: "tools-username-generator", templateUrl: "username-generator.component.html", - standalone: false, + imports: [ + BaseCardDirective, + CardComponent, + ColorPasswordComponent, + AriaDisableDirective, + TooltipDirective, + BitIconButtonComponent, + CopyClickDirective, + SectionComponent, + SectionHeaderComponent, + TypographyModule, + NgClass, + ReactiveFormsModule, + SelectComponent, + FormFieldModule, + CatchallSettingsComponent, + ForwarderSettingsComponent, + SubaddressSettingsComponent, + UsernameSettingsComponent, + AsyncPipe, + JslibModule, + I18nPipe, + ], }) export class UsernameGeneratorComponent implements OnInit, OnChanges, OnDestroy { /** Instantiates the username generator diff --git a/libs/tools/generator/components/src/username-settings.component.ts b/libs/tools/generator/components/src/username-settings.component.ts index fae1a3aca04..f4ccf362b69 100644 --- a/libs/tools/generator/components/src/username-settings.component.ts +++ b/libs/tools/generator/components/src/username-settings.component.ts @@ -8,15 +8,18 @@ import { Output, SimpleChanges, } from "@angular/core"; -import { FormBuilder } from "@angular/forms"; +import { FormBuilder, ReactiveFormsModule } from "@angular/forms"; import { map, ReplaySubject, skip, Subject, takeUntil, withLatestFrom } from "rxjs"; +import { JslibModule } from "@bitwarden/angular/jslib.module"; import { Account } from "@bitwarden/common/auth/abstractions/account.service"; +import { FormFieldModule, CheckboxModule } from "@bitwarden/components"; import { CredentialGeneratorService, EffUsernameGenerationOptions, BuiltIn, } from "@bitwarden/generator-core"; +import { I18nPipe } from "@bitwarden/ui-common"; /** Options group for usernames */ // FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush @@ -24,7 +27,7 @@ import { @Component({ selector: "tools-username-settings", templateUrl: "username-settings.component.html", - standalone: false, + imports: [ReactiveFormsModule, FormFieldModule, CheckboxModule, JslibModule, I18nPipe], }) export class UsernameSettingsComponent implements OnInit, OnChanges, OnDestroy { /** Instantiates the component From 404e07b6bdfae803c0ecc073f318182d99270381 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann <mail@quexten.com> Date: Thu, 11 Dec 2025 12:47:00 +0100 Subject: [PATCH 046/188] [PM-27225] Fix nothing showing when biometrics unavailable (#17209) * Fix nothing showing when biometrics unavailable * Cleanup * Switch to tooltip * Fix type error * Fix type check * Fix includes * Fix types * Fix tests * Add missing return * Add DesktopDisconnected to canUseBiometrics * Apply suggestions * Move comment * Cleanup * Fix typing for null value * Add tests * Fix QA bugs --- .../foreground-browser-biometrics.ts | 6 +- .../src/app/services/services.module.ts | 5 +- .../biometrics/renderer-biometrics.service.ts | 10 + .../src/lock/components/lock.component.html | 1 + .../lock/components/lock.component.spec.ts | 249 +++++++++++++++++- .../src/lock/components/lock.component.ts | 40 ++- 6 files changed, 305 insertions(+), 6 deletions(-) diff --git a/apps/browser/src/key-management/biometrics/foreground-browser-biometrics.ts b/apps/browser/src/key-management/biometrics/foreground-browser-biometrics.ts index b6e84fee31a..d803a457a81 100644 --- a/apps/browser/src/key-management/biometrics/foreground-browser-biometrics.ts +++ b/apps/browser/src/key-management/biometrics/foreground-browser-biometrics.ts @@ -48,7 +48,11 @@ export class ForegroundBrowserBiometricsService extends BiometricsService { result: BiometricsStatus; error: string; }>(BiometricsCommands.GetBiometricsStatusForUser, { userId: id }); - return response.result; + if (response != null) { + return response.result; + } else { + return BiometricsStatus.DesktopDisconnected; + } } async getShouldAutopromptNow(): Promise<boolean> { diff --git a/apps/desktop/src/app/services/services.module.ts b/apps/desktop/src/app/services/services.module.ts index 1b373f08881..e4dd144fa20 100644 --- a/apps/desktop/src/app/services/services.module.ts +++ b/apps/desktop/src/app/services/services.module.ts @@ -51,6 +51,7 @@ import { } from "@bitwarden/common/auth/abstractions/auth.service"; import { MasterPasswordApiService } from "@bitwarden/common/auth/abstractions/master-password-api.service.abstraction"; import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; +import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions"; import { ClientType } from "@bitwarden/common/enums"; @@ -167,12 +168,12 @@ const safeProviders: SafeProvider[] = [ safeProvider({ provide: BiometricsService, useClass: RendererBiometricsService, - deps: [], + deps: [TokenService], }), safeProvider({ provide: DesktopBiometricsService, useClass: RendererBiometricsService, - deps: [], + deps: [TokenService], }), safeProvider(NativeMessagingService), safeProvider(BiometricMessageHandlerService), diff --git a/apps/desktop/src/key-management/biometrics/renderer-biometrics.service.ts b/apps/desktop/src/key-management/biometrics/renderer-biometrics.service.ts index 8e28d3ca614..3a47086b1aa 100644 --- a/apps/desktop/src/key-management/biometrics/renderer-biometrics.service.ts +++ b/apps/desktop/src/key-management/biometrics/renderer-biometrics.service.ts @@ -1,5 +1,7 @@ import { Injectable } from "@angular/core"; +import { firstValueFrom } from "rxjs"; +import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { UserId } from "@bitwarden/common/types/guid"; import { UserKey } from "@bitwarden/common/types/key"; @@ -13,6 +15,10 @@ import { DesktopBiometricsService } from "./desktop.biometrics.service"; */ @Injectable() export class RendererBiometricsService extends DesktopBiometricsService { + constructor(private tokenService: TokenService) { + super(); + } + async authenticateWithBiometrics(): Promise<boolean> { return await ipc.keyManagement.biometric.authenticateWithBiometrics(); } @@ -31,6 +37,10 @@ export class RendererBiometricsService extends DesktopBiometricsService { } async getBiometricsStatusForUser(id: UserId): Promise<BiometricsStatus> { + if ((await firstValueFrom(this.tokenService.hasAccessToken$(id))) === false) { + return BiometricsStatus.NotEnabledInConnectedDesktopApp; + } + return await ipc.keyManagement.biometric.getBiometricsStatusForUser(id); } diff --git a/libs/key-management-ui/src/lock/components/lock.component.html b/libs/key-management-ui/src/lock/components/lock.component.html index 77f603204b3..71201361a0c 100644 --- a/libs/key-management-ui/src/lock/components/lock.component.html +++ b/libs/key-management-ui/src/lock/components/lock.component.html @@ -16,6 +16,7 @@ [disabled]="unlockingViaBiometrics || !biometricsAvailable" [loading]="unlockingViaBiometrics" block + [bitTooltip]="biometricUnavailabilityReason" (click)="unlockViaBiometrics()" > <span> {{ biometricUnlockBtnText | i18n }}</span> diff --git a/libs/key-management-ui/src/lock/components/lock.component.spec.ts b/libs/key-management-ui/src/lock/components/lock.component.spec.ts index 943beef8091..5d35746ff19 100644 --- a/libs/key-management-ui/src/lock/components/lock.component.spec.ts +++ b/libs/key-management-ui/src/lock/components/lock.component.spec.ts @@ -11,7 +11,7 @@ import { JslibModule } from "@bitwarden/angular/jslib.module"; import { LogoutService } from "@bitwarden/auth/common"; import { InternalPolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { Account, AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { VerificationType } from "@bitwarden/common/auth/enums/verification-type"; import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason"; @@ -57,6 +57,7 @@ import { import { LockComponentService, UnlockOption, + UnlockOptionValue, UnlockOptions, } from "../services/lock-component.service"; @@ -878,4 +879,250 @@ describe("LockComponent", () => { expect(mockRouter.navigate).not.toHaveBeenCalled(); }); }); + + describe("setDefaultActiveUnlockOption", () => { + it.each([ + [ + "biometrics enabled", + { + biometrics: { enabled: true, biometricsStatus: BiometricsStatus.Available }, + pin: { enabled: false }, + masterPassword: { enabled: false }, + } as UnlockOptions, + UnlockOption.Biometrics, + ], + [ + "biometrics disabled, pin enabled", + { + biometrics: { enabled: false, biometricsStatus: BiometricsStatus.NotEnabledLocally }, + pin: { enabled: true }, + masterPassword: { enabled: false }, + } as UnlockOptions, + UnlockOption.Pin, + ], + [ + "biometrics and pin disabled, masterPassword enabled", + { + biometrics: { enabled: false, biometricsStatus: BiometricsStatus.NotEnabledLocally }, + pin: { enabled: false }, + masterPassword: { enabled: true }, + } as UnlockOptions, + UnlockOption.MasterPassword, + ], + [ + "hardware unavailable, no other options", + { + biometrics: { enabled: false, biometricsStatus: BiometricsStatus.HardwareUnavailable }, + pin: { enabled: false }, + masterPassword: { enabled: false }, + } as UnlockOptions, + UnlockOption.Biometrics, + ], + [ + "desktop disconnected, no other options", + { + biometrics: { enabled: false, biometricsStatus: BiometricsStatus.DesktopDisconnected }, + pin: { enabled: false }, + masterPassword: { enabled: false }, + } as UnlockOptions, + UnlockOption.Biometrics, + ], + [ + "not enabled in connected desktop app, no other options", + { + biometrics: { + enabled: false, + biometricsStatus: BiometricsStatus.NotEnabledInConnectedDesktopApp, + }, + pin: { enabled: false }, + masterPassword: { enabled: false }, + } as UnlockOptions, + UnlockOption.Biometrics, + ], + [ + "biometrics over pin priority", + { + biometrics: { enabled: true, biometricsStatus: BiometricsStatus.Available }, + pin: { enabled: true }, + masterPassword: { enabled: false }, + } as UnlockOptions, + UnlockOption.Biometrics, + ], + [ + "biometrics over masterPassword priority", + { + biometrics: { enabled: true, biometricsStatus: BiometricsStatus.Available }, + pin: { enabled: false }, + masterPassword: { enabled: true }, + } as UnlockOptions, + UnlockOption.Biometrics, + ], + [ + "pin over masterPassword priority", + { + biometrics: { enabled: false, biometricsStatus: BiometricsStatus.NotEnabledLocally }, + pin: { enabled: true }, + masterPassword: { enabled: true }, + } as UnlockOptions, + UnlockOption.Pin, + ], + [ + "all options enabled", + { + biometrics: { enabled: true, biometricsStatus: BiometricsStatus.Available }, + pin: { enabled: true }, + masterPassword: { enabled: true }, + } as UnlockOptions, + UnlockOption.Biometrics, + ], + ])( + "should set active unlock option to $1 when %s", + async ( + description: string, + unlockOptions: UnlockOptions, + expectedOption: UnlockOptionValue, + ) => { + await component["setDefaultActiveUnlockOption"](unlockOptions); + + expect(component.activeUnlockOption).toBe(expectedOption); + }, + ); + }); + + describe("handleActiveAccountChange", () => { + const mockActiveAccount: Account = { + id: userId, + email: "test@example.com", + name: "Test User", + } as Account; + + beforeEach(async () => { + component.activeAccount = mockActiveAccount; + }); + + it("should return early when account already has user key", async () => { + mockKeyService.hasUserKey.mockResolvedValue(true); + + await component["handleActiveAccountChange"](mockActiveAccount); + + expect(mockKeyService.hasUserKey).toHaveBeenCalledWith(userId); + expect(mockAnonLayoutWrapperDataService.setAnonLayoutWrapperData).not.toHaveBeenCalled(); + }); + + it("should set email as page subtitle when account is unlocked", async () => { + mockKeyService.hasUserKey.mockResolvedValue(false); + mockLockComponentService.getAvailableUnlockOptions$.mockReturnValue( + of({ + biometrics: { enabled: true, biometricsStatus: BiometricsStatus.Available }, + pin: { enabled: false }, + masterPassword: { enabled: false }, + } as UnlockOptions), + ); + mockBiometricService.getBiometricsStatusForUser.mockResolvedValue(BiometricsStatus.Available); + + await component["handleActiveAccountChange"](mockActiveAccount); + + expect(mockAnonLayoutWrapperDataService.setAnonLayoutWrapperData).toHaveBeenCalledWith({ + pageSubtitle: mockActiveAccount.email, + }); + }); + + it("should logout user when no unlock options are available", async () => { + mockKeyService.hasUserKey.mockResolvedValue(false); + mockLockComponentService.getAvailableUnlockOptions$.mockReturnValue( + of({ + biometrics: { enabled: false, biometricsStatus: BiometricsStatus.UnlockNeeded }, + pin: { enabled: false }, + masterPassword: { enabled: false }, + } as UnlockOptions), + ); + mockBiometricService.getBiometricsStatusForUser.mockResolvedValue( + BiometricsStatus.UnlockNeeded, + ); + + await component["handleActiveAccountChange"](mockActiveAccount); + + expect(mockLogService.warning).toHaveBeenCalledWith( + "[LockComponent] User cannot unlock again. Logging out!", + ); + expect(mockLogoutService.logout).toHaveBeenCalledWith(userId); + }); + + it("should not logout when master password is enabled", async () => { + mockKeyService.hasUserKey.mockResolvedValue(false); + mockLockComponentService.getAvailableUnlockOptions$.mockReturnValue( + of({ + biometrics: { enabled: false, biometricsStatus: BiometricsStatus.UnlockNeeded }, + pin: { enabled: false }, + masterPassword: { enabled: true }, + } as UnlockOptions), + ); + mockBiometricService.getBiometricsStatusForUser.mockResolvedValue( + BiometricsStatus.UnlockNeeded, + ); + + await component["handleActiveAccountChange"](mockActiveAccount); + + expect(mockLogoutService.logout).not.toHaveBeenCalled(); + expect(component.activeUnlockOption).toBe(UnlockOption.MasterPassword); + }); + + it("should not logout when pin is enabled", async () => { + mockKeyService.hasUserKey.mockResolvedValue(false); + mockLockComponentService.getAvailableUnlockOptions$.mockReturnValue( + of({ + biometrics: { enabled: false, biometricsStatus: BiometricsStatus.UnlockNeeded }, + pin: { enabled: true }, + masterPassword: { enabled: false }, + } as UnlockOptions), + ); + mockBiometricService.getBiometricsStatusForUser.mockResolvedValue( + BiometricsStatus.UnlockNeeded, + ); + + await component["handleActiveAccountChange"](mockActiveAccount); + + expect(mockLogoutService.logout).not.toHaveBeenCalled(); + expect(component.activeUnlockOption).toBe(UnlockOption.Pin); + }); + + it("should not logout when biometrics is available", async () => { + mockKeyService.hasUserKey.mockResolvedValue(false); + mockLockComponentService.getAvailableUnlockOptions$.mockReturnValue( + of({ + biometrics: { enabled: true, biometricsStatus: BiometricsStatus.Available }, + pin: { enabled: false }, + masterPassword: { enabled: false }, + } as UnlockOptions), + ); + mockBiometricService.getBiometricsStatusForUser.mockResolvedValue(BiometricsStatus.Available); + + await component["handleActiveAccountChange"](mockActiveAccount); + + expect(mockLogoutService.logout).not.toHaveBeenCalled(); + expect(component.activeUnlockOption).toBe(UnlockOption.Biometrics); + }); + + it("should not logout when biometrics is temporarily unavailable but no other options", async () => { + mockKeyService.hasUserKey.mockResolvedValue(false); + mockLockComponentService.getAvailableUnlockOptions$.mockReturnValue( + of({ + biometrics: { + enabled: false, + biometricsStatus: BiometricsStatus.HardwareUnavailable, + }, + pin: { enabled: false }, + masterPassword: { enabled: false }, + } as UnlockOptions), + ); + mockBiometricService.getBiometricsStatusForUser.mockResolvedValue( + BiometricsStatus.HardwareUnavailable, + ); + + await component["handleActiveAccountChange"](mockActiveAccount); + + expect(mockLogoutService.logout).not.toHaveBeenCalled(); + expect(component.activeUnlockOption).toBe(UnlockOption.Biometrics); + }); + }); }); diff --git a/libs/key-management-ui/src/lock/components/lock.component.ts b/libs/key-management-ui/src/lock/components/lock.component.ts index ae8cdc843cf..ec7ef822335 100644 --- a/libs/key-management-ui/src/lock/components/lock.component.ts +++ b/libs/key-management-ui/src/lock/components/lock.component.ts @@ -44,6 +44,7 @@ import { SyncService } from "@bitwarden/common/platform/sync"; import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength"; import { UserKey } from "@bitwarden/common/types/key"; import { + TooltipDirective, AsyncActionsModule, AnonLayoutWrapperDataService, ButtonModule, @@ -87,6 +88,12 @@ type AfterUnlockActions = { /// Fixes safari autoprompt behavior const AUTOPROMPT_BIOMETRICS_PROCESS_RELOAD_DELAY = 5000; +const BIOMETRIC_UNLOCK_TEMPORARY_UNAVAILABLE_STATUSES = [ + BiometricsStatus.HardwareUnavailable, + BiometricsStatus.DesktopDisconnected, + BiometricsStatus.NotEnabledInConnectedDesktopApp, +]; + // FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush // eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ @@ -101,6 +108,7 @@ const AUTOPROMPT_BIOMETRICS_PROCESS_RELOAD_DELAY = 5000; AsyncActionsModule, IconButtonModule, MasterPasswordLockComponent, + TooltipDirective, ], }) export class LockComponent implements OnInit, OnDestroy { @@ -212,6 +220,10 @@ export class LockComponent implements OnInit, OnDestroy { this.unlockOptions = await firstValueFrom( this.lockComponentService.getAvailableUnlockOptions$(this.activeAccount.id), ); + if (this.activeUnlockOption == null) { + this.loading = false; + await this.setDefaultActiveUnlockOption(this.unlockOptions); + } } }), takeUntil(this.destroy$), @@ -280,7 +292,22 @@ export class LockComponent implements OnInit, OnDestroy { this.lockComponentService.getAvailableUnlockOptions$(activeAccount.id), ); - this.setDefaultActiveUnlockOption(this.unlockOptions); + const canUseBiometrics = [ + BiometricsStatus.Available, + ...BIOMETRIC_UNLOCK_TEMPORARY_UNAVAILABLE_STATUSES, + ].includes(await this.biometricService.getBiometricsStatusForUser(activeAccount.id)); + if ( + !this.unlockOptions?.masterPassword.enabled && + !this.unlockOptions?.pin.enabled && + !canUseBiometrics + ) { + // User has no available unlock options, force logout. This happens for TDE users without a masterpassword, that don't have a persistent unlock method set. + this.logService.warning("[LockComponent] User cannot unlock again. Logging out!"); + await this.logoutService.logout(activeAccount.id); + return; + } + + await this.setDefaultActiveUnlockOption(this.unlockOptions); if (this.unlockOptions?.biometrics.enabled) { await this.handleBiometricsUnlockEnabled(); @@ -303,7 +330,7 @@ export class LockComponent implements OnInit, OnDestroy { }); } - private setDefaultActiveUnlockOption(unlockOptions: UnlockOptions | null) { + private async setDefaultActiveUnlockOption(unlockOptions: UnlockOptions | null) { // Priorities should be Biometrics > Pin > Master Password for speed if (unlockOptions?.biometrics.enabled) { this.activeUnlockOption = UnlockOption.Biometrics; @@ -311,6 +338,15 @@ export class LockComponent implements OnInit, OnDestroy { this.activeUnlockOption = UnlockOption.Pin; } else if (unlockOptions?.masterPassword.enabled) { this.activeUnlockOption = UnlockOption.MasterPassword; + } else if ( + unlockOptions != null && + BIOMETRIC_UNLOCK_TEMPORARY_UNAVAILABLE_STATUSES.includes( + unlockOptions.biometrics.biometricsStatus, + ) + ) { + // If biometrics is temporarily unavailable for masterpassword-less users, but they have biometrics configured, + // then show the biometrics screen so the user knows why they can't unlock, and to give them the option to log out. + this.activeUnlockOption = UnlockOption.Biometrics; } } From 51d29f777e38a7fec15b355579696d2873388647 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann <mail@quexten.com> Date: Thu, 11 Dec 2025 13:01:09 +0100 Subject: [PATCH 047/188] [PM-24353] Drop legacy pin support (#17328) * Drop legacy pin support * Fix cli build * Fix browser build * Remove pin key * Fix comment * Fix CI / tests * Add migration to remove key * Inline export key * Extract vault export key generation * Cleanup * Add migrator * Fix mv2 build --- .../browser/src/background/main.background.ts | 9 +- .../service-container/service-container.ts | 9 +- .../src/services/jslib-services.module.ts | 15 +-- .../default-key-generation.service.ts | 8 ++ .../key-generation/key-generation.service.ts | 15 +++ .../pin/pin-state.service.abstraction.ts | 8 -- .../pin/pin-state.service.implementation.ts | 18 +-- .../pin/pin-state.service.spec.ts | 106 ---------------- .../pin/pin.service.abstraction.ts | 11 +- .../pin/pin.service.implementation.ts | 119 ++++-------------- .../key-management/pin/pin.service.spec.ts | 94 +------------- .../src/key-management/pin/pin.state.ts | 16 --- .../default-process-reload.service.ts | 2 +- libs/common/src/types/key.ts | 2 - .../src/components/importer-providers.ts | 4 +- ...warden-password-protected-importer.spec.ts | 10 +- .../bitwarden-password-protected-importer.ts | 6 +- .../src/services/import.service.spec.ts | 8 +- libs/importer/src/services/import.service.ts | 6 +- libs/state/src/state-migrations/migrate.ts | 6 +- .../migrations/74-remove-legacy-pin.spec.ts | 50 ++++++++ .../migrations/74-remove-legacy-pin.ts | 30 +++++ .../src/services/base-vault-export.service.ts | 7 +- .../individual-vault-export.service.spec.ts | 8 +- .../individual-vault-export.service.ts | 6 +- .../src/services/org-vault-export.service.ts | 6 +- 26 files changed, 175 insertions(+), 404 deletions(-) create mode 100644 libs/state/src/state-migrations/migrations/74-remove-legacy-pin.spec.ts create mode 100644 libs/state/src/state-migrations/migrations/74-remove-legacy-pin.ts diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index 1bd47186914..2540571abb0 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -841,10 +841,7 @@ export default class MainBackground { ); this.pinService = new PinService( - this.accountService, this.encryptService, - this.kdfConfigService, - this.keyGenerationService, this.logService, this.keyService, this.sdkService, @@ -1112,7 +1109,7 @@ export default class MainBackground { this.collectionService, this.keyService, this.encryptService, - this.pinService, + this.keyGenerationService, this.accountService, this.restrictedItemTypesService, ); @@ -1120,7 +1117,7 @@ export default class MainBackground { this.individualVaultExportService = new IndividualVaultExportService( this.folderService, this.cipherService, - this.pinService, + this.keyGenerationService, this.keyService, this.encryptService, this.cryptoFunctionService, @@ -1134,7 +1131,7 @@ export default class MainBackground { this.organizationVaultExportService = new OrganizationVaultExportService( this.cipherService, this.exportApiService, - this.pinService, + this.keyGenerationService, this.keyService, this.encryptService, this.cryptoFunctionService, diff --git a/apps/cli/src/service-container/service-container.ts b/apps/cli/src/service-container/service-container.ts index 83c64c61423..2d4ea7d00b5 100644 --- a/apps/cli/src/service-container/service-container.ts +++ b/apps/cli/src/service-container/service-container.ts @@ -492,10 +492,7 @@ export class ServiceContainer { const pinStateService = new PinStateService(this.stateProvider); this.pinService = new PinService( - this.accountService, this.encryptService, - this.kdfConfigService, - this.keyGenerationService, this.logService, this.keyService, this.sdkService, @@ -908,7 +905,7 @@ export class ServiceContainer { this.collectionService, this.keyService, this.encryptService, - this.pinService, + this.keyGenerationService, this.accountService, this.restrictedItemTypesService, ); @@ -916,7 +913,7 @@ export class ServiceContainer { this.individualExportService = new IndividualVaultExportService( this.folderService, this.cipherService, - this.pinService, + this.keyGenerationService, this.keyService, this.encryptService, this.cryptoFunctionService, @@ -930,7 +927,7 @@ export class ServiceContainer { this.organizationExportService = new OrganizationVaultExportService( this.cipherService, this.vaultExportApiService, - this.pinService, + this.keyGenerationService, this.keyService, this.encryptService, this.cryptoFunctionService, diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index b26db7e9056..816e09fd45d 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -952,7 +952,7 @@ const safeProviders: SafeProvider[] = [ deps: [ FolderServiceAbstraction, CipherServiceAbstraction, - PinServiceAbstraction, + KeyGenerationService, KeyService, EncryptService, CryptoFunctionServiceAbstraction, @@ -972,7 +972,7 @@ const safeProviders: SafeProvider[] = [ deps: [ CipherServiceAbstraction, VaultExportApiService, - PinServiceAbstraction, + KeyGenerationService, KeyService, EncryptService, CryptoFunctionServiceAbstraction, @@ -1357,16 +1357,7 @@ const safeProviders: SafeProvider[] = [ safeProvider({ provide: PinServiceAbstraction, useClass: PinService, - deps: [ - AccountServiceAbstraction, - EncryptService, - KdfConfigService, - KeyGenerationService, - LogService, - KeyService, - SdkService, - PinStateServiceAbstraction, - ], + deps: [EncryptService, LogService, KeyService, SdkService, PinStateServiceAbstraction], }), safeProvider({ provide: WebAuthnLoginPrfKeyServiceAbstraction, diff --git a/libs/common/src/key-management/crypto/key-generation/default-key-generation.service.ts b/libs/common/src/key-management/crypto/key-generation/default-key-generation.service.ts index 8e8d2de1ce4..5f5da741707 100644 --- a/libs/common/src/key-management/crypto/key-generation/default-key-generation.service.ts +++ b/libs/common/src/key-management/crypto/key-generation/default-key-generation.service.ts @@ -91,4 +91,12 @@ export class DefaultKeyGenerationService implements KeyGenerationService { return new SymmetricCryptoKey(newKey); } + + async deriveVaultExportKey( + password: string, + salt: string, + kdfConfig: KdfConfig, + ): Promise<SymmetricCryptoKey> { + return await this.stretchKey(await this.deriveKeyFromPassword(password, salt, kdfConfig)); + } } diff --git a/libs/common/src/key-management/crypto/key-generation/key-generation.service.ts b/libs/common/src/key-management/crypto/key-generation/key-generation.service.ts index d6be436384e..ddc3829aa9f 100644 --- a/libs/common/src/key-management/crypto/key-generation/key-generation.service.ts +++ b/libs/common/src/key-management/crypto/key-generation/key-generation.service.ts @@ -87,4 +87,19 @@ export abstract class KeyGenerationService { * @returns 64 byte derived key. */ abstract stretchKey(key: SymmetricCryptoKey): Promise<SymmetricCryptoKey>; + + /** + * Derives a 64 byte key for encrypting and decrypting vault exports. + * + * @deprecated Do not use this for new use-cases. + * @param password Password to derive the key from. + * @param salt Salt for the key derivation function. + * @param kdfConfig Configuration for the key derivation function. + * @returns 64 byte derived key. + */ + abstract deriveVaultExportKey( + password: string, + salt: string, + kdfConfig: KdfConfig, + ): Promise<SymmetricCryptoKey>; } diff --git a/libs/common/src/key-management/pin/pin-state.service.abstraction.ts b/libs/common/src/key-management/pin/pin-state.service.abstraction.ts index cd10b8b7fe2..4aef268c1c4 100644 --- a/libs/common/src/key-management/pin/pin-state.service.abstraction.ts +++ b/libs/common/src/key-management/pin/pin-state.service.abstraction.ts @@ -45,14 +45,6 @@ export abstract class PinStateServiceAbstraction { pinLockType: PinLockType, ): Promise<PasswordProtectedKeyEnvelope | null>; - /** - * Gets the user's legacy PIN-protected UserKey - * @deprecated Use {@link getPinProtectedUserKeyEnvelope} instead. Only for migration support. - * @param userId The user's id - * @throws If the user id is not provided - */ - abstract getLegacyPinKeyEncryptedUserKeyPersistent(userId: UserId): Promise<EncString | null>; - /** * Sets the PIN state for the user * @deprecated - This is not a public API. DO NOT USE IT diff --git a/libs/common/src/key-management/pin/pin-state.service.implementation.ts b/libs/common/src/key-management/pin/pin-state.service.implementation.ts index 0bf6cb60fb0..d5b2608f280 100644 --- a/libs/common/src/key-management/pin/pin-state.service.implementation.ts +++ b/libs/common/src/key-management/pin/pin-state.service.implementation.ts @@ -13,7 +13,6 @@ import { PIN_PROTECTED_USER_KEY_ENVELOPE_PERSISTENT, PIN_PROTECTED_USER_KEY_ENVELOPE_EPHEMERAL, USER_KEY_ENCRYPTED_PIN, - PIN_KEY_ENCRYPTED_USER_KEY_PERSISTENT, } from "./pin.state"; export class PinStateService implements PinStateServiceAbstraction { @@ -36,9 +35,7 @@ export class PinStateService implements PinStateServiceAbstraction { assertNonNullish(userId, "userId"); const isPersistentPinSet = - (await this.getPinProtectedUserKeyEnvelope(userId, "PERSISTENT")) != null || - // Deprecated - (await this.getLegacyPinKeyEncryptedUserKeyPersistent(userId)) != null; + (await this.getPinProtectedUserKeyEnvelope(userId, "PERSISTENT")) != null; const isPinSet = (await firstValueFrom(this.stateProvider.getUserState$(USER_KEY_ENCRYPTED_PIN, userId))) != null; @@ -71,16 +68,6 @@ export class PinStateService implements PinStateServiceAbstraction { } } - async getLegacyPinKeyEncryptedUserKeyPersistent(userId: UserId): Promise<EncString | null> { - assertNonNullish(userId, "userId"); - - return await firstValueFrom( - this.stateProvider - .getUserState$(PIN_KEY_ENCRYPTED_USER_KEY_PERSISTENT, userId) - .pipe(map((value) => (value ? new EncString(value) : null))), - ); - } - async setPinState( userId: UserId, pinProtectedUserKeyEnvelope: PasswordProtectedKeyEnvelope, @@ -116,9 +103,6 @@ export class PinStateService implements PinStateServiceAbstraction { await this.stateProvider.setUserState(USER_KEY_ENCRYPTED_PIN, null, userId); await this.stateProvider.setUserState(PIN_PROTECTED_USER_KEY_ENVELOPE_EPHEMERAL, null, userId); await this.stateProvider.setUserState(PIN_PROTECTED_USER_KEY_ENVELOPE_PERSISTENT, null, userId); - - // Note: This can be deleted after sufficiently many PINs are migrated and the state is removed. - await this.stateProvider.setUserState(PIN_KEY_ENCRYPTED_USER_KEY_PERSISTENT, null, userId); } async clearEphemeralPinState(userId: UserId): Promise<void> { diff --git a/libs/common/src/key-management/pin/pin-state.service.spec.ts b/libs/common/src/key-management/pin/pin-state.service.spec.ts index be85a15e6d3..7406701c28d 100644 --- a/libs/common/src/key-management/pin/pin-state.service.spec.ts +++ b/libs/common/src/key-management/pin/pin-state.service.spec.ts @@ -13,7 +13,6 @@ import { USER_KEY_ENCRYPTED_PIN, PIN_PROTECTED_USER_KEY_ENVELOPE_EPHEMERAL, PIN_PROTECTED_USER_KEY_ENVELOPE_PERSISTENT, - PIN_KEY_ENCRYPTED_USER_KEY_PERSISTENT, } from "./pin.state"; describe("PinStateService", () => { @@ -121,21 +120,6 @@ describe("PinStateService", () => { expect(result).toBe("PERSISTENT"); }); - it("should return 'PERSISTENT' if a legacy pin key encrypted user key (persistent) is found", async () => { - // Arrange - await stateProvider.setUserState( - PIN_KEY_ENCRYPTED_USER_KEY_PERSISTENT, - mockUserKeyEncryptedPin, - mockUserId, - ); - - // Act - const result = await sut.getPinLockType(mockUserId); - - // Assert - expect(result).toBe("PERSISTENT"); - }); - it("should return 'EPHEMERAL' if only user key encrypted pin is found", async () => { // Arrange await stateProvider.setUserState(USER_KEY_ENCRYPTED_PIN, mockUserKeyEncryptedPin, mockUserId); @@ -164,7 +148,6 @@ describe("PinStateService", () => { null, mockUserId, ); - await stateProvider.setUserState(PIN_KEY_ENCRYPTED_USER_KEY_PERSISTENT, null, mockUserId); await stateProvider.setUserState(USER_KEY_ENCRYPTED_PIN, null, mockUserId); // Act @@ -290,45 +273,6 @@ describe("PinStateService", () => { }); }); - describe("getLegacyPinKeyEncryptedUserKeyPersistent()", () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - test.each([null, undefined])("throws if userId is %p", async (userId) => { - // Act & Assert - await expect(() => - sut.getLegacyPinKeyEncryptedUserKeyPersistent(userId as any), - ).rejects.toThrow("userId is null or undefined."); - }); - - it("should return EncString when legacy key is set", async () => { - // Arrange - await stateProvider.setUserState( - PIN_KEY_ENCRYPTED_USER_KEY_PERSISTENT, - mockUserKeyEncryptedPin, - mockUserId, - ); - - // Act - const result = await sut.getLegacyPinKeyEncryptedUserKeyPersistent(mockUserId); - - // Assert - expect(result?.encryptedString).toEqual(mockUserKeyEncryptedPin); - }); - - test.each([null, undefined])("should return null when legacy key is %p", async (value) => { - // Arrange - await stateProvider.setUserState(PIN_KEY_ENCRYPTED_USER_KEY_PERSISTENT, value, mockUserId); - - // Act - const result = await sut.getLegacyPinKeyEncryptedUserKeyPersistent(mockUserId); - - // Assert - expect(result).toBeNull(); - }); - }); - describe("setPinState()", () => { beforeEach(() => { jest.clearAllMocks(); @@ -464,22 +408,6 @@ describe("PinStateService", () => { expect(result).toBeNull(); }); - it("clears legacy PIN key encrypted user key persistent", async () => { - // Arrange - await stateProvider.setUserState( - PIN_KEY_ENCRYPTED_USER_KEY_PERSISTENT, - mockUserKeyEncryptedPin, - mockUserId, - ); - - // Act - await sut.clearPinState(mockUserId); - - // Assert - const result = await sut.getLegacyPinKeyEncryptedUserKeyPersistent(mockUserId); - expect(result).toBeNull(); - }); - it("clears all PIN state when all types are set", async () => { // Arrange - set up all possible PIN state await sut.setPinState( @@ -494,17 +422,11 @@ describe("PinStateService", () => { mockUserKeyEncryptedPin, "EPHEMERAL", ); - await stateProvider.setUserState( - PIN_KEY_ENCRYPTED_USER_KEY_PERSISTENT, - mockUserKeyEncryptedPin, - mockUserId, - ); // Verify all state is set before clearing expect(await firstValueFrom(sut.userKeyEncryptedPin$(mockUserId))).not.toBeNull(); expect(await sut.getPinProtectedUserKeyEnvelope(mockUserId, "EPHEMERAL")).not.toBeNull(); expect(await sut.getPinProtectedUserKeyEnvelope(mockUserId, "PERSISTENT")).not.toBeNull(); - expect(await sut.getLegacyPinKeyEncryptedUserKeyPersistent(mockUserId)).not.toBeNull(); // Act await sut.clearPinState(mockUserId); @@ -513,7 +435,6 @@ describe("PinStateService", () => { expect(await firstValueFrom(sut.userKeyEncryptedPin$(mockUserId))).toBeNull(); expect(await sut.getPinProtectedUserKeyEnvelope(mockUserId, "EPHEMERAL")).toBeNull(); expect(await sut.getPinProtectedUserKeyEnvelope(mockUserId, "PERSISTENT")).toBeNull(); - expect(await sut.getLegacyPinKeyEncryptedUserKeyPersistent(mockUserId)).toBeNull(); }); it("results in PIN lock type DISABLED after clearing", async () => { @@ -545,7 +466,6 @@ describe("PinStateService", () => { expect(await firstValueFrom(sut.userKeyEncryptedPin$(mockUserId))).toBeNull(); expect(await sut.getPinProtectedUserKeyEnvelope(mockUserId, "EPHEMERAL")).toBeNull(); expect(await sut.getPinProtectedUserKeyEnvelope(mockUserId, "PERSISTENT")).toBeNull(); - expect(await sut.getLegacyPinKeyEncryptedUserKeyPersistent(mockUserId)).toBeNull(); expect(await sut.getPinLockType(mockUserId)).toBe("DISABLED"); }); }); @@ -623,32 +543,6 @@ describe("PinStateService", () => { expect(ephemeralResult).toBeNull(); }); - it("does not clear legacy PIN key encrypted user key persistent", async () => { - // Arrange - set up ephemeral state and legacy state - await sut.setPinState( - mockUserId, - mockEphemeralEnvelope, - mockUserKeyEncryptedPin, - "EPHEMERAL", - ); - await stateProvider.setUserState( - PIN_KEY_ENCRYPTED_USER_KEY_PERSISTENT, - mockUserKeyEncryptedPin, - mockUserId, - ); - - // Act - await sut.clearEphemeralPinState(mockUserId); - - // Assert - legacy PIN should still be present - const legacyResult = await sut.getLegacyPinKeyEncryptedUserKeyPersistent(mockUserId); - expect(legacyResult?.encryptedString).toEqual(mockUserKeyEncryptedPin); - - // Assert - ephemeral envelope should be cleared - const ephemeralResult = await sut.getPinProtectedUserKeyEnvelope(mockUserId, "EPHEMERAL"); - expect(ephemeralResult).toBeNull(); - }); - it("changes PIN lock type from EPHEMERAL to DISABLED when no other PIN state exists", async () => { // Arrange - set up only ephemeral PIN state await sut.setPinState( diff --git a/libs/common/src/key-management/pin/pin.service.abstraction.ts b/libs/common/src/key-management/pin/pin.service.abstraction.ts index b242320c06e..c4551d3522f 100644 --- a/libs/common/src/key-management/pin/pin.service.abstraction.ts +++ b/libs/common/src/key-management/pin/pin.service.abstraction.ts @@ -1,8 +1,5 @@ -// eslint-disable-next-line no-restricted-imports -import { KdfConfig } from "@bitwarden/key-management"; - import { UserId } from "../../types/guid"; -import { PinKey, UserKey } from "../../types/key"; +import { UserKey } from "../../types/key"; import { PinLockType } from "./pin-lock-type"; @@ -69,10 +66,4 @@ export abstract class PinServiceAbstraction { * @deprecated This is not deprecated, but only meant to be called by KeyService. DO NOT USE IT. */ abstract userUnlocked(userId: UserId): Promise<void>; - - /** - * Makes a PinKey from the provided PIN. - * @deprecated - Note: This is currently re-used by vault exports, which is still permitted but should be refactored out to use a different construct. - */ - abstract makePinKey(pin: string, salt: string, kdfConfig: KdfConfig): Promise<PinKey>; } diff --git a/libs/common/src/key-management/pin/pin.service.implementation.ts b/libs/common/src/key-management/pin/pin.service.implementation.ts index 29a10d1a0a4..da6d3f20eaf 100644 --- a/libs/common/src/key-management/pin/pin.service.implementation.ts +++ b/libs/common/src/key-management/pin/pin.service.implementation.ts @@ -1,18 +1,15 @@ import { firstValueFrom, map } from "rxjs"; // eslint-disable-next-line no-restricted-imports -import { KdfConfig, KdfConfigService, KeyService } from "@bitwarden/key-management"; +import { KeyService } from "@bitwarden/key-management"; -import { AccountService } from "../../auth/abstractions/account.service"; import { assertNonNullish } from "../../auth/utils"; import { EncryptService } from "../../key-management/crypto/abstractions/encrypt.service"; -import { EncString } from "../../key-management/crypto/models/enc-string"; import { LogService } from "../../platform/abstractions/log.service"; import { SdkService } from "../../platform/abstractions/sdk/sdk.service"; import { SymmetricCryptoKey } from "../../platform/models/domain/symmetric-crypto-key"; import { UserId } from "../../types/guid"; -import { PinKey, UserKey } from "../../types/key"; -import { KeyGenerationService } from "../crypto"; +import { UserKey } from "../../types/key"; import { firstValueFromOrThrow } from "../utils"; import { PinLockType } from "./pin-lock-type"; @@ -21,10 +18,7 @@ import { PinServiceAbstraction } from "./pin.service.abstraction"; export class PinService implements PinServiceAbstraction { constructor( - private accountService: AccountService, private encryptService: EncryptService, - private kdfConfigService: KdfConfigService, - private keyGenerationService: KeyGenerationService, private logService: LogService, private keyService: KeyService, private sdkService: SdkService, @@ -56,19 +50,6 @@ export class PinService implements PinServiceAbstraction { // On first unlock, set the ephemeral pin envelope, if it is not set yet const pin = await this.getPin(userId); await this.setPin(pin, "EPHEMERAL", userId); - } else if ((await this.pinStateService.getPinLockType(userId)) === "PERSISTENT") { - // Encrypted migration for persistent pin unlock to pin envelopes. - // This will be removed at the earliest in 2026.1.0 - // - // ----- ENCRYPTION MIGRATION ----- - // Pin-key encrypted user-keys are eagerly migrated to the new pin-protected user key envelope format. - if ((await this.pinStateService.getLegacyPinKeyEncryptedUserKeyPersistent(userId)) != null) { - this.logService.info( - "[Pin Service] Migrating legacy PIN key to PinProtectedUserKeyEnvelope", - ); - const pin = await this.getPin(userId); - await this.setPin(pin, "PERSISTENT", userId); - } } } @@ -144,86 +125,30 @@ export class PinService implements PinServiceAbstraction { assertNonNullish(pin, "pin"); assertNonNullish(userId, "userId"); - const hasPinProtectedKeyEnvelopeSet = - (await this.pinStateService.getPinProtectedUserKeyEnvelope(userId, "EPHEMERAL")) != null || - (await this.pinStateService.getPinProtectedUserKeyEnvelope(userId, "PERSISTENT")) != null; + this.logService.info("[Pin Service] Pin-unlock via PinProtectedUserKeyEnvelope"); - if (hasPinProtectedKeyEnvelopeSet) { - this.logService.info("[Pin Service] Pin-unlock via PinProtectedUserKeyEnvelope"); + const pinLockType = await this.pinStateService.getPinLockType(userId); + const envelope = await this.pinStateService.getPinProtectedUserKeyEnvelope(userId, pinLockType); - const pinLockType = await this.pinStateService.getPinLockType(userId); - const envelope = await this.pinStateService.getPinProtectedUserKeyEnvelope( - userId, - pinLockType, + try { + // Use the sdk to create an enrollment, not yet persisting it to state + const startTime = performance.now(); + const userKeyBytes = await firstValueFrom( + this.sdkService.client$.pipe( + map((sdk) => { + if (!sdk) { + throw new Error("SDK not available"); + } + return sdk.crypto().unseal_password_protected_key_envelope(pin, envelope!); + }), + ), ); + this.logService.measure(startTime, "Crypto", "PinService", "UnsealPinEnvelope"); - try { - // Use the sdk to create an enrollment, not yet persisting it to state - const startTime = performance.now(); - const userKeyBytes = await firstValueFrom( - this.sdkService.client$.pipe( - map((sdk) => { - if (!sdk) { - throw new Error("SDK not available"); - } - return sdk.crypto().unseal_password_protected_key_envelope(pin, envelope!); - }), - ), - ); - this.logService.measure(startTime, "Crypto", "PinService", "UnsealPinEnvelope"); - - return new SymmetricCryptoKey(userKeyBytes) as UserKey; - } catch (error) { - this.logService.error(`Failed to unseal pin: ${error}`); - return null; - } - } else { - this.logService.info("[Pin Service] Pin-unlock via legacy PinKeyEncryptedUserKey"); - - // This branch is deprecated and will be removed in the future, but is kept for migration. - try { - const pinKeyEncryptedUserKey = - await this.pinStateService.getLegacyPinKeyEncryptedUserKeyPersistent(userId); - const email = await firstValueFrom( - this.accountService.accounts$.pipe(map((accounts) => accounts[userId].email)), - ); - const kdfConfig = await this.kdfConfigService.getKdfConfig(userId); - return await this.decryptUserKey(pin, email, kdfConfig, pinKeyEncryptedUserKey!); - } catch (error) { - this.logService.error(`Error decrypting user key with pin: ${error}`); - return null; - } + return new SymmetricCryptoKey(userKeyBytes) as UserKey; + } catch (error) { + this.logService.error(`Failed to unseal pin: ${error}`); + return null; } } - - /// Anything below here is deprecated and will be removed subsequently - - async makePinKey(pin: string, salt: string, kdfConfig: KdfConfig): Promise<PinKey> { - const startTime = performance.now(); - const pinKey = await this.keyGenerationService.deriveKeyFromPassword(pin, salt, kdfConfig); - this.logService.measure(startTime, "Crypto", "PinService", "makePinKey"); - - return (await this.keyGenerationService.stretchKey(pinKey)) as PinKey; - } - - /** - * Decrypts the UserKey with the provided PIN. - * @deprecated - * @throws If the PIN does not match the PIN that was used to encrypt the user key - * @throws If the salt, or KDF don't match the salt / KDF used to encrypt the user key - */ - private async decryptUserKey( - pin: string, - salt: string, - kdfConfig: KdfConfig, - pinKeyEncryptedUserKey: EncString, - ): Promise<UserKey> { - assertNonNullish(pin, "pin"); - assertNonNullish(salt, "salt"); - assertNonNullish(kdfConfig, "kdfConfig"); - assertNonNullish(pinKeyEncryptedUserKey, "pinKeyEncryptedUserKey"); - const pinKey = await this.makePinKey(pin, salt, kdfConfig); - const userKey = await this.encryptService.unwrapSymmetricKey(pinKeyEncryptedUserKey, pinKey); - return userKey as UserKey; - } } diff --git a/libs/common/src/key-management/pin/pin.service.spec.ts b/libs/common/src/key-management/pin/pin.service.spec.ts index 8d78255bb1b..644fd1d8d75 100644 --- a/libs/common/src/key-management/pin/pin.service.spec.ts +++ b/libs/common/src/key-management/pin/pin.service.spec.ts @@ -2,17 +2,15 @@ import { mock } from "jest-mock-extended"; import { BehaviorSubject, filter } from "rxjs"; // eslint-disable-next-line no-restricted-imports -import { DEFAULT_KDF_CONFIG, KdfConfigService, KeyService } from "@bitwarden/key-management"; +import { KeyService } from "@bitwarden/key-management"; import { PasswordProtectedKeyEnvelope } from "@bitwarden/sdk-internal"; import { MockSdkService } from "../..//platform/spec/mock-sdk.service"; -import { FakeAccountService, mockAccountServiceWith, mockEnc } from "../../../spec"; import { LogService } from "../../platform/abstractions/log.service"; import { Utils } from "../../platform/misc/utils"; import { SymmetricCryptoKey } from "../../platform/models/domain/symmetric-crypto-key"; import { UserId } from "../../types/guid"; -import { PinKey, UserKey } from "../../types/key"; -import { KeyGenerationService } from "../crypto"; +import { UserKey } from "../../types/key"; import { EncryptService } from "../crypto/abstractions/encrypt.service"; import { EncryptedString, EncString } from "../crypto/models/enc-string"; @@ -22,16 +20,10 @@ import { PinService } from "./pin.service.implementation"; describe("PinService", () => { let sut: PinService; - let accountService: FakeAccountService; - const encryptService = mock<EncryptService>(); - const kdfConfigService = mock<KdfConfigService>(); - const keyGenerationService = mock<KeyGenerationService>(); const logService = mock<LogService>(); const mockUserId = Utils.newGuid() as UserId; const mockUserKey = new SymmetricCryptoKey(new Uint8Array(64)) as UserKey; - const mockPinKey = new SymmetricCryptoKey(randomBytes(32)) as PinKey; - const mockUserEmail = "user@example.com"; const mockPin = "1234"; const mockUserKeyEncryptedPin = new EncString("userKeyEncryptedPin"); const mockEphemeralEnvelope = "mock-ephemeral-envelope" as PasswordProtectedKeyEnvelope; @@ -42,7 +34,6 @@ describe("PinService", () => { const behaviorSubject = new BehaviorSubject<{ userId: UserId; userKey: UserKey }>(null); beforeEach(() => { - accountService = mockAccountServiceWith(mockUserId, { email: mockUserEmail }); (keyService as any)["unlockedUserKeys$"] = behaviorSubject .asObservable() .pipe(filter((x) => x != null)); @@ -50,16 +41,7 @@ describe("PinService", () => { .mockDeep() .unseal_password_protected_key_envelope.mockReturnValue(new Uint8Array(64)); - sut = new PinService( - accountService, - encryptService, - kdfConfigService, - keyGenerationService, - logService, - keyService, - sdkService, - pinStateService, - ); + sut = new PinService(encryptService, logService, keyService, sdkService, pinStateService); }); it("should instantiate the PinService", () => { @@ -89,26 +71,6 @@ describe("PinService", () => { ); }); - it("should migrate legacy persistent PIN if needed", async () => { - // Arrange - pinStateService.getPinLockType.mockResolvedValue("PERSISTENT"); - pinStateService.getLegacyPinKeyEncryptedUserKeyPersistent.mockResolvedValue( - mockEnc("legacy-key"), - ); - const getPinSpy = jest.spyOn(sut, "getPin").mockResolvedValue(mockPin); - const setPinSpy = jest.spyOn(sut, "setPin").mockResolvedValue(); - - // Act - await sut.userUnlocked(mockUserId); - - // Assert - expect(getPinSpy).toHaveBeenCalledWith(mockUserId); - expect(setPinSpy).toHaveBeenCalledWith(mockPin, "PERSISTENT", mockUserId); - expect(logService.info).toHaveBeenCalledWith( - "[Pin Service] Migrating legacy PIN key to PinProtectedUserKeyEnvelope", - ); - }); - it("should do nothing if no migration or setup is needed", async () => { // Arrange pinStateService.getPinLockType.mockResolvedValue("DISABLED"); @@ -124,28 +86,6 @@ describe("PinService", () => { }); }); - describe("makePinKey()", () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - it("should make a PinKey", async () => { - // Arrange - keyGenerationService.deriveKeyFromPassword.mockResolvedValue(mockPinKey); - - // Act - await sut.makePinKey(mockPin, mockUserEmail, DEFAULT_KDF_CONFIG); - - // Assert - expect(keyGenerationService.deriveKeyFromPassword).toHaveBeenCalledWith( - mockPin, - mockUserEmail, - DEFAULT_KDF_CONFIG, - ); - expect(keyGenerationService.stretchKey).toHaveBeenCalledWith(mockPinKey); - }); - }); - describe("getPin()", () => { beforeEach(() => { jest.clearAllMocks(); @@ -383,7 +323,6 @@ describe("PinService", () => { jest.clearAllMocks(); pinStateService.userKeyEncryptedPin$.mockReset(); pinStateService.getPinProtectedUserKeyEnvelope.mockReset(); - pinStateService.getLegacyPinKeyEncryptedUserKeyPersistent.mockReset(); }); it("should throw an error if userId is null", async () => { @@ -423,32 +362,5 @@ describe("PinService", () => { // Assert expect(result).toEqual(mockUserKey); }); - - it("should return userkey with legacy pin PERSISTENT", async () => { - keyGenerationService.deriveKeyFromPassword.mockResolvedValue(mockPinKey); - keyGenerationService.stretchKey.mockResolvedValue(mockPinKey); - kdfConfigService.getKdfConfig.mockResolvedValue(DEFAULT_KDF_CONFIG); - encryptService.unwrapSymmetricKey.mockResolvedValue(mockUserKey); - - // Arrange - const mockPin = "1234"; - pinStateService.userKeyEncryptedPin$.mockReturnValueOnce( - new BehaviorSubject(mockUserKeyEncryptedPin), - ); - pinStateService.getLegacyPinKeyEncryptedUserKeyPersistent.mockResolvedValueOnce( - mockUserKeyEncryptedPin, - ); - - // Act - const result = await sut.decryptUserKeyWithPin(mockPin, mockUserId); - - // Assert - expect(result).toEqual(mockUserKey); - }); }); }); - -// Test helpers -function randomBytes(length: number): Uint8Array { - return new Uint8Array(Array.from({ length }, (_, k) => k % 255)); -} diff --git a/libs/common/src/key-management/pin/pin.state.ts b/libs/common/src/key-management/pin/pin.state.ts index 4ad0524035f..c3bbad7644c 100644 --- a/libs/common/src/key-management/pin/pin.state.ts +++ b/libs/common/src/key-management/pin/pin.state.ts @@ -3,22 +3,6 @@ import { PasswordProtectedKeyEnvelope } from "@bitwarden/sdk-internal"; import { EncryptedString } from "../crypto/models/enc-string"; -/** - * The persistent (stored on disk) version of the UserKey, encrypted by the PinKey. - * - * @deprecated - * @remarks Persists through a client reset. Used when `requireMasterPasswordOnClientRestart` is disabled. - * @see SetPinComponent.setPinForm.requireMasterPasswordOnClientRestart - */ -export const PIN_KEY_ENCRYPTED_USER_KEY_PERSISTENT = new UserKeyDefinition<EncryptedString>( - PIN_DISK, - "pinKeyEncryptedUserKeyPersistent", - { - deserializer: (jsonValue) => jsonValue, - clearOn: ["logout"], - }, -); - /** * The persistent (stored on disk) version of the UserKey, stored in a `PasswordProtectedKeyEnvelope`. * diff --git a/libs/common/src/key-management/services/default-process-reload.service.ts b/libs/common/src/key-management/services/default-process-reload.service.ts index ddbcf7e5530..ecd9ddafa40 100644 --- a/libs/common/src/key-management/services/default-process-reload.service.ts +++ b/libs/common/src/key-management/services/default-process-reload.service.ts @@ -56,7 +56,7 @@ export class DefaultProcessReloadService implements ProcessReloadServiceAbstract return; } - // If there is an active user, check if they have a pinKeyEncryptedUserKeyEphemeral. If so, prevent process reload upon lock. + // If there is an active user, check if they have an ephemeral PIN. If so, prevent process reload upon lock. const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id; if (userId != null) { if ((await this.pinService.getPinLockType(userId)) === "EPHEMERAL") { diff --git a/libs/common/src/types/key.ts b/libs/common/src/types/key.ts index 46dcc14a8b2..3c5487b30e0 100644 --- a/libs/common/src/types/key.ts +++ b/libs/common/src/types/key.ts @@ -9,8 +9,6 @@ export type PrfKey = Opaque<SymmetricCryptoKey, "PrfKey">; export type UserKey = Opaque<SymmetricCryptoKey, "UserKey">; /** @deprecated Interacting with the master key directly is prohibited. Use a high level function from MasterPasswordService instead. */ export type MasterKey = Opaque<SymmetricCryptoKey, "MasterKey">; -/** @deprecated */ -export type PinKey = Opaque<SymmetricCryptoKey, "PinKey">; export type OrgKey = Opaque<SymmetricCryptoKey, "OrgKey">; export type ProviderKey = Opaque<SymmetricCryptoKey, "ProviderKey">; export type CipherKey = Opaque<SymmetricCryptoKey, "CipherKey">; diff --git a/libs/importer/src/components/importer-providers.ts b/libs/importer/src/components/importer-providers.ts index c48f7c1b096..18c148ebe2e 100644 --- a/libs/importer/src/components/importer-providers.ts +++ b/libs/importer/src/components/importer-providers.ts @@ -7,8 +7,8 @@ import { safeProvider, SafeProvider } from "@bitwarden/angular/platform/utils/sa import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { KeyGenerationService } from "@bitwarden/common/key-management/crypto"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; -import { PinServiceAbstraction } from "@bitwarden/common/key-management/pin/pin.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"; @@ -84,7 +84,7 @@ export const ImporterProviders: SafeProvider[] = [ CollectionService, KeyService, EncryptService, - PinServiceAbstraction, + KeyGenerationService, AccountService, RestrictedItemTypesService, ], 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 44a55af8f62..6e98b21977d 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 @@ -2,8 +2,8 @@ import { mock, MockProxy } from "jest-mock-extended"; import { of } from "rxjs"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { KeyGenerationService } from "@bitwarden/common/key-management/crypto"; 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"; @@ -24,7 +24,7 @@ describe("BitwardenPasswordProtectedImporter", () => { let encryptService: MockProxy<EncryptService>; let i18nService: MockProxy<I18nService>; let cipherService: MockProxy<CipherService>; - let pinService: MockProxy<PinServiceAbstraction>; + let keyGenerationService: MockProxy<KeyGenerationService>; let accountService: MockProxy<AccountService>; const password = Utils.newGuid(); const promptForPassword_callback = async () => { @@ -36,7 +36,7 @@ describe("BitwardenPasswordProtectedImporter", () => { encryptService = mock<EncryptService>(); i18nService = mock<I18nService>(); cipherService = mock<CipherService>(); - pinService = mock<PinServiceAbstraction>(); + keyGenerationService = mock<KeyGenerationService>(); accountService = mock<AccountService>(); accountService.activeAccount$ = of({ @@ -71,7 +71,7 @@ describe("BitwardenPasswordProtectedImporter", () => { encryptService, i18nService, cipherService, - pinService, + keyGenerationService, accountService, promptForPassword_callback, ); @@ -105,7 +105,7 @@ describe("BitwardenPasswordProtectedImporter", () => { encryptService, i18nService, cipherService, - pinService, + keyGenerationService, accountService, promptForPassword_callback, ); diff --git a/libs/importer/src/importers/bitwarden/bitwarden-password-protected-importer.ts b/libs/importer/src/importers/bitwarden/bitwarden-password-protected-importer.ts index 7062089482d..b685ddf0fb5 100644 --- a/libs/importer/src/importers/bitwarden/bitwarden-password-protected-importer.ts +++ b/libs/importer/src/importers/bitwarden/bitwarden-password-protected-importer.ts @@ -1,9 +1,9 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +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 { PinServiceAbstraction } from "@bitwarden/common/key-management/pin/pin.service.abstraction"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; @@ -29,7 +29,7 @@ export class BitwardenPasswordProtectedImporter extends BitwardenJsonImporter im encryptService: EncryptService, i18nService: I18nService, cipherService: CipherService, - private pinService: PinServiceAbstraction, + private keyGenerationService: KeyGenerationService, accountService: AccountService, private promptForPassword_callback: () => Promise<string>, ) { @@ -86,7 +86,7 @@ export class BitwardenPasswordProtectedImporter extends BitwardenJsonImporter im ? new PBKDF2KdfConfig(jdoc.kdfIterations) : new Argon2KdfConfig(jdoc.kdfIterations, jdoc.kdfMemory, jdoc.kdfParallelism); - this.key = await this.pinService.makePinKey(password, jdoc.salt, kdfConfig); + this.key = await this.keyGenerationService.deriveVaultExportKey(password, jdoc.salt, kdfConfig); const encKeyValidation = new EncString(jdoc.encKeyValidation_DO_NOT_EDIT); diff --git a/libs/importer/src/services/import.service.spec.ts b/libs/importer/src/services/import.service.spec.ts index b1c028ff063..b82772669de 100644 --- a/libs/importer/src/services/import.service.spec.ts +++ b/libs/importer/src/services/import.service.spec.ts @@ -8,8 +8,8 @@ import { CollectionView, } from "@bitwarden/admin-console/common"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { KeyGenerationService } from "@bitwarden/common/key-management/crypto"; 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 { CollectionId, OrganizationId } from "@bitwarden/common/types/guid"; @@ -36,7 +36,7 @@ describe("ImportService", () => { let collectionService: MockProxy<CollectionService>; let keyService: MockProxy<KeyService>; let encryptService: MockProxy<EncryptService>; - let pinService: MockProxy<PinServiceAbstraction>; + let keyGenerationService: MockProxy<KeyGenerationService>; let accountService: MockProxy<AccountService>; let restrictedItemTypesService: MockProxy<RestrictedItemTypesService>; @@ -48,7 +48,7 @@ describe("ImportService", () => { collectionService = mock<CollectionService>(); keyService = mock<KeyService>(); encryptService = mock<EncryptService>(); - pinService = mock<PinServiceAbstraction>(); + keyGenerationService = mock<KeyGenerationService>(); restrictedItemTypesService = mock<RestrictedItemTypesService>(); importService = new ImportService( @@ -59,7 +59,7 @@ describe("ImportService", () => { collectionService, keyService, encryptService, - pinService, + keyGenerationService, accountService, restrictedItemTypesService, ); diff --git a/libs/importer/src/services/import.service.ts b/libs/importer/src/services/import.service.ts index f62054f9414..400beae5179 100644 --- a/libs/importer/src/services/import.service.ts +++ b/libs/importer/src/services/import.service.ts @@ -12,8 +12,8 @@ import { } from "@bitwarden/admin-console/common"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { getUserId } from "@bitwarden/common/auth/services/account.service"; +import { KeyGenerationService } from "@bitwarden/common/key-management/crypto"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; -import { PinServiceAbstraction } from "@bitwarden/common/key-management/pin/pin.service.abstraction"; import { ImportCiphersRequest } from "@bitwarden/common/models/request/import-ciphers.request"; import { ImportOrganizationCiphersRequest } from "@bitwarden/common/models/request/import-organization-ciphers.request"; import { KvpRequest } from "@bitwarden/common/models/request/kvp.request"; @@ -119,7 +119,7 @@ export class ImportService implements ImportServiceAbstraction { private collectionService: CollectionService, private keyService: KeyService, private encryptService: EncryptService, - private pinService: PinServiceAbstraction, + private keyGenerationService: KeyGenerationService, private accountService: AccountService, private restrictedItemTypesService: RestrictedItemTypesService, ) {} @@ -238,7 +238,7 @@ export class ImportService implements ImportServiceAbstraction { this.encryptService, this.i18nService, this.cipherService, - this.pinService, + this.keyGenerationService, this.accountService, promptForPassword_callback, ); diff --git a/libs/state/src/state-migrations/migrate.ts b/libs/state/src/state-migrations/migrate.ts index bf4cd17adba..a051c25695a 100644 --- a/libs/state/src/state-migrations/migrate.ts +++ b/libs/state/src/state-migrations/migrate.ts @@ -70,12 +70,13 @@ import { RemoveAcBannersDismissed } from "./migrations/70-remove-ac-banner-dismi import { RemoveNewCustomizationOptionsCalloutDismissed } from "./migrations/71-remove-new-customization-options-callout-dismissed"; import { RemoveAccountDeprovisioningBannerDismissed } from "./migrations/72-remove-account-deprovisioning-banner-dismissed"; import { AddMasterPasswordUnlockData } from "./migrations/73-add-master-password-unlock-data"; +import { RemoveLegacyPin } from "./migrations/74-remove-legacy-pin"; import { MoveStateVersionMigrator } from "./migrations/8-move-state-version"; import { MoveBrowserSettingsToGlobal } from "./migrations/9-move-browser-settings-to-global"; import { MinVersionMigrator } from "./migrations/min-version"; export const MIN_VERSION = 3; -export const CURRENT_VERSION = 73; +export const CURRENT_VERSION = 74; export type MinVersion = typeof MIN_VERSION; export function createMigrationBuilder() { @@ -150,7 +151,8 @@ export function createMigrationBuilder() { .with(RemoveAcBannersDismissed, 69, 70) .with(RemoveNewCustomizationOptionsCalloutDismissed, 70, 71) .with(RemoveAccountDeprovisioningBannerDismissed, 71, 72) - .with(AddMasterPasswordUnlockData, 72, CURRENT_VERSION); + .with(AddMasterPasswordUnlockData, 72, 73) + .with(RemoveLegacyPin, 73, CURRENT_VERSION); } export async function currentVersion( diff --git a/libs/state/src/state-migrations/migrations/74-remove-legacy-pin.spec.ts b/libs/state/src/state-migrations/migrations/74-remove-legacy-pin.spec.ts new file mode 100644 index 00000000000..842410b2706 --- /dev/null +++ b/libs/state/src/state-migrations/migrations/74-remove-legacy-pin.spec.ts @@ -0,0 +1,50 @@ +import { runMigrator } from "../migration-helper.spec"; +import { IRREVERSIBLE } from "../migrator"; + +import { RemoveLegacyPin } from "./74-remove-legacy-pin"; + +describe("RemoveLegacyPin", () => { + const sut = new RemoveLegacyPin(73, 74); + + describe("migrate", () => { + it("deletes legacy pin from all users", async () => { + const output = await runMigrator(sut, { + global_account_accounts: { + user1: { + email: "user1@email.com", + name: "User 1", + emailVerified: true, + }, + user2: { + email: "user2@email.com", + name: "User 2", + emailVerified: true, + }, + }, + user_user1_pinUnlock_pinKeyEncryptedUserKeyPersistent: "abc", + user_user2_pinUnlock_pinKeyEncryptedUserKeyPersistent: "def", + }); + + expect(output).toEqual({ + global_account_accounts: { + user1: { + email: "user1@email.com", + name: "User 1", + emailVerified: true, + }, + user2: { + email: "user2@email.com", + name: "User 2", + emailVerified: true, + }, + }, + }); + }); + }); + + describe("rollback", () => { + it("is irreversible", async () => { + await expect(runMigrator(sut, {}, "rollback")).rejects.toThrow(IRREVERSIBLE); + }); + }); +}); diff --git a/libs/state/src/state-migrations/migrations/74-remove-legacy-pin.ts b/libs/state/src/state-migrations/migrations/74-remove-legacy-pin.ts new file mode 100644 index 00000000000..277ae5832b5 --- /dev/null +++ b/libs/state/src/state-migrations/migrations/74-remove-legacy-pin.ts @@ -0,0 +1,30 @@ +import { KeyDefinitionLike, MigrationHelper } from "../migration-helper"; +import { IRREVERSIBLE, Migrator } from "../migrator"; + +type ExpectedAccountType = NonNullable<unknown>; + +export const PinProtectedUserKey: KeyDefinitionLike = { + key: "pinKeyEncryptedUserKeyPersistent", + stateDefinition: { + name: "pinUnlock", + }, +}; + +export class RemoveLegacyPin extends Migrator<73, 74> { + async migrate(helper: MigrationHelper): Promise<void> { + const accounts = await helper.getAccounts<ExpectedAccountType>(); + async function migrateAccount(userId: string, account: ExpectedAccountType): Promise<void> { + const pinProtectedUserKey = await helper.getFromUser(userId, PinProtectedUserKey); + + if (pinProtectedUserKey != null) { + await helper.removeFromUser(userId, PinProtectedUserKey); + } + } + + await Promise.all([...accounts.map(({ userId, account }) => migrateAccount(userId, account))]); + } + + async rollback(helper: MigrationHelper): Promise<void> { + throw IRREVERSIBLE; + } +} diff --git a/libs/tools/export/vault-export/vault-export-core/src/services/base-vault-export.service.ts b/libs/tools/export/vault-export/vault-export-core/src/services/base-vault-export.service.ts index c11ab3d9e6b..620f465789c 100644 --- a/libs/tools/export/vault-export/vault-export-core/src/services/base-vault-export.service.ts +++ b/libs/tools/export/vault-export/vault-export-core/src/services/base-vault-export.service.ts @@ -1,8 +1,8 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore +import { KeyGenerationService } from "@bitwarden/common/key-management/crypto"; import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; -import { PinServiceAbstraction } from "@bitwarden/common/key-management/pin/pin.service.abstraction"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { UserId } from "@bitwarden/common/types/guid"; import { CipherType } from "@bitwarden/common/vault/enums"; @@ -12,7 +12,7 @@ import { KdfConfig, KdfConfigService, KdfType } from "@bitwarden/key-management" import { BitwardenCsvExportType, BitwardenPasswordProtectedFileFormat } from "../types"; export class BaseVaultExportService { constructor( - protected pinService: PinServiceAbstraction, + protected keyGenerationService: KeyGenerationService, protected encryptService: EncryptService, private cryptoFunctionService: CryptoFunctionService, private kdfConfigService: KdfConfigService, @@ -26,7 +26,8 @@ export class BaseVaultExportService { const kdfConfig: KdfConfig = await this.kdfConfigService.getKdfConfig(userId); const salt = Utils.fromBufferToB64(await this.cryptoFunctionService.randomBytes(16)); - const key = await this.pinService.makePinKey(password, salt, kdfConfig); + + const key = await this.keyGenerationService.deriveVaultExportKey(password, salt, kdfConfig); const encKeyValidation = await this.encryptService.encryptString(Utils.newGuid(), key); const encText = await this.encryptService.encryptString(clearText, key); diff --git a/libs/tools/export/vault-export/vault-export-core/src/services/individual-vault-export.service.spec.ts b/libs/tools/export/vault-export/vault-export-core/src/services/individual-vault-export.service.spec.ts index 4214873feed..33dde9ae51a 100644 --- a/libs/tools/export/vault-export/vault-export-core/src/services/individual-vault-export.service.spec.ts +++ b/libs/tools/export/vault-export/vault-export-core/src/services/individual-vault-export.service.spec.ts @@ -3,13 +3,13 @@ import * as JSZip from "jszip"; import { BehaviorSubject, of } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { KeyGenerationService } from "@bitwarden/common/key-management/crypto"; import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { EncryptedString, EncString, } from "@bitwarden/common/key-management/crypto/models/enc-string"; -import { PinServiceAbstraction } from "@bitwarden/common/key-management/pin/pin.service.abstraction"; import { CipherWithIdExport } from "@bitwarden/common/models/export/cipher-with-ids.export"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { CipherId, emptyGuid, UserId } from "@bitwarden/common/types/guid"; @@ -169,7 +169,7 @@ describe("VaultExportService", () => { let exportService: IndividualVaultExportService; let cryptoFunctionService: MockProxy<CryptoFunctionService>; let cipherService: MockProxy<CipherService>; - let pinService: MockProxy<PinServiceAbstraction>; + let keyGenerationService: MockProxy<KeyGenerationService>; let folderService: MockProxy<FolderService>; let keyService: MockProxy<KeyService>; let encryptService: MockProxy<EncryptService>; @@ -184,7 +184,7 @@ describe("VaultExportService", () => { beforeEach(() => { cryptoFunctionService = mock<CryptoFunctionService>(); cipherService = mock<CipherService>(); - pinService = mock<PinServiceAbstraction>(); + keyGenerationService = mock<KeyGenerationService>(); folderService = mock<FolderService>(); keyService = mock<KeyService>(); encryptService = mock<EncryptService>(); @@ -220,7 +220,7 @@ describe("VaultExportService", () => { exportService = new IndividualVaultExportService( folderService, cipherService, - pinService, + keyGenerationService, keyService, encryptService, cryptoFunctionService, diff --git a/libs/tools/export/vault-export/vault-export-core/src/services/individual-vault-export.service.ts b/libs/tools/export/vault-export/vault-export-core/src/services/individual-vault-export.service.ts index e7a97801e09..ddda96b21e0 100644 --- a/libs/tools/export/vault-export/vault-export-core/src/services/individual-vault-export.service.ts +++ b/libs/tools/export/vault-export/vault-export-core/src/services/individual-vault-export.service.ts @@ -5,9 +5,9 @@ import * as papa from "papaparse"; import { firstValueFrom } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { KeyGenerationService } from "@bitwarden/common/key-management/crypto"; import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; -import { PinServiceAbstraction } from "@bitwarden/common/key-management/pin/pin.service.abstraction"; import { CipherWithIdExport, FolderWithIdExport } from "@bitwarden/common/models/export"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { CipherId, UserId } from "@bitwarden/common/types/guid"; @@ -42,7 +42,7 @@ export class IndividualVaultExportService constructor( private folderService: FolderService, private cipherService: CipherService, - pinService: PinServiceAbstraction, + keyGenerationService: KeyGenerationService, private keyService: KeyService, encryptService: EncryptService, cryptoFunctionService: CryptoFunctionService, @@ -50,7 +50,7 @@ export class IndividualVaultExportService private apiService: ApiService, private restrictedItemTypesService: RestrictedItemTypesService, ) { - super(pinService, encryptService, cryptoFunctionService, kdfConfigService); + super(keyGenerationService, encryptService, cryptoFunctionService, kdfConfigService); } /** Creates an export of an individual vault (My Vault). Based on the provided format it will either be unencrypted, encrypted or password protected and in case zip is selected will include attachments 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 678dd600f94..ed3a16516f2 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 @@ -10,9 +10,9 @@ import { CollectionDetailsResponse, CollectionView, } from "@bitwarden/admin-console/common"; +import { KeyGenerationService } from "@bitwarden/common/key-management/crypto"; import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; -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"; @@ -46,7 +46,7 @@ export class OrganizationVaultExportService constructor( private cipherService: CipherService, private vaultExportApiService: VaultExportApiService, - pinService: PinServiceAbstraction, + keyGenerationService: KeyGenerationService, private keyService: KeyService, encryptService: EncryptService, cryptoFunctionService: CryptoFunctionService, @@ -54,7 +54,7 @@ export class OrganizationVaultExportService kdfConfigService: KdfConfigService, private restrictedItemTypesService: RestrictedItemTypesService, ) { - super(pinService, encryptService, cryptoFunctionService, kdfConfigService); + super(keyGenerationService, encryptService, cryptoFunctionService, kdfConfigService); } /** Creates a password protected export of an organizational vault. From 33d909b0bbe0ce0d9658404a4bbc998eb18627ed Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 11 Dec 2025 14:28:40 +0100 Subject: [PATCH 048/188] [deps] Platform: Update Rust crate rand to v0.9.2 (#17550) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- apps/desktop/desktop_native/Cargo.lock | 12 ++++++------ apps/desktop/desktop_native/Cargo.toml | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/desktop/desktop_native/Cargo.lock b/apps/desktop/desktop_native/Cargo.lock index 53eee09d9b8..5978659f21e 100644 --- a/apps/desktop/desktop_native/Cargo.lock +++ b/apps/desktop/desktop_native/Cargo.lock @@ -130,7 +130,7 @@ dependencies = [ "enumflags2", "futures-channel", "futures-util", - "rand 0.9.1", + "rand 0.9.2", "serde", "serde_repr", "tokio", @@ -590,7 +590,7 @@ dependencies = [ "hex", "oo7", "pbkdf2", - "rand 0.9.1", + "rand 0.9.2", "rusqlite", "security-framework", "serde", @@ -833,7 +833,7 @@ dependencies = [ "memsec", "oo7", "pin-project", - "rand 0.9.1", + "rand 0.9.2", "scopeguard", "secmem-proc", "security-framework", @@ -2153,7 +2153,7 @@ dependencies = [ "num", "num-bigint-dig", "pbkdf2", - "rand 0.9.1", + "rand 0.9.2", "serde", "sha2", "subtle", @@ -2536,9 +2536,9 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.1" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.3", diff --git a/apps/desktop/desktop_native/Cargo.toml b/apps/desktop/desktop_native/Cargo.toml index 74a1b6bb1da..26f791fd660 100644 --- a/apps/desktop/desktop_native/Cargo.toml +++ b/apps/desktop/desktop_native/Cargo.toml @@ -48,7 +48,7 @@ napi-derive = "=3.2.5" oo7 = "=0.5.0" pin-project = "=1.1.10" pkcs8 = "=0.10.2" -rand = "=0.9.1" +rand = "=0.9.2" rsa = "=0.9.6" russh-cryptovec = "=0.7.3" scopeguard = "=1.2.0" From bcc2bda4174c6e1c61feee1fc9a84a24bdbcda07 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann <mail@quexten.com> Date: Thu, 11 Dec 2025 14:29:48 +0100 Subject: [PATCH 049/188] Fix kdf prompt not working on browser (#17902) --- .../encrypted-migrations-scheduler.service.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/angular/src/key-management/encrypted-migration/encrypted-migrations-scheduler.service.ts b/libs/angular/src/key-management/encrypted-migration/encrypted-migrations-scheduler.service.ts index cf79c65e998..dd248a582d3 100644 --- a/libs/angular/src/key-management/encrypted-migration/encrypted-migrations-scheduler.service.ts +++ b/libs/angular/src/key-management/encrypted-migration/encrypted-migrations-scheduler.service.ts @@ -38,7 +38,7 @@ export const ENCRYPTED_MIGRATION_DISMISSED = new UserKeyDefinition<Date>( }, ); const DISMISS_TIME_HOURS = 24; -const VAULT_ROUTE = "/vault"; +const VAULT_ROUTES = ["/vault", "/tabs/vault", "/tabs/current"]; /** * This services schedules encrypted migrations for users on clients that are interactive (non-cli), and handles manual interaction, @@ -85,7 +85,7 @@ export class DefaultEncryptedMigrationsSchedulerService implements EncryptedMigr ]).pipe( filter( ([authStatus, _date, url]) => - authStatus === AuthenticationStatus.Unlocked && url === VAULT_ROUTE, + authStatus === AuthenticationStatus.Unlocked && VAULT_ROUTES.includes(url), ), concatMap(() => this.runMigrationsIfNeeded(userId)), ), From 458da1adc0eca479ff35f5a4b1b327b37ad33d53 Mon Sep 17 00:00:00 2001 From: Daniel James Smith <2670567+djsmith85@users.noreply.github.com> Date: Thu, 11 Dec 2025 14:38:33 +0100 Subject: [PATCH 050/188] [PM-29565] Delete deprecated callout component (#17895) * Replace usages of app-callout with bit-callout * Delete callout.component --------- Co-authored-by: Daniel James Smith <djsmith85@users.noreply.github.com> --- .../components/approve-ssh-request.html | 10 ++- .../components/approve-ssh-request.ts | 2 + .../auth/verify-recover-delete.component.html | 2 +- ...ify-recover-delete-provider.component.html | 2 +- .../src/components/callout.component.html | 26 ------- .../src/components/callout.component.ts | 70 ------------------- libs/angular/src/jslib.module.ts | 3 - 7 files changed, 8 insertions(+), 107 deletions(-) delete mode 100644 libs/angular/src/components/callout.component.html delete mode 100644 libs/angular/src/components/callout.component.ts diff --git a/apps/desktop/src/platform/components/approve-ssh-request.html b/apps/desktop/src/platform/components/approve-ssh-request.html index 55092788079..c691891487e 100644 --- a/apps/desktop/src/platform/components/approve-ssh-request.html +++ b/apps/desktop/src/platform/components/approve-ssh-request.html @@ -2,13 +2,11 @@ <bit-dialog> <div class="tw-font-medium" bitDialogTitle>{{ "sshkeyApprovalTitle" | i18n }}</div> <div bitDialogContent> - <app-callout - type="warning" - title="{{ 'agentForwardingWarningTitle' | i18n }}" - *ngIf="params.isAgentForwarding" - > + @if (params.isAgentForwarding) { + <bit-callout type="warning" title="{{ 'agentForwardingWarningTitle' | i18n }}"> {{ 'agentForwardingWarningText' | i18n }} - </app-callout> + </bit-callout> + } <b>{{params.applicationName}}</b> {{ "sshkeyApprovalMessageInfix" | i18n }} <b>{{params.cipherName}}</b> diff --git a/apps/desktop/src/platform/components/approve-ssh-request.ts b/apps/desktop/src/platform/components/approve-ssh-request.ts index 1741124774d..a2cae3d59e7 100644 --- a/apps/desktop/src/platform/components/approve-ssh-request.ts +++ b/apps/desktop/src/platform/components/approve-ssh-request.ts @@ -12,6 +12,7 @@ import { FormFieldModule, IconButtonModule, DialogService, + CalloutModule, } from "@bitwarden/components"; export interface ApproveSshRequestParams { @@ -35,6 +36,7 @@ export interface ApproveSshRequestParams { ReactiveFormsModule, AsyncActionsModule, FormFieldModule, + CalloutModule, ], }) export class ApproveSshRequestComponent { diff --git a/apps/web/src/app/auth/verify-recover-delete.component.html b/apps/web/src/app/auth/verify-recover-delete.component.html index 02581b21418..27eda24a118 100644 --- a/apps/web/src/app/auth/verify-recover-delete.component.html +++ b/apps/web/src/app/auth/verify-recover-delete.component.html @@ -1,5 +1,5 @@ <form [bitSubmit]="submit" [formGroup]="formGroup"> - <app-callout type="warning">{{ "deleteAccountWarning" | i18n }}</app-callout> + <bit-callout type="warning">{{ "deleteAccountWarning" | i18n }}</bit-callout> <p bitTypography="body1" class="tw-text-center"> <strong>{{ email }}</strong> </p> diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/verify-recover-delete-provider.component.html b/bitwarden_license/bit-web/src/app/admin-console/providers/verify-recover-delete-provider.component.html index e1f99122b22..6965dbe8198 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/verify-recover-delete-provider.component.html +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/verify-recover-delete-provider.component.html @@ -1,5 +1,5 @@ <h2 class="tw-text-center tw-mb-4">{{ "deleteProvider" | i18n }}</h2> -<app-callout type="warning">{{ "deleteProviderWarning" | i18n }}</app-callout> +<bit-callout type="warning">{{ "deleteProviderWarning" | i18n }}</bit-callout> <p class="tw-text-center"> <strong>{{ name }}</strong> </p> diff --git a/libs/angular/src/components/callout.component.html b/libs/angular/src/components/callout.component.html deleted file mode 100644 index 7e352fa0ced..00000000000 --- a/libs/angular/src/components/callout.component.html +++ /dev/null @@ -1,26 +0,0 @@ -<bit-callout [icon]="icon" [title]="title" [type]="$any(type)" [useAlertRole]="useAlertRole"> - <div class="tw-pl-7 tw-m-0" *ngIf="enforcedPolicyOptions"> - {{ enforcedPolicyMessage }} - <ul> - <li *ngIf="enforcedPolicyOptions?.minComplexity > 0"> - {{ "policyInEffectMinComplexity" | i18n: getPasswordScoreAlertDisplay() }} - </li> - <li *ngIf="enforcedPolicyOptions?.minLength > 0"> - {{ "policyInEffectMinLength" | i18n: enforcedPolicyOptions?.minLength.toString() }} - </li> - <li *ngIf="enforcedPolicyOptions?.requireUpper"> - {{ "policyInEffectUppercase" | i18n }} - </li> - <li *ngIf="enforcedPolicyOptions?.requireLower"> - {{ "policyInEffectLowercase" | i18n }} - </li> - <li *ngIf="enforcedPolicyOptions?.requireNumbers"> - {{ "policyInEffectNumbers" | i18n }} - </li> - <li *ngIf="enforcedPolicyOptions?.requireSpecial"> - {{ "policyInEffectSpecial" | i18n: "!@#$%^&*" }} - </li> - </ul> - </div> - <ng-content></ng-content> -</bit-callout> diff --git a/libs/angular/src/components/callout.component.ts b/libs/angular/src/components/callout.component.ts deleted file mode 100644 index 9630b761076..00000000000 --- a/libs/angular/src/components/callout.component.ts +++ /dev/null @@ -1,70 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Component, Input, OnInit } from "@angular/core"; - -import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { CalloutTypes } from "@bitwarden/components"; - -/** - * @deprecated use the CL's `CalloutComponent` instead - */ -// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush -// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection -@Component({ - selector: "app-callout", - templateUrl: "callout.component.html", - standalone: false, -}) -export class DeprecatedCalloutComponent implements OnInit { - // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals - // eslint-disable-next-line @angular-eslint/prefer-signals - @Input() type: CalloutTypes = "info"; - // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals - // eslint-disable-next-line @angular-eslint/prefer-signals - @Input() icon: string; - // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals - // eslint-disable-next-line @angular-eslint/prefer-signals - @Input() title: string; - // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals - // eslint-disable-next-line @angular-eslint/prefer-signals - @Input() enforcedPolicyOptions: MasterPasswordPolicyOptions; - // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals - // eslint-disable-next-line @angular-eslint/prefer-signals - @Input() enforcedPolicyMessage: string; - // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals - // eslint-disable-next-line @angular-eslint/prefer-signals - @Input() useAlertRole = false; - - calloutStyle: string; - - constructor(private i18nService: I18nService) {} - - ngOnInit() { - this.calloutStyle = this.type; - - if (this.enforcedPolicyMessage === undefined) { - this.enforcedPolicyMessage = this.i18nService.t("masterPasswordPolicyInEffect"); - } - } - - getPasswordScoreAlertDisplay() { - if (this.enforcedPolicyOptions == null) { - return ""; - } - - let str: string; - switch (this.enforcedPolicyOptions.minComplexity) { - case 4: - str = this.i18nService.t("strong"); - break; - case 3: - str = this.i18nService.t("good"); - break; - default: - str = this.i18nService.t("weak"); - break; - } - return str + " (" + this.enforcedPolicyOptions.minComplexity + ")"; - } -} diff --git a/libs/angular/src/jslib.module.ts b/libs/angular/src/jslib.module.ts index 446530a1111..8d222a4aaf9 100644 --- a/libs/angular/src/jslib.module.ts +++ b/libs/angular/src/jslib.module.ts @@ -26,7 +26,6 @@ import { import { TwoFactorIconComponent } from "./auth/components/two-factor-icon.component"; import { NotPremiumDirective } from "./billing/directives/not-premium.directive"; -import { DeprecatedCalloutComponent } from "./components/callout.component"; import { A11yInvalidDirective } from "./directives/a11y-invalid.directive"; import { ApiActionDirective } from "./directives/api-action.directive"; import { BoxRowDirective } from "./directives/box-row.directive"; @@ -86,7 +85,6 @@ import { IconComponent } from "./vault/components/icon.component"; A11yInvalidDirective, ApiActionDirective, BoxRowDirective, - DeprecatedCalloutComponent, CopyTextDirective, CreditCardNumberPipe, EllipsisPipe, @@ -115,7 +113,6 @@ import { IconComponent } from "./vault/components/icon.component"; AutofocusDirective, ToastModule, BoxRowDirective, - DeprecatedCalloutComponent, CopyTextDirective, CreditCardNumberPipe, EllipsisPipe, From dc763f6291e70126ec9492bbe991592bc464c836 Mon Sep 17 00:00:00 2001 From: Daniel James Smith <2670567+djsmith85@users.noreply.github.com> Date: Thu, 11 Dec 2025 14:58:21 +0100 Subject: [PATCH 051/188] Group all tokio related packages in renovate (#17922) Co-authored-by: Daniel James Smith <djsmith85@users.noreply.github.com> --- .github/renovate.json5 | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 858dcccc094..96e16776545 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -214,6 +214,8 @@ "simplelog", "style-loader", "sysinfo", + "tokio", + "tokio-util", "tracing", "tracing-subscriber", "ts-node", @@ -260,6 +262,11 @@ groupName: "windows", matchPackageNames: ["windows", "windows-core", "windows-future", "windows-registry"], }, + { + // We need to group all tokio-related packages together to avoid build errors caused by version incompatibilities. + groupName: "tokio", + matchPackageNames: ["bytes", "tokio", "tokio-util"], + }, { // We group all webpack build-related minor and patch updates together to reduce PR noise. // We include patch updates here because we want PRs for webpack patch updates and it's in this group. From 50dff4e0326b3b474ea30cea6d12c358b21a26fe Mon Sep 17 00:00:00 2001 From: Brandon Treston <btreston@bitwarden.com> Date: Thu, 11 Dec 2025 10:30:05 -0500 Subject: [PATCH 052/188] [PM-28422] Client batching for member actions (#17805) * add member action batching, feature flag, and implement batching for reinvite * clean up, fix tests, remove redundant tests * cleanup * clean up tests * bump cloud limit to 8k --- .../common/people-table-data-source.ts | 2 +- .../members/members.component.ts | 2 +- .../member-actions.service.spec.ts | 343 +++++++++++++++--- .../member-actions/member-actions.service.ts | 89 ++++- 4 files changed, 378 insertions(+), 58 deletions(-) diff --git a/apps/web/src/app/admin-console/common/people-table-data-source.ts b/apps/web/src/app/admin-console/common/people-table-data-source.ts index 0228edb1e8c..9ac370d8c0d 100644 --- a/apps/web/src/app/admin-console/common/people-table-data-source.ts +++ b/apps/web/src/app/admin-console/common/people-table-data-source.ts @@ -24,7 +24,7 @@ export const MaxCheckedCount = 500; * Maximum for bulk reinvite operations when the IncreaseBulkReinviteLimitForCloud * feature flag is enabled on cloud environments. */ -export const CloudBulkReinviteLimit = 4000; +export const CloudBulkReinviteLimit = 8000; /** * Returns true if the user matches the status, or where the status is `null`, if the user is active (not revoked). diff --git a/apps/web/src/app/admin-console/organizations/members/members.component.ts b/apps/web/src/app/admin-console/organizations/members/members.component.ts index ac25278a636..51a2a6dafc0 100644 --- a/apps/web/src/app/admin-console/organizations/members/members.component.ts +++ b/apps/web/src/app/admin-console/organizations/members/members.component.ts @@ -443,7 +443,7 @@ export class MembersComponent extends BaseMembersComponent<OrganizationUserView> try { const result = await this.memberActionsService.bulkReinvite( organization, - filteredUsers.map((user) => user.id), + filteredUsers.map((user) => user.id as UserId), ); if (!result.successful) { diff --git a/apps/web/src/app/admin-console/organizations/members/services/member-actions/member-actions.service.spec.ts b/apps/web/src/app/admin-console/organizations/members/services/member-actions/member-actions.service.spec.ts index e856ab7afd1..80a330b0db1 100644 --- a/apps/web/src/app/admin-console/organizations/members/services/member-actions/member-actions.service.spec.ts +++ b/apps/web/src/app/admin-console/organizations/members/services/member-actions/member-actions.service.spec.ts @@ -4,6 +4,7 @@ import { of } from "rxjs"; import { OrganizationUserApiService, OrganizationUserBulkResponse, + OrganizationUserService, } from "@bitwarden/admin-console/common"; import { OrganizationUserType, @@ -22,9 +23,8 @@ import { newGuid } from "@bitwarden/guid"; import { KeyService } from "@bitwarden/key-management"; import { OrganizationUserView } from "../../../core/views/organization-user.view"; -import { OrganizationUserService } from "../organization-user/organization-user.service"; -import { MemberActionsService } from "./member-actions.service"; +import { REQUESTS_PER_BATCH, MemberActionsService } from "./member-actions.service"; describe("MemberActionsService", () => { let service: MemberActionsService; @@ -308,41 +308,308 @@ describe("MemberActionsService", () => { }); describe("bulkReinvite", () => { - const userIds = [newGuid(), newGuid(), newGuid()]; + const userIds = [newGuid() as UserId, newGuid() as UserId, newGuid() as UserId]; - it("should successfully reinvite multiple users", async () => { - const mockResponse = { - data: userIds.map((id) => ({ - id, - error: null, - })), - continuationToken: null, - } as ListResponse<OrganizationUserBulkResponse>; - organizationUserApiService.postManyOrganizationUserReinvite.mockResolvedValue(mockResponse); - - const result = await service.bulkReinvite(mockOrganization, userIds); - - expect(result).toEqual({ - successful: mockResponse, - failed: [], + describe("when feature flag is false", () => { + beforeEach(() => { + configService.getFeatureFlag$.mockReturnValue(of(false)); + }); + + it("should successfully reinvite multiple users", async () => { + const mockResponse = new ListResponse( + { + data: userIds.map((id) => ({ + id, + error: null, + })), + continuationToken: null, + }, + OrganizationUserBulkResponse, + ); + + organizationUserApiService.postManyOrganizationUserReinvite.mockResolvedValue(mockResponse); + + const result = await service.bulkReinvite(mockOrganization, userIds); + + expect(result.failed).toEqual([]); + expect(result.successful).toBeDefined(); + expect(result.successful).toEqual(mockResponse); + expect(organizationUserApiService.postManyOrganizationUserReinvite).toHaveBeenCalledWith( + organizationId, + userIds, + ); + }); + + it("should handle bulk reinvite errors", async () => { + const errorMessage = "Bulk reinvite failed"; + organizationUserApiService.postManyOrganizationUserReinvite.mockRejectedValue( + new Error(errorMessage), + ); + + const result = await service.bulkReinvite(mockOrganization, userIds); + + expect(result.successful).toBeUndefined(); + expect(result.failed).toHaveLength(3); + expect(result.failed[0]).toEqual({ id: userIds[0], error: errorMessage }); }); - expect(organizationUserApiService.postManyOrganizationUserReinvite).toHaveBeenCalledWith( - organizationId, - userIds, - ); }); - it("should handle bulk reinvite errors", async () => { - const errorMessage = "Bulk reinvite failed"; - organizationUserApiService.postManyOrganizationUserReinvite.mockRejectedValue( - new Error(errorMessage), - ); + describe("when feature flag is true (batching behavior)", () => { + beforeEach(() => { + configService.getFeatureFlag$.mockReturnValue(of(true)); + }); + it("should process users in a single batch when count equals REQUESTS_PER_BATCH", async () => { + const userIdsBatch = Array.from({ length: REQUESTS_PER_BATCH }, () => newGuid() as UserId); + const mockResponse = new ListResponse( + { + data: userIdsBatch.map((id) => ({ + id, + error: null, + })), + continuationToken: null, + }, + OrganizationUserBulkResponse, + ); - const result = await service.bulkReinvite(mockOrganization, userIds); + organizationUserApiService.postManyOrganizationUserReinvite.mockResolvedValue(mockResponse); - expect(result.successful).toBeUndefined(); - expect(result.failed).toHaveLength(3); - expect(result.failed[0]).toEqual({ id: userIds[0], error: errorMessage }); + const result = await service.bulkReinvite(mockOrganization, userIdsBatch); + + expect(result.successful).toBeDefined(); + expect(result.successful?.response).toHaveLength(REQUESTS_PER_BATCH); + expect(result.failed).toHaveLength(0); + expect(organizationUserApiService.postManyOrganizationUserReinvite).toHaveBeenCalledTimes( + 1, + ); + expect(organizationUserApiService.postManyOrganizationUserReinvite).toHaveBeenCalledWith( + organizationId, + userIdsBatch, + ); + }); + + it("should process users in multiple batches when count exceeds REQUESTS_PER_BATCH", async () => { + const totalUsers = REQUESTS_PER_BATCH + 100; + const userIdsBatch = Array.from({ length: totalUsers }, () => newGuid() as UserId); + + const mockResponse1 = new ListResponse( + { + data: userIdsBatch.slice(0, REQUESTS_PER_BATCH).map((id) => ({ + id, + error: null, + })), + continuationToken: null, + }, + OrganizationUserBulkResponse, + ); + + const mockResponse2 = new ListResponse( + { + data: userIdsBatch.slice(REQUESTS_PER_BATCH).map((id) => ({ + id, + error: null, + })), + continuationToken: null, + }, + OrganizationUserBulkResponse, + ); + + organizationUserApiService.postManyOrganizationUserReinvite + .mockResolvedValueOnce(mockResponse1) + .mockResolvedValueOnce(mockResponse2); + + const result = await service.bulkReinvite(mockOrganization, userIdsBatch); + + expect(result.successful).toBeDefined(); + expect(result.successful?.response).toHaveLength(totalUsers); + expect(result.failed).toHaveLength(0); + expect(organizationUserApiService.postManyOrganizationUserReinvite).toHaveBeenCalledTimes( + 2, + ); + expect(organizationUserApiService.postManyOrganizationUserReinvite).toHaveBeenNthCalledWith( + 1, + organizationId, + userIdsBatch.slice(0, REQUESTS_PER_BATCH), + ); + expect(organizationUserApiService.postManyOrganizationUserReinvite).toHaveBeenNthCalledWith( + 2, + organizationId, + userIdsBatch.slice(REQUESTS_PER_BATCH), + ); + }); + + it("should aggregate results across multiple successful batches", async () => { + const totalUsers = REQUESTS_PER_BATCH + 50; + const userIdsBatch = Array.from({ length: totalUsers }, () => newGuid() as UserId); + + const mockResponse1 = new ListResponse( + { + data: userIdsBatch.slice(0, REQUESTS_PER_BATCH).map((id) => ({ + id, + error: null, + })), + continuationToken: null, + }, + OrganizationUserBulkResponse, + ); + + const mockResponse2 = new ListResponse( + { + data: userIdsBatch.slice(REQUESTS_PER_BATCH).map((id) => ({ + id, + error: null, + })), + continuationToken: null, + }, + OrganizationUserBulkResponse, + ); + + organizationUserApiService.postManyOrganizationUserReinvite + .mockResolvedValueOnce(mockResponse1) + .mockResolvedValueOnce(mockResponse2); + + const result = await service.bulkReinvite(mockOrganization, userIdsBatch); + + expect(result.successful).toBeDefined(); + expect(result.successful?.response).toHaveLength(totalUsers); + expect(result.successful?.response.slice(0, REQUESTS_PER_BATCH)).toEqual( + mockResponse1.data, + ); + expect(result.successful?.response.slice(REQUESTS_PER_BATCH)).toEqual(mockResponse2.data); + expect(result.failed).toHaveLength(0); + }); + + it("should handle mixed individual errors across multiple batches", async () => { + const totalUsers = REQUESTS_PER_BATCH + 4; + const userIdsBatch = Array.from({ length: totalUsers }, () => newGuid() as UserId); + + const mockResponse1 = new ListResponse( + { + data: userIdsBatch.slice(0, REQUESTS_PER_BATCH).map((id, index) => ({ + id, + error: index % 10 === 0 ? "Rate limit exceeded" : null, + })), + continuationToken: null, + }, + OrganizationUserBulkResponse, + ); + + const mockResponse2 = new ListResponse( + { + data: [ + { id: userIdsBatch[REQUESTS_PER_BATCH], error: null }, + { id: userIdsBatch[REQUESTS_PER_BATCH + 1], error: "Invalid email" }, + { id: userIdsBatch[REQUESTS_PER_BATCH + 2], error: null }, + { id: userIdsBatch[REQUESTS_PER_BATCH + 3], error: "User suspended" }, + ], + continuationToken: null, + }, + OrganizationUserBulkResponse, + ); + + organizationUserApiService.postManyOrganizationUserReinvite + .mockResolvedValueOnce(mockResponse1) + .mockResolvedValueOnce(mockResponse2); + + const result = await service.bulkReinvite(mockOrganization, userIdsBatch); + + // Count expected failures: every 10th index (0, 10, 20, ..., 490) in first batch + 2 explicit in second batch + // Indices 0 to REQUESTS_PER_BATCH-1 where index % 10 === 0: that's floor((BATCH_SIZE-1)/10) + 1 values + const expectedFailuresInBatch1 = Math.floor((REQUESTS_PER_BATCH - 1) / 10) + 1; + const expectedFailuresInBatch2 = 2; + const expectedTotalFailures = expectedFailuresInBatch1 + expectedFailuresInBatch2; + const expectedSuccesses = totalUsers - expectedTotalFailures; + + expect(result.successful).toBeDefined(); + expect(result.successful?.response).toHaveLength(expectedSuccesses); + expect(result.failed).toHaveLength(expectedTotalFailures); + expect(result.failed.some((f) => f.error === "Rate limit exceeded")).toBe(true); + expect(result.failed.some((f) => f.error === "Invalid email")).toBe(true); + expect(result.failed.some((f) => f.error === "User suspended")).toBe(true); + }); + + it("should aggregate all failures when all batches fail", async () => { + const totalUsers = REQUESTS_PER_BATCH + 100; + const userIdsBatch = Array.from({ length: totalUsers }, () => newGuid() as UserId); + const errorMessage = "All batches failed"; + + organizationUserApiService.postManyOrganizationUserReinvite.mockRejectedValue( + new Error(errorMessage), + ); + + const result = await service.bulkReinvite(mockOrganization, userIdsBatch); + + expect(result.successful).toBeUndefined(); + expect(result.failed).toHaveLength(totalUsers); + expect(result.failed.every((f) => f.error === errorMessage)).toBe(true); + expect(organizationUserApiService.postManyOrganizationUserReinvite).toHaveBeenCalledTimes( + 2, + ); + }); + + it("should handle empty data in batch response", async () => { + const totalUsers = REQUESTS_PER_BATCH + 50; + const userIdsBatch = Array.from({ length: totalUsers }, () => newGuid() as UserId); + + const mockResponse1 = new ListResponse( + { + data: userIdsBatch.slice(0, REQUESTS_PER_BATCH).map((id) => ({ + id, + error: null, + })), + continuationToken: null, + }, + OrganizationUserBulkResponse, + ); + + const mockResponse2 = new ListResponse( + { + data: [], + continuationToken: null, + }, + OrganizationUserBulkResponse, + ); + + organizationUserApiService.postManyOrganizationUserReinvite + .mockResolvedValueOnce(mockResponse1) + .mockResolvedValueOnce(mockResponse2); + + const result = await service.bulkReinvite(mockOrganization, userIdsBatch); + + expect(result.successful).toBeDefined(); + expect(result.successful?.response).toHaveLength(REQUESTS_PER_BATCH); + expect(result.failed).toHaveLength(0); + }); + + it("should process batches sequentially in order", async () => { + const totalUsers = REQUESTS_PER_BATCH * 2; + const userIdsBatch = Array.from({ length: totalUsers }, () => newGuid() as UserId); + const callOrder: number[] = []; + + organizationUserApiService.postManyOrganizationUserReinvite.mockImplementation( + async (orgId, ids) => { + const batchIndex = ids.includes(userIdsBatch[0]) ? 1 : 2; + callOrder.push(batchIndex); + + return new ListResponse( + { + data: ids.map((id) => ({ + id, + error: null, + })), + continuationToken: null, + }, + OrganizationUserBulkResponse, + ); + }, + ); + + await service.bulkReinvite(mockOrganization, userIdsBatch); + + expect(callOrder).toEqual([1, 2]); + expect(organizationUserApiService.postManyOrganizationUserReinvite).toHaveBeenCalledTimes( + 2, + ); + }); }); }); @@ -427,14 +694,6 @@ describe("MemberActionsService", () => { expect(result).toBe(false); }); - it("should not allow reset password when organization lacks public and private keys", () => { - const org = { ...mockOrganization, hasPublicAndPrivateKeys: false } as Organization; - - const result = service.allowResetPassword(mockOrgUser, org, resetPasswordEnabled); - - expect(result).toBe(false); - }); - it("should not allow reset password when user is not enrolled in reset password", () => { const user = { ...mockOrgUser, resetPasswordEnrolled: false } as OrganizationUserView; @@ -443,12 +702,6 @@ describe("MemberActionsService", () => { expect(result).toBe(false); }); - it("should not allow reset password when reset password is disabled", () => { - const result = service.allowResetPassword(mockOrgUser, mockOrganization, false); - - expect(result).toBe(false); - }); - it("should not allow reset password when user status is not confirmed", () => { const user = { ...mockOrgUser, diff --git a/apps/web/src/app/admin-console/organizations/members/services/member-actions/member-actions.service.ts b/apps/web/src/app/admin-console/organizations/members/services/member-actions/member-actions.service.ts index 5e19e26954e..f3774e3cb25 100644 --- a/apps/web/src/app/admin-console/organizations/members/services/member-actions/member-actions.service.ts +++ b/apps/web/src/app/admin-console/organizations/members/services/member-actions/member-actions.service.ts @@ -20,9 +20,12 @@ import { EncryptService } from "@bitwarden/common/key-management/crypto/abstract import { ListResponse } from "@bitwarden/common/models/response/list.response"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { KeyService } from "@bitwarden/key-management"; +import { UserId } from "@bitwarden/user-core"; import { OrganizationUserView } from "../../../core/views/organization-user.view"; +export const REQUESTS_PER_BATCH = 500; + export interface MemberActionResult { success: boolean; error?: string; @@ -162,20 +165,36 @@ export class MemberActionsService { } } - async bulkReinvite(organization: Organization, userIds: string[]): Promise<BulkActionResult> { - try { - const result = await this.organizationUserApiService.postManyOrganizationUserReinvite( - organization.id, - userIds, - ); - return { successful: result, failed: [] }; - } catch (error) { - return { - failed: userIds.map((id) => ({ id, error: (error as Error).message ?? String(error) })), - }; + async bulkReinvite(organization: Organization, userIds: UserId[]): Promise<BulkActionResult> { + const increaseBulkReinviteLimitForCloud = await firstValueFrom( + this.configService.getFeatureFlag$(FeatureFlag.IncreaseBulkReinviteLimitForCloud), + ); + if (increaseBulkReinviteLimitForCloud) { + return await this.vNextBulkReinvite(organization, userIds); + } else { + try { + const result = await this.organizationUserApiService.postManyOrganizationUserReinvite( + organization.id, + userIds, + ); + return { successful: result, failed: [] }; + } catch (error) { + return { + failed: userIds.map((id) => ({ id, error: (error as Error).message ?? String(error) })), + }; + } } } + async vNextBulkReinvite( + organization: Organization, + userIds: UserId[], + ): Promise<BulkActionResult> { + return this.processBatchedOperation(userIds, REQUESTS_PER_BATCH, (batch) => + this.organizationUserApiService.postManyOrganizationUserReinvite(organization.id, batch), + ); + } + allowResetPassword( orgUser: OrganizationUserView, organization: Organization, @@ -207,4 +226,52 @@ export class MemberActionsService { orgUser.status === OrganizationUserStatusType.Confirmed ); } + + /** + * Processes user IDs in sequential batches and aggregates results. + * @param userIds - Array of user IDs to process + * @param batchSize - Number of IDs to process per batch + * @param processBatch - Async function that processes a single batch and returns the result + * @returns Aggregated bulk action result + */ + private async processBatchedOperation( + userIds: UserId[], + batchSize: number, + processBatch: (batch: string[]) => Promise<ListResponse<OrganizationUserBulkResponse>>, + ): Promise<BulkActionResult> { + const allSuccessful: OrganizationUserBulkResponse[] = []; + const allFailed: { id: string; error: string }[] = []; + + for (let i = 0; i < userIds.length; i += batchSize) { + const batch = userIds.slice(i, i + batchSize); + + try { + const result = await processBatch(batch); + + if (result?.data) { + for (const response of result.data) { + if (response.error) { + allFailed.push({ id: response.id, error: response.error }); + } else { + allSuccessful.push(response); + } + } + } + } catch (error) { + allFailed.push( + ...batch.map((id) => ({ id, error: (error as Error).message ?? String(error) })), + ); + } + } + + const successful = + allSuccessful.length > 0 + ? new ListResponse(allSuccessful, OrganizationUserBulkResponse) + : undefined; + + return { + successful, + failed: allFailed, + }; + } } From f7d2dd0cd0b3a83dc060d81a037ca9217dcc93da Mon Sep 17 00:00:00 2001 From: neuronull <9162534+neuronull@users.noreply.github.com> Date: Thu, 11 Dec 2025 11:13:22 -0700 Subject: [PATCH 053/188] Desktop use debug level file filter if developer build (#17910) --- apps/desktop/src/platform/services/electron-log.main.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/desktop/src/platform/services/electron-log.main.service.ts b/apps/desktop/src/platform/services/electron-log.main.service.ts index 947f4449271..e148f7a45c8 100644 --- a/apps/desktop/src/platform/services/electron-log.main.service.ts +++ b/apps/desktop/src/platform/services/electron-log.main.service.ts @@ -22,7 +22,7 @@ export class ElectronLogMainService extends BaseLogService { return; } - log.transports.file.level = "info"; + log.transports.file.level = isDev() ? "debug" : "info"; if (this.logDir != null) { log.transports.file.resolvePathFn = () => path.join(this.logDir, "app.log"); } From 4c971c70c01de3bdab4ea0906abb7112461cc6a2 Mon Sep 17 00:00:00 2001 From: Bryan Cunningham <bcunningham@bitwarden.com> Date: Thu, 11 Dec 2025 13:56:13 -0500 Subject: [PATCH 054/188] [CL-927] anon layout header actions slot (#17796) * add a slot for consumers to show user actions in anon layout header * remove commented code * ensure logo stays top aligned * switch to dashed naming * fix ngif statements * remove empty selector * remove unnecessary containers * use smaller logo on smaller screens * remove commented code from extension layout * remove dupe slot * only take extension screenshots on small screens * take screenshot at 380 * take large and small screenshot * update story to use new control flow --- ...tension-anon-layout-wrapper.component.html | 24 +++------ .../extension-anon-layout-wrapper.stories.ts | 5 ++ .../anon-layout-wrapper.component.html | 1 + .../anon-layout-wrapper.stories.ts | 14 +++++ .../anon-layout/anon-layout.component.html | 54 +++++++++++-------- .../src/anon-layout/anon-layout.stories.ts | 21 +++++++- 6 files changed, 78 insertions(+), 41 deletions(-) diff --git a/apps/browser/src/popup/components/extension-anon-layout-wrapper/extension-anon-layout-wrapper.component.html b/apps/browser/src/popup/components/extension-anon-layout-wrapper/extension-anon-layout-wrapper.component.html index dcd0496ed30..7a1815b86ed 100644 --- a/apps/browser/src/popup/components/extension-anon-layout-wrapper/extension-anon-layout-wrapper.component.html +++ b/apps/browser/src/popup/components/extension-anon-layout-wrapper/extension-anon-layout-wrapper.component.html @@ -1,31 +1,21 @@ <popup-page [disablePadding]="true"> - <popup-header - slot="header" - [background]="'alt'" - [showBackButton]="showBackButton" - [pageTitle]="''" - > - <div class="tw-w-32"> - <bit-icon *ngIf="showLogo" [icon]="logo" [ariaLabel]="'appLogoLabel' | i18n"></bit-icon> - </div> - - <ng-container slot="end"> - <app-pop-out></app-pop-out> - <app-current-account *ngIf="showAcctSwitcher && hasLoggedInAccount"></app-current-account> - </ng-container> - </popup-header> - <auth-anon-layout [title]="pageTitle" [subtitle]="pageSubtitle" [icon]="pageIcon" [showReadonlyHostname]="showReadonlyHostname" - [hideLogo]="true" + [hideLogo]="!showLogo" [maxWidth]="maxWidth" [hideFooter]="hideFooter" [hideCardWrapper]="hideCardWrapper" > <router-outlet></router-outlet> + <div class="tw-flex tw-gap-2" slot="header-actions"> + <app-pop-out></app-pop-out> + @if (showAcctSwitcher && hasLoggedInAccount) { + <app-current-account></app-current-account> + } + </div> <router-outlet slot="secondary" name="secondary"></router-outlet> <router-outlet slot="environment-selector" name="environment-selector"></router-outlet> </auth-anon-layout> diff --git a/apps/browser/src/popup/components/extension-anon-layout-wrapper/extension-anon-layout-wrapper.stories.ts b/apps/browser/src/popup/components/extension-anon-layout-wrapper/extension-anon-layout-wrapper.stories.ts index 7a30e15582c..57ef285bdf5 100644 --- a/apps/browser/src/popup/components/extension-anon-layout-wrapper/extension-anon-layout-wrapper.stories.ts +++ b/apps/browser/src/popup/components/extension-anon-layout-wrapper/extension-anon-layout-wrapper.stories.ts @@ -238,6 +238,11 @@ export const DefaultContentExample: Story = { }, ], }), + parameters: { + chromatic: { + viewports: [380, 1280], + }, + }, }; // Dynamic Content Example diff --git a/libs/components/src/anon-layout/anon-layout-wrapper.component.html b/libs/components/src/anon-layout/anon-layout-wrapper.component.html index 73a3d34261b..1079329448b 100644 --- a/libs/components/src/anon-layout/anon-layout-wrapper.component.html +++ b/libs/components/src/anon-layout/anon-layout-wrapper.component.html @@ -7,6 +7,7 @@ [hideCardWrapper]="hideCardWrapper" [hideBackgroundIllustration]="hideBackgroundIllustration" > + <router-outlet slot="header-actions" name="header-actions"></router-outlet> <router-outlet></router-outlet> <router-outlet slot="secondary" name="secondary"></router-outlet> <router-outlet slot="environment-selector" name="environment-selector"></router-outlet> diff --git a/libs/components/src/anon-layout/anon-layout-wrapper.stories.ts b/libs/components/src/anon-layout/anon-layout-wrapper.stories.ts index 76fcc8976c7..63181e04649 100644 --- a/libs/components/src/anon-layout/anon-layout-wrapper.stories.ts +++ b/libs/components/src/anon-layout/anon-layout-wrapper.stories.ts @@ -130,6 +130,15 @@ export class DefaultSecondaryOutletExampleComponent {} }) export class DefaultEnvSelectorOutletExampleComponent {} +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection +@Component({ + selector: "bit-header-actions-outlet-example-component", + template: "<p>Header Actions Outlet Example: <br> your header actions component goes here</p>", + standalone: false, +}) +export class DefaultHeaderActionsOutletExampleComponent {} + export const DefaultContentExample: Story = { render: (args) => ({ props: args, @@ -171,6 +180,11 @@ export const DefaultContentExample: Story = { component: DefaultEnvSelectorOutletExampleComponent, outlet: "environment-selector", }, + { + path: "", + component: DefaultHeaderActionsOutletExampleComponent, + outlet: "header-actions", + }, ], }, ], diff --git a/libs/components/src/anon-layout/anon-layout.component.html b/libs/components/src/anon-layout/anon-layout.component.html index 15f7d107542..edb73bbf588 100644 --- a/libs/components/src/anon-layout/anon-layout.component.html +++ b/libs/components/src/anon-layout/anon-layout.component.html @@ -5,13 +5,19 @@ 'tw-min-h-full': clientType === 'browser' || clientType === 'desktop', }" > - <a - *ngIf="!hideLogo()" - [routerLink]="['/']" - class="tw-w-[200px] tw-block tw-mb-12 [&>*]:tw-align-top" - > - <bit-icon [icon]="logo" [ariaLabel]="'appLogoLabel' | i18n"></bit-icon> - </a> + <div class="tw-flex tw-justify-between tw-items-center tw-w-full tw-mb-12"> + @if (!hideLogo()) { + <a + [routerLink]="['/']" + class="tw-w-32 sm:tw-w-[200px] tw-self-center sm:tw-self-start tw-block [&>*]:tw-align-top" + > + <bit-icon [icon]="logo" [ariaLabel]="'appLogoLabel' | i18n"></bit-icon> + </a> + } + <div class="tw-ms-auto"> + <ng-content select="[slot=header-actions]"></ng-content> + </div> + </div> <div class="tw-text-center tw-mb-4 sm:tw-mb-6 tw-mx-auto" [ngClass]="maxWidthClass"> @let iconInput = icon(); @@ -25,7 +31,7 @@ <bit-icon [icon]="iconInput"></bit-icon> </div> - <ng-container *ngIf="title()"> + @if (title()) { <!-- Small screens --> <h1 bitTypography="h2" class="tw-mt-2 sm:tw-hidden"> {{ title() }} @@ -34,9 +40,11 @@ <h1 bitTypography="h1" class="tw-mt-2 tw-hidden sm:tw-block"> {{ title() }} </h1> - </ng-container> + } - <div *ngIf="subtitle()" class="tw-text-sm sm:tw-text-base">{{ subtitle() }}</div> + @if (subtitle()) { + <div class="tw-text-sm sm:tw-text-base">{{ subtitle() }}</div> + } </div> <div @@ -57,18 +65,20 @@ <ng-content select="[slot=secondary]"></ng-content> </div> - <footer *ngIf="!hideFooter()" class="tw-text-center tw-mt-4 sm:tw-mt-6"> - <div *ngIf="showReadonlyHostname()" bitTypography="body2"> - {{ "accessing" | i18n }} {{ hostname }} - </div> - <ng-container *ngIf="!showReadonlyHostname()"> - <ng-content select="[slot=environment-selector]"></ng-content> - </ng-container> - <ng-container *ngIf="!hideYearAndVersion"> - <div bitTypography="body2">&copy; {{ year }} Bitwarden Inc.</div> - <div bitTypography="body2">{{ version }}</div> - </ng-container> - </footer> + @if (!hideFooter()) { + <footer class="tw-text-center tw-mt-4 sm:tw-mt-6"> + @if (showReadonlyHostname()) { + <div bitTypography="body2">{{ "accessing" | i18n }} {{ hostname }}</div> + } @else { + <ng-content select="[slot=environment-selector]"></ng-content> + } + + @if (!hideYearAndVersion) { + <div bitTypography="body2">&copy; {{ year }} Bitwarden Inc.</div> + <div bitTypography="body2">{{ version }}</div> + } + </footer> + } @if (!hideBackgroundIllustration()) { <div diff --git a/libs/components/src/anon-layout/anon-layout.stories.ts b/libs/components/src/anon-layout/anon-layout.stories.ts index fb3bacb838f..01cdc04ad73 100644 --- a/libs/components/src/anon-layout/anon-layout.stories.ts +++ b/libs/components/src/anon-layout/anon-layout.stories.ts @@ -8,6 +8,7 @@ import { EnvironmentService } from "@bitwarden/common/platform/abstractions/envi import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { AvatarModule } from "../avatar"; import { ButtonModule } from "../button"; import { I18nMockService } from "../utils/i18n-mock.service"; @@ -23,6 +24,7 @@ type StoryArgs = AnonLayoutComponent & { showSecondary: boolean; useDefaultIcon: boolean; icon: Icon; + includeHeaderActions: boolean; }; export default { @@ -30,7 +32,7 @@ export default { component: AnonLayoutComponent, decorators: [ moduleMetadata({ - imports: [ButtonModule, RouterModule], + imports: [ButtonModule, RouterModule, AvatarModule], providers: [ { provide: PlatformUtilsService, @@ -76,6 +78,14 @@ export default { [hideFooter]="hideFooter" [hideBackgroundIllustration]="hideBackgroundIllustration" > + @if (includeHeaderActions) { + <div slot="header-actions" class="tw-flex tw-items-center tw-gap-2"> + <bit-avatar + size="small" + text="Bob Loblaw" + ></bit-avatar> + </div> + } <ng-container [ngSwitch]="contentLength"> <div *ngSwitchCase="'thin'" class="tw-text-center"> <div class="tw-font-medium">Thin Content</div></div> <div *ngSwitchCase="'long'"> @@ -116,7 +126,7 @@ export default { hideLogo: { control: "boolean" }, hideFooter: { control: "boolean" }, hideBackgroundIllustration: { control: "boolean" }, - + includeHeaderActions: { control: "boolean" }, contentLength: { control: "radio", options: ["normal", "long", "thin"], @@ -138,6 +148,7 @@ export default { hideBackgroundIllustration: false, contentLength: "normal", showSecondary: false, + includeHeaderActions: false, }, } satisfies Meta<StoryArgs>; @@ -188,6 +199,12 @@ export const SecondaryContent: Story = { }, }; +export const WithHeaderActions: Story = { + args: { + includeHeaderActions: true, + }, +}; + export const NoTitle: Story = { args: { title: undefined } }; export const NoSubtitle: Story = { args: { subtitle: undefined } }; From 22e9c6a72f948b9a26b70c4caa9bc636d6a67e43 Mon Sep 17 00:00:00 2001 From: neuronull <9162534+neuronull@users.noreply.github.com> Date: Thu, 11 Dec 2025 12:44:51 -0700 Subject: [PATCH 055/188] Re-apply desktop native debug log level debug builds and fix build workflow (#17908) * Reapply "Desktop Native compile debug builds with debug log level (#17357)" (#17815) This reverts commit 5386b58f2329eaed2acb9178560eeca9a265bb16. * Use release mode if workflow called from upstream * fix bug in build script * revert napi build command to not use --release * forward caller's args to napi * js things * shell thangs * use platform agnostic expansion * Revert "use platform agnostic expansion" This reverts commit 5ee629f822a5bccbc68817bc9b3e846eb85b1639. * powershell expansion --- .github/workflows/build-desktop.yml | 20 ++++++++++++----- apps/desktop/desktop_native/build.js | 6 ++--- apps/desktop/desktop_native/napi/package.json | 2 +- .../desktop_native/napi/scripts/build.js | 22 +++++++++++++++++++ apps/desktop/desktop_native/napi/src/lib.rs | 14 +++++++++--- 5 files changed, 52 insertions(+), 12 deletions(-) create mode 100644 apps/desktop/desktop_native/napi/scripts/build.js diff --git a/.github/workflows/build-desktop.yml b/.github/workflows/build-desktop.yml index 6978edd8b3c..efb94e44c7a 100644 --- a/.github/workflows/build-desktop.yml +++ b/.github/workflows/build-desktop.yml @@ -583,7 +583,9 @@ jobs: - name: Build Native Module if: steps.cache.outputs.cache-hit != 'true' working-directory: apps/desktop/desktop_native - run: node build.js cross-platform + env: + MODE: ${{ github.event_name == 'workflow_call' && '--release' || '' }} + run: node build.js cross-platform "$env:MODE" - name: Build run: npm run build @@ -846,7 +848,9 @@ jobs: - name: Build Native Module if: steps.cache.outputs.cache-hit != 'true' working-directory: apps/desktop/desktop_native - run: node build.js cross-platform + env: + MODE: ${{ github.event_name == 'workflow_call' && '--release' || '' }} + run: node build.js cross-platform "$env:MODE" - name: Build run: npm run build @@ -1202,7 +1206,9 @@ jobs: - name: Build Native Module if: steps.cache.outputs.cache-hit != 'true' working-directory: apps/desktop/desktop_native - run: node build.js cross-platform + env: + MODE: ${{ github.event_name == 'workflow_call' && '--release' || '' }} + run: node build.js cross-platform "$MODE" - name: Build application (dev) run: npm run build @@ -1424,7 +1430,9 @@ jobs: - name: Build Native Module if: steps.cache.outputs.cache-hit != 'true' working-directory: apps/desktop/desktop_native - run: node build.js cross-platform + env: + MODE: ${{ github.event_name == 'workflow_call' && '--release' || '' }} + run: node build.js cross-platform "$MODE" - name: Build if: steps.build-cache.outputs.cache-hit != 'true' @@ -1705,7 +1713,9 @@ jobs: - name: Build Native Module if: steps.cache.outputs.cache-hit != 'true' working-directory: apps/desktop/desktop_native - run: node build.js cross-platform + env: + MODE: ${{ github.event_name == 'workflow_call' && '--release' || '' }} + run: node build.js cross-platform "$MODE" - name: Build if: steps.build-cache.outputs.cache-hit != 'true' diff --git a/apps/desktop/desktop_native/build.js b/apps/desktop/desktop_native/build.js index e267e28a08c..54a6dba8326 100644 --- a/apps/desktop/desktop_native/build.js +++ b/apps/desktop/desktop_native/build.js @@ -113,8 +113,8 @@ if (process.platform === "linux") { platformTargets.forEach(([target, _]) => { installTarget(target); - buildNapiModule(target); - buildProxyBin(target); - buildImporterBinaries(target); + buildNapiModule(target, mode === "release"); + buildProxyBin(target, mode === "release"); + buildImporterBinaries(target, mode === "release"); buildProcessIsolation(); }); diff --git a/apps/desktop/desktop_native/napi/package.json b/apps/desktop/desktop_native/napi/package.json index 5401207c252..0717bfd53ea 100644 --- a/apps/desktop/desktop_native/napi/package.json +++ b/apps/desktop/desktop_native/napi/package.json @@ -3,7 +3,7 @@ "version": "0.1.0", "description": "", "scripts": { - "build": "napi build --platform --no-js", + "build": "node scripts/build.js", "test": "cargo test" }, "author": "", diff --git a/apps/desktop/desktop_native/napi/scripts/build.js b/apps/desktop/desktop_native/napi/scripts/build.js new file mode 100644 index 00000000000..ad24b99d2fb --- /dev/null +++ b/apps/desktop/desktop_native/napi/scripts/build.js @@ -0,0 +1,22 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ +const { execSync } = require('child_process'); + +const args = process.argv.slice(2); + +const isRelease = args.includes('--release'); + +const argsString = args.join(' '); + +if (isRelease) { + console.log('Building release mode.'); + + execSync(`napi build --platform --no-js ${argsString}`, { stdio: 'inherit'}); + +} else { + console.log('Building debug mode.'); + + execSync(`napi build --platform --no-js ${argsString}`, { + stdio: 'inherit', + env: { ...process.env, RUST_LOG: 'debug' } + }); +} diff --git a/apps/desktop/desktop_native/napi/src/lib.rs b/apps/desktop/desktop_native/napi/src/lib.rs index 25dfdd08336..fe084349501 100644 --- a/apps/desktop/desktop_native/napi/src/lib.rs +++ b/apps/desktop/desktop_native/napi/src/lib.rs @@ -994,7 +994,7 @@ pub mod logging { }; use tracing::Level; use tracing_subscriber::{ - filter::{EnvFilter, LevelFilter}, + filter::EnvFilter, fmt::format::{DefaultVisitor, Writer}, layer::SubscriberExt, util::SubscriberInitExt, @@ -1082,9 +1082,17 @@ pub mod logging { pub fn init_napi_log(js_log_fn: ThreadsafeFunction<FnArgs<(LogLevel, String)>>) { let _ = JS_LOGGER.0.set(js_log_fn); + // the log level hierarchy is determined by: + // - if RUST_LOG is detected at runtime + // - if RUST_LOG is provided at compile time + // - default to INFO let filter = EnvFilter::builder() - // set the default log level to INFO. - .with_default_directive(LevelFilter::INFO.into()) + .with_default_directive( + option_env!("RUST_LOG") + .unwrap_or("info") + .parse() + .expect("should provide valid log level at compile time."), + ) // parse directives from the RUST_LOG environment variable, // overriding the default directive for matching targets. .from_env_lossy(); From 4576a52fd186e4a11c24edde1a6ee99f7d3d9b09 Mon Sep 17 00:00:00 2001 From: Dave <3836813+enmande@users.noreply.github.com> Date: Thu, 11 Dec 2025 15:03:10 -0500 Subject: [PATCH 056/188] fix(token-service) [PM-15333]: Portable App Is Not Portable (#17781) * feat(token-service) [PM-15333]: Update Portable secure storage resolution to use disk. * feat(token-service) [PM-15333]: Move isWindowsPortable evaluation to preload with other platform evaluations. --- apps/desktop/src/app/services/services.module.ts | 10 +++++++++- apps/desktop/src/platform/preload.ts | 2 ++ libs/common/src/auth/services/token.service.ts | 14 +++++++++++--- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/apps/desktop/src/app/services/services.module.ts b/apps/desktop/src/app/services/services.module.ts index e4dd144fa20..59021a556e4 100644 --- a/apps/desktop/src/app/services/services.module.ts +++ b/apps/desktop/src/app/services/services.module.ts @@ -203,8 +203,16 @@ const safeProviders: SafeProvider[] = [ // We manually override the value of SUPPORTS_SECURE_STORAGE here to avoid // the TokenService having to inject the PlatformUtilsService which introduces a // circular dependency on Desktop only. + // + // For Windows portable builds, we disable secure storage to ensure tokens are + // stored on disk (in bitwarden-appdata) rather than in Windows Credential + // Manager, making them portable across machines. This allows users to move the USB drive + // between computers while maintaining authentication. + // + // Note: Portable mode does not use secure storage for read/write/clear operations, + // preventing any collision with tokens from a regular desktop installation. provide: SUPPORTS_SECURE_STORAGE, - useValue: ELECTRON_SUPPORTS_SECURE_STORAGE, + useValue: ELECTRON_SUPPORTS_SECURE_STORAGE && !ipc.platform.isWindowsPortable, }), safeProvider({ provide: DEFAULT_VAULT_TIMEOUT, diff --git a/apps/desktop/src/platform/preload.ts b/apps/desktop/src/platform/preload.ts index a45ac753b3f..5f643242a9c 100644 --- a/apps/desktop/src/platform/preload.ts +++ b/apps/desktop/src/platform/preload.ts @@ -17,6 +17,7 @@ import { isFlatpak, isMacAppStore, isSnapStore, + isWindowsPortable, isWindowsStore, } from "../utils"; @@ -133,6 +134,7 @@ export default { isDev: isDev(), isMacAppStore: isMacAppStore(), isWindowsStore: isWindowsStore(), + isWindowsPortable: isWindowsPortable(), isFlatpak: isFlatpak(), isSnapStore: isSnapStore(), isAppImage: isAppImage(), diff --git a/libs/common/src/auth/services/token.service.ts b/libs/common/src/auth/services/token.service.ts index c02bc85f124..ce272705341 100644 --- a/libs/common/src/auth/services/token.service.ts +++ b/libs/common/src/auth/services/token.service.ts @@ -445,13 +445,15 @@ export class TokenService implements TokenServiceAbstraction { // we can't determine storage location w/out vaultTimeoutAction and vaultTimeout // but we can simply clear all locations to avoid the need to require those parameters. + // When secure storage is supported, clear the encryption key from secure storage. + // When not supported (e.g., portable builds), tokens are stored on disk and this step is skipped. if (this.platformSupportsSecureStorage) { - // Always clear the access token key when clearing the access token - // The next set of the access token will create a new access token key + // Always clear the access token key when clearing the access token. + // The next set of the access token will create a new access token key. await this.clearAccessTokenKey(userId); } - // Platform doesn't support secure storage, so use state provider implementation + // Clear tokens from disk storage (all platforms) await this.singleUserStateProvider.get(userId, ACCESS_TOKEN_DISK).update((_) => null, { shouldUpdate: (previousValue) => previousValue !== null, }); @@ -478,6 +480,9 @@ export class TokenService implements TokenServiceAbstraction { return null; } + // When platformSupportsSecureStorage=true, tokens on disk are encrypted and require + // decryption keys from secure storage. When false (e.g., portable builds), tokens are + // stored on disk. if (this.platformSupportsSecureStorage) { let accessTokenKey: AccessTokenKey; try { @@ -1118,6 +1123,9 @@ export class TokenService implements TokenServiceAbstraction { ) { return TokenStorageLocation.Memory; } else { + // Secure storage (e.g., OS credential manager) is preferred when available. + // Desktop portable builds set platformSupportsSecureStorage=false to store tokens + // on disk for portability across machines. if (useSecureStorage && this.platformSupportsSecureStorage) { return TokenStorageLocation.SecureStorage; } From 7c0337c12dc693e22cc3023961fccced6ec97fc9 Mon Sep 17 00:00:00 2001 From: Andy Pixley <3723676+pixman20@users.noreply.github.com> Date: Thu, 11 Dec 2025 16:14:57 -0500 Subject: [PATCH 057/188] [BRE-1391] Fixing desktop tar.gz to include version (#17933) --- .github/workflows/release-desktop.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/release-desktop.yml b/.github/workflows/release-desktop.yml index 7f87a1e5628..2239cb1268f 100644 --- a/.github/workflows/release-desktop.yml +++ b/.github/workflows/release-desktop.yml @@ -98,6 +98,14 @@ jobs: working-directory: apps/desktop/artifacts run: mv "Bitwarden-${PKG_VERSION}-universal.pkg" "Bitwarden-${PKG_VERSION}-universal.pkg.archive" + - name: Rename .tar.gz to include version + env: + PKG_VERSION: ${{ steps.version.outputs.version }} + working-directory: apps/desktop/artifacts + run: | + mv "bitwarden_desktop_x64.tar.gz" "bitwarden_${PKG_VERSION}_x64.tar.gz" + mv "bitwarden_desktop_arm64.tar.gz" "bitwarden_${PKG_VERSION}_arm64.tar.gz" + - name: Create Release uses: ncipollo/release-action@b7eabc95ff50cbeeedec83973935c8f306dfcd0b # v1.20.0 if: ${{ steps.release_channel.outputs.channel == 'latest' && github.event.inputs.release_type != 'Dry Run' }} From 9f1496b21834d40025c1f5eb64ca637798ba1ba8 Mon Sep 17 00:00:00 2001 From: Github Actions <actions@github.com> Date: Thu, 11 Dec 2025 21:25:36 +0000 Subject: [PATCH 058/188] Bumped Desktop client to 2025.12.1 --- apps/desktop/package.json | 2 +- apps/desktop/src/package-lock.json | 4 ++-- apps/desktop/src/package.json | 2 +- package-lock.json | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 5e85d34cebc..97ab8585a69 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.12.0", + "version": "2025.12.1", "keywords": [ "bitwarden", "password", diff --git a/apps/desktop/src/package-lock.json b/apps/desktop/src/package-lock.json index 44fdb5c23b0..9d8eae15791 100644 --- a/apps/desktop/src/package-lock.json +++ b/apps/desktop/src/package-lock.json @@ -1,12 +1,12 @@ { "name": "@bitwarden/desktop", - "version": "2025.12.0", + "version": "2025.12.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@bitwarden/desktop", - "version": "2025.12.0", + "version": "2025.12.1", "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 4c396304f4a..2ac5d339a95 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.12.0", + "version": "2025.12.1", "author": "Bitwarden Inc. <hello@bitwarden.com> (https://bitwarden.com)", "homepage": "https://bitwarden.com", "license": "GPL-3.0", diff --git a/package-lock.json b/package-lock.json index dc8694f77b6..3a600667ff7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -278,7 +278,7 @@ }, "apps/desktop": { "name": "@bitwarden/desktop", - "version": "2025.12.0", + "version": "2025.12.1", "hasInstallScript": true, "license": "GPL-3.0" }, From d77930428564e98a1b3809c6e64990682bdc6d6e Mon Sep 17 00:00:00 2001 From: Jordan Aasen <166539328+jaasen-livefront@users.noreply.github.com> Date: Thu, 11 Dec 2025 15:45:32 -0800 Subject: [PATCH 059/188] [PM-25388] - remove reference to android/ios icons (#17763) * remove android/ios icons as they're not in the icon lib * fix tests --- .../src/vault/icon/build-cipher-icon.spec.ts | 43 ++++++++++--------- .../src/vault/icon/build-cipher-icon.ts | 6 ++- 2 files changed, 26 insertions(+), 23 deletions(-) diff --git a/libs/common/src/vault/icon/build-cipher-icon.spec.ts b/libs/common/src/vault/icon/build-cipher-icon.spec.ts index 90ccaaec3a6..67a1151aa8e 100644 --- a/libs/common/src/vault/icon/build-cipher-icon.spec.ts +++ b/libs/common/src/vault/icon/build-cipher-icon.spec.ts @@ -13,18 +13,19 @@ describe("buildCipherIcon", () => { }, } as any as CipherView; - it.each([true, false])("handles android app URIs for showFavicon setting %s", (showFavicon) => { - setUri("androidapp://test.example"); + // @TODO Uncomment once we have Android and iOS icons https://bitwarden.atlassian.net/browse/PM-29028 + // it.each([true, false])("handles android app URIs for showFavicon setting %s", (showFavicon) => { + // setUri("androidapp://test.example"); - const iconDetails = buildCipherIcon(iconServerUrl, cipher, showFavicon); + // const iconDetails = buildCipherIcon(iconServerUrl, cipher, showFavicon); - expect(iconDetails).toEqual({ - icon: "bwi-android", - image: null, - fallbackImage: "", - imageEnabled: showFavicon, - }); - }); + // expect(iconDetails).toEqual({ + // icon: "bwi-android", + // image: null, + // fallbackImage: "", + // imageEnabled: showFavicon, + // }); + // }); it("does not mark as an android app if the protocol is not androidapp", () => { // This weird URI points to test.androidapp with a default port and path of /.example @@ -40,18 +41,18 @@ describe("buildCipherIcon", () => { }); }); - it.each([true, false])("handles ios app URIs for showFavicon setting %s", (showFavicon) => { - setUri("iosapp://test.example"); + // @TODO Uncomment once we have Android and iOS icons https://bitwarden.atlassian.net/browse/PM-29028 + // it.each([true, false])("handles ios app URIs for showFavicon setting %s", (showFavicon) => { + // setUri("iosapp://test.example"); - const iconDetails = buildCipherIcon(iconServerUrl, cipher, showFavicon); - - expect(iconDetails).toEqual({ - icon: "bwi-apple", - image: null, - fallbackImage: "", - imageEnabled: showFavicon, - }); - }); + // const iconDetails = buildCipherIcon(iconServerUrl, cipher, showFavicon); + // expect(iconDetails).toEqual({ + // icon: "bwi-apple", + // image: null, + // fallbackImage: "", + // imageEnabled: showFavicon, + // }); + // }); it("does not mark as an ios app if the protocol is not iosapp", () => { // This weird URI points to test.iosapp with a default port and path of /.example diff --git a/libs/common/src/vault/icon/build-cipher-icon.ts b/libs/common/src/vault/icon/build-cipher-icon.ts index a081511d792..77787874d8e 100644 --- a/libs/common/src/vault/icon/build-cipher-icon.ts +++ b/libs/common/src/vault/icon/build-cipher-icon.ts @@ -49,10 +49,12 @@ export function buildCipherIcon( let isWebsite = false; if (hostnameUri.indexOf("androidapp://") === 0) { - icon = "bwi-android"; + // @TODO Re-add once we have Android icon https://bitwarden.atlassian.net/browse/PM-29028 + // icon = "bwi-android"; image = null; } else if (hostnameUri.indexOf("iosapp://") === 0) { - icon = "bwi-apple"; + // @TODO Re-add once we have iOS icon https://bitwarden.atlassian.net/browse/PM-29028 + // icon = "bwi-apple"; image = null; } else if ( showFavicon && From 2c4034ec7ce54c6da8537eb05534317ac5ba33bb Mon Sep 17 00:00:00 2001 From: Jordan Aasen <166539328+jaasen-livefront@users.noreply.github.com> Date: Thu, 11 Dec 2025 15:47:26 -0800 Subject: [PATCH 060/188] update popup router cache when navigating after file upload (#17694) --- .../platform/popup/view-cache/popup-router-cache.service.ts | 4 ++-- .../vault-v2/attachments/attachments-v2.component.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) 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 abb7c6405c2..6e5218c9f27 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 @@ -120,8 +120,8 @@ export class PopupRouterCacheService { /** * Navigate back in history */ - async back() { - if (!BrowserPopupUtils.inPopup(window)) { + async back(updateCache = false) { + if (!updateCache && !BrowserPopupUtils.inPopup(window)) { this.location.back(); return; } diff --git a/apps/browser/src/vault/popup/components/vault-v2/attachments/attachments-v2.component.ts b/apps/browser/src/vault/popup/components/vault-v2/attachments/attachments-v2.component.ts index 295496c701f..29282d293de 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/attachments/attachments-v2.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/attachments/attachments-v2.component.ts @@ -51,6 +51,6 @@ export class AttachmentsV2Component { /** Navigate the user back to the edit screen after uploading an attachment */ async navigateBack() { - await this.popupRouterCacheService.back(); + await this.popupRouterCacheService.back(true); } } From 81350d98df070655e395acce37d1137d60aa9e3d Mon Sep 17 00:00:00 2001 From: Jordan Aasen <166539328+jaasen-livefront@users.noreply.github.com> Date: Thu, 11 Dec 2025 15:48:43 -0800 Subject: [PATCH 061/188] fix alignment in hidden/pw fields (#17877) --- 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 a4af25a2492..1ead9f82273 100644 --- a/libs/components/src/form-field/form-field.component.html +++ b/libs/components/src/form-field/form-field.component.html @@ -83,7 +83,7 @@ <ng-container *ngTemplateOutlet="labelContent"></ng-container> </label> <div - class="tw-gap-1 tw-flex tw-min-h-[1.85rem] tw-border-0 tw-border-solid" + class="tw-gap-1 tw-flex tw-min-h-[1.85rem] tw-border-0 tw-border-solid tw-items-center" [ngClass]="{ 'tw-border-secondary-300/50 tw-border-b tw-pb-[2px]': !disableReadOnlyBorder, 'tw-border-transparent tw-pb-[3px]': disableReadOnlyBorder, From be9d0c0291891708a6ee5de28be8a59c6055f814 Mon Sep 17 00:00:00 2001 From: Andreas Coroiu <acoroiu@bitwarden.com> Date: Fri, 12 Dec 2025 15:00:03 +0100 Subject: [PATCH 062/188] Transfer node-forge ownership to KM (#17941) * chore: move node-forge to KM * chore: sort dependencies --- .github/renovate.json5 | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 96e16776545..ca57ccf4f86 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -154,7 +154,6 @@ "@types/glob", "@types/lowdb", "@types/node", - "@types/node-forge", "@types/node-ipc", "@yao-pkg/pkg", "anyhow", @@ -192,7 +191,6 @@ "napi", "napi-build", "napi-derive", - "node-forge", "node-ipc", "nx", "oo7", @@ -415,14 +413,16 @@ }, { matchPackageNames: [ + "@types/node-forge", "aes", "big-integer", "cbc", + "linux-keyutils", + "memsec", + "node-forge", "rsa", "russh-cryptovec", "sha2", - "memsec", - "linux-keyutils", ], description: "Key Management owned dependencies", commitMessagePrefix: "[deps] KM:", From 27d82aaf286d6ef862864c21edef988bc57fd34e Mon Sep 17 00:00:00 2001 From: Todd Martin <106564991+trmartin4@users.noreply.github.com> Date: Fri, 12 Dec 2025 10:03:31 -0500 Subject: [PATCH 063/188] feat(accounts): Add creationDate of account to AccountInfo * Add creationDate of account to AccountInfo * Added initialization of creationDate. * Removed extra changes. * Fixed tests to initialize creation date * Added helper method to abstract account initialization in tests. * More test updates. * Linting * Additional test fixes. * Fixed spec reference * Fixed imports * Linting. * Fixed browser test. * Modified tsconfig to reference spec file. * Fixed import. * Removed dependency on os. This is necessary so that the @bitwarden/common/spec lib package can be referenced in tests without node. * Revert "Removed dependency on os. This is necessary so that the @bitwarden/common/spec lib package can be referenced in tests without node." This reverts commit 669f6557b6561f65ff513c14c2b3e8a55bef4035. * Updated stories to hard-code new field. * Removed changes to tsconfig * Revert "Removed changes to tsconfig" This reverts commit b7d916e8dc70be453f7092138416ce2e3c09ed57. --- .../services/account-switcher.service.spec.ts | 16 ++-- .../notification.background.spec.ts | 12 +-- .../browser/main-context-menu-handler.spec.ts | 8 +- .../extension-anon-layout-wrapper.stories.ts | 3 + .../popup/send-v2/send-v2.component.spec.ts | 8 +- .../open-attachments.component.spec.ts | 8 +- .../commands/unlock.command.spec.ts | 8 +- .../fido2-create.component.spec.ts | 8 +- .../desktop-autotype-policy.service.spec.ts | 8 +- .../biometric-message-handler.service.spec.ts | 16 ++-- .../unified-upgrade-dialog.component.spec.ts | 8 +- .../upgrade-nav-button.component.spec.ts | 8 +- .../services/upgrade-payment.service.spec.ts | 45 ++++++----- .../pages/breach-report.component.spec.ts | 8 +- .../user-key-rotation.service.spec.ts | 8 +- .../navigation-switcher.stories.ts | 3 + .../product-switcher.stories.ts | 3 + .../services/vault-banners.service.spec.ts | 15 +++- .../guards/provider-permissions.guard.spec.ts | 8 +- .../secrets/secret.service.spec.ts | 9 ++- .../services/sm-porting-api.service.spec.ts | 9 ++- .../access-policy.service.spec.ts | 9 ++- .../src/auth/guards/auth.guard.spec.ts | 24 +++--- .../src/auth/guards/lock.guard.spec.ts | 24 +++--- ...edirect-to-vault-if-unlocked.guard.spec.ts | 8 +- .../tde-decryption-required.guard.spec.ts | 8 +- .../src/auth/guards/unauth.guard.spec.ts | 8 +- .../login-approval-dialog.component.spec.ts | 8 +- .../default-change-password.service.spec.ts | 9 ++- ...ypted-migrations-scheduler.service.spec.ts | 15 ++-- .../common/login-strategies/login.strategy.ts | 1 + .../services/accounts/lock.services.spec.ts | 26 ++++--- libs/common/spec/fake-account-service.ts | 28 +++++-- .../src/auth/abstractions/account.service.ts | 11 ++- .../src/auth/services/account.service.spec.ts | 76 ++++++++++++++++++- .../src/auth/services/account.service.ts | 5 ++ .../auth-request-answering.service.spec.ts | 11 ++- .../src/auth/services/auth.service.spec.ts | 27 ++++--- ...-enrollment.service.implementation.spec.ts | 8 +- .../services/vault-timeout.service.spec.ts | 14 ++-- ...ult-server-notifications.multiuser.spec.ts | 13 +++- ...fault-server-notifications.service.spec.ts | 21 ++++- .../default-environment.service.spec.ts | 19 +++-- .../fido2/fido2-authenticator.service.spec.ts | 9 ++- .../services/sdk/default-sdk.service.spec.ts | 7 +- .../services/sdk/register-sdk.service.spec.ts | 12 ++- .../src/platform/sync/default-sync.service.ts | 1 + libs/common/src/services/api.service.spec.ts | 8 +- .../tools/extension/extension.service.spec.ts | 14 +++- .../tools/send/services/send.service.spec.ts | 8 +- .../tools/state/user-state-subject.spec.ts | 15 ++-- ...warden-password-protected-importer.spec.ts | 15 ++-- .../master-password-lock.component.spec.ts | 8 +- .../generator-metadata-provider.spec.ts | 13 +++- .../generator-profile-provider.spec.ts | 29 ++++--- ...fault-credential-generator.service.spec.ts | 9 ++- .../send-list-filters.component.spec.ts | 9 ++- .../login-credentials-view.component.spec.ts | 8 +- .../add-edit-folder-dialog.component.spec.ts | 9 +-- tsconfig.base.json | 1 + 60 files changed, 491 insertions(+), 276 deletions(-) diff --git a/apps/browser/src/auth/popup/account-switching/services/account-switcher.service.spec.ts b/apps/browser/src/auth/popup/account-switching/services/account-switcher.service.spec.ts index 4bacd453803..f3be535f00e 100644 --- a/apps/browser/src/auth/popup/account-switching/services/account-switcher.service.spec.ts +++ b/apps/browser/src/auth/popup/account-switching/services/account-switcher.service.spec.ts @@ -15,6 +15,7 @@ import { } from "@bitwarden/common/platform/abstractions/environment.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; +import { mockAccountInfoWith } from "@bitwarden/common/spec"; import { UserId } from "@bitwarden/common/types/guid"; import { AccountSwitcherService } from "./account-switcher.service"; @@ -71,11 +72,10 @@ describe("AccountSwitcherService", () => { describe("availableAccounts$", () => { it("should return all logged in accounts and an add account option when accounts are less than 5", async () => { - const accountInfo: AccountInfo = { + const accountInfo = mockAccountInfoWith({ name: "Test User 1", email: "test1@email.com", - emailVerified: true, - }; + }); avatarService.getUserAvatarColor$.mockReturnValue(of("#cccccc")); accountsSubject.next({ ["1" as UserId]: accountInfo, ["2" as UserId]: accountInfo }); @@ -109,11 +109,10 @@ describe("AccountSwitcherService", () => { const seedAccounts: Record<UserId, AccountInfo> = {}; const seedStatuses: Record<UserId, AuthenticationStatus> = {}; for (let i = 0; i < numberOfAccounts; i++) { - seedAccounts[`${i}` as UserId] = { + seedAccounts[`${i}` as UserId] = mockAccountInfoWith({ email: `test${i}@email.com`, - emailVerified: true, name: "Test User ${i}", - }; + }); seedStatuses[`${i}` as UserId] = AuthenticationStatus.Unlocked; } avatarService.getUserAvatarColor$.mockReturnValue(of("#cccccc")); @@ -133,11 +132,10 @@ describe("AccountSwitcherService", () => { ); it("excludes logged out accounts", async () => { - const user1AccountInfo: AccountInfo = { + const user1AccountInfo = mockAccountInfoWith({ name: "Test User 1", email: "", - emailVerified: true, - }; + }); accountsSubject.next({ ["1" as UserId]: user1AccountInfo }); authStatusSubject.next({ ["1" as UserId]: AuthenticationStatus.LoggedOut }); accountsSubject.next({ diff --git a/apps/browser/src/autofill/background/notification.background.spec.ts b/apps/browser/src/autofill/background/notification.background.spec.ts index 8df21bc66ef..ab16788ea6f 100644 --- a/apps/browser/src/autofill/background/notification.background.spec.ts +++ b/apps/browser/src/autofill/background/notification.background.spec.ts @@ -4,7 +4,7 @@ import { BehaviorSubject, firstValueFrom, of } from "rxjs"; import { CollectionService } from "@bitwarden/admin-console/common"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; -import { AccountInfo, AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { AuthService } from "@bitwarden/common/auth/services/auth.service"; import { ExtensionCommand } from "@bitwarden/common/autofill/constants"; @@ -17,6 +17,7 @@ import { MessagingService } from "@bitwarden/common/platform/abstractions/messag import { ThemeTypes } from "@bitwarden/common/platform/enums"; import { SelfHostedEnvironment } from "@bitwarden/common/platform/services/default-environment.service"; import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service"; +import { mockAccountInfoWith } from "@bitwarden/common/spec"; import { UserId } from "@bitwarden/common/types/guid"; import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; @@ -80,11 +81,12 @@ describe("NotificationBackground", () => { const organizationService = mock<OrganizationService>(); const userId = "testId" as UserId; - const activeAccountSubject = new BehaviorSubject<{ id: UserId } & AccountInfo>({ + const activeAccountSubject = new BehaviorSubject({ id: userId, - email: "test@example.com", - emailVerified: true, - name: "Test User", + ...mockAccountInfoWith({ + email: "test@example.com", + name: "Test User", + }), }); beforeEach(() => { diff --git a/apps/browser/src/autofill/browser/main-context-menu-handler.spec.ts b/apps/browser/src/autofill/browser/main-context-menu-handler.spec.ts index 1348928b7e9..1738485f289 100644 --- a/apps/browser/src/autofill/browser/main-context-menu-handler.spec.ts +++ b/apps/browser/src/autofill/browser/main-context-menu-handler.spec.ts @@ -18,6 +18,7 @@ import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/s import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { mockAccountInfoWith } from "@bitwarden/common/spec"; import { UserId } from "@bitwarden/common/types/guid"; import { CipherType } from "@bitwarden/common/vault/enums"; import { Cipher } from "@bitwarden/common/vault/models/domain/cipher"; @@ -123,9 +124,10 @@ describe("context-menu", () => { autofillSettingsService.enableContextMenu$ = of(true); accountService.activeAccount$ = of({ id: "userId" as UserId, - email: "", - emailVerified: false, - name: undefined, + ...mockAccountInfoWith({ + email: "", + name: undefined, + }), }); }); diff --git a/apps/browser/src/popup/components/extension-anon-layout-wrapper/extension-anon-layout-wrapper.stories.ts b/apps/browser/src/popup/components/extension-anon-layout-wrapper/extension-anon-layout-wrapper.stories.ts index 57ef285bdf5..8fdae06e28a 100644 --- a/apps/browser/src/popup/components/extension-anon-layout-wrapper/extension-anon-layout-wrapper.stories.ts +++ b/apps/browser/src/popup/components/extension-anon-layout-wrapper/extension-anon-layout-wrapper.stories.ts @@ -76,11 +76,14 @@ const decorators = (options: { { provide: AccountService, useValue: { + // We can't use mockAccountInfoWith() here because we can't take a dependency on @bitwarden/common/spec. + // This is because that package relies on jest dependencies that aren't available here. activeAccount$: of({ id: "test-user-id" as UserId, name: "Test User 1", email: "test@email.com", emailVerified: true, + creationDate: "2024-01-01T00:00:00.000Z", }), }, }, diff --git a/apps/browser/src/tools/popup/send-v2/send-v2.component.spec.ts b/apps/browser/src/tools/popup/send-v2/send-v2.component.spec.ts index 6d79f430a37..6e73d9811f2 100644 --- a/apps/browser/src/tools/popup/send-v2/send-v2.component.spec.ts +++ b/apps/browser/src/tools/popup/send-v2/send-v2.component.spec.ts @@ -16,6 +16,7 @@ import { EnvironmentService } from "@bitwarden/common/platform/abstractions/envi 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 { mockAccountInfoWith } from "@bitwarden/common/spec"; import { SendType } from "@bitwarden/common/tools/send/enums/send-type"; import { SendView } from "@bitwarden/common/tools/send/models/view/send.view"; import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction"; @@ -96,9 +97,10 @@ describe("SendV2Component", () => { useValue: { activeAccount$: of({ id: "123", - email: "test@email.com", - emailVerified: true, - name: "Test User", + ...mockAccountInfoWith({ + email: "test@email.com", + name: "Test User", + }), }), }, }, diff --git a/apps/browser/src/vault/popup/components/vault-v2/attachments/open-attachments/open-attachments.component.spec.ts b/apps/browser/src/vault/popup/components/vault-v2/attachments/open-attachments/open-attachments.component.spec.ts index 459b328c44e..e9636e09873 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/attachments/open-attachments/open-attachments.component.spec.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/attachments/open-attachments/open-attachments.component.spec.ts @@ -11,6 +11,7 @@ import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abs import { ProductTierType } from "@bitwarden/common/billing/enums"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { mockAccountInfoWith } from "@bitwarden/common/spec"; import { CipherId, OrganizationId, UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { PremiumUpgradePromptService } from "@bitwarden/common/vault/abstractions/premium-upgrade-prompt.service"; @@ -60,9 +61,10 @@ describe("OpenAttachmentsComponent", () => { const accountService = { activeAccount$: of({ id: mockUserId, - email: "test@email.com", - emailVerified: true, - name: "Test User", + ...mockAccountInfoWith({ + email: "test@email.com", + name: "Test User", + }), }), }; const formStatusChange$ = new BehaviorSubject<"enabled" | "disabled">("enabled"); diff --git a/apps/cli/src/key-management/commands/unlock.command.spec.ts b/apps/cli/src/key-management/commands/unlock.command.spec.ts index 70e9a8fd232..50ef414ec37 100644 --- a/apps/cli/src/key-management/commands/unlock.command.spec.ts +++ b/apps/cli/src/key-management/commands/unlock.command.spec.ts @@ -15,6 +15,7 @@ import { ConfigService } from "@bitwarden/common/platform/abstractions/config/co import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; +import { mockAccountInfoWith } from "@bitwarden/common/spec"; import { CsprngArray } from "@bitwarden/common/types/csprng"; import { MasterKey, UserKey } from "@bitwarden/common/types/key"; import { KeyService } from "@bitwarden/key-management"; @@ -48,9 +49,10 @@ describe("UnlockCommand", () => { const mockMasterPassword = "testExample"; const activeAccount: Account = { id: "user-id" as UserId, - email: "user@example.com", - emailVerified: true, - name: "User", + ...mockAccountInfoWith({ + email: "user@example.com", + name: "User", + }), }; const mockUserKey = new SymmetricCryptoKey(new Uint8Array(64)) as UserKey; const mockSessionKey = new Uint8Array(64) as CsprngArray; diff --git a/apps/desktop/src/autofill/modal/credentials/fido2-create.component.spec.ts b/apps/desktop/src/autofill/modal/credentials/fido2-create.component.spec.ts index 778215895ee..dbef860aafe 100644 --- a/apps/desktop/src/autofill/modal/credentials/fido2-create.component.spec.ts +++ b/apps/desktop/src/autofill/modal/credentials/fido2-create.component.spec.ts @@ -7,6 +7,7 @@ import { AccountService, Account } from "@bitwarden/common/auth/abstractions/acc import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { mockAccountInfoWith } from "@bitwarden/common/spec"; import { UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherRepromptType, CipherType } from "@bitwarden/common/vault/enums"; @@ -40,9 +41,10 @@ describe("Fido2CreateComponent", () => { const activeAccountSubject = new BehaviorSubject<Account | null>({ id: "test-user-id" as UserId, - email: "test@example.com", - emailVerified: true, - name: "Test User", + ...mockAccountInfoWith({ + email: "test@example.com", + name: "Test User", + }), }); beforeEach(async () => { 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 555e6ceef5b..907da2fe85c 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 @@ -10,6 +10,7 @@ import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { Account, UserId } from "@bitwarden/common/platform/models/domain/account"; +import { mockAccountInfoWith } from "@bitwarden/common/spec"; import { DesktopAutotypeDefaultSettingPolicy } from "./desktop-autotype-policy.service"; @@ -30,9 +31,10 @@ describe("DesktopAutotypeDefaultSettingPolicy", () => { beforeEach(() => { mockAccountSubject = new BehaviorSubject<Account | null>({ id: mockUserId, - email: "test@example.com", - emailVerified: true, - name: "Test User", + ...mockAccountInfoWith({ + email: "test@example.com", + name: "Test User", + }), }); mockFeatureFlagSubject = new BehaviorSubject<boolean>(true); mockAuthStatusSubject = new BehaviorSubject<AuthenticationStatus>( diff --git a/apps/desktop/src/services/biometric-message-handler.service.spec.ts b/apps/desktop/src/services/biometric-message-handler.service.spec.ts index 49d346bfa3a..3b343fcc0fb 100644 --- a/apps/desktop/src/services/biometric-message-handler.service.spec.ts +++ b/apps/desktop/src/services/biometric-message-handler.service.spec.ts @@ -2,7 +2,7 @@ import { NgZone } from "@angular/core"; import { mock, MockProxy } from "jest-mock-extended"; import { BehaviorSubject, filter, firstValueFrom, of, take, timeout, timer } from "rxjs"; -import { AccountInfo, AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +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"; import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service"; @@ -10,7 +10,7 @@ import { EncryptService } from "@bitwarden/common/key-management/crypto/abstract import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; -import { FakeAccountService } from "@bitwarden/common/spec"; +import { mockAccountInfoWith, FakeAccountService } from "@bitwarden/common/spec"; import { CsprngArray } from "@bitwarden/common/types/csprng"; import { UserId } from "@bitwarden/common/types/guid"; import { DialogService } from "@bitwarden/components"; @@ -23,17 +23,15 @@ import { BiometricMessageHandlerService } from "./biometric-message-handler.serv const SomeUser = "SomeUser" as UserId; const AnotherUser = "SomeOtherUser" as UserId; -const accounts: Record<UserId, AccountInfo> = { - [SomeUser]: { +const accounts = { + [SomeUser]: mockAccountInfoWith({ name: "some user", email: "some.user@example.com", - emailVerified: true, - }, - [AnotherUser]: { + }), + [AnotherUser]: mockAccountInfoWith({ name: "some other user", email: "some.other.user@example.com", - emailVerified: true, - }, + }), }; describe("BiometricMessageHandlerService", () => { diff --git a/apps/web/src/app/billing/individual/upgrade/unified-upgrade-dialog/unified-upgrade-dialog.component.spec.ts b/apps/web/src/app/billing/individual/upgrade/unified-upgrade-dialog/unified-upgrade-dialog.component.spec.ts index b28a7b8c4a2..6bc0efb9e96 100644 --- a/apps/web/src/app/billing/individual/upgrade/unified-upgrade-dialog/unified-upgrade-dialog.component.spec.ts +++ b/apps/web/src/app/billing/individual/upgrade/unified-upgrade-dialog/unified-upgrade-dialog.component.spec.ts @@ -10,6 +10,7 @@ import { PersonalSubscriptionPricingTierId, PersonalSubscriptionPricingTierIds, } from "@bitwarden/common/billing/types/subscription-pricing-tier"; +import { mockAccountInfoWith } from "@bitwarden/common/spec"; import { UserId } from "@bitwarden/common/types/guid"; import { DIALOG_DATA, DialogRef } from "@bitwarden/components"; @@ -63,9 +64,10 @@ describe("UnifiedUpgradeDialogComponent", () => { const mockAccount: Account = { id: "user-id" as UserId, - email: "test@example.com", - emailVerified: true, - name: "Test User", + ...mockAccountInfoWith({ + email: "test@example.com", + name: "Test User", + }), }; const defaultDialogData: UnifiedUpgradeDialogParams = { diff --git a/apps/web/src/app/billing/individual/upgrade/upgrade-nav-button/upgrade-nav-button/upgrade-nav-button.component.spec.ts b/apps/web/src/app/billing/individual/upgrade/upgrade-nav-button/upgrade-nav-button/upgrade-nav-button.component.spec.ts index 787936c102e..f5df248cbbf 100644 --- a/apps/web/src/app/billing/individual/upgrade/upgrade-nav-button/upgrade-nav-button/upgrade-nav-button.component.spec.ts +++ b/apps/web/src/app/billing/individual/upgrade/upgrade-nav-button/upgrade-nav-button/upgrade-nav-button.component.spec.ts @@ -7,6 +7,7 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { Account, AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { mockAccountInfoWith } from "@bitwarden/common/spec"; import { UserId } from "@bitwarden/common/types/guid"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { DialogRef, DialogService } from "@bitwarden/components"; @@ -32,9 +33,10 @@ describe("UpgradeNavButtonComponent", () => { const mockAccount: Account = { id: "user-id" as UserId, - email: "test@example.com", - emailVerified: true, - name: "Test User", + ...mockAccountInfoWith({ + email: "test@example.com", + name: "Test User", + }), }; beforeEach(async () => { diff --git a/apps/web/src/app/billing/individual/upgrade/upgrade-payment/services/upgrade-payment.service.spec.ts b/apps/web/src/app/billing/individual/upgrade/upgrade-payment/services/upgrade-payment.service.spec.ts index 9d17d62e4dc..81169d719b6 100644 --- a/apps/web/src/app/billing/individual/upgrade/upgrade-payment/services/upgrade-payment.service.spec.ts +++ b/apps/web/src/app/billing/individual/upgrade/upgrade-payment/services/upgrade-payment.service.spec.ts @@ -13,6 +13,7 @@ import { PaymentMethodType, PlanType } from "@bitwarden/common/billing/enums"; import { PersonalSubscriptionPricingTierIds } from "@bitwarden/common/billing/types/subscription-pricing-tier"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { SyncService } from "@bitwarden/common/platform/sync"; +import { mockAccountInfoWith } from "@bitwarden/common/spec"; import { UserId } from "@bitwarden/common/types/guid"; import { LogService } from "@bitwarden/logging"; @@ -46,11 +47,12 @@ describe("UpgradePaymentService", () => { let sut: UpgradePaymentService; - const mockAccount = { + const mockAccount: Account = { id: "user-id" as UserId, - email: "test@example.com", - emailVerified: true, - name: "Test User", + ...mockAccountInfoWith({ + email: "test@example.com", + name: "Test User", + }), }; const mockTokenizedPaymentMethod: TokenizedPaymentMethod = { @@ -151,9 +153,10 @@ describe("UpgradePaymentService", () => { const mockAccount: Account = { id: "user-id" as UserId, - email: "test@example.com", - name: "Test User", - emailVerified: true, + ...mockAccountInfoWith({ + email: "test@example.com", + name: "Test User", + }), }; const paidOrgData = { @@ -203,9 +206,10 @@ describe("UpgradePaymentService", () => { const mockAccount: Account = { id: "user-id" as UserId, - email: "test@example.com", - name: "Test User", - emailVerified: true, + ...mockAccountInfoWith({ + email: "test@example.com", + name: "Test User", + }), }; const paidOrgData = { @@ -255,9 +259,10 @@ describe("UpgradePaymentService", () => { const mockAccount: Account = { id: "user-id" as UserId, - email: "test@example.com", - name: "Test User", - emailVerified: true, + ...mockAccountInfoWith({ + email: "test@example.com", + name: "Test User", + }), }; mockAccountService.activeAccount$ = of(mockAccount); @@ -289,9 +294,10 @@ describe("UpgradePaymentService", () => { const mockAccount: Account = { id: "user-id" as UserId, - email: "test@example.com", - name: "Test User", - emailVerified: true, + ...mockAccountInfoWith({ + email: "test@example.com", + name: "Test User", + }), }; const expectedCredit = 25.5; @@ -353,9 +359,10 @@ describe("UpgradePaymentService", () => { const mockAccount: Account = { id: "user-id" as UserId, - email: "test@example.com", - name: "Test User", - emailVerified: true, + ...mockAccountInfoWith({ + email: "test@example.com", + name: "Test User", + }), }; const paidOrgData = { diff --git a/apps/web/src/app/dirt/reports/pages/breach-report.component.spec.ts b/apps/web/src/app/dirt/reports/pages/breach-report.component.spec.ts index 143dce3915e..886267e3189 100644 --- a/apps/web/src/app/dirt/reports/pages/breach-report.component.spec.ts +++ b/apps/web/src/app/dirt/reports/pages/breach-report.component.spec.ts @@ -10,6 +10,7 @@ import { AuditService } from "@bitwarden/common/abstractions/audit.service"; import { AccountInfo, AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BreachAccountResponse } from "@bitwarden/common/dirt/models/response/breach-account.response"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { mockAccountInfoWith } from "@bitwarden/common/spec"; import { UserId } from "@bitwarden/common/types/guid"; import { BreachReportComponent } from "./breach-report.component"; @@ -38,9 +39,10 @@ describe("BreachReportComponent", () => { let accountService: MockProxy<AccountService>; const activeAccountSubject = new BehaviorSubject<{ id: UserId } & AccountInfo>({ id: "testId" as UserId, - email: "test@example.com", - emailVerified: true, - name: "Test User", + ...mockAccountInfoWith({ + email: "test@example.com", + name: "Test User", + }), }); beforeEach(async () => { diff --git a/apps/web/src/app/key-management/key-rotation/user-key-rotation.service.spec.ts b/apps/web/src/app/key-management/key-rotation/user-key-rotation.service.spec.ts index f4b50b4a772..c0b734f17cc 100644 --- a/apps/web/src/app/key-management/key-rotation/user-key-rotation.service.spec.ts +++ b/apps/web/src/app/key-management/key-rotation/user-key-rotation.service.spec.ts @@ -30,6 +30,7 @@ import { SdkLoadService } from "@bitwarden/common/platform/abstractions/sdk/sdk- import { HashPurpose } from "@bitwarden/common/platform/enums"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; +import { mockAccountInfoWith } from "@bitwarden/common/spec"; import { SendWithIdRequest } from "@bitwarden/common/tools/send/models/request/send-with-id.request"; import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction"; import { UserId } from "@bitwarden/common/types/guid"; @@ -286,9 +287,10 @@ describe("KeyRotationService", () => { const mockUser = { id: "mockUserId" as UserId, - email: "mockEmail", - emailVerified: true, - name: "mockName", + ...mockAccountInfoWith({ + email: "mockEmail", + name: "mockName", + }), }; const mockTrustedPublicKeys = [Utils.fromUtf8ToArray("test-public-key")]; diff --git a/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.stories.ts b/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.stories.ts index 88132e56384..ea6e972e431 100644 --- a/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.stories.ts +++ b/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.stories.ts @@ -75,11 +75,14 @@ class MockSyncService implements Partial<SyncService> { } class MockAccountService implements Partial<AccountService> { + // We can't use mockAccountInfoWith() here because we can't take a dependency on @bitwarden/common/spec. + // This is because that package relies on jest dependencies that aren't available here. activeAccount$?: Observable<Account> = of({ id: "test-user-id" as UserId, name: "Test User 1", email: "test@email.com", emailVerified: true, + creationDate: "2024-01-01T00:00:00.000Z", }); } diff --git a/apps/web/src/app/layouts/product-switcher/product-switcher.stories.ts b/apps/web/src/app/layouts/product-switcher/product-switcher.stories.ts index 4581f5981e6..d412530a635 100644 --- a/apps/web/src/app/layouts/product-switcher/product-switcher.stories.ts +++ b/apps/web/src/app/layouts/product-switcher/product-switcher.stories.ts @@ -75,11 +75,14 @@ class MockSyncService implements Partial<SyncService> { } class MockAccountService implements Partial<AccountService> { + // We can't use mockAccountInfoWith() here because we can't take a dependency on @bitwarden/common/spec. + // This is because that package relies on jest dependencies that aren't available here. activeAccount$?: Observable<Account> = of({ id: "test-user-id" as UserId, name: "Test User 1", email: "test@email.com", emailVerified: true, + creationDate: "2024-01-01T00:00:00.000Z", }); } 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 6b46cd89956..2ba9dd6fad4 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 @@ -2,14 +2,18 @@ import { TestBed } from "@angular/core/testing"; import { BehaviorSubject, firstValueFrom, take, timeout } from "rxjs"; import { AuthRequestServiceAbstraction } from "@bitwarden/auth/common"; -import { AccountInfo, AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; 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"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { StateProvider } from "@bitwarden/common/platform/state"; -import { FakeStateProvider, mockAccountServiceWith } from "@bitwarden/common/spec"; +import { + FakeStateProvider, + mockAccountServiceWith, + mockAccountInfoWith, +} from "@bitwarden/common/spec"; import { UserId } from "@bitwarden/common/types/guid"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; @@ -27,8 +31,11 @@ describe("VaultBannersService", () => { const fakeStateProvider = new FakeStateProvider(mockAccountServiceWith(userId)); const getEmailVerified = jest.fn().mockResolvedValue(true); const lastSync$ = new BehaviorSubject<Date | null>(null); - const accounts$ = new BehaviorSubject<Record<UserId, AccountInfo>>({ - [userId]: { email: "test@bitwarden.com", emailVerified: true, name: "name" } as AccountInfo, + const accounts$ = new BehaviorSubject({ + [userId]: mockAccountInfoWith({ + email: "test@bitwarden.com", + name: "name", + }), }); const pendingAuthRequests$ = new BehaviorSubject<Array<AuthRequestResponse>>([]); diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/guards/provider-permissions.guard.spec.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/guards/provider-permissions.guard.spec.ts index a0a881dbad7..99d54eedc29 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/guards/provider-permissions.guard.spec.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/guards/provider-permissions.guard.spec.ts @@ -10,6 +10,7 @@ import { ProviderUserType } from "@bitwarden/common/admin-console/enums"; import { Provider } from "@bitwarden/common/admin-console/models/domain/provider"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { mockAccountInfoWith } from "@bitwarden/common/spec"; import { UserId } from "@bitwarden/common/types/guid"; import { ToastService } from "@bitwarden/components"; import { newGuid } from "@bitwarden/guid"; @@ -41,9 +42,10 @@ describe("Provider Permissions Guard", () => { accountService.activeAccount$ = of({ id: mockUserId, - email: "test@example.com", - emailVerified: true, - name: "Test User", + ...mockAccountInfoWith({ + email: "test@example.com", + name: "Test User", + }), }); route = mock<ActivatedRouteSnapshot>({ diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/secrets/secret.service.spec.ts b/bitwarden_license/bit-web/src/app/secrets-manager/secrets/secret.service.spec.ts index 056f7cfe255..606cb835ff1 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/secrets/secret.service.spec.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/secrets/secret.service.spec.ts @@ -6,6 +6,7 @@ import { AccountInfo, AccountService } from "@bitwarden/common/auth/abstractions 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 { mockAccountInfoWith } from "@bitwarden/common/spec"; import { CsprngArray } from "@bitwarden/common/types/csprng"; import { OrganizationId, UserId } from "@bitwarden/common/types/guid"; import { OrgKey } from "@bitwarden/common/types/key"; @@ -37,9 +38,11 @@ describe("SecretService", () => { let accountService: MockProxy<AccountService> = mock<AccountService>(); const activeAccountSubject = new BehaviorSubject<{ id: UserId } & AccountInfo>({ id: "testId" as UserId, - email: "test@example.com", - emailVerified: true, - name: "Test User", + ...mockAccountInfoWith({ + email: "test@example.com", + name: "Test User", + emailVerified: true, + }), }); beforeEach(() => { diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/settings/services/sm-porting-api.service.spec.ts b/bitwarden_license/bit-web/src/app/secrets-manager/settings/services/sm-porting-api.service.spec.ts index a4f77e6de0b..aa722e31681 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/settings/services/sm-porting-api.service.spec.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/settings/services/sm-porting-api.service.spec.ts @@ -7,6 +7,7 @@ import { EncryptService } from "@bitwarden/common/key-management/crypto/abstract import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; +import { mockAccountInfoWith } from "@bitwarden/common/spec"; import { CsprngArray } from "@bitwarden/common/types/csprng"; import { OrganizationId, UserId } from "@bitwarden/common/types/guid"; import { OrgKey } from "@bitwarden/common/types/key"; @@ -38,9 +39,11 @@ describe("SecretsManagerPortingApiService", () => { let accountService: MockProxy<AccountService>; const activeAccountSubject = new BehaviorSubject<{ id: UserId } & AccountInfo>({ id: "testId" as UserId, - email: "test@example.com", - emailVerified: true, - name: "Test User", + ...mockAccountInfoWith({ + email: "test@example.com", + name: "Test User", + emailVerified: true, + }), }); beforeEach(() => { diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/access-policy.service.spec.ts b/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/access-policy.service.spec.ts index 37a0dc06837..903bfd35122 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/access-policy.service.spec.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/access-policy.service.spec.ts @@ -31,7 +31,7 @@ import { PeopleAccessPoliciesRequest } from "./models/requests/people-access-pol import { ProjectServiceAccountsAccessPoliciesRequest } from "./models/requests/project-service-accounts-access-policies.request"; import { ServiceAccountGrantedPoliciesRequest } from "./models/requests/service-account-granted-policies.request"; -import { trackEmissions } from "@bitwarden/common/../spec"; +import { trackEmissions, mockAccountInfoWith } from "@bitwarden/common/../spec"; const SomeCsprngArray = new Uint8Array(64) as CsprngArray; const SomeOrganization = "some organization" as OrganizationId; @@ -52,9 +52,10 @@ describe("AccessPolicyService", () => { let accountService: MockProxy<AccountService>; const activeAccountSubject = new BehaviorSubject<{ id: UserId } & AccountInfo>({ id: "testId" as UserId, - email: "test@example.com", - emailVerified: true, - name: "Test User", + ...mockAccountInfoWith({ + email: "test@example.com", + name: "Test User", + }), }); beforeEach(() => { diff --git a/libs/angular/src/auth/guards/auth.guard.spec.ts b/libs/angular/src/auth/guards/auth.guard.spec.ts index fccfcd58874..335e31ec4d8 100644 --- a/libs/angular/src/auth/guards/auth.guard.spec.ts +++ b/libs/angular/src/auth/guards/auth.guard.spec.ts @@ -5,11 +5,7 @@ import { MockProxy, mock } from "jest-mock-extended"; import { BehaviorSubject, of } from "rxjs"; import { EmptyComponent } from "@bitwarden/angular/platform/guard/feature-flag.guard.spec"; -import { - Account, - AccountInfo, - AccountService, -} from "@bitwarden/common/auth/abstractions/account.service"; +import { Account, 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"; import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason"; @@ -18,6 +14,7 @@ import { KeyConnectorService } from "@bitwarden/common/key-management/key-connec 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 { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; +import { mockAccountInfoWith } from "@bitwarden/common/spec"; import { UserId } from "@bitwarden/common/types/guid"; import { authGuard } from "./auth.guard"; @@ -38,16 +35,13 @@ describe("AuthGuard", () => { const accountService: MockProxy<AccountService> = mock<AccountService>(); const activeAccountSubject = new BehaviorSubject<Account | null>(null); accountService.activeAccount$ = activeAccountSubject; - activeAccountSubject.next( - Object.assign( - { - name: "Test User 1", - email: "test@email.com", - emailVerified: true, - } as AccountInfo, - { id: "test-id" as UserId }, - ), - ); + activeAccountSubject.next({ + id: "test-id" as UserId, + ...mockAccountInfoWith({ + name: "Test User 1", + email: "test@email.com", + }), + }); if (featureFlag) { configService.getFeatureFlag.mockResolvedValue(true); diff --git a/libs/angular/src/auth/guards/lock.guard.spec.ts b/libs/angular/src/auth/guards/lock.guard.spec.ts index da89ee786b7..af36df06097 100644 --- a/libs/angular/src/auth/guards/lock.guard.spec.ts +++ b/libs/angular/src/auth/guards/lock.guard.spec.ts @@ -5,11 +5,7 @@ import { MockProxy, mock } from "jest-mock-extended"; import { BehaviorSubject, of } from "rxjs"; import { EmptyComponent } from "@bitwarden/angular/platform/guard/feature-flag.guard.spec"; -import { - Account, - AccountInfo, - AccountService, -} from "@bitwarden/common/auth/abstractions/account.service"; +import { Account, AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; @@ -20,6 +16,7 @@ import { KeyConnectorDomainConfirmation } from "@bitwarden/common/key-management import { VaultTimeoutSettingsService } from "@bitwarden/common/key-management/vault-timeout"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { mockAccountInfoWith } from "@bitwarden/common/spec"; import { UserId } from "@bitwarden/common/types/guid"; import { KeyService } from "@bitwarden/key-management"; @@ -68,16 +65,13 @@ describe("lockGuard", () => { const accountService: MockProxy<AccountService> = mock<AccountService>(); const activeAccountSubject = new BehaviorSubject<Account | null>(null); accountService.activeAccount$ = activeAccountSubject; - activeAccountSubject.next( - Object.assign( - { - name: "Test User 1", - email: "test@email.com", - emailVerified: true, - } as AccountInfo, - { id: "test-id" as UserId }, - ), - ); + activeAccountSubject.next({ + id: "test-id" as UserId, + ...mockAccountInfoWith({ + name: "Test User 1", + email: "test@email.com", + }), + }); const testBed = TestBed.configureTestingModule({ imports: [ diff --git a/libs/angular/src/auth/guards/redirect-to-vault-if-unlocked/redirect-to-vault-if-unlocked.guard.spec.ts b/libs/angular/src/auth/guards/redirect-to-vault-if-unlocked/redirect-to-vault-if-unlocked.guard.spec.ts index 004499beede..6dc91fbb925 100644 --- a/libs/angular/src/auth/guards/redirect-to-vault-if-unlocked/redirect-to-vault-if-unlocked.guard.spec.ts +++ b/libs/angular/src/auth/guards/redirect-to-vault-if-unlocked/redirect-to-vault-if-unlocked.guard.spec.ts @@ -7,6 +7,7 @@ import { EmptyComponent } from "@bitwarden/angular/platform/guard/feature-flag.g import { Account, 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"; +import { mockAccountInfoWith } from "@bitwarden/common/spec"; import { UserId } from "@bitwarden/common/types/guid"; import { redirectToVaultIfUnlockedGuard } from "./redirect-to-vault-if-unlocked.guard"; @@ -14,9 +15,10 @@ import { redirectToVaultIfUnlockedGuard } from "./redirect-to-vault-if-unlocked. describe("redirectToVaultIfUnlockedGuard", () => { const activeUser: Account = { id: "userId" as UserId, - email: "test@email.com", - emailVerified: true, - name: "Test User", + ...mockAccountInfoWith({ + email: "test@email.com", + name: "Test User", + }), }; const setup = (activeUser: Account | null, authStatus: AuthenticationStatus | null) => { diff --git a/libs/angular/src/auth/guards/tde-decryption-required.guard.spec.ts b/libs/angular/src/auth/guards/tde-decryption-required.guard.spec.ts index 4408452a2a2..17df6d1d76b 100644 --- a/libs/angular/src/auth/guards/tde-decryption-required.guard.spec.ts +++ b/libs/angular/src/auth/guards/tde-decryption-required.guard.spec.ts @@ -9,6 +9,7 @@ import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { DeviceTrustServiceAbstraction } from "@bitwarden/common/key-management/device-trust/abstractions/device-trust.service.abstraction"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { mockAccountInfoWith } from "@bitwarden/common/spec"; import { UserId } from "@bitwarden/common/types/guid"; import { KeyService } from "@bitwarden/key-management"; @@ -17,9 +18,10 @@ import { tdeDecryptionRequiredGuard } from "./tde-decryption-required.guard"; describe("tdeDecryptionRequiredGuard", () => { const activeUser: Account = { id: "fake_user_id" as UserId, - email: "test@email.com", - emailVerified: true, - name: "Test User", + ...mockAccountInfoWith({ + email: "test@email.com", + name: "Test User", + }), }; const setup = ( diff --git a/libs/angular/src/auth/guards/unauth.guard.spec.ts b/libs/angular/src/auth/guards/unauth.guard.spec.ts index c696b849558..284f595f81a 100644 --- a/libs/angular/src/auth/guards/unauth.guard.spec.ts +++ b/libs/angular/src/auth/guards/unauth.guard.spec.ts @@ -10,6 +10,7 @@ import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { DeviceTrustServiceAbstraction } from "@bitwarden/common/key-management/device-trust/abstractions/device-trust.service.abstraction"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { mockAccountInfoWith } from "@bitwarden/common/spec"; import { UserId } from "@bitwarden/common/types/guid"; import { KeyService } from "@bitwarden/key-management"; @@ -18,9 +19,10 @@ import { unauthGuardFn } from "./unauth.guard"; describe("UnauthGuard", () => { const activeUser: Account = { id: "fake_user_id" as UserId, - email: "test@email.com", - emailVerified: true, - name: "Test User", + ...mockAccountInfoWith({ + email: "test@email.com", + name: "Test User", + }), }; const setup = ( diff --git a/libs/angular/src/auth/login-approval/login-approval-dialog.component.spec.ts b/libs/angular/src/auth/login-approval/login-approval-dialog.component.spec.ts index b21264eb7c8..4dc7522c1b8 100644 --- a/libs/angular/src/auth/login-approval/login-approval-dialog.component.spec.ts +++ b/libs/angular/src/auth/login-approval/login-approval-dialog.component.spec.ts @@ -11,6 +11,7 @@ import { DevicesServiceAbstraction } from "@bitwarden/common/auth/abstractions/d import { AuthRequestResponse } from "@bitwarden/common/auth/models/response/auth-request.response"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; +import { mockAccountInfoWith } from "@bitwarden/common/spec"; import { UserId } from "@bitwarden/common/types/guid"; import { DialogRef, DIALOG_DATA, ToastService } from "@bitwarden/components"; import { LogService } from "@bitwarden/logging"; @@ -48,10 +49,11 @@ describe("LoginApprovalDialogComponent", () => { validationService = mock<ValidationService>(); accountService.activeAccount$ = of({ - email: testEmail, id: "test-user-id" as UserId, - emailVerified: true, - name: null, + ...mockAccountInfoWith({ + email: testEmail, + name: null, + }), }); await TestBed.configureTestingModule({ diff --git a/libs/angular/src/auth/password-management/change-password/default-change-password.service.spec.ts b/libs/angular/src/auth/password-management/change-password/default-change-password.service.spec.ts index d14e33c1fdc..5dfc5ffa245 100644 --- a/libs/angular/src/auth/password-management/change-password/default-change-password.service.spec.ts +++ b/libs/angular/src/auth/password-management/change-password/default-change-password.service.spec.ts @@ -8,6 +8,7 @@ import { MasterPasswordApiService } from "@bitwarden/common/auth/abstractions/ma import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string"; import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; +import { mockAccountInfoWith } from "@bitwarden/common/spec"; import { UserId } from "@bitwarden/common/types/guid"; import { MasterKey, UserKey } from "@bitwarden/common/types/key"; import { KeyService, PBKDF2KdfConfig } from "@bitwarden/key-management"; @@ -26,9 +27,11 @@ describe("DefaultChangePasswordService", () => { const user: Account = { id: userId, - email: "email", - emailVerified: false, - name: "name", + ...mockAccountInfoWith({ + email: "email", + name: "name", + emailVerified: false, + }), }; const passwordInputResult: PasswordInputResult = { diff --git a/libs/angular/src/key-management/encrypted-migration/encrypted-migrations-scheduler.service.spec.ts b/libs/angular/src/key-management/encrypted-migration/encrypted-migrations-scheduler.service.spec.ts index 76cfbc0bfdd..610ec5923eb 100644 --- a/libs/angular/src/key-management/encrypted-migration/encrypted-migrations-scheduler.service.spec.ts +++ b/libs/angular/src/key-management/encrypted-migration/encrypted-migrations-scheduler.service.spec.ts @@ -2,14 +2,13 @@ import { Router } from "@angular/router"; import { mock } from "jest-mock-extended"; import { of } from "rxjs"; -import { AccountInfo } 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"; import { EncryptedMigrator } from "@bitwarden/common/key-management/encrypted-migrator/encrypted-migrator.abstraction"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { SingleUserState, StateProvider } from "@bitwarden/common/platform/state"; import { SyncService } from "@bitwarden/common/platform/sync"; -import { FakeAccountService } from "@bitwarden/common/spec"; +import { mockAccountInfoWith, FakeAccountService } from "@bitwarden/common/spec"; import { UserId } from "@bitwarden/common/types/guid"; import { DialogService, ToastService } from "@bitwarden/components"; import { LogService } from "@bitwarden/logging"; @@ -22,17 +21,15 @@ import { PromptMigrationPasswordComponent } from "./prompt-migration-password.co const SomeUser = "SomeUser" as UserId; const AnotherUser = "SomeOtherUser" as UserId; -const accounts: Record<UserId, AccountInfo> = { - [SomeUser]: { +const accounts = { + [SomeUser]: mockAccountInfoWith({ name: "some user", email: "some.user@example.com", - emailVerified: true, - }, - [AnotherUser]: { + }), + [AnotherUser]: mockAccountInfoWith({ name: "some other user", email: "some.other.user@example.com", - emailVerified: true, - }, + }), }; describe("DefaultEncryptedMigrationsSchedulerService", () => { diff --git a/libs/auth/src/common/login-strategies/login.strategy.ts b/libs/auth/src/common/login-strategies/login.strategy.ts index ae375c8b2f5..acb32969f08 100644 --- a/libs/auth/src/common/login-strategies/login.strategy.ts +++ b/libs/auth/src/common/login-strategies/login.strategy.ts @@ -189,6 +189,7 @@ export abstract class LoginStrategy { name: accountInformation.name, email: accountInformation.email ?? "", emailVerified: accountInformation.email_verified ?? false, + creationDate: undefined, // We don't get a creation date in the token. See https://bitwarden.atlassian.net/browse/PM-29551 for consolidation plans. }); // User env must be seeded from currently set env before switching to the account diff --git a/libs/auth/src/common/services/accounts/lock.services.spec.ts b/libs/auth/src/common/services/accounts/lock.services.spec.ts index e22a6f71581..41e3768d80b 100644 --- a/libs/auth/src/common/services/accounts/lock.services.spec.ts +++ b/libs/auth/src/common/services/accounts/lock.services.spec.ts @@ -8,7 +8,7 @@ import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key- import { VaultTimeoutSettingsService } from "@bitwarden/common/key-management/vault-timeout"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { SystemService } from "@bitwarden/common/platform/abstractions/system.service"; -import { mockAccountServiceWith } from "@bitwarden/common/spec"; +import { mockAccountServiceWith, mockAccountInfoWith } 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"; @@ -79,17 +79,21 @@ describe("DefaultLockService", () => { ); it("locks the active account last", async () => { - await accountService.addAccount(mockUser2, { - name: "name2", - email: "email2@example.com", - emailVerified: false, - }); + await accountService.addAccount( + mockUser2, + mockAccountInfoWith({ + name: "name2", + email: "email2@example.com", + }), + ); - await accountService.addAccount(mockUser3, { - name: "name3", - email: "name3@example.com", - emailVerified: false, - }); + await accountService.addAccount( + mockUser3, + mockAccountInfoWith({ + name: "name3", + email: "name3@example.com", + }), + ); const lockSpy = jest.spyOn(sut, "lock").mockResolvedValue(undefined); diff --git a/libs/common/spec/fake-account-service.ts b/libs/common/spec/fake-account-service.ts index 389975dc2e1..ed8b7796966 100644 --- a/libs/common/spec/fake-account-service.ts +++ b/libs/common/spec/fake-account-service.ts @@ -6,19 +6,26 @@ import { ReplaySubject, combineLatest, map, Observable } from "rxjs"; import { Account, AccountInfo, AccountService } from "../src/auth/abstractions/account.service"; import { UserId } from "../src/types/guid"; +/** + * Creates a mock AccountInfo object with sensible defaults that can be overridden. + * Use this when you need just an AccountInfo object in tests. + */ +export function mockAccountInfoWith(info: Partial<AccountInfo> = {}): AccountInfo { + return { + name: "name", + email: "email", + emailVerified: true, + creationDate: "2024-01-01T00:00:00.000Z", + ...info, + }; +} + export function mockAccountServiceWith( userId: UserId, info: Partial<AccountInfo> = {}, activity: Record<UserId, Date> = {}, ): FakeAccountService { - const fullInfo: AccountInfo = { - ...info, - ...{ - name: "name", - email: "email", - emailVerified: true, - }, - }; + const fullInfo = mockAccountInfoWith(info); const fullActivity = { [userId]: new Date(), ...activity }; @@ -104,6 +111,10 @@ export class FakeAccountService implements AccountService { await this.mock.setAccountEmailVerified(userId, emailVerified); } + async setAccountCreationDate(userId: UserId, creationDate: string): Promise<void> { + await this.mock.setAccountCreationDate(userId, creationDate); + } + async switchAccount(userId: UserId): Promise<void> { const next = userId == null ? null : { id: userId, ...this.accountsSubject["_buffer"]?.[0]?.[userId] }; @@ -127,4 +138,5 @@ const loggedOutInfo: AccountInfo = { name: undefined, email: "", emailVerified: false, + creationDate: undefined, }; diff --git a/libs/common/src/auth/abstractions/account.service.ts b/libs/common/src/auth/abstractions/account.service.ts index 8b0280feb01..78822f3ebd5 100644 --- a/libs/common/src/auth/abstractions/account.service.ts +++ b/libs/common/src/auth/abstractions/account.service.ts @@ -2,14 +2,11 @@ import { Observable } from "rxjs"; import { UserId } from "../../types/guid"; -/** - * Holds information about an account for use in the AccountService - * if more information is added, be sure to update the equality method. - */ export type AccountInfo = { email: string; emailVerified: boolean; name: string | undefined; + creationDate: string | undefined; }; export type Account = { id: UserId } & AccountInfo; @@ -75,6 +72,12 @@ export abstract class AccountService { * @param emailVerified */ abstract setAccountEmailVerified(userId: UserId, emailVerified: boolean): Promise<void>; + /** + * updates the `accounts$` observable with the creation date for the account. + * @param userId + * @param creationDate + */ + abstract setAccountCreationDate(userId: UserId, creationDate: string): Promise<void>; /** * updates the `accounts$` observable with the new VerifyNewDeviceLogin property for the account. * @param userId diff --git a/libs/common/src/auth/services/account.service.spec.ts b/libs/common/src/auth/services/account.service.spec.ts index 3e3c878eaac..f517b61ffb6 100644 --- a/libs/common/src/auth/services/account.service.spec.ts +++ b/libs/common/src/auth/services/account.service.spec.ts @@ -6,6 +6,7 @@ import { MockProxy, mock } from "jest-mock-extended"; import { firstValueFrom } from "rxjs"; +import { mockAccountInfoWith } from "../../../spec/fake-account-service"; import { FakeGlobalState } from "../../../spec/fake-state"; import { FakeGlobalStateProvider, @@ -27,7 +28,7 @@ import { } from "./account.service"; describe("accountInfoEqual", () => { - const accountInfo: AccountInfo = { name: "name", email: "email", emailVerified: true }; + const accountInfo = mockAccountInfoWith(); it("compares nulls", () => { expect(accountInfoEqual(null, null)).toBe(true); @@ -64,6 +65,23 @@ describe("accountInfoEqual", () => { expect(accountInfoEqual(accountInfo, same)).toBe(true); expect(accountInfoEqual(accountInfo, different)).toBe(false); }); + + it("compares creationDate", () => { + const same = { ...accountInfo }; + const different = { ...accountInfo, creationDate: "2024-12-31T00:00:00.000Z" }; + + expect(accountInfoEqual(accountInfo, same)).toBe(true); + expect(accountInfoEqual(accountInfo, different)).toBe(false); + }); + + it("compares undefined creationDate", () => { + const accountWithoutCreationDate = mockAccountInfoWith({ creationDate: undefined }); + const same = { ...accountWithoutCreationDate }; + const different = { ...accountWithoutCreationDate, creationDate: "2024-01-01T00:00:00.000Z" }; + + expect(accountInfoEqual(accountWithoutCreationDate, same)).toBe(true); + expect(accountInfoEqual(accountWithoutCreationDate, different)).toBe(false); + }); }); describe("accountService", () => { @@ -76,7 +94,10 @@ describe("accountService", () => { let activeAccountIdState: FakeGlobalState<UserId>; let accountActivityState: FakeGlobalState<Record<UserId, Date>>; const userId = Utils.newGuid() as UserId; - const userInfo = { email: "email", name: "name", emailVerified: true }; + const userInfo = mockAccountInfoWith({ + email: "email", + name: "name", + }); beforeEach(() => { messagingService = mock(); @@ -253,6 +274,56 @@ describe("accountService", () => { }); }); + describe("setCreationDate", () => { + const initialState = { [userId]: userInfo }; + beforeEach(() => { + accountsState.stateSubject.next(initialState); + }); + + it("should update the account with a new creation date", async () => { + const newCreationDate = "2024-12-31T00:00:00.000Z"; + await sut.setAccountCreationDate(userId, newCreationDate); + const currentState = await firstValueFrom(accountsState.state$); + + expect(currentState).toEqual({ + [userId]: { ...userInfo, creationDate: newCreationDate }, + }); + }); + + it("should not update if the creation date is the same", async () => { + await sut.setAccountCreationDate(userId, userInfo.creationDate); + const currentState = await firstValueFrom(accountsState.state$); + + expect(currentState).toEqual(initialState); + }); + + it("should update from undefined to a defined creation date", async () => { + const accountWithoutCreationDate = mockAccountInfoWith({ + ...userInfo, + creationDate: undefined, + }); + accountsState.stateSubject.next({ [userId]: accountWithoutCreationDate }); + + const newCreationDate = "2024-06-15T12:30:00.000Z"; + await sut.setAccountCreationDate(userId, newCreationDate); + const currentState = await firstValueFrom(accountsState.state$); + + expect(currentState).toEqual({ + [userId]: { ...accountWithoutCreationDate, creationDate: newCreationDate }, + }); + }); + + it("should update to a different creation date string format", async () => { + const newCreationDate = "2023-03-15T08:45:30.123Z"; + await sut.setAccountCreationDate(userId, newCreationDate); + const currentState = await firstValueFrom(accountsState.state$); + + expect(currentState).toEqual({ + [userId]: { ...userInfo, creationDate: newCreationDate }, + }); + }); + }); + describe("setAccountVerifyNewDeviceLogin", () => { const initialState = true; beforeEach(() => { @@ -294,6 +365,7 @@ describe("accountService", () => { email: "", emailVerified: false, name: undefined, + creationDate: undefined, }, }); }); diff --git a/libs/common/src/auth/services/account.service.ts b/libs/common/src/auth/services/account.service.ts index fb4b590ce77..1b028d1eba9 100644 --- a/libs/common/src/auth/services/account.service.ts +++ b/libs/common/src/auth/services/account.service.ts @@ -62,6 +62,7 @@ const LOGGED_OUT_INFO: AccountInfo = { email: "", emailVerified: false, name: undefined, + creationDate: undefined, }; /** @@ -167,6 +168,10 @@ export class AccountServiceImplementation implements InternalAccountService { await this.setAccountInfo(userId, { emailVerified }); } + async setAccountCreationDate(userId: UserId, creationDate: string): Promise<void> { + await this.setAccountInfo(userId, { creationDate }); + } + async clean(userId: UserId) { await this.setAccountInfo(userId, LOGGED_OUT_INFO); await this.removeAccountActivity(userId); diff --git a/libs/common/src/auth/services/auth-request-answering/auth-request-answering.service.spec.ts b/libs/common/src/auth/services/auth-request-answering/auth-request-answering.service.spec.ts index 0b12e1cb661..a44dde04f5f 100644 --- a/libs/common/src/auth/services/auth-request-answering/auth-request-answering.service.spec.ts +++ b/libs/common/src/auth/services/auth-request-answering/auth-request-answering.service.spec.ts @@ -15,6 +15,7 @@ import { SystemNotificationEvent, SystemNotificationsService, } from "@bitwarden/common/platform/system-notifications/system-notifications.service"; +import { mockAccountInfoWith } from "@bitwarden/common/spec"; import { UserId } from "@bitwarden/user-core"; import { AuthRequestAnsweringService } from "./auth-request-answering.service"; @@ -48,14 +49,16 @@ describe("AuthRequestAnsweringService", () => { // Common defaults authService.activeAccountStatus$ = of(AuthenticationStatus.Locked); - accountService.activeAccount$ = of({ - id: userId, + const accountInfo = mockAccountInfoWith({ email: "user@example.com", - emailVerified: true, name: "User", }); + accountService.activeAccount$ = of({ + id: userId, + ...accountInfo, + }); accountService.accounts$ = of({ - [userId]: { email: "user@example.com", emailVerified: true, name: "User" }, + [userId]: accountInfo, }); (masterPasswordService.forceSetPasswordReason$ as jest.Mock).mockReturnValue( of(ForceSetPasswordReason.None), diff --git a/libs/common/src/auth/services/auth.service.spec.ts b/libs/common/src/auth/services/auth.service.spec.ts index 5dcb8c372e5..c7ff55e6bb1 100644 --- a/libs/common/src/auth/services/auth.service.spec.ts +++ b/libs/common/src/auth/services/auth.service.spec.ts @@ -10,6 +10,7 @@ import { makeStaticByteArray, mockAccountServiceWith, trackEmissions, + mockAccountInfoWith, } from "../../../spec"; import { ApiService } from "../../abstractions/api.service"; import { MessagingService } from "../../platform/abstractions/messaging.service"; @@ -58,9 +59,10 @@ describe("AuthService", () => { const accountInfo = { status: AuthenticationStatus.Unlocked, id: userId, - email: "email", - emailVerified: false, - name: "name", + ...mockAccountInfoWith({ + email: "email", + name: "name", + }), }; beforeEach(() => { @@ -112,9 +114,10 @@ describe("AuthService", () => { const accountInfo2 = { status: AuthenticationStatus.Unlocked, id: Utils.newGuid() as UserId, - email: "email2", - emailVerified: false, - name: "name2", + ...mockAccountInfoWith({ + email: "email2", + name: "name2", + }), }; const emissions = trackEmissions(sut.activeAccountStatus$); @@ -131,11 +134,13 @@ describe("AuthService", () => { it("requests auth status for all known users", async () => { const userId2 = Utils.newGuid() as UserId; - await accountService.addAccount(userId2, { - email: "email2", - emailVerified: false, - name: "name2", - }); + await accountService.addAccount( + userId2, + mockAccountInfoWith({ + email: "email2", + name: "name2", + }), + ); const mockFn = jest.fn().mockReturnValue(of(AuthenticationStatus.Locked)); sut.authStatusFor$ = mockFn; diff --git a/libs/common/src/auth/services/password-reset-enrollment.service.implementation.spec.ts b/libs/common/src/auth/services/password-reset-enrollment.service.implementation.spec.ts index 7e6e0d53f57..693992d4c4a 100644 --- a/libs/common/src/auth/services/password-reset-enrollment.service.implementation.spec.ts +++ b/libs/common/src/auth/services/password-reset-enrollment.service.implementation.spec.ts @@ -8,12 +8,13 @@ import { OrganizationUserApiService } from "@bitwarden/admin-console/common"; // eslint-disable-next-line no-restricted-imports import { KeyService } from "@bitwarden/key-management"; +import { mockAccountInfoWith } from "../../../spec/fake-account-service"; import { OrganizationApiServiceAbstraction } from "../../admin-console/abstractions/organization/organization-api.service.abstraction"; import { OrganizationAutoEnrollStatusResponse } from "../../admin-console/models/response/organization-auto-enroll-status.response"; import { EncryptService } from "../../key-management/crypto/abstractions/encrypt.service"; import { I18nService } from "../../platform/abstractions/i18n.service"; import { UserId } from "../../types/guid"; -import { Account, AccountInfo, AccountService } from "../abstractions/account.service"; +import { Account, AccountService } from "../abstractions/account.service"; import { PasswordResetEnrollmentServiceImplementation } from "./password-reset-enrollment.service.implementation"; @@ -96,11 +97,10 @@ describe("PasswordResetEnrollmentServiceImplementation", () => { const encryptedKey = { encryptedString: "encryptedString" }; organizationApiService.getKeys.mockResolvedValue(orgKeyResponse as any); - const user1AccountInfo: AccountInfo = { + const user1AccountInfo = mockAccountInfoWith({ name: "Test User 1", email: "test1@email.com", - emailVerified: true, - }; + }); activeAccountSubject.next(Object.assign(user1AccountInfo, { id: "userId" as UserId })); keyService.userKey$.mockReturnValue(of({ key: "key" } as any)); diff --git a/libs/common/src/key-management/vault-timeout/services/vault-timeout.service.spec.ts b/libs/common/src/key-management/vault-timeout/services/vault-timeout.service.spec.ts index 51eec18f173..8f7f93f368c 100644 --- a/libs/common/src/key-management/vault-timeout/services/vault-timeout.service.spec.ts +++ b/libs/common/src/key-management/vault-timeout/services/vault-timeout.service.spec.ts @@ -7,7 +7,7 @@ import { BehaviorSubject, from, of } from "rxjs"; // eslint-disable-next-line no-restricted-imports import { LockService, LogoutService } from "@bitwarden/auth/common"; -import { FakeAccountService, mockAccountServiceWith } from "../../../../spec"; +import { FakeAccountService, mockAccountServiceWith, mockAccountInfoWith } from "../../../../spec"; import { AccountInfo } from "../../../auth/abstractions/account.service"; import { AuthService } from "../../../auth/abstractions/auth.service"; import { AuthenticationStatus } from "../../../auth/enums/authentication-status"; @@ -109,19 +109,19 @@ describe("VaultTimeoutService", () => { if (globalSetups?.userId) { accountService.activeAccountSubject.next({ id: globalSetups.userId as UserId, - email: null, - emailVerified: false, - name: null, + ...mockAccountInfoWith({ + email: null, + name: null, + }), }); } accountService.accounts$ = of( Object.entries(accounts).reduce( (agg, [id]) => { - agg[id] = { + agg[id] = mockAccountInfoWith({ email: "", - emailVerified: true, name: "", - }; + }); return agg; }, {} as Record<string, AccountInfo>, diff --git a/libs/common/src/platform/server-notifications/internal/default-server-notifications.multiuser.spec.ts b/libs/common/src/platform/server-notifications/internal/default-server-notifications.multiuser.spec.ts index cd1bf97150c..46178f62a07 100644 --- a/libs/common/src/platform/server-notifications/internal/default-server-notifications.multiuser.spec.ts +++ b/libs/common/src/platform/server-notifications/internal/default-server-notifications.multiuser.spec.ts @@ -7,6 +7,7 @@ import { InternalPolicyService } from "@bitwarden/common/admin-console/abstracti import { AuthRequestAnsweringServiceAbstraction } from "@bitwarden/common/auth/abstractions/auth-request-answering/auth-request-answering.service.abstraction"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; +import { mockAccountInfoWith } from "../../../../spec"; import { AccountService } from "../../../auth/abstractions/account.service"; import { AuthService } from "../../../auth/abstractions/auth.service"; import { AuthenticationStatus } from "../../../auth/enums/authentication-status"; @@ -163,9 +164,10 @@ describe("DefaultServerNotificationsService (multi-user)", () => { } else { activeUserAccount$.next({ id: userId, - email: "email", - name: "Test Name", - emailVerified: true, + ...mockAccountInfoWith({ + email: "email", + name: "Test Name", + }), }); } } @@ -174,7 +176,10 @@ describe("DefaultServerNotificationsService (multi-user)", () => { const currentAccounts = (userAccounts$.getValue() as Record<string, any>) ?? {}; userAccounts$.next({ ...currentAccounts, - [userId]: { email: "email", name: "Test Name", emailVerified: true }, + [userId]: mockAccountInfoWith({ + email: "email", + name: "Test Name", + }), } as any); } diff --git a/libs/common/src/platform/server-notifications/internal/default-server-notifications.service.spec.ts b/libs/common/src/platform/server-notifications/internal/default-server-notifications.service.spec.ts index 4a9b0809ac9..9c84981b7f9 100644 --- a/libs/common/src/platform/server-notifications/internal/default-server-notifications.service.spec.ts +++ b/libs/common/src/platform/server-notifications/internal/default-server-notifications.service.spec.ts @@ -8,7 +8,7 @@ import { InternalPolicyService } from "@bitwarden/common/admin-console/abstracti import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { AuthRequestAnsweringServiceAbstraction } from "@bitwarden/common/auth/abstractions/auth-request-answering/auth-request-answering.service.abstraction"; -import { awaitAsync } from "../../../../spec"; +import { awaitAsync, mockAccountInfoWith } from "../../../../spec"; import { Matrix } from "../../../../spec/matrix"; import { AccountService } from "../../../auth/abstractions/account.service"; import { AuthService } from "../../../auth/abstractions/auth.service"; @@ -139,11 +139,18 @@ describe("NotificationsService", () => { activeAccount.next(null); accounts.next({} as any); } else { - activeAccount.next({ id: userId, email: "email", name: "Test Name", emailVerified: true }); + const accountInfo = mockAccountInfoWith({ + email: "email", + name: "Test Name", + }); + activeAccount.next({ + id: userId, + ...accountInfo, + }); const current = (accounts.getValue() as Record<string, any>) ?? {}; accounts.next({ ...current, - [userId]: { email: "email", name: "Test Name", emailVerified: true }, + [userId]: accountInfo, } as any); } } @@ -349,7 +356,13 @@ describe("NotificationsService", () => { describe("processNotification", () => { beforeEach(async () => { appIdService.getAppId.mockResolvedValue("test-app-id"); - activeAccount.next({ id: mockUser1, email: "email", name: "Test Name", emailVerified: true }); + activeAccount.next({ + id: mockUser1, + ...mockAccountInfoWith({ + email: "email", + name: "Test Name", + }), + }); }); describe("NotificationType.LogOut", () => { diff --git a/libs/common/src/platform/services/default-environment.service.spec.ts b/libs/common/src/platform/services/default-environment.service.spec.ts index 553f80f83b8..9e8a41616a3 100644 --- a/libs/common/src/platform/services/default-environment.service.spec.ts +++ b/libs/common/src/platform/services/default-environment.service.spec.ts @@ -1,6 +1,6 @@ import { firstValueFrom } from "rxjs"; -import { FakeStateProvider, awaitAsync } from "../../../spec"; +import { FakeStateProvider, awaitAsync, mockAccountInfoWith } from "../../../spec"; import { FakeAccountService } from "../../../spec/fake-account-service"; import { UserId } from "../../types/guid"; import { CloudRegion, Region } from "../abstractions/environment.service"; @@ -28,16 +28,14 @@ describe("EnvironmentService", () => { beforeEach(async () => { accountService = new FakeAccountService({ - [testUser]: { + [testUser]: mockAccountInfoWith({ name: "name", email: "email", - emailVerified: false, - }, - [alternateTestUser]: { + }), + [alternateTestUser]: mockAccountInfoWith({ name: "name", email: "email", - emailVerified: false, - }, + }), }); stateProvider = new FakeStateProvider(accountService); @@ -47,9 +45,10 @@ describe("EnvironmentService", () => { const switchUser = async (userId: UserId) => { accountService.activeAccountSubject.next({ id: userId, - email: "test@example.com", - name: `Test Name ${userId}`, - emailVerified: false, + ...mockAccountInfoWith({ + email: "test@example.com", + name: `Test Name ${userId}`, + }), }); await awaitAsync(); }; diff --git a/libs/common/src/platform/services/fido2/fido2-authenticator.service.spec.ts b/libs/common/src/platform/services/fido2/fido2-authenticator.service.spec.ts index fef64399b40..9c50bd1ab65 100644 --- a/libs/common/src/platform/services/fido2/fido2-authenticator.service.spec.ts +++ b/libs/common/src/platform/services/fido2/fido2-authenticator.service.spec.ts @@ -3,7 +3,7 @@ import { TextEncoder } from "util"; import { mock, MockProxy } from "jest-mock-extended"; import { BehaviorSubject, of } from "rxjs"; -import { mockAccountServiceWith } from "../../../../spec"; +import { mockAccountServiceWith, mockAccountInfoWith } from "../../../../spec"; import { Account } from "../../../auth/abstractions/account.service"; import { CipherId, UserId } from "../../../types/guid"; import { CipherService, EncryptionContext } from "../../../vault/abstractions/cipher.service"; @@ -40,9 +40,10 @@ describe("FidoAuthenticatorService", () => { const userId = "testId" as UserId; const activeAccountSubject = new BehaviorSubject<Account | null>({ id: userId, - email: "test@example.com", - emailVerified: true, - name: "Test User", + ...mockAccountInfoWith({ + email: "test@example.com", + name: "Test User", + }), }); let cipherService!: MockProxy<CipherService>; diff --git a/libs/common/src/platform/services/sdk/default-sdk.service.spec.ts b/libs/common/src/platform/services/sdk/default-sdk.service.spec.ts index 1286ea7b7f9..fb9c1fae77e 100644 --- a/libs/common/src/platform/services/sdk/default-sdk.service.spec.ts +++ b/libs/common/src/platform/services/sdk/default-sdk.service.spec.ts @@ -12,9 +12,9 @@ import { FakeAccountService, FakeStateProvider, mockAccountServiceWith, + mockAccountInfoWith, } from "../../../../spec"; import { ApiService } from "../../../abstractions/api.service"; -import { AccountInfo } from "../../../auth/abstractions/account.service"; import { EncryptedString } from "../../../key-management/crypto/models/enc-string"; import { UserId } from "../../../types/guid"; import { UserKey } from "../../../types/key"; @@ -92,7 +92,10 @@ describe("DefaultSdkService", () => { .calledWith(userId) .mockReturnValue(new BehaviorSubject(mock<Environment>())); accountService.accounts$ = of({ - [userId]: { email: "email", emailVerified: true, name: "name" } as AccountInfo, + [userId]: mockAccountInfoWith({ + email: "email", + name: "name", + }), }); kdfConfigService.getKdfConfig$ .calledWith(userId) diff --git a/libs/common/src/platform/services/sdk/register-sdk.service.spec.ts b/libs/common/src/platform/services/sdk/register-sdk.service.spec.ts index 0a05ac8dbf4..1f4d086f729 100644 --- a/libs/common/src/platform/services/sdk/register-sdk.service.spec.ts +++ b/libs/common/src/platform/services/sdk/register-sdk.service.spec.ts @@ -8,9 +8,9 @@ import { FakeAccountService, FakeStateProvider, mockAccountServiceWith, + mockAccountInfoWith, } from "../../../../spec"; import { ApiService } from "../../../abstractions/api.service"; -import { AccountInfo } from "../../../auth/abstractions/account.service"; import { UserId } from "../../../types/guid"; import { ConfigService } from "../../abstractions/config/config.service"; import { Environment, EnvironmentService } from "../../abstractions/environment.service"; @@ -76,7 +76,10 @@ describe("DefaultRegisterSdkService", () => { .calledWith(userId) .mockReturnValue(new BehaviorSubject(mock<Environment>())); accountService.accounts$ = of({ - [userId]: { email: "email", emailVerified: true, name: "name" } as AccountInfo, + [userId]: mockAccountInfoWith({ + email: "email", + name: "name", + }), }); }); @@ -125,7 +128,10 @@ describe("DefaultRegisterSdkService", () => { it("destroys the internal SDK client when the account is removed (logout)", async () => { const accounts$ = new BehaviorSubject({ - [userId]: { email: "email", emailVerified: true, name: "name" } as AccountInfo, + [userId]: mockAccountInfoWith({ + email: "email", + name: "name", + }), }); accountService.accounts$ = accounts$; diff --git a/libs/common/src/platform/sync/default-sync.service.ts b/libs/common/src/platform/sync/default-sync.service.ts index 910702bddd0..8d2ccaffa18 100644 --- a/libs/common/src/platform/sync/default-sync.service.ts +++ b/libs/common/src/platform/sync/default-sync.service.ts @@ -272,6 +272,7 @@ export class DefaultSyncService extends CoreSyncService { await this.tokenService.setSecurityStamp(response.securityStamp, response.id); await this.accountService.setAccountEmailVerified(response.id, response.emailVerified); await this.accountService.setAccountVerifyNewDeviceLogin(response.id, response.verifyDevices); + await this.accountService.setAccountCreationDate(response.id, response.creationDate); await this.billingAccountProfileStateService.setHasPremium( response.premiumPersonally, diff --git a/libs/common/src/services/api.service.spec.ts b/libs/common/src/services/api.service.spec.ts index 1fb8f86697f..9ab84ecb16b 100644 --- a/libs/common/src/services/api.service.spec.ts +++ b/libs/common/src/services/api.service.spec.ts @@ -6,6 +6,7 @@ import { ObservedValueOf, of } from "rxjs"; import { LogoutReason } from "@bitwarden/auth/common"; import { UserId } from "@bitwarden/user-core"; +import { mockAccountInfoWith } from "../../spec"; import { AccountService } from "../auth/abstractions/account.service"; import { TokenService } from "../auth/abstractions/token.service"; import { DeviceType } from "../enums"; @@ -55,9 +56,10 @@ describe("ApiService", () => { accountService.activeAccount$ = of({ id: testActiveUser, - email: "user1@example.com", - emailVerified: true, - name: "Test Name", + ...mockAccountInfoWith({ + email: "user1@example.com", + name: "Test Name", + }), } satisfies ObservedValueOf<AccountService["activeAccount$"]>); httpOperations = mock(); diff --git a/libs/common/src/tools/extension/extension.service.spec.ts b/libs/common/src/tools/extension/extension.service.spec.ts index 9959488feca..c0dec8728fe 100644 --- a/libs/common/src/tools/extension/extension.service.spec.ts +++ b/libs/common/src/tools/extension/extension.service.spec.ts @@ -1,7 +1,12 @@ import { mock } from "jest-mock-extended"; import { BehaviorSubject, firstValueFrom } from "rxjs"; -import { FakeAccountService, FakeStateProvider, awaitAsync } from "../../../spec"; +import { + FakeAccountService, + FakeStateProvider, + awaitAsync, + mockAccountInfoWith, +} from "../../../spec"; import { Account } from "../../auth/abstractions/account.service"; import { EXTENSION_DISK, UserKeyDefinition } from "../../platform/state"; import { UserId } from "../../types/guid"; @@ -21,9 +26,10 @@ import { SimpleLogin } from "./vendor/simplelogin"; const SomeUser = "some user" as UserId; const SomeAccount = { id: SomeUser, - email: "someone@example.com", - emailVerified: true, - name: "Someone", + ...mockAccountInfoWith({ + email: "someone@example.com", + name: "Someone", + }), }; const SomeAccount$ = new BehaviorSubject<Account>(SomeAccount); diff --git a/libs/common/src/tools/send/services/send.service.spec.ts b/libs/common/src/tools/send/services/send.service.spec.ts index 96fb2f43c88..397ae905e31 100644 --- a/libs/common/src/tools/send/services/send.service.spec.ts +++ b/libs/common/src/tools/send/services/send.service.spec.ts @@ -11,6 +11,7 @@ import { FakeStateProvider, awaitAsync, mockAccountServiceWith, + mockAccountInfoWith, } from "../../../../spec"; import { KeyGenerationService } from "../../../key-management/crypto"; import { EncryptService } from "../../../key-management/crypto/abstractions/encrypt.service"; @@ -71,9 +72,10 @@ describe("SendService", () => { accountService.activeAccountSubject.next({ id: mockUserId, - email: "email", - emailVerified: false, - name: "name", + ...mockAccountInfoWith({ + email: "email", + name: "name", + }), }); // Initial encrypted state diff --git a/libs/common/src/tools/state/user-state-subject.spec.ts b/libs/common/src/tools/state/user-state-subject.spec.ts index a6d452d37fd..b88c358b6ab 100644 --- a/libs/common/src/tools/state/user-state-subject.spec.ts +++ b/libs/common/src/tools/state/user-state-subject.spec.ts @@ -6,6 +6,7 @@ import { awaitAsync, FakeAccountService, FakeStateProvider, + mockAccountInfoWith, ObservableTracker, } from "../../../spec"; import { Account } from "../../auth/abstractions/account.service"; @@ -23,17 +24,19 @@ import { UserStateSubject } from "./user-state-subject"; const SomeUser = "some user" as UserId; const SomeAccount = { id: SomeUser, - email: "someone@example.com", - emailVerified: true, - name: "Someone", + ...mockAccountInfoWith({ + email: "someone@example.com", + name: "Someone", + }), }; const SomeAccount$ = new BehaviorSubject<Account>(SomeAccount); const SomeOtherAccount = { id: "some other user" as UserId, - email: "someone@example.com", - emailVerified: true, - name: "Someone", + ...mockAccountInfoWith({ + email: "someone@example.com", + name: "Someone", + }), }; type TestType = { foo: string }; 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 6e98b21977d..fdf92cac751 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 @@ -6,6 +6,7 @@ import { KeyGenerationService } from "@bitwarden/common/key-management/crypto"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { mockAccountInfoWith } from "@bitwarden/common/spec"; 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"; @@ -41,9 +42,10 @@ describe("BitwardenPasswordProtectedImporter", () => { accountService.activeAccount$ = of({ id: emptyGuid as UserId, - email: "test@example.com", - emailVerified: true, - name: "Test User", + ...mockAccountInfoWith({ + email: "test@example.com", + name: "Test User", + }), }); const mockOrgId = emptyGuid as OrganizationId; @@ -96,9 +98,10 @@ describe("BitwardenPasswordProtectedImporter", () => { beforeEach(() => { accountService.activeAccount$ = of({ id: emptyGuid as UserId, - email: "test@example.com", - emailVerified: true, - name: "Test User", + ...mockAccountInfoWith({ + email: "test@example.com", + name: "Test User", + }), }); importer = new BitwardenPasswordProtectedImporter( keyService, diff --git a/libs/key-management-ui/src/lock/components/master-password-lock/master-password-lock.component.spec.ts b/libs/key-management-ui/src/lock/components/master-password-lock/master-password-lock.component.spec.ts index d40cc98df11..71287e7684c 100644 --- a/libs/key-management-ui/src/lock/components/master-password-lock/master-password-lock.component.spec.ts +++ b/libs/key-management-ui/src/lock/components/master-password-lock/master-password-lock.component.spec.ts @@ -11,6 +11,7 @@ import { MasterPasswordUnlockService } from "@bitwarden/common/key-management/ma import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; +import { mockAccountInfoWith } from "@bitwarden/common/spec"; import { UserKey } from "@bitwarden/common/types/key"; import { AsyncActionsModule, @@ -39,9 +40,10 @@ describe("MasterPasswordLockComponent", () => { const mockMasterPassword = "testExample"; const activeAccount: Account = { id: "user-id" as UserId, - email: "user@example.com", - emailVerified: true, - name: "User", + ...mockAccountInfoWith({ + email: "user@example.com", + name: "User", + }), }; const mockUserKey = new SymmetricCryptoKey(new Uint8Array(64)) as UserKey; diff --git a/libs/tools/generator/core/src/providers/generator-metadata-provider.spec.ts b/libs/tools/generator/core/src/providers/generator-metadata-provider.spec.ts index 376b46cd6e8..39ff74ad901 100644 --- a/libs/tools/generator/core/src/providers/generator-metadata-provider.spec.ts +++ b/libs/tools/generator/core/src/providers/generator-metadata-provider.spec.ts @@ -25,7 +25,11 @@ import { deepFreeze } from "@bitwarden/common/tools/util"; import { UserId } from "@bitwarden/common/types/guid"; import { BitwardenClient } from "@bitwarden/sdk-internal"; -import { FakeAccountService, FakeStateProvider } from "../../../../../common/spec"; +import { + FakeAccountService, + FakeStateProvider, + mockAccountInfoWith, +} from "../../../../../common/spec"; import { Algorithm, AlgorithmsByType, CredentialAlgorithm, Type, Types } from "../metadata"; import catchall from "../metadata/email/catchall"; import plusAddress from "../metadata/email/plus-address"; @@ -40,9 +44,10 @@ import { GeneratorMetadataProvider } from "./generator-metadata-provider"; const SomeUser = "some user" as UserId; const SomeAccount = { id: SomeUser, - email: "someone@example.com", - emailVerified: true, - name: "Someone", + ...mockAccountInfoWith({ + email: "someone@example.com", + name: "Someone", + }), }; const SomeAccount$ = new BehaviorSubject<Account>(SomeAccount); diff --git a/libs/tools/generator/core/src/providers/generator-profile-provider.spec.ts b/libs/tools/generator/core/src/providers/generator-profile-provider.spec.ts index 924849b1c22..088bf543fee 100644 --- a/libs/tools/generator/core/src/providers/generator-profile-provider.spec.ts +++ b/libs/tools/generator/core/src/providers/generator-profile-provider.spec.ts @@ -15,7 +15,12 @@ import { UserStateSubjectDependencyProvider } from "@bitwarden/common/tools/stat import { StateConstraints } from "@bitwarden/common/tools/types"; import { OrganizationId, PolicyId, UserId } from "@bitwarden/common/types/guid"; -import { FakeStateProvider, FakeAccountService, awaitAsync } from "../../../../../common/spec"; +import { + FakeStateProvider, + FakeAccountService, + awaitAsync, + mockAccountInfoWith, +} from "../../../../../common/spec"; import { CoreProfileMetadata, ProfileContext } from "../metadata/profile-metadata"; import { GeneratorConstraints } from "../types"; @@ -31,21 +36,25 @@ const UnverifiedEmailUser = "UnverifiedEmailUser" as UserId; const accounts: Record<UserId, Account> = { [SomeUser]: { id: SomeUser, - name: "some user", - email: "some.user@example.com", - emailVerified: true, + ...mockAccountInfoWith({ + name: "some user", + email: "some.user@example.com", + }), }, [AnotherUser]: { id: AnotherUser, - name: "some other user", - email: "some.other.user@example.com", - emailVerified: true, + ...mockAccountInfoWith({ + name: "some other user", + email: "some.other.user@example.com", + }), }, [UnverifiedEmailUser]: { id: UnverifiedEmailUser, - name: "a user with an unverfied email", - email: "unverified@example.com", - emailVerified: false, + ...mockAccountInfoWith({ + name: "a user with an unverfied email", + email: "unverified@example.com", + emailVerified: false, + }), }, }; const accountService = new FakeAccountService(accounts); diff --git a/libs/tools/generator/core/src/services/default-credential-generator.service.spec.ts b/libs/tools/generator/core/src/services/default-credential-generator.service.spec.ts index 81e7ae6ac63..e459bb47f47 100644 --- a/libs/tools/generator/core/src/services/default-credential-generator.service.spec.ts +++ b/libs/tools/generator/core/src/services/default-credential-generator.service.spec.ts @@ -8,7 +8,7 @@ import { Vendor } from "@bitwarden/common/tools/extension/vendor/data"; import { SemanticLogger, ifEnabledSemanticLoggerProvider } from "@bitwarden/common/tools/log"; import { UserId } from "@bitwarden/common/types/guid"; -import { awaitAsync } from "../../../../../common/spec"; +import { awaitAsync, mockAccountInfoWith } from "../../../../../common/spec"; import { Algorithm, CredentialAlgorithm, @@ -56,9 +56,10 @@ describe("DefaultCredentialGeneratorService", () => { // Use a hard-coded value for mockAccount account = { id: "test-account-id" as UserId, - emailVerified: true, - email: "test@example.com", - name: "Test User", + ...mockAccountInfoWith({ + email: "test@example.com", + name: "Test User", + }), }; system = { diff --git a/libs/tools/send/send-ui/src/send-list-filters/send-list-filters.component.spec.ts b/libs/tools/send/send-ui/src/send-list-filters/send-list-filters.component.spec.ts index b832ac36caf..ca77c94898b 100644 --- a/libs/tools/send/send-ui/src/send-list-filters/send-list-filters.component.spec.ts +++ b/libs/tools/send/send-ui/src/send-list-filters/send-list-filters.component.spec.ts @@ -8,6 +8,7 @@ import { JslibModule } from "@bitwarden/angular/jslib.module"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { mockAccountInfoWith } from "@bitwarden/common/spec"; import { UserId } from "@bitwarden/common/types/guid"; import { ChipSelectComponent } from "@bitwarden/components"; @@ -31,9 +32,11 @@ describe("SendListFiltersComponent", () => { accountService.activeAccount$ = of({ id: userId, - email: "test@email.com", - emailVerified: true, - name: "Test User", + ...mockAccountInfoWith({ + email: "test@email.com", + name: "Test User", + emailVerified: true, + }), }); billingAccountProfileStateService.hasPremiumFromAnySource$.mockReturnValue(of(true)); diff --git a/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.spec.ts b/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.spec.ts index 42144e646d4..9ff8f7c83da 100644 --- a/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.spec.ts +++ b/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.spec.ts @@ -11,6 +11,7 @@ import { EventType } from "@bitwarden/common/enums"; 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 { mockAccountInfoWith } from "@bitwarden/common/spec"; import { UserId } from "@bitwarden/common/types/guid"; import { PremiumUpgradePromptService } from "@bitwarden/common/vault/abstractions/premium-upgrade-prompt.service"; import { CipherType } from "@bitwarden/common/vault/enums"; @@ -34,9 +35,10 @@ describe("LoginCredentialsViewComponent", () => { const hasPremiumFromAnySource$ = new BehaviorSubject<boolean>(true); const mockAccount = { id: "test-user-id" as UserId, - email: "test@example.com", - emailVerified: true, - name: "Test User", + ...mockAccountInfoWith({ + email: "test@example.com", + name: "Test User", + }), type: 0, status: 0, kdf: 0, diff --git a/libs/vault/src/components/add-edit-folder-dialog/add-edit-folder-dialog.component.spec.ts b/libs/vault/src/components/add-edit-folder-dialog/add-edit-folder-dialog.component.spec.ts index 68b0d9dfcf5..9bf53826333 100644 --- a/libs/vault/src/components/add-edit-folder-dialog/add-edit-folder-dialog.component.spec.ts +++ b/libs/vault/src/components/add-edit-folder-dialog/add-edit-folder-dialog.component.spec.ts @@ -2,9 +2,10 @@ import { ComponentFixture, TestBed } from "@angular/core/testing"; import { NoopAnimationsModule } from "@angular/platform-browser/animations"; import { BehaviorSubject } from "rxjs"; -import { AccountInfo, AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { mockAccountInfoWith } from "@bitwarden/common/spec"; import { UserId } from "@bitwarden/common/types/guid"; import { FolderApiServiceAbstraction } from "@bitwarden/common/vault/abstractions/folder/folder-api.service.abstraction"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; @@ -47,11 +48,7 @@ describe("AddEditFolderDialogComponent", () => { showToast.mockClear(); const userId = "" as UserId; - const accountInfo: AccountInfo = { - email: "", - emailVerified: true, - name: undefined, - }; + const accountInfo = mockAccountInfoWith(); await TestBed.configureTestingModule({ imports: [AddEditFolderDialogComponent, NoopAnimationsModule], diff --git a/tsconfig.base.json b/tsconfig.base.json index 2d105d4263d..ae4b9f5f601 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -29,6 +29,7 @@ "@bitwarden/browser/*": ["./apps/browser/src/*"], "@bitwarden/cli/*": ["./apps/cli/src/*"], "@bitwarden/client-type": ["libs/client-type/src/index.ts"], + "@bitwarden/common/spec": ["./libs/common/spec"], "@bitwarden/common/*": ["./libs/common/src/*"], "@bitwarden/components": ["./libs/components/src"], "@bitwarden/core-test-utils": ["libs/core-test-utils/src/index.ts"], From 944d324985d9b78059e6215f3cbdeaf19215e37f Mon Sep 17 00:00:00 2001 From: adudek-bw <adudek@bitwarden.com> Date: Fri, 12 Dec 2025 12:38:35 -0500 Subject: [PATCH 064/188] [PM-27081] Fix chromium direct import for linux (#17894) * Fix chromium direct import for linux --- .../chromium_importer/src/chromium/platform/linux.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/desktop/desktop_native/chromium_importer/src/chromium/platform/linux.rs b/apps/desktop/desktop_native/chromium_importer/src/chromium/platform/linux.rs index f542e23129a..6fb6e6134c7 100644 --- a/apps/desktop/desktop_native/chromium_importer/src/chromium/platform/linux.rs +++ b/apps/desktop/desktop_native/chromium_importer/src/chromium/platform/linux.rs @@ -18,7 +18,7 @@ use crate::{ pub(crate) const SUPPORTED_BROWSERS: &[BrowserConfig] = &[ BrowserConfig { name: "Chrome", - data_dir: &[".config/google-chrome"], + data_dir: &[".config/google-chrome", "snap/chromium/common/chromium"], }, BrowserConfig { name: "Chromium", From 14dd732b522eff9cc69d771f413af259aca51053 Mon Sep 17 00:00:00 2001 From: Alex Dragovich <46065570+itsadrago@users.noreply.github.com> Date: Fri, 12 Dec 2025 09:43:34 -0800 Subject: [PATCH 065/188] [PM-23258] changing verbiage from import data to import items (#17123) * [PM-23258] changing verbiage from import data to import items * [PM-23258] Removing vault and data from import and export titles, navs, and buttons * [PM-23258] more verbiage changes * [PM-23258] reverting unnecessary change * [PM-23258] removing unused text from messages json files * [PM-23258] small text changes from design * [PM-23258] including secrets manager changes --- apps/browser/src/_locales/en/messages.json | 18 +++++++----------- .../export/export-browser-v2.component.html | 4 ++-- .../import/import-browser-v2.component.html | 4 ++-- .../settings/vault-settings-v2.component.html | 6 +++--- .../tools/export/export-desktop.component.html | 4 ++-- .../tools/import/import-desktop.component.html | 4 ++-- apps/desktop/src/locales/en/messages.json | 15 +++++++-------- apps/desktop/src/main/menu/menu.file.ts | 12 ++++++------ .../layouts/organization-layout.component.html | 4 ++-- .../organization-settings-routing.module.ts | 4 ++-- .../src/app/layouts/user-layout.component.html | 4 ++-- apps/web/src/app/oss-routing.module.ts | 4 ++-- .../app/tools/import/import-web.component.html | 2 +- .../app/tools/import/org-import.component.html | 2 +- .../vault-export/export-web.component.html | 2 +- .../org-vault-export.component.html | 2 +- apps/web/src/locales/en/messages.json | 15 +++------------ .../layout/navigation.component.html | 4 ++-- .../settings/porting/sm-export.component.html | 2 +- .../settings/porting/sm-export.component.ts | 2 +- .../settings/porting/sm-import.component.html | 2 +- .../settings/settings-routing.module.ts | 4 ++-- .../dialog/file-password-prompt.component.html | 2 +- .../src/components/export.component.ts | 2 +- 24 files changed, 55 insertions(+), 69 deletions(-) diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index 09ea964823c..36d69fb09f5 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -436,8 +436,8 @@ "sync": { "message": "Sync" }, - "syncVaultNow": { - "message": "Sync vault now" + "syncNow": { + "message": "Sync now" }, "lastSync": { "message": "Last sync:" @@ -455,9 +455,6 @@ "bitWebVaultApp": { "message": "Bitwarden web app" }, - "importItems": { - "message": "Import items" - }, "select": { "message": "Select" }, @@ -1325,8 +1322,11 @@ "exportFrom": { "message": "Export from" }, - "exportVault": { - "message": "Export vault" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "File format" @@ -4215,10 +4215,6 @@ "ignore": { "message": "Ignore" }, - "importData": { - "message": "Import data", - "description": "Used for the header of the import dialog, the import button and within the file-password-prompt" - }, "importError": { "message": "Import error" }, diff --git a/apps/browser/src/tools/popup/settings/export/export-browser-v2.component.html b/apps/browser/src/tools/popup/settings/export/export-browser-v2.component.html index d6bf3a3a253..5473bbe620e 100644 --- a/apps/browser/src/tools/popup/settings/export/export-browser-v2.component.html +++ b/apps/browser/src/tools/popup/settings/export/export-browser-v2.component.html @@ -1,5 +1,5 @@ <popup-page> - <popup-header slot="header" [pageTitle]="'exportVault' | i18n" showBackButton> + <popup-header slot="header" [pageTitle]="'export' | i18n" showBackButton> <ng-container slot="end"> <app-pop-out></app-pop-out> </ng-container> @@ -21,7 +21,7 @@ bitFormButton buttonType="primary" > - {{ "exportVault" | i18n }} + {{ "export" | i18n }} </button> <button bitButton type="button" buttonType="secondary" [popupBackAction]> {{ "cancel" | i18n }} diff --git a/apps/browser/src/tools/popup/settings/import/import-browser-v2.component.html b/apps/browser/src/tools/popup/settings/import/import-browser-v2.component.html index 5458b46535a..db58e3f0227 100644 --- a/apps/browser/src/tools/popup/settings/import/import-browser-v2.component.html +++ b/apps/browser/src/tools/popup/settings/import/import-browser-v2.component.html @@ -1,5 +1,5 @@ <popup-page> - <popup-header slot="header" [pageTitle]="'importData' | i18n" showBackButton> + <popup-header slot="header" [pageTitle]="'import' | i18n" showBackButton> <ng-container slot="end"> <app-pop-out></app-pop-out> </ng-container> @@ -22,7 +22,7 @@ bitFormButton buttonType="primary" > - {{ "importData" | i18n }} + {{ "import" | i18n }} </button> </popup-footer> </popup-page> 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 225640137e8..c042af8cbac 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 @@ -15,7 +15,7 @@ <bit-item> <button type="button" bit-item-content (click)="import()"> <div class="tw-flex tw-items-center tw-justify-center tw-gap-2"> - <p>{{ "importItems" | i18n }}</p> + <p>{{ "import" | i18n }}</p> <span *ngIf="emptyVaultImportBadge$ | async" bitBadge @@ -30,7 +30,7 @@ </bit-item> <bit-item> <a bit-item-content routerLink="/export"> - {{ "exportVault" | i18n }} + {{ "export" | i18n }} <i slot="end" class="bwi bwi-angle-right" aria-hidden="true"></i> </a> </bit-item> @@ -64,7 +64,7 @@ </bit-item> <bit-item> <button type="button" bit-item-content (click)="sync()"> - {{ "syncVaultNow" | i18n }} + {{ "syncNow" | i18n }} <span slot="secondary">{{ lastSync }}</span> <i slot="end" class="bwi bwi-refresh" aria-hidden="true"></i> </button> diff --git a/apps/desktop/src/app/tools/export/export-desktop.component.html b/apps/desktop/src/app/tools/export/export-desktop.component.html index 9aa59c5a636..a969b86b950 100644 --- a/apps/desktop/src/app/tools/export/export-desktop.component.html +++ b/apps/desktop/src/app/tools/export/export-desktop.component.html @@ -1,5 +1,5 @@ <bit-dialog #dialog dialogSize="large"> - <span bitDialogTitle>{{ "exportVault" | i18n }}</span> + <span bitDialogTitle>{{ "export" | i18n }}</span> <ng-container bitDialogContent> <tools-export (formLoading)="this.loading = $event" @@ -17,7 +17,7 @@ bitFormButton buttonType="primary" > - {{ "exportVault" | i18n }} + {{ "export" | i18n }} </button> <button type="button" bitButton bitFormButton buttonType="secondary" bitDialogClose> {{ "cancel" | i18n }} diff --git a/apps/desktop/src/app/tools/import/import-desktop.component.html b/apps/desktop/src/app/tools/import/import-desktop.component.html index 3ee2384691b..b5011f4243e 100644 --- a/apps/desktop/src/app/tools/import/import-desktop.component.html +++ b/apps/desktop/src/app/tools/import/import-desktop.component.html @@ -1,5 +1,5 @@ <bit-dialog #dialog dialogSize="large" background="alt"> - <span bitDialogTitle>{{ "importData" | i18n }}</span> + <span bitDialogTitle>{{ "import" | i18n }}</span> <ng-container bitDialogContent> <div class="tw-relative"> <tools-import @@ -27,7 +27,7 @@ bitFormButton buttonType="primary" > - {{ "importData" | i18n }} + {{ "import" | i18n }} </button> <button type="button" bitButton bitFormButton buttonType="secondary" bitDialogClose> {{ "cancel" | i18n }} diff --git a/apps/desktop/src/locales/en/messages.json b/apps/desktop/src/locales/en/messages.json index 48e346d9c68..3659c75bfca 100644 --- a/apps/desktop/src/locales/en/messages.json +++ b/apps/desktop/src/locales/en/messages.json @@ -1198,8 +1198,8 @@ "followUs": { "message": "Follow us" }, - "syncVault": { - "message": "Sync vault" + "syncNow": { + "message": "Sync now" }, "changeMasterPass": { "message": "Change master password" @@ -1775,8 +1775,11 @@ "exportFrom": { "message": "Export from" }, - "exportVault": { - "message": "Export vault" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "File format" @@ -3492,10 +3495,6 @@ "aliasDomain": { "message": "Alias domain" }, - "importData": { - "message": "Import data", - "description": "Used for the desktop menu item and the header of the import dialog" - }, "importError": { "message": "Import error" }, diff --git a/apps/desktop/src/main/menu/menu.file.ts b/apps/desktop/src/main/menu/menu.file.ts index a8cdb347a77..eb5f5a9d747 100644 --- a/apps/desktop/src/main/menu/menu.file.ts +++ b/apps/desktop/src/main/menu/menu.file.ts @@ -146,8 +146,8 @@ export class FileMenu extends FirstMenu implements IMenubarMenu { private get syncVault(): MenuItemConstructorOptions { return { - id: "syncVault", - label: this.localize("syncVault"), + id: "syncNow", + label: this.localize("syncNow"), click: () => this.sendMessage("syncVault"), enabled: this.hasAuthenticatedAccounts, }; @@ -155,8 +155,8 @@ export class FileMenu extends FirstMenu implements IMenubarMenu { private get importVault(): MenuItemConstructorOptions { return { - id: "importVault", - label: this.localize("importData"), + id: "import", + label: this.localize("import"), click: () => this.sendMessage("importVault"), enabled: !this._isLocked, }; @@ -164,8 +164,8 @@ export class FileMenu extends FirstMenu implements IMenubarMenu { private get exportVault(): MenuItemConstructorOptions { return { - id: "exportVault", - label: this.localize("exportVault"), + id: "export", + label: this.localize("export"), click: () => this.sendMessage("exportVault"), enabled: !this._isLocked, }; diff --git a/apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.html b/apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.html index 59bc03babd4..85bfe0bce0a 100644 --- a/apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.html +++ b/apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.html @@ -104,12 +104,12 @@ *ngIf="organization.use2fa && organization.isOwner" ></bit-nav-item> <bit-nav-item - [text]="'importData' | i18n" + [text]="'import' | i18n" route="settings/tools/import" *ngIf="organization.canAccessImport" ></bit-nav-item> <bit-nav-item - [text]="'exportVault' | i18n" + [text]="'export' | i18n" route="settings/tools/export" *ngIf="canAccessExport$ | async" ></bit-nav-item> diff --git a/apps/web/src/app/admin-console/organizations/settings/organization-settings-routing.module.ts b/apps/web/src/app/admin-console/organizations/settings/organization-settings-routing.module.ts index a644086628c..61d7d0e04ac 100644 --- a/apps/web/src/app/admin-console/organizations/settings/organization-settings-routing.module.ts +++ b/apps/web/src/app/admin-console/organizations/settings/organization-settings-routing.module.ts @@ -57,7 +57,7 @@ const routes: Routes = [ ), canActivate: [organizationPermissionsGuard((org) => org.canAccessImport)], data: { - titleId: "importData", + titleId: "import", }, }, { @@ -68,7 +68,7 @@ const routes: Routes = [ ), canActivate: [organizationPermissionsGuard((org) => org.canAccessExport)], data: { - titleId: "exportVault", + titleId: "export", }, }, ], diff --git a/apps/web/src/app/layouts/user-layout.component.html b/apps/web/src/app/layouts/user-layout.component.html index 9f474062120..27955a2b0ca 100644 --- a/apps/web/src/app/layouts/user-layout.component.html +++ b/apps/web/src/app/layouts/user-layout.component.html @@ -6,8 +6,8 @@ <bit-nav-item icon="bwi-send" [text]="'send' | i18n" route="sends"></bit-nav-item> <bit-nav-group icon="bwi-wrench" [text]="'tools' | i18n" route="tools"> <bit-nav-item [text]="'generator' | i18n" route="tools/generator"></bit-nav-item> - <bit-nav-item [text]="'importData' | i18n" route="tools/import"></bit-nav-item> - <bit-nav-item [text]="'exportVault' | i18n" route="tools/export"></bit-nav-item> + <bit-nav-item [text]="'import' | i18n" route="tools/import"></bit-nav-item> + <bit-nav-item [text]="'export' | i18n" route="tools/export"></bit-nav-item> </bit-nav-group> <bit-nav-item icon="bwi-sliders" [text]="'reports' | i18n" route="reports"></bit-nav-item> <bit-nav-group icon="bwi-cog" [text]="'settings' | i18n" route="settings"> diff --git a/apps/web/src/app/oss-routing.module.ts b/apps/web/src/app/oss-routing.module.ts index e3c9da635f9..b97cbcac72a 100644 --- a/apps/web/src/app/oss-routing.module.ts +++ b/apps/web/src/app/oss-routing.module.ts @@ -748,7 +748,7 @@ const routes: Routes = [ loadComponent: () => import("./tools/import/import-web.component").then((mod) => mod.ImportWebComponent), data: { - titleId: "importData", + titleId: "import", } satisfies RouteDataProperties, }, { @@ -758,7 +758,7 @@ const routes: Routes = [ (mod) => mod.ExportWebComponent, ), data: { - titleId: "exportVault", + titleId: "export", } satisfies RouteDataProperties, }, { diff --git a/apps/web/src/app/tools/import/import-web.component.html b/apps/web/src/app/tools/import/import-web.component.html index 2db158a14e2..d6f7e0db0cd 100644 --- a/apps/web/src/app/tools/import/import-web.component.html +++ b/apps/web/src/app/tools/import/import-web.component.html @@ -15,6 +15,6 @@ bitFormButton buttonType="primary" > - {{ "importData" | i18n }} + {{ "import" | i18n }} </button> </bit-container> diff --git a/apps/web/src/app/tools/import/org-import.component.html b/apps/web/src/app/tools/import/org-import.component.html index 25efa9ec0c7..00e4a7690a2 100644 --- a/apps/web/src/app/tools/import/org-import.component.html +++ b/apps/web/src/app/tools/import/org-import.component.html @@ -16,6 +16,6 @@ bitFormButton buttonType="primary" > - {{ "importData" | i18n }} + {{ "import" | i18n }} </button> </bit-container> diff --git a/apps/web/src/app/tools/vault-export/export-web.component.html b/apps/web/src/app/tools/vault-export/export-web.component.html index e3d0ca75d25..1ff34f4c988 100644 --- a/apps/web/src/app/tools/vault-export/export-web.component.html +++ b/apps/web/src/app/tools/vault-export/export-web.component.html @@ -15,6 +15,6 @@ bitFormButton buttonType="primary" > - {{ "confirmFormat" | i18n }} + {{ "export" | i18n }} </button> </bit-container> diff --git a/apps/web/src/app/tools/vault-export/org-vault-export.component.html b/apps/web/src/app/tools/vault-export/org-vault-export.component.html index 01975272e76..e781a839896 100644 --- a/apps/web/src/app/tools/vault-export/org-vault-export.component.html +++ b/apps/web/src/app/tools/vault-export/org-vault-export.component.html @@ -16,6 +16,6 @@ bitFormButton buttonType="primary" > - {{ "confirmFormat" | i18n }} + {{ "export" | i18n }} </button> </bit-container> diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 3b0554547c5..0f8b0c1b466 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -1975,12 +1975,6 @@ "exportFrom": { "message": "Export from" }, - "exportVault": { - "message": "Export vault" - }, - "exportSecrets": { - "message": "Export secrets" - }, "fileFormat": { "message": "File format" }, @@ -1993,9 +1987,6 @@ "confirmMasterPassword": { "message": "Confirm master password" }, - "confirmFormat": { - "message": "Confirm format" - }, "filePassword": { "message": "File password" }, @@ -2306,6 +2297,9 @@ "tools": { "message": "Tools" }, + "import": { + "message": "Import" + }, "importData": { "message": "Import data" }, @@ -8757,9 +8751,6 @@ "server": { "message": "Server" }, - "exportData": { - "message": "Export data" - }, "exportingOrganizationSecretDataTitle": { "message": "Exporting Organization Secret Data" }, diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/layout/navigation.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/layout/navigation.component.html index 0ea8caef4d6..ac70e1920ee 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/layout/navigation.component.html +++ b/bitwarden_license/bit-web/src/app/secrets-manager/layout/navigation.component.html @@ -52,12 +52,12 @@ [relativeTo]="route.parent" > <bit-nav-item - [text]="'importData' | i18n" + [text]="'import' | i18n" route="settings/import" [relativeTo]="route.parent" ></bit-nav-item> <bit-nav-item - [text]="'exportData' | i18n" + [text]="'export' | i18n" route="settings/export" [relativeTo]="route.parent" ></bit-nav-item> diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/settings/porting/sm-export.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/settings/porting/sm-export.component.html index 9e1f2e01591..113c51327b2 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/settings/porting/sm-export.component.html +++ b/bitwarden_license/bit-web/src/app/secrets-manager/settings/porting/sm-export.component.html @@ -17,6 +17,6 @@ </bit-form-field> <button bitButton bitFormButton type="submit" buttonType="primary"> - {{ "exportData" | i18n }} + {{ "export" | i18n }} </button> </form> diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/settings/porting/sm-export.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/settings/porting/sm-export.component.ts index e2b66d9ffa6..5e6f81d99d6 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/settings/porting/sm-export.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/settings/porting/sm-export.component.ts @@ -124,7 +124,7 @@ export class SecretsManagerExportComponent implements OnInit, OnDestroy { const ref = openUserVerificationPrompt(this.dialogService, { data: { confirmDescription: "exportSecretsWarningDesc", - confirmButtonText: "exportSecrets", + confirmButtonText: "export", modalTitle: "confirmSecretsExport", }, }); diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/settings/porting/sm-import.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/settings/porting/sm-import.component.html index 353d8d8c8ed..3a663dbcbe9 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/settings/porting/sm-import.component.html +++ b/bitwarden_license/bit-web/src/app/secrets-manager/settings/porting/sm-import.component.html @@ -36,6 +36,6 @@ <bit-hint>{{ "acceptedFormats" | i18n }} Bitwarden (json)</bit-hint> </bit-form-field> <button bitButton bitFormButton type="submit" buttonType="primary"> - {{ "importData" | i18n }} + {{ "import" | i18n }} </button> </form> diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/settings/settings-routing.module.ts b/bitwarden_license/bit-web/src/app/secrets-manager/settings/settings-routing.module.ts index ddc9964060e..31029d134fa 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/settings/settings-routing.module.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/settings/settings-routing.module.ts @@ -12,7 +12,7 @@ const routes: Routes = [ component: SecretsManagerImportComponent, canActivate: [organizationPermissionsGuard((org) => org.isAdmin)], data: { - titleId: "importData", + titleId: "import", }, }, { @@ -20,7 +20,7 @@ const routes: Routes = [ component: SecretsManagerExportComponent, canActivate: [organizationPermissionsGuard((org) => org.isAdmin)], data: { - titleId: "exportData", + titleId: "export", }, }, ]; diff --git a/libs/importer/src/components/dialog/file-password-prompt.component.html b/libs/importer/src/components/dialog/file-password-prompt.component.html index d663ec0f4d3..1c0bcdca31d 100644 --- a/libs/importer/src/components/dialog/file-password-prompt.component.html +++ b/libs/importer/src/components/dialog/file-password-prompt.component.html @@ -21,7 +21,7 @@ <ng-container bitDialogFooter> <button bitButton buttonType="primary" type="submit"> - <span>{{ "importData" | i18n }}</span> + <span>{{ "import" | i18n }}</span> </button> <button bitButton bitDialogClose buttonType="secondary" type="button"> <span>{{ "cancel" | i18n }}</span> 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 e81217e54c2..232fb40aeb2 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 @@ -620,7 +620,7 @@ export class ExportComponent implements OnInit, OnDestroy, AfterViewInit { title: "confirmVaultExport", bodyText: confirmDescription, confirmButtonOptions: { - text: "exportVault", + text: "continue", type: "primary", }, }); From 4e913df0ffe79e97f5e025320980c1104a55da10 Mon Sep 17 00:00:00 2001 From: Alex <55413326+AlexRubik@users.noreply.github.com> Date: Fri, 12 Dec 2025 14:07:02 -0500 Subject: [PATCH 066/188] make checkbox selection updates immutable (#17939) --- .../all-applications.component.ts | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications/all-applications.component.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications/all-applications.component.ts index 3a9159ad68c..95453ffa41a 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications/all-applications.component.ts +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications/all-applications.component.ts @@ -143,16 +143,14 @@ export class AllApplicationsComponent implements OnInit { onCheckboxChange = (applicationName: string, event: Event) => { const isChecked = (event.target as HTMLInputElement).checked; - if (isChecked) { - this.selectedUrls.update((selectedUrls) => { - selectedUrls.add(applicationName); - return selectedUrls; - }); - } else { - this.selectedUrls.update((selectedUrls) => { - selectedUrls.delete(applicationName); - return selectedUrls; - }); - } + this.selectedUrls.update((selectedUrls) => { + const nextSelected = new Set(selectedUrls); + if (isChecked) { + nextSelected.add(applicationName); + } else { + nextSelected.delete(applicationName); + } + return nextSelected; + }); }; } From 1b305c3c239a0fd9f448b161057f132419a7cffd Mon Sep 17 00:00:00 2001 From: Maciej Zieniuk <167752252+mzieniukbw@users.noreply.github.com> Date: Mon, 15 Dec 2025 11:36:34 +0100 Subject: [PATCH 067/188] [PM-26049] Auto key not stored due to vault timeout write vs read race condition for cli (#17707) * auto key not stored due to vault timeout race condition being null for cli * fix unit test default state * neglected electron key service test cleanup * bad merge - fix formatting --- .../electron-key.service.spec.ts | 49 ++++++------------- .../src/key-management/vault-timeout/index.ts | 2 + .../vault-timeout-settings.service.ts | 6 ++- libs/key-management/src/key.service.spec.ts | 4 +- libs/key-management/src/key.service.ts | 4 +- 5 files changed, 28 insertions(+), 37 deletions(-) diff --git a/apps/desktop/src/key-management/electron-key.service.spec.ts b/apps/desktop/src/key-management/electron-key.service.spec.ts index cc1d68ed050..c5e010b1766 100644 --- a/apps/desktop/src/key-management/electron-key.service.spec.ts +++ b/apps/desktop/src/key-management/electron-key.service.spec.ts @@ -13,11 +13,13 @@ import { UserKey } from "@bitwarden/common/types/key"; import { BiometricStateService, KdfConfigService } from "@bitwarden/key-management"; import { - makeSymmetricCryptoKey, FakeAccountService, - mockAccountServiceWith, FakeStateProvider, + makeSymmetricCryptoKey, + mockAccountServiceWith, } from "../../../../libs/common/spec"; +// eslint-disable-next-line no-restricted-imports +import { VAULT_TIMEOUT } from "../../../../libs/common/src/key-management/vault-timeout"; import { DesktopBiometricsService } from "./biometrics/desktop.biometrics.service"; import { ElectronKeyService } from "./electron-key.service"; @@ -40,11 +42,13 @@ describe("ElectronKeyService", () => { let accountService: FakeAccountService; let masterPasswordService: FakeMasterPasswordService; - beforeEach(() => { + beforeEach(async () => { accountService = mockAccountServiceWith(mockUserId); masterPasswordService = new FakeMasterPasswordService(); stateProvider = new FakeStateProvider(accountService); + await stateProvider.setUserState(VAULT_TIMEOUT, 10, mockUserId); + keyService = new ElectronKeyService( masterPasswordService, keyGenerationService, @@ -79,38 +83,17 @@ describe("ElectronKeyService", () => { expect(biometricStateService.getBiometricUnlockEnabled).toHaveBeenCalledWith(mockUserId); }); - describe("biometric unlock enabled", () => { - beforeEach(() => { - biometricStateService.getBiometricUnlockEnabled.mockResolvedValue(true); - }); + it("sets biometric key when biometric unlock enabled", async () => { + biometricStateService.getBiometricUnlockEnabled.mockResolvedValue(true); - it("sets null biometric client key half and biometric unlock key when require password on start disabled", async () => { - biometricStateService.getRequirePasswordOnStart.mockResolvedValue(false); + await keyService.setUserKey(userKey, mockUserId); - await keyService.setUserKey(userKey, mockUserId); - - expect(biometricService.setBiometricProtectedUnlockKeyForUser).toHaveBeenCalledWith( - mockUserId, - userKey, - ); - expect(biometricStateService.setEncryptedClientKeyHalf).not.toHaveBeenCalled(); - expect(biometricStateService.getBiometricUnlockEnabled).toHaveBeenCalledWith(mockUserId); - }); - - describe("require password on start enabled", () => { - beforeEach(() => { - biometricStateService.getRequirePasswordOnStart.mockResolvedValue(true); - }); - - it("sets biometric key", async () => { - await keyService.setUserKey(userKey, mockUserId); - - expect(biometricService.setBiometricProtectedUnlockKeyForUser).toHaveBeenCalledWith( - mockUserId, - userKey, - ); - }); - }); + expect(biometricService.setBiometricProtectedUnlockKeyForUser).toHaveBeenCalledWith( + mockUserId, + userKey, + ); + expect(biometricStateService.setEncryptedClientKeyHalf).not.toHaveBeenCalled(); + expect(biometricStateService.getBiometricUnlockEnabled).toHaveBeenCalledWith(mockUserId); }); }); }); diff --git a/libs/common/src/key-management/vault-timeout/index.ts b/libs/common/src/key-management/vault-timeout/index.ts index ba32c12c9fb..1879de5353c 100644 --- a/libs/common/src/key-management/vault-timeout/index.ts +++ b/libs/common/src/key-management/vault-timeout/index.ts @@ -10,3 +10,5 @@ export { VaultTimeoutNumberType, VaultTimeoutStringType, } from "./types/vault-timeout.type"; +// Only used by desktop's electron-key.service.spec.ts test +export { VAULT_TIMEOUT } from "./services/vault-timeout-settings.state"; diff --git a/libs/common/src/key-management/vault-timeout/services/vault-timeout-settings.service.ts b/libs/common/src/key-management/vault-timeout/services/vault-timeout-settings.service.ts index dc0c5620518..b8bc859d11c 100644 --- a/libs/common/src/key-management/vault-timeout/services/vault-timeout-settings.service.ts +++ b/libs/common/src/key-management/vault-timeout/services/vault-timeout-settings.service.ts @@ -13,6 +13,7 @@ import { shareReplay, switchMap, tap, + concatMap, } from "rxjs"; // This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. @@ -150,7 +151,7 @@ export class VaultTimeoutSettingsService implements VaultTimeoutSettingsServiceA return from( this.determineVaultTimeout(currentVaultTimeout, maxSessionTimeoutPolicyData), ).pipe( - tap((vaultTimeout: VaultTimeout) => { + concatMap(async (vaultTimeout: VaultTimeout) => { this.logService.debug( "[VaultTimeoutSettingsService] Determined vault timeout is %o for user id %s", vaultTimeout, @@ -159,8 +160,9 @@ export class VaultTimeoutSettingsService implements VaultTimeoutSettingsServiceA // As a side effect, set the new value determined by determineVaultTimeout into state if it's different from the current if (vaultTimeout !== currentVaultTimeout) { - return this.stateProvider.setUserState(VAULT_TIMEOUT, vaultTimeout, userId); + await this.stateProvider.setUserState(VAULT_TIMEOUT, vaultTimeout, userId); } + return vaultTimeout; }), catchError((error: unknown) => { // Protect outer observable from canceling on error by catching and returning EMPTY diff --git a/libs/key-management/src/key.service.spec.ts b/libs/key-management/src/key.service.spec.ts index a5b4eb01c7c..c0af62fe6e9 100644 --- a/libs/key-management/src/key.service.spec.ts +++ b/libs/key-management/src/key.service.spec.ts @@ -69,11 +69,13 @@ describe("keyService", () => { let accountService: FakeAccountService; let masterPasswordService: FakeMasterPasswordService; - beforeEach(() => { + beforeEach(async () => { accountService = mockAccountServiceWith(mockUserId); masterPasswordService = new FakeMasterPasswordService(); stateProvider = new FakeStateProvider(accountService); + await stateProvider.setUserState(VAULT_TIMEOUT, VaultTimeoutStringType.Never, mockUserId); + keyService = new DefaultKeyService( masterPasswordService, keyGenerationService, diff --git a/libs/key-management/src/key.service.ts b/libs/key-management/src/key.service.ts index f0e5f6ee08e..621a8135d1e 100644 --- a/libs/key-management/src/key.service.ts +++ b/libs/key-management/src/key.service.ts @@ -691,7 +691,9 @@ export class DefaultKeyService implements KeyServiceAbstraction { // the VaultTimeoutSettingsSvc and this service. // This should be fixed as part of the PM-7082 - Auto Key Service work. const vaultTimeout = await firstValueFrom( - this.stateProvider.getUserState$(VAULT_TIMEOUT, userId), + this.stateProvider + .getUserState$(VAULT_TIMEOUT, userId) + .pipe(filter((timeout) => timeout != null)), ); shouldStoreKey = vaultTimeout == VaultTimeoutStringType.Never; From bab2684bbd06f0b48c34016ceaca5720a35333e0 Mon Sep 17 00:00:00 2001 From: Oscar Hinton <Hinton@users.noreply.github.com> Date: Mon, 15 Dec 2025 11:37:15 +0100 Subject: [PATCH 068/188] Migrate avatar to OnPush (#17389) --- .../components/src/avatar/avatar.component.ts | 39 +++++++++++++++---- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/libs/components/src/avatar/avatar.component.ts b/libs/components/src/avatar/avatar.component.ts index 2ba85e32772..f50807dd506 100644 --- a/libs/components/src/avatar/avatar.component.ts +++ b/libs/components/src/avatar/avatar.component.ts @@ -1,5 +1,5 @@ import { NgClass } from "@angular/common"; -import { Component, computed, input } from "@angular/core"; +import { ChangeDetectionStrategy, Component, computed, input } from "@angular/core"; import { Utils } from "@bitwarden/common/platform/misc/utils"; @@ -14,13 +14,11 @@ const SizeClasses: Record<SizeTypes, string[]> = { }; /** - * Avatars display a unique color that helps a user visually recognize their logged in account. - - * A variance in color across the avatar component is important as it is used in Account Switching as a - * visual indicator to recognize which of a personal or work account a user is logged into. -*/ -// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush -// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection + * Avatars display a unique color that helps a user visually recognize their logged in account. + * + * A variance in color across the avatar component is important as it is used in Account Switching as a + * visual indicator to recognize which of a personal or work account a user is logged into. + */ @Component({ selector: "bit-avatar", template: ` @@ -49,13 +47,38 @@ const SizeClasses: Record<SizeTypes, string[]> = { </span> `, imports: [NgClass], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class AvatarComponent { + /** + * Whether to display a border around the avatar. + */ readonly border = input(false); + + /** + * Custom background color for the avatar. If not provided, a color will be generated based on the id or text. + */ readonly color = input<string>(); + + /** + * Unique identifier used to generate a consistent background color. Takes precedence over text for color generation. + */ readonly id = input<string>(); + + /** + * Text to display in the avatar. The first letters of words (up to 2 characters) will be shown. + * Also used to generate background color if id is not provided. + */ readonly text = input<string>(); + + /** + * Title attribute for the avatar. If not provided, falls back to the text value. + */ readonly title = input<string>(); + + /** + * Size of the avatar. + */ readonly size = input<SizeTypes>("default"); protected readonly svgCharCount = 2; From d0ddf7d84115e056111c40c33844e9c57001900e Mon Sep 17 00:00:00 2001 From: Github Actions <actions@github.com> Date: Mon, 15 Dec 2025 15:28:27 +0000 Subject: [PATCH 069/188] Bumped client version(s) --- apps/web/package.json | 2 +- package-lock.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/web/package.json b/apps/web/package.json index 344a78f2a2c..a5399de920e 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -1,6 +1,6 @@ { "name": "@bitwarden/web-vault", - "version": "2025.12.0", + "version": "2025.12.1", "scripts": { "build:oss": "webpack", "build:bit": "webpack -c ../../bitwarden_license/bit-web/webpack.config.js", diff --git a/package-lock.json b/package-lock.json index 3a600667ff7..a1e2f1121a7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -491,7 +491,7 @@ }, "apps/web": { "name": "@bitwarden/web-vault", - "version": "2025.12.0" + "version": "2025.12.1" }, "libs/admin-console": { "name": "@bitwarden/admin-console", From 898c5d366a9186ef4264831dbf4b35b3e593368b Mon Sep 17 00:00:00 2001 From: Oscar Hinton <Hinton@users.noreply.github.com> Date: Mon, 15 Dec 2025 16:42:35 +0100 Subject: [PATCH 070/188] [PM-28809] Migrate last auth from LooseComponentsModule (#17650) Migrates the last auth owned components from `LooseComponentsModule`. --- .../src/app/auth/recover-delete.component.ts | 27 ++++++++++--- .../auth/recover-two-factor.component.spec.ts | 38 +++++-------------- .../app/auth/recover-two-factor.component.ts | 27 +++++++++++-- .../app/auth/verify-email-token.component.ts | 3 -- .../auth/verify-recover-delete.component.ts | 25 +++++++++--- .../src/app/shared/loose-components.module.ts | 16 +------- 6 files changed, 73 insertions(+), 63 deletions(-) diff --git a/apps/web/src/app/auth/recover-delete.component.ts b/apps/web/src/app/auth/recover-delete.component.ts index 00b14f9a402..921d96270bf 100644 --- a/apps/web/src/app/auth/recover-delete.component.ts +++ b/apps/web/src/app/auth/recover-delete.component.ts @@ -1,21 +1,37 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore import { Component } from "@angular/core"; -import { FormControl, FormGroup, Validators } from "@angular/forms"; -import { Router } from "@angular/router"; +import { FormControl, FormGroup, ReactiveFormsModule, Validators } from "@angular/forms"; +import { Router, RouterLink } from "@angular/router"; +import { JslibModule } from "@bitwarden/angular/jslib.module"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { DeleteRecoverRequest } from "@bitwarden/common/models/request/delete-recover.request"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { ToastService } from "@bitwarden/components"; +import { + AsyncActionsModule, + ButtonModule, + FormFieldModule, + ToastService, + TypographyModule, +} from "@bitwarden/components"; +import { I18nPipe } from "@bitwarden/ui-common"; // FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush // eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "app-recover-delete", templateUrl: "recover-delete.component.html", - standalone: false, + imports: [ + ReactiveFormsModule, + RouterLink, + JslibModule, + AsyncActionsModule, + ButtonModule, + FormFieldModule, + I18nPipe, + TypographyModule, + ], }) export class RecoverDeleteComponent { protected recoverDeleteForm = new FormGroup({ @@ -29,7 +45,6 @@ export class RecoverDeleteComponent { constructor( private router: Router, private apiService: ApiService, - private platformUtilsService: PlatformUtilsService, private i18nService: I18nService, private toastService: ToastService, ) {} diff --git a/apps/web/src/app/auth/recover-two-factor.component.spec.ts b/apps/web/src/app/auth/recover-two-factor.component.spec.ts index c3792cfd3f3..b7a68ff9f07 100644 --- a/apps/web/src/app/auth/recover-two-factor.component.spec.ts +++ b/apps/web/src/app/auth/recover-two-factor.component.spec.ts @@ -1,5 +1,5 @@ import { ComponentFixture, TestBed } from "@angular/core/testing"; -import { Router } from "@angular/router"; +import { Router, provideRouter } from "@angular/router"; import { mock, MockProxy } from "jest-mock-extended"; import { @@ -7,69 +7,49 @@ import { LoginSuccessHandlerService, PasswordLoginCredentials, } from "@bitwarden/auth/common"; -import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result"; import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; import { ToastService } from "@bitwarden/components"; -import { KeyService } from "@bitwarden/key-management"; -import { I18nPipe } from "@bitwarden/ui-common"; import { RecoverTwoFactorComponent } from "./recover-two-factor.component"; describe("RecoverTwoFactorComponent", () => { let component: RecoverTwoFactorComponent; let fixture: ComponentFixture<RecoverTwoFactorComponent>; - - // Mock Services let mockRouter: MockProxy<Router>; - let mockApiService: MockProxy<ApiService>; - let mockPlatformUtilsService: MockProxy<PlatformUtilsService>; let mockI18nService: MockProxy<I18nService>; - let mockKeyService: MockProxy<KeyService>; let mockLoginStrategyService: MockProxy<LoginStrategyServiceAbstraction>; let mockToastService: MockProxy<ToastService>; - let mockConfigService: MockProxy<ConfigService>; let mockLoginSuccessHandlerService: MockProxy<LoginSuccessHandlerService>; let mockLogService: MockProxy<LogService>; let mockValidationService: MockProxy<ValidationService>; - beforeEach(() => { - mockRouter = mock<Router>(); - mockApiService = mock<ApiService>(); - mockPlatformUtilsService = mock<PlatformUtilsService>(); + beforeEach(async () => { mockI18nService = mock<I18nService>(); - mockKeyService = mock<KeyService>(); mockLoginStrategyService = mock<LoginStrategyServiceAbstraction>(); mockToastService = mock<ToastService>(); - mockConfigService = mock<ConfigService>(); mockLoginSuccessHandlerService = mock<LoginSuccessHandlerService>(); mockLogService = mock<LogService>(); mockValidationService = mock<ValidationService>(); - TestBed.configureTestingModule({ - declarations: [RecoverTwoFactorComponent], + await TestBed.configureTestingModule({ + imports: [RecoverTwoFactorComponent], providers: [ - { provide: Router, useValue: mockRouter }, - { provide: ApiService, useValue: mockApiService }, - { provide: PlatformUtilsService, mockPlatformUtilsService }, + provideRouter([]), { provide: I18nService, useValue: mockI18nService }, - { provide: KeyService, useValue: mockKeyService }, { provide: LoginStrategyServiceAbstraction, useValue: mockLoginStrategyService }, { provide: ToastService, useValue: mockToastService }, - { provide: ConfigService, useValue: mockConfigService }, { provide: LoginSuccessHandlerService, useValue: mockLoginSuccessHandlerService }, { provide: LogService, useValue: mockLogService }, { provide: ValidationService, useValue: mockValidationService }, ], - imports: [I18nPipe], - // FIXME(PM-18598): Replace unknownElements and unknownProperties with actual imports - errorOnUnknownElements: false, - }); + }).compileComponents(); + + mockRouter = TestBed.inject(Router) as MockProxy<Router>; + jest.spyOn(mockRouter, "navigate"); fixture = TestBed.createComponent(RecoverTwoFactorComponent); component = fixture.componentInstance; diff --git a/apps/web/src/app/auth/recover-two-factor.component.ts b/apps/web/src/app/auth/recover-two-factor.component.ts index 20f40b5319a..5d160e4ed91 100644 --- a/apps/web/src/app/auth/recover-two-factor.component.ts +++ b/apps/web/src/app/auth/recover-two-factor.component.ts @@ -1,8 +1,9 @@ import { Component, DestroyRef, OnInit } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; -import { FormControl, FormGroup, Validators } from "@angular/forms"; -import { Router } from "@angular/router"; +import { FormControl, FormGroup, ReactiveFormsModule, Validators } from "@angular/forms"; +import { Router, RouterLink } from "@angular/router"; +import { JslibModule } from "@bitwarden/angular/jslib.module"; import { LoginStrategyServiceAbstraction, PasswordLoginCredentials, @@ -14,14 +15,32 @@ 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 { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; -import { ToastService } from "@bitwarden/components"; +import { + AsyncActionsModule, + ButtonModule, + FormFieldModule, + LinkModule, + ToastService, + TypographyModule, +} from "@bitwarden/components"; +import { I18nPipe } from "@bitwarden/ui-common"; // FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush // eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "app-recover-two-factor", templateUrl: "recover-two-factor.component.html", - standalone: false, + imports: [ + ReactiveFormsModule, + RouterLink, + JslibModule, + AsyncActionsModule, + ButtonModule, + FormFieldModule, + I18nPipe, + LinkModule, + TypographyModule, + ], }) export class RecoverTwoFactorComponent implements OnInit { formGroup = new FormGroup({ diff --git a/apps/web/src/app/auth/verify-email-token.component.ts b/apps/web/src/app/auth/verify-email-token.component.ts index 30bfcf95bbf..fe70f876bc4 100644 --- a/apps/web/src/app/auth/verify-email-token.component.ts +++ b/apps/web/src/app/auth/verify-email-token.component.ts @@ -10,7 +10,6 @@ import { TokenService } from "@bitwarden/common/auth/abstractions/token.service" import { VerifyEmailRequest } from "@bitwarden/common/models/request/verify-email.request"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { ToastService } from "@bitwarden/components"; // FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush @@ -18,12 +17,10 @@ import { ToastService } from "@bitwarden/components"; @Component({ selector: "app-verify-email-token", templateUrl: "verify-email-token.component.html", - standalone: false, }) export class VerifyEmailTokenComponent implements OnInit { constructor( private router: Router, - private platformUtilsService: PlatformUtilsService, private i18nService: I18nService, private route: ActivatedRoute, private apiService: ApiService, diff --git a/apps/web/src/app/auth/verify-recover-delete.component.ts b/apps/web/src/app/auth/verify-recover-delete.component.ts index 06d6096c3de..16968fcd161 100644 --- a/apps/web/src/app/auth/verify-recover-delete.component.ts +++ b/apps/web/src/app/auth/verify-recover-delete.component.ts @@ -1,22 +1,36 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore import { Component, OnInit } from "@angular/core"; -import { FormGroup } from "@angular/forms"; -import { ActivatedRoute, Router } from "@angular/router"; +import { FormGroup, ReactiveFormsModule } from "@angular/forms"; +import { ActivatedRoute, Router, RouterLink } from "@angular/router"; import { first } from "rxjs/operators"; +import { JslibModule } from "@bitwarden/angular/jslib.module"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { VerifyDeleteRecoverRequest } from "@bitwarden/common/models/request/verify-delete-recover.request"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { ToastService } from "@bitwarden/components"; +import { + AsyncActionsModule, + ButtonModule, + CalloutComponent, + ToastService, + TypographyModule, +} from "@bitwarden/components"; // FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush // eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "app-verify-recover-delete", templateUrl: "verify-recover-delete.component.html", - standalone: false, + imports: [ + ReactiveFormsModule, + RouterLink, + JslibModule, + AsyncActionsModule, + ButtonModule, + CalloutComponent, + TypographyModule, + ], }) export class VerifyRecoverDeleteComponent implements OnInit { email: string; @@ -28,7 +42,6 @@ export class VerifyRecoverDeleteComponent implements OnInit { constructor( private router: Router, private apiService: ApiService, - private platformUtilsService: PlatformUtilsService, private i18nService: I18nService, private route: ActivatedRoute, private toastService: ToastService, diff --git a/apps/web/src/app/shared/loose-components.module.ts b/apps/web/src/app/shared/loose-components.module.ts index f096ef6a292..ff7a5f02c77 100644 --- a/apps/web/src/app/shared/loose-components.module.ts +++ b/apps/web/src/app/shared/loose-components.module.ts @@ -1,9 +1,5 @@ import { NgModule } from "@angular/core"; -import { RecoverDeleteComponent } from "../auth/recover-delete.component"; -import { RecoverTwoFactorComponent } from "../auth/recover-two-factor.component"; -import { VerifyEmailTokenComponent } from "../auth/verify-email-token.component"; -import { VerifyRecoverDeleteComponent } from "../auth/verify-recover-delete.component"; import { FreeBitwardenFamiliesComponent } from "../billing/members/free-bitwarden-families.component"; import { SponsoredFamiliesComponent } from "../billing/settings/sponsored-families.component"; import { SponsoringOrgRowComponent } from "../billing/settings/sponsoring-org-row.component"; @@ -18,20 +14,10 @@ import { SharedModule } from "./shared.module"; @NgModule({ imports: [SharedModule, HeaderModule, OrganizationBadgeModule, PipesModule], declarations: [ - RecoverDeleteComponent, - RecoverTwoFactorComponent, SponsoredFamiliesComponent, FreeBitwardenFamiliesComponent, SponsoringOrgRowComponent, - VerifyEmailTokenComponent, - VerifyRecoverDeleteComponent, - ], - exports: [ - RecoverDeleteComponent, - RecoverTwoFactorComponent, - SponsoredFamiliesComponent, - VerifyEmailTokenComponent, - VerifyRecoverDeleteComponent, ], + exports: [SponsoredFamiliesComponent], }) export class LooseComponentsModule {} From 721f253ef9ba5252c2a839a200c7c9384052ab9d Mon Sep 17 00:00:00 2001 From: Max <mpower@bitwarden.com> Date: Mon, 15 Dec 2025 16:51:31 +0100 Subject: [PATCH 071/188] [PM-28536] Add phishing blocker setting to account security (#17527) * added phishing blocker toggle * design improvements * Fix TypeScript strict mode errors in PhishingDetectionSettingsServiceAbstraction * Camel case messages * Update PhishingDetectionService.initialize parameter ordering * Add comments to PhishingDetectionSettingsServiceAbstraction * Change state from global to user settings * Remove clear on logout phishing-detection-settings * PM-28536 making a change from getActive to getUser because of method being deprecated * Moved phishing detection services to own file * Added new phishing detection availability service to expose complex enable logic * Add test cases for PhishingDetectionAvailabilityService * Remove phishing detection availability in favor of one settings service * Extract phishing detection settings service abstraction to own file * Update phishing detection-settings service to include availability logic. Updated dependencies * Add test cases for phishing detection element. Added missing dependencies in testbed setup * Update services in extension * Switch checkbox to bit-switch component * Remove comment * Remove comment * Fix prettier vs lint spacing * Replace deprecated active user state. Updated test cases * Fix account-security test failing * Update comments * Renamed variable * Removed obsolete message * Remove unused variable * Removed unused import --------- Co-authored-by: Leslie Tilton <23057410+Banrion@users.noreply.github.com> Co-authored-by: Graham Walker <gwalker@bitwarden.com> Co-authored-by: Tom <144813356+ttalty@users.noreply.github.com> --- apps/browser/src/_locales/en/messages.json | 9 + .../settings/account-security.component.html | 14 ++ .../account-security.component.spec.ts | 145 ++++++++++--- .../settings/account-security.component.ts | 20 ++ .../browser/src/background/main.background.ts | 11 +- .../phishing-detection.service.spec.ts | 20 +- .../services/phishing-detection.service.ts | 28 +-- .../src/popup/services/services.module.ts | 14 ++ ...-detection-settings.service.abstraction.ts | 37 ++++ ...hishing-detection-settings.service.spec.ts | 203 ++++++++++++++++++ .../phishing-detection-settings.service.ts | 116 ++++++++++ 11 files changed, 555 insertions(+), 62 deletions(-) create mode 100644 libs/common/src/dirt/services/abstractions/phishing-detection-settings.service.abstraction.ts create mode 100644 libs/common/src/dirt/services/phishing-detection/phishing-detection-settings.service.spec.ts create mode 100644 libs/common/src/dirt/services/phishing-detection/phishing-detection-settings.service.ts diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index 36d69fb09f5..2ace8ff8b96 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -4801,6 +4801,15 @@ "accountSecurity": { "message": "Account security" }, + "phishingBlocker": { + "message": "Phishing Blocker" + }, + "enablePhishingDetection": { + "message": "Phishing detection" + }, + "enablePhishingDetectionDesc": { + "message": "Display warning before accessing suspected phishing sites" + }, "notifications": { "message": "Notifications" }, diff --git a/apps/browser/src/auth/popup/settings/account-security.component.html b/apps/browser/src/auth/popup/settings/account-security.component.html index b5d725b4a82..bb6b141c6c5 100644 --- a/apps/browser/src/auth/popup/settings/account-security.component.html +++ b/apps/browser/src/auth/popup/settings/account-security.component.html @@ -128,6 +128,20 @@ </bit-item> </bit-section> + <bit-section *ngIf="phishingDetectionAvailable$ | async"> + <bit-section-header> + <h2 bitTypography="h6">{{ "phishingBlocker" | i18n }}</h2> + </bit-section-header> + <bit-card> + <bit-switch formControlName="enablePhishingDetection" id="phishingDetectionAction"> + <bit-label for="phishingDetectionAction">{{ + "enablePhishingDetection" | i18n + }}</bit-label> + <bit-hint>{{ "enablePhishingDetectionDesc" | i18n }}</bit-hint> + </bit-switch> + </bit-card> + </bit-section> + <bit-section disableMargin> <bit-section-header> <h2 bitTypography="h6">{{ "otherOptions" | i18n }}</h2> diff --git a/apps/browser/src/auth/popup/settings/account-security.component.spec.ts b/apps/browser/src/auth/popup/settings/account-security.component.spec.ts index d0ab4793301..0f799fe7d4d 100644 --- a/apps/browser/src/auth/popup/settings/account-security.component.spec.ts +++ b/apps/browser/src/auth/popup/settings/account-security.component.spec.ts @@ -3,9 +3,10 @@ import { ComponentFixture, TestBed } from "@angular/core/testing"; import { By } from "@angular/platform-browser"; import { ActivatedRoute } from "@angular/router"; import { mock } from "jest-mock-extended"; -import { firstValueFrom, of } from "rxjs"; +import { firstValueFrom, of, BehaviorSubject } from "rxjs"; import { CollectionService } from "@bitwarden/admin-console/common"; +import { NudgesService } from "@bitwarden/angular/vault"; import { LockService } from "@bitwarden/auth/common"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; @@ -14,12 +15,15 @@ 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 { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; +import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions"; +import { PhishingDetectionSettingsServiceAbstraction } from "@bitwarden/common/dirt/services/abstractions/phishing-detection-settings.service.abstraction"; import { PinServiceAbstraction } from "@bitwarden/common/key-management/pin/pin.service.abstraction"; import { VaultTimeoutSettingsService, VaultTimeoutStringType, VaultTimeoutAction, } from "@bitwarden/common/key-management/vault-timeout"; +import { ProfileResponse } from "@bitwarden/common/models/response/profile.response"; 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"; @@ -27,12 +31,12 @@ 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 { MessageSender } from "@bitwarden/common/platform/messaging"; -import { Utils } from "@bitwarden/common/platform/misc/utils"; import { StateProvider } from "@bitwarden/common/platform/state"; import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec"; import { UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { DialogService, ToastService } from "@bitwarden/components"; +import { newGuid } from "@bitwarden/guid"; import { BiometricStateService, BiometricsService, KeyService } from "@bitwarden/key-management"; import { BrowserApi } from "../../../platform/browser/browser-api"; @@ -54,18 +58,27 @@ describe("AccountSecurityComponent", () => { let component: AccountSecurityComponent; let fixture: ComponentFixture<AccountSecurityComponent>; - const mockUserId = Utils.newGuid() as UserId; + const mockUserId = newGuid() as UserId; + const accountService: FakeAccountService = mockAccountServiceWith(mockUserId); - const vaultTimeoutSettingsService = mock<VaultTimeoutSettingsService>(); + const apiService = mock<ApiService>(); + const billingService = mock<BillingAccountProfileStateService>(); const biometricStateService = mock<BiometricStateService>(); - const policyService = mock<PolicyService>(); - const pinServiceAbstraction = mock<PinServiceAbstraction>(); - const keyService = mock<KeyService>(); - const validationService = mock<ValidationService>(); - const dialogService = mock<DialogService>(); - const platformUtilsService = mock<PlatformUtilsService>(); - const lockService = mock<LockService>(); const configService = mock<ConfigService>(); + const dialogService = mock<DialogService>(); + const keyService = mock<KeyService>(); + const lockService = mock<LockService>(); + const policyService = mock<PolicyService>(); + const phishingDetectionSettingsService = mock<PhishingDetectionSettingsServiceAbstraction>(); + const pinServiceAbstraction = mock<PinServiceAbstraction>(); + const platformUtilsService = mock<PlatformUtilsService>(); + const validationService = mock<ValidationService>(); + const vaultNudgesService = mock<NudgesService>(); + const vaultTimeoutSettingsService = mock<VaultTimeoutSettingsService>(); + + // Mock subjects to control the phishing detection observables + let phishingAvailableSubject: BehaviorSubject<boolean>; + let phishingEnabledSubject: BehaviorSubject<boolean>; beforeEach(async () => { await TestBed.configureTestingModule({ @@ -73,29 +86,38 @@ describe("AccountSecurityComponent", () => { { provide: AccountService, useValue: accountService }, { provide: AccountSecurityComponent, useValue: mock<AccountSecurityComponent>() }, { provide: ActivatedRoute, useValue: mock<ActivatedRoute>() }, + { provide: ApiService, useValue: apiService }, + { + provide: BillingAccountProfileStateService, + useValue: billingService, + }, { provide: BiometricsService, useValue: mock<BiometricsService>() }, { provide: BiometricStateService, useValue: biometricStateService }, + { provide: CipherService, useValue: mock<CipherService>() }, + { provide: CollectionService, useValue: mock<CollectionService>() }, + { provide: ConfigService, useValue: configService }, { provide: DialogService, useValue: dialogService }, { provide: EnvironmentService, useValue: mock<EnvironmentService>() }, { provide: I18nService, useValue: mock<I18nService>() }, - { provide: MessageSender, useValue: mock<MessageSender>() }, { provide: KeyService, useValue: keyService }, + { provide: LockService, useValue: lockService }, + { provide: LogService, useValue: mock<LogService>() }, + { provide: MessageSender, useValue: mock<MessageSender>() }, + { provide: NudgesService, useValue: vaultNudgesService }, + { provide: OrganizationService, useValue: mock<OrganizationService>() }, { provide: PinServiceAbstraction, useValue: pinServiceAbstraction }, + { + provide: PhishingDetectionSettingsServiceAbstraction, + useValue: phishingDetectionSettingsService, + }, { provide: PlatformUtilsService, useValue: platformUtilsService }, { provide: PolicyService, useValue: policyService }, { provide: PopupRouterCacheService, useValue: mock<PopupRouterCacheService>() }, + { provide: StateProvider, useValue: mock<StateProvider>() }, { provide: ToastService, useValue: mock<ToastService>() }, { provide: UserVerificationService, useValue: mock<UserVerificationService>() }, - { provide: VaultTimeoutSettingsService, useValue: vaultTimeoutSettingsService }, - { provide: StateProvider, useValue: mock<StateProvider>() }, - { provide: CipherService, useValue: mock<CipherService>() }, - { provide: ApiService, useValue: mock<ApiService>() }, - { provide: LogService, useValue: mock<LogService>() }, - { provide: OrganizationService, useValue: mock<OrganizationService>() }, - { provide: CollectionService, useValue: mock<CollectionService>() }, { provide: ValidationService, useValue: validationService }, - { provide: LockService, useValue: lockService }, - { provide: ConfigService, useValue: configService }, + { provide: VaultTimeoutSettingsService, useValue: vaultTimeoutSettingsService }, ], }) .overrideComponent(AccountSecurityComponent, { @@ -110,10 +132,13 @@ describe("AccountSecurityComponent", () => { }) .compileComponents(); - fixture = TestBed.createComponent(AccountSecurityComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - + apiService.getProfile.mockResolvedValue( + mock<ProfileResponse>({ + id: mockUserId, + creationDate: new Date().toISOString(), + }), + ); + vaultNudgesService.showNudgeSpotlight$.mockReturnValue(of(false)); vaultTimeoutSettingsService.getVaultTimeoutByUserId$.mockReturnValue( of(VaultTimeoutStringType.OnLocked), ); @@ -123,8 +148,25 @@ describe("AccountSecurityComponent", () => { vaultTimeoutSettingsService.getVaultTimeoutActionByUserId$.mockReturnValue( of(VaultTimeoutAction.Lock), ); + vaultTimeoutSettingsService.availableVaultTimeoutActions$.mockReturnValue(of([])); biometricStateService.promptAutomatically$ = of(false); pinServiceAbstraction.isPinSet.mockResolvedValue(false); + configService.getFeatureFlag$.mockReturnValue(of(false)); + billingService.hasPremiumPersonally$.mockReturnValue(of(true)); + + policyService.policiesByType$.mockReturnValue(of([null])); + + // Mock readonly observables for phishing detection using BehaviorSubjects so + // tests can push different values after component creation. + phishingAvailableSubject = new BehaviorSubject<boolean>(true); + phishingEnabledSubject = new BehaviorSubject<boolean>(true); + + (phishingDetectionSettingsService.available$ as any) = phishingAvailableSubject.asObservable(); + (phishingDetectionSettingsService.enabled$ as any) = phishingEnabledSubject.asObservable(); + + fixture = TestBed.createComponent(AccountSecurityComponent); + component = fixture.componentInstance; + fixture.detectChanges(); }); afterEach(() => { @@ -233,6 +275,59 @@ describe("AccountSecurityComponent", () => { expect(pinInputElement).toBeNull(); }); + describe("phishing detection UI and setting", () => { + it("updates phishing detection setting when form value changes", async () => { + policyService.policiesByType$.mockReturnValue(of([null])); + + phishingAvailableSubject.next(true); + phishingEnabledSubject.next(true); + + // Init component + await component.ngOnInit(); + fixture.detectChanges(); + + // Initial form value should match enabled$ observable defaulting to true + expect(component.form.controls.enablePhishingDetection.value).toBe(true); + + // Change the form value to false + component.form.controls.enablePhishingDetection.setValue(false); + fixture.detectChanges(); + // Wait briefly to allow any debounced or async valueChanges handlers to run + // fixture.whenStable() does not work here + await new Promise((resolve) => setTimeout(resolve, 0)); + + expect(phishingDetectionSettingsService.setEnabled).toHaveBeenCalledWith(mockUserId, false); + }); + + it("shows phishing detection element when available$ is true", async () => { + policyService.policiesByType$.mockReturnValue(of([null])); + phishingAvailableSubject.next(true); + phishingEnabledSubject.next(true); + + await component.ngOnInit(); + fixture.detectChanges(); + + const phishingDetectionElement = fixture.debugElement.query( + By.css("#phishingDetectionAction"), + ); + expect(phishingDetectionElement).not.toBeNull(); + }); + + it("hides phishing detection element when available$ is false", async () => { + policyService.policiesByType$.mockReturnValue(of([null])); + phishingAvailableSubject.next(false); + phishingEnabledSubject.next(true); + + await component.ngOnInit(); + fixture.detectChanges(); + + const phishingDetectionElement = fixture.debugElement.query( + By.css("#phishingDetectionAction"), + ); + expect(phishingDetectionElement).toBeNull(); + }); + }); + describe("updateBiometric", () => { let browserApiSpy: jest.SpyInstance; diff --git a/apps/browser/src/auth/popup/settings/account-security.component.ts b/apps/browser/src/auth/popup/settings/account-security.component.ts index 4ff29c8853e..7c36754c894 100644 --- a/apps/browser/src/auth/popup/settings/account-security.component.ts +++ b/apps/browser/src/auth/popup/settings/account-security.component.ts @@ -32,6 +32,7 @@ import { getFirstPolicy } from "@bitwarden/common/admin-console/services/policy/ import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { getUserId } from "@bitwarden/common/auth/services/account.service"; +import { PhishingDetectionSettingsServiceAbstraction } from "@bitwarden/common/dirt/services/abstractions/phishing-detection-settings.service.abstraction"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { PinServiceAbstraction } from "@bitwarden/common/key-management/pin/pin.service.abstraction"; import { @@ -62,6 +63,7 @@ import { SelectModule, TypographyModule, ToastService, + SwitchComponent, } from "@bitwarden/components"; import { KeyService, @@ -110,6 +112,7 @@ import { AwaitDesktopDialogComponent } from "./await-desktop-dialog.component"; SpotlightComponent, TypographyModule, SessionTimeoutInputLegacyComponent, + SwitchComponent, ], }) export class AccountSecurityComponent implements OnInit, OnDestroy { @@ -130,6 +133,7 @@ export class AccountSecurityComponent implements OnInit, OnDestroy { pinLockWithMasterPassword: false, biometric: false, enableAutoBiometricsPrompt: true, + enablePhishingDetection: true, }); protected showAccountSecurityNudge$: Observable<boolean> = @@ -141,6 +145,7 @@ export class AccountSecurityComponent implements OnInit, OnDestroy { ); protected readonly consolidatedSessionTimeoutComponent$: Observable<boolean>; + protected readonly phishingDetectionAvailable$: Observable<boolean>; protected refreshTimeoutSettings$ = new BehaviorSubject<void>(undefined); private destroy$ = new Subject<void>(); @@ -167,10 +172,14 @@ export class AccountSecurityComponent implements OnInit, OnDestroy { private vaultNudgesService: NudgesService, private validationService: ValidationService, private logService: LogService, + private phishingDetectionSettingsService: PhishingDetectionSettingsServiceAbstraction, ) { this.consolidatedSessionTimeoutComponent$ = this.configService.getFeatureFlag$( FeatureFlag.ConsolidatedSessionTimeoutComponent, ); + + // Check if user phishing detection available + this.phishingDetectionAvailable$ = this.phishingDetectionSettingsService.available$; } async ngOnInit() { @@ -251,6 +260,7 @@ export class AccountSecurityComponent implements OnInit, OnDestroy { enableAutoBiometricsPrompt: await firstValueFrom( this.biometricStateService.promptAutomatically$, ), + enablePhishingDetection: await firstValueFrom(this.phishingDetectionSettingsService.enabled$), }; this.form.patchValue(initialValues, { emitEvent: false }); @@ -361,6 +371,16 @@ export class AccountSecurityComponent implements OnInit, OnDestroy { ) .subscribe(); + this.form.controls.enablePhishingDetection.valueChanges + .pipe( + concatMap(async (enabled) => { + const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + await this.phishingDetectionSettingsService.setEnabled(userId, enabled); + }), + takeUntil(this.destroy$), + ) + .subscribe(); + this.refreshTimeoutSettings$ .pipe( switchMap(() => diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index 2540571abb0..7b509380f6d 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -82,7 +82,9 @@ import { import { isUrlInList } from "@bitwarden/common/autofill/utils"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { DefaultBillingAccountProfileStateService } from "@bitwarden/common/billing/services/account/billing-account-profile-state.service"; +import { PhishingDetectionSettingsServiceAbstraction } from "@bitwarden/common/dirt/services/abstractions/phishing-detection-settings.service.abstraction"; import { HibpApiService } from "@bitwarden/common/dirt/services/hibp-api.service"; +import { PhishingDetectionSettingsService } from "@bitwarden/common/dirt/services/phishing-detection/phishing-detection-settings.service"; import { ClientType } from "@bitwarden/common/enums"; import { ProcessReloadServiceAbstraction } from "@bitwarden/common/key-management/abstractions/process-reload.service"; import { @@ -497,6 +499,7 @@ export default class MainBackground { // DIRT private phishingDataService: PhishingDataService; + private phishingDetectionSettingsService: PhishingDetectionSettingsServiceAbstraction; constructor() { const logoutCallback = async (logoutReason: LogoutReason, userId?: UserId) => @@ -1475,12 +1478,18 @@ export default class MainBackground { this.platformUtilsService, ); - PhishingDetectionService.initialize( + this.phishingDetectionSettingsService = new PhishingDetectionSettingsService( this.accountService, this.billingAccountProfileStateService, this.configService, + this.organizationService, + this.stateProvider, + ); + + PhishingDetectionService.initialize( this.logService, this.phishingDataService, + this.phishingDetectionSettingsService, messageListener, ); 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 e33b4b1b4f1..ceb18bd1573 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 @@ -1,9 +1,7 @@ import { mock, MockProxy } from "jest-mock-extended"; import { Observable, of } from "rxjs"; -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 { PhishingDetectionSettingsServiceAbstraction } from "@bitwarden/common/dirt/services/abstractions/phishing-detection-settings.service.abstraction"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { MessageListener } from "@bitwarden/messaging"; @@ -11,17 +9,12 @@ import { PhishingDataService } from "./phishing-data.service"; import { PhishingDetectionService } from "./phishing-detection.service"; describe("PhishingDetectionService", () => { - let accountService: AccountService; - let billingAccountProfileStateService: BillingAccountProfileStateService; - let configService: ConfigService; let logService: LogService; let phishingDataService: MockProxy<PhishingDataService>; let messageListener: MockProxy<MessageListener>; + let phishingDetectionSettingsService: MockProxy<PhishingDetectionSettingsServiceAbstraction>; beforeEach(() => { - accountService = { getAccount$: jest.fn(() => of(null)) } as any; - billingAccountProfileStateService = {} as any; - configService = { getFeatureFlag$: jest.fn(() => of(false)) } as any; logService = { info: jest.fn(), debug: jest.fn(), warning: jest.fn(), error: jest.fn() } as any; phishingDataService = mock(); messageListener = mock<MessageListener>({ @@ -29,16 +22,17 @@ describe("PhishingDetectionService", () => { return new Observable(); }, }); + phishingDetectionSettingsService = mock<PhishingDetectionSettingsServiceAbstraction>({ + on$: of(true), + }); }); it("should initialize without errors", () => { expect(() => { PhishingDetectionService.initialize( - accountService, - billingAccountProfileStateService, - configService, logService, phishingDataService, + phishingDetectionSettingsService, messageListener, ); }).not.toThrow(); @@ -61,6 +55,7 @@ describe("PhishingDetectionService", () => { // logService, // phishingDataService, // messageListener, + // phishingDetectionSettingsService, // ); // }); @@ -81,6 +76,7 @@ describe("PhishingDetectionService", () => { // logService, // phishingDataService, // messageListener, + // phishingDetectionSettingsService, // ); // }); }); 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 4917e740be8..e04d08559ab 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,21 +1,16 @@ import { - combineLatest, concatMap, distinctUntilChanged, EMPTY, filter, map, merge, - of, Subject, switchMap, tap, } from "rxjs"; -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 { PhishingDetectionSettingsServiceAbstraction } from "@bitwarden/common/dirt/services/abstractions/phishing-detection-settings.service.abstraction"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { CommandDefinition, MessageListener } from "@bitwarden/messaging"; @@ -50,11 +45,9 @@ export class PhishingDetectionService { private static _didInit = false; static initialize( - accountService: AccountService, - billingAccountProfileStateService: BillingAccountProfileStateService, - configService: ConfigService, logService: LogService, phishingDataService: PhishingDataService, + phishingDetectionSettingsService: PhishingDetectionSettingsServiceAbstraction, messageListener: MessageListener, ) { if (this._didInit) { @@ -118,22 +111,9 @@ export class PhishingDetectionService { .messages$(PHISHING_DETECTION_CANCEL_COMMAND) .pipe(switchMap((message) => BrowserApi.closeTab(message.tabId))); - const activeAccountHasAccess$ = combineLatest([ - accountService.activeAccount$, - configService.getFeatureFlag$(FeatureFlag.PhishingDetection), - ]).pipe( - switchMap(([account, featureEnabled]) => { - if (!account) { - logService.debug("[PhishingDetectionService] No active account."); - return of(false); - } - return billingAccountProfileStateService - .hasPremiumFromAnySource$(account.id) - .pipe(map((hasPremium) => hasPremium && featureEnabled)); - }), - ); + const phishingDetectionActive$ = phishingDetectionSettingsService.on$; - const initSub = activeAccountHasAccess$ + const initSub = phishingDetectionActive$ .pipe( distinctUntilChanged(), switchMap((activeUserHasAccess) => { diff --git a/apps/browser/src/popup/services/services.module.ts b/apps/browser/src/popup/services/services.module.ts index bb89eff1147..39c53b7da56 100644 --- a/apps/browser/src/popup/services/services.module.ts +++ b/apps/browser/src/popup/services/services.module.ts @@ -41,6 +41,7 @@ import { 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 { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { AccountService, @@ -67,6 +68,8 @@ import { UserNotificationSettingsServiceAbstraction, } from "@bitwarden/common/autofill/services/user-notification-settings.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; +import { PhishingDetectionSettingsServiceAbstraction } from "@bitwarden/common/dirt/services/abstractions/phishing-detection-settings.service.abstraction"; +import { PhishingDetectionSettingsService } from "@bitwarden/common/dirt/services/phishing-detection/phishing-detection-settings.service"; import { ClientType } from "@bitwarden/common/enums"; import { KeyGenerationService } from "@bitwarden/common/key-management/crypto"; import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service"; @@ -512,6 +515,17 @@ const safeProviders: SafeProvider[] = [ useClass: UserNotificationSettingsService, deps: [StateProvider], }), + safeProvider({ + provide: PhishingDetectionSettingsServiceAbstraction, + useClass: PhishingDetectionSettingsService, + deps: [ + AccountService, + BillingAccountProfileStateService, + ConfigService, + OrganizationService, + StateProvider, + ], + }), safeProvider({ provide: MessageListener, useFactory: (subject: Subject<Message<Record<string, unknown>>>, ngZone: NgZone) => diff --git a/libs/common/src/dirt/services/abstractions/phishing-detection-settings.service.abstraction.ts b/libs/common/src/dirt/services/abstractions/phishing-detection-settings.service.abstraction.ts new file mode 100644 index 00000000000..6c915c2dcbe --- /dev/null +++ b/libs/common/src/dirt/services/abstractions/phishing-detection-settings.service.abstraction.ts @@ -0,0 +1,37 @@ +import { Observable } from "rxjs"; + +import { UserId } from "@bitwarden/user-core"; + +/** + * Abstraction for phishing detection settings + */ +export abstract class PhishingDetectionSettingsServiceAbstraction { + /** + * An observable for whether phishing detection is available for the active user account. + * + * Access is granted only when the PhishingDetection feature flag is enabled and + * at least one of the following is true for the active account: + * - the user has a personal premium subscription + * - the user is a member of a Family org (ProductTierType.Families) + * - the user is a member of an Enterprise org with `usePhishingBlocker` enabled + * + * Note: Non-specified organization types (e.g., Team orgs) do not grant access. + */ + abstract readonly available$: Observable<boolean>; + /** + * An observable for whether phishing detection is on for the active user account + * + * This is true when {@link available$} is true and when {@link enabled$} is true + */ + abstract readonly on$: Observable<boolean>; + /** + * An observable for whether phishing detection is enabled + */ + abstract readonly enabled$: Observable<boolean>; + /** + * Sets whether phishing detection is enabled + * + * @param enabled True to enable, false to disable + */ + abstract setEnabled: (userId: UserId, enabled: boolean) => Promise<void>; +} diff --git a/libs/common/src/dirt/services/phishing-detection/phishing-detection-settings.service.spec.ts b/libs/common/src/dirt/services/phishing-detection/phishing-detection-settings.service.spec.ts new file mode 100644 index 00000000000..23e311d9445 --- /dev/null +++ b/libs/common/src/dirt/services/phishing-detection/phishing-detection-settings.service.spec.ts @@ -0,0 +1,203 @@ +import { mock, MockProxy } from "jest-mock-extended"; +import { BehaviorSubject, firstValueFrom, Subject } from "rxjs"; + +import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { Account, AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions"; +import { ProductTierType } from "@bitwarden/common/billing/enums"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; + +import { FakeAccountService, FakeStateProvider, mockAccountServiceWith } from "../../../../spec"; +import { UserId } from "../../../types/guid"; + +import { PhishingDetectionSettingsService } from "./phishing-detection-settings.service"; + +describe("PhishingDetectionSettingsService", () => { + // Mock services + let mockAccountService: MockProxy<AccountService>; + let mockBillingService: MockProxy<BillingAccountProfileStateService>; + let mockConfigService: MockProxy<ConfigService>; + let mockOrganizationService: MockProxy<OrganizationService>; + + // RxJS Subjects we control in the tests + let activeAccountSubject: BehaviorSubject<Account | null>; + let featureFlagSubject: BehaviorSubject<boolean>; + let premiumStatusSubject: BehaviorSubject<boolean>; + let organizationsSubject: BehaviorSubject<Organization[]>; + + let service: PhishingDetectionSettingsService; + let stateProvider: FakeStateProvider; + + // Constant mock data + const familyOrg = mock<Organization>({ + canAccess: true, + isMember: true, + usersGetPremium: true, + productTierType: ProductTierType.Families, + usePhishingBlocker: true, + }); + const teamOrg = mock<Organization>({ + canAccess: true, + isMember: true, + usersGetPremium: true, + productTierType: ProductTierType.Teams, + usePhishingBlocker: true, + }); + const enterpriseOrg = mock<Organization>({ + canAccess: true, + isMember: true, + usersGetPremium: true, + productTierType: ProductTierType.Enterprise, + usePhishingBlocker: true, + }); + + const mockUserId = "mock-user-id" as UserId; + const account = mock<Account>({ id: mockUserId }); + const accountService: FakeAccountService = mockAccountServiceWith(mockUserId); + + beforeEach(() => { + // Initialize subjects + activeAccountSubject = new BehaviorSubject<Account | null>(null); + featureFlagSubject = new BehaviorSubject<boolean>(false); + premiumStatusSubject = new BehaviorSubject<boolean>(false); + organizationsSubject = new BehaviorSubject<Organization[]>([]); + + // Default implementations for required functions + mockAccountService = mock<AccountService>(); + mockAccountService.activeAccount$ = activeAccountSubject.asObservable(); + + mockBillingService = mock<BillingAccountProfileStateService>(); + mockBillingService.hasPremiumPersonally$.mockReturnValue(premiumStatusSubject.asObservable()); + + mockConfigService = mock<ConfigService>(); + mockConfigService.getFeatureFlag$.mockReturnValue(featureFlagSubject.asObservable()); + + mockOrganizationService = mock<OrganizationService>(); + mockOrganizationService.organizations$.mockReturnValue(organizationsSubject.asObservable()); + + stateProvider = new FakeStateProvider(accountService); + service = new PhishingDetectionSettingsService( + mockAccountService, + mockBillingService, + mockConfigService, + mockOrganizationService, + stateProvider, + ); + }); + + // Helper to easily get the result of the observable we are testing + const getAccess = () => firstValueFrom(service.available$); + + describe("enabled$", () => { + it("should default to true if an account is logged in", async () => { + activeAccountSubject.next(account); + const result = await firstValueFrom(service.enabled$); + expect(result).toBe(true); + }); + + it("should return the stored value", async () => { + activeAccountSubject.next(account); + + await service.setEnabled(mockUserId, false); + const resultDisabled = await firstValueFrom(service.enabled$); + expect(resultDisabled).toBe(false); + + await service.setEnabled(mockUserId, true); + const resultEnabled = await firstValueFrom(service.enabled$); + expect(resultEnabled).toBe(true); + }); + }); + + describe("setEnabled", () => { + it("should update the stored value", async () => { + activeAccountSubject.next(account); + await service.setEnabled(mockUserId, false); + let result = await firstValueFrom(service.enabled$); + expect(result).toBe(false); + + await service.setEnabled(mockUserId, true); + result = await firstValueFrom(service.enabled$); + expect(result).toBe(true); + }); + }); + + it("returns false immediately when the feature flag is disabled, regardless of other conditions", async () => { + activeAccountSubject.next(account); + premiumStatusSubject.next(true); + organizationsSubject.next([familyOrg]); + + featureFlagSubject.next(false); + + await expect(getAccess()).resolves.toBe(false); + }); + + it("returns false if there is no active account present yet", async () => { + activeAccountSubject.next(null); // No active account + featureFlagSubject.next(true); // Flag is on + + await expect(getAccess()).resolves.toBe(false); + }); + + it("returns true when feature flag is enabled and user has premium personally", async () => { + activeAccountSubject.next(account); + featureFlagSubject.next(true); + organizationsSubject.next([]); + premiumStatusSubject.next(true); + + await expect(getAccess()).resolves.toBe(true); + }); + + it("returns true when feature flag is enabled and user is in a Family Organization", async () => { + activeAccountSubject.next(account); + featureFlagSubject.next(true); + premiumStatusSubject.next(false); // User has no personal premium + + organizationsSubject.next([familyOrg]); + + await expect(getAccess()).resolves.toBe(true); + }); + + it("returns true when feature flag is enabled and user is in an Enterprise org with phishing blocker enabled", async () => { + activeAccountSubject.next(account); + featureFlagSubject.next(true); + premiumStatusSubject.next(false); + organizationsSubject.next([enterpriseOrg]); + + await expect(getAccess()).resolves.toBe(true); + }); + + it("returns false when user has no access through personal premium or organizations", async () => { + activeAccountSubject.next(account); + featureFlagSubject.next(true); + premiumStatusSubject.next(false); + organizationsSubject.next([teamOrg]); // Team org does not give access + + await expect(getAccess()).resolves.toBe(false); + }); + + it("shares/caches the available$ result between multiple subscribers", async () => { + // Use a plain Subject for this test so we control when the premium observable emits + // and avoid the BehaviorSubject's initial emission which can race with subscriptions. + // Provide the Subject directly as the mock return value for the billing service + const oneTimePremium = new Subject<boolean>(); + mockBillingService.hasPremiumPersonally$.mockReturnValueOnce(oneTimePremium.asObservable()); + + activeAccountSubject.next(account); + featureFlagSubject.next(true); + organizationsSubject.next([]); + + const p1 = firstValueFrom(service.available$); + const p2 = firstValueFrom(service.available$); + + // Trigger the pipeline + oneTimePremium.next(true); + + const [first, second] = await Promise.all([p1, p2]); + + expect(first).toBe(true); + expect(second).toBe(true); + // The billing function should have been called at most once due to caching + expect(mockBillingService.hasPremiumPersonally$).toHaveBeenCalledTimes(1); + }); +}); diff --git a/libs/common/src/dirt/services/phishing-detection/phishing-detection-settings.service.ts b/libs/common/src/dirt/services/phishing-detection/phishing-detection-settings.service.ts new file mode 100644 index 00000000000..36d50f60de7 --- /dev/null +++ b/libs/common/src/dirt/services/phishing-detection/phishing-detection-settings.service.ts @@ -0,0 +1,116 @@ +import { combineLatest, Observable, of, switchMap } from "rxjs"; +import { catchError, distinctUntilChanged, map, shareReplay } from "rxjs/operators"; + +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"; +import { ProductTierType } from "@bitwarden/common/billing/enums"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; +import { UserId } from "@bitwarden/user-core"; + +import { PHISHING_DETECTION_DISK, StateProvider, UserKeyDefinition } from "../../../platform/state"; +import { PhishingDetectionSettingsServiceAbstraction } from "../abstractions/phishing-detection-settings.service.abstraction"; + +const ENABLE_PHISHING_DETECTION = new UserKeyDefinition( + PHISHING_DETECTION_DISK, + "enablePhishingDetection", + { + deserializer: (value: boolean) => value ?? true, // Default: enabled + clearOn: [], + }, +); + +export class PhishingDetectionSettingsService implements PhishingDetectionSettingsServiceAbstraction { + readonly available$: Observable<boolean>; + readonly enabled$: Observable<boolean>; + readonly on$: Observable<boolean>; + + constructor( + private accountService: AccountService, + private billingService: BillingAccountProfileStateService, + private configService: ConfigService, + private organizationService: OrganizationService, + private stateProvider: StateProvider, + ) { + this.available$ = this.buildAvailablePipeline$().pipe( + distinctUntilChanged(), + shareReplay({ bufferSize: 1, refCount: true }), + ); + this.enabled$ = this.buildEnabledPipeline$().pipe( + distinctUntilChanged(), + shareReplay({ bufferSize: 1, refCount: true }), + ); + + this.on$ = combineLatest([this.available$, this.enabled$]).pipe( + map(([available, enabled]) => available && enabled), + distinctUntilChanged(), + shareReplay({ bufferSize: 1, refCount: true }), + ); + } + + async setEnabled(userId: UserId, enabled: boolean): Promise<void> { + await this.stateProvider.getUser(userId, ENABLE_PHISHING_DETECTION).update(() => enabled); + } + + /** + * Builds the observable pipeline to determine if phishing detection is available to the user + * + * @returns An observable pipeline that determines if phishing detection is available + */ + private buildAvailablePipeline$(): Observable<boolean> { + return combineLatest([ + this.accountService.activeAccount$, + this.configService.getFeatureFlag$(FeatureFlag.PhishingDetection), + ]).pipe( + switchMap(([account, featureEnabled]) => { + if (!account || !featureEnabled) { + return of(false); + } + return combineLatest([ + this.billingService.hasPremiumPersonally$(account.id).pipe(catchError(() => of(false))), + this.organizationService.organizations$(account.id).pipe(catchError(() => of([]))), + ]).pipe( + map(([hasPremium, organizations]) => hasPremium || this.orgGrantsAccess(organizations)), + catchError(() => of(false)), + ); + }), + ); + } + + /** + * Builds the observable pipeline to determine if phishing detection is enabled by the user + * + * @returns True if phishing detection is enabled for the active user + */ + private buildEnabledPipeline$(): Observable<boolean> { + return this.accountService.activeAccount$.pipe( + switchMap((account) => { + if (!account) { + return of(false); + } + return this.stateProvider.getUserState$(ENABLE_PHISHING_DETECTION, account.id); + }), + map((enabled) => enabled ?? true), + ); + } + + /** + * Determines if any of the user's organizations grant access to phishing detection + * + * @param organizations The organizations the user is a member of + * @returns True if any organization grants access to phishing detection + */ + private orgGrantsAccess(organizations: Organization[]): boolean { + return organizations.some((org) => { + if (!org.canAccess || !org.isMember || !org.usersGetPremium) { + return false; + } + return ( + org.productTierType === ProductTierType.Families || + (org.productTierType === ProductTierType.Enterprise && org.usePhishingBlocker) + ); + }); + } +} From 3d06668497af7eaa662972a982686a090f4b959e Mon Sep 17 00:00:00 2001 From: Vijay Oommen <voommen@livefront.com> Date: Mon, 15 Dec 2025 10:30:22 -0600 Subject: [PATCH 072/188] [PM-28450] Single integration service (#17925) --- .../configuration/datadog-configuration.ts | 11 +- .../models/configuration/hec-configuration.ts | 11 +- .../configuration/webhook-configuration.ts | 9 +- .../models/integration-builder.ts | 94 +++ .../datadog-template.ts | 11 +- .../configuration-template/hec-template.ts | 11 +- .../webhook-template.ts | 9 +- .../organization-integration-configuration.ts | 12 +- .../organization-integration-service-type.ts | 6 +- .../models/organization-integration.ts | 16 +- ...g-organization-integration-service.spec.ts | 184 ----- ...atadog-organization-integration-service.ts | 350 ---------- ...c-organization-integration-service.spec.ts | 201 ------ .../hec-organization-integration-service.ts | 353 ---------- ...ganization-integration-api.service.spec.ts | 6 +- .../organization-integration-service.spec.ts | 633 ++++++++++++++++++ .../organization-integration-service.ts | 313 +++++++++ .../integration-card.component.spec.ts | 109 +-- .../integration-card.component.ts | 75 ++- .../integration-grid.component.spec.ts | 9 +- .../integrations.component.html | 137 ++-- .../integrations.component.ts | 54 +- .../organization-integrations.module.ts | 12 +- .../integrations.component.spec.ts | 9 +- .../integrations/integrations.module.ts | 12 +- 25 files changed, 1305 insertions(+), 1342 deletions(-) create mode 100644 bitwarden_license/bit-common/src/dirt/organization-integrations/models/integration-builder.ts delete mode 100644 bitwarden_license/bit-common/src/dirt/organization-integrations/services/datadog-organization-integration-service.spec.ts delete mode 100644 bitwarden_license/bit-common/src/dirt/organization-integrations/services/datadog-organization-integration-service.ts delete mode 100644 bitwarden_license/bit-common/src/dirt/organization-integrations/services/hec-organization-integration-service.spec.ts delete mode 100644 bitwarden_license/bit-common/src/dirt/organization-integrations/services/hec-organization-integration-service.ts create mode 100644 bitwarden_license/bit-common/src/dirt/organization-integrations/services/organization-integration-service.spec.ts create mode 100644 bitwarden_license/bit-common/src/dirt/organization-integrations/services/organization-integration-service.ts 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 index e788ebba7f2..51217a85877 100644 --- 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 @@ -1,14 +1,15 @@ -import { OrganizationIntegrationServiceType } from "../organization-integration-service-type"; +import { OrgIntegrationConfiguration } from "../integration-builder"; +import { OrganizationIntegrationServiceName } from "../organization-integration-service-type"; -export class DatadogConfiguration { +export class DatadogConfiguration implements OrgIntegrationConfiguration { uri: string; apiKey: string; - service: OrganizationIntegrationServiceType; + service: OrganizationIntegrationServiceName; - constructor(uri: string, apiKey: string, service: string) { + constructor(uri: string, apiKey: string, service: OrganizationIntegrationServiceName) { this.uri = uri; this.apiKey = apiKey; - this.service = service as OrganizationIntegrationServiceType; + this.service = service; } toString(): string { diff --git a/bitwarden_license/bit-common/src/dirt/organization-integrations/models/configuration/hec-configuration.ts b/bitwarden_license/bit-common/src/dirt/organization-integrations/models/configuration/hec-configuration.ts index cdb7a5f265a..d7e0cec1840 100644 --- a/bitwarden_license/bit-common/src/dirt/organization-integrations/models/configuration/hec-configuration.ts +++ b/bitwarden_license/bit-common/src/dirt/organization-integrations/models/configuration/hec-configuration.ts @@ -1,15 +1,16 @@ -import { OrganizationIntegrationServiceType } from "../organization-integration-service-type"; +import { OrgIntegrationConfiguration } from "../integration-builder"; +import { OrganizationIntegrationServiceName } from "../organization-integration-service-type"; -export class HecConfiguration { +export class HecConfiguration implements OrgIntegrationConfiguration { uri: string; scheme = "Bearer"; token: string; - service: OrganizationIntegrationServiceType; + service: OrganizationIntegrationServiceName; - constructor(uri: string, token: string, service: string) { + constructor(uri: string, token: string, service: OrganizationIntegrationServiceName) { this.uri = uri; this.token = token; - this.service = service as OrganizationIntegrationServiceType; + this.service = service; } toString(): string { diff --git a/bitwarden_license/bit-common/src/dirt/organization-integrations/models/configuration/webhook-configuration.ts b/bitwarden_license/bit-common/src/dirt/organization-integrations/models/configuration/webhook-configuration.ts index a4dca7378ba..2b9ed6f7bda 100644 --- a/bitwarden_license/bit-common/src/dirt/organization-integrations/models/configuration/webhook-configuration.ts +++ b/bitwarden_license/bit-common/src/dirt/organization-integrations/models/configuration/webhook-configuration.ts @@ -1,11 +1,16 @@ +import { OrgIntegrationConfiguration } from "../integration-builder"; +import { OrganizationIntegrationServiceName } from "../organization-integration-service-type"; + // Added to reflect how future webhook integrations could be structured within the OrganizationIntegration -export class WebhookConfiguration { +export class WebhookConfiguration implements OrgIntegrationConfiguration { propA: string; propB: string; + service: OrganizationIntegrationServiceName; - constructor(propA: string, propB: string) { + constructor(propA: string, propB: string, service: OrganizationIntegrationServiceName) { this.propA = propA; this.propB = propB; + this.service = service; } toString(): string { diff --git a/bitwarden_license/bit-common/src/dirt/organization-integrations/models/integration-builder.ts b/bitwarden_license/bit-common/src/dirt/organization-integrations/models/integration-builder.ts new file mode 100644 index 00000000000..ae790a67408 --- /dev/null +++ b/bitwarden_license/bit-common/src/dirt/organization-integrations/models/integration-builder.ts @@ -0,0 +1,94 @@ +import { DatadogConfiguration } from "./configuration/datadog-configuration"; +import { HecConfiguration } from "./configuration/hec-configuration"; +import { DatadogTemplate } from "./integration-configuration-config/configuration-template/datadog-template"; +import { HecTemplate } from "./integration-configuration-config/configuration-template/hec-template"; +import { OrganizationIntegrationServiceName } from "./organization-integration-service-type"; +import { OrganizationIntegrationType } from "./organization-integration-type"; + +/** + * Defines the structure for organization integration configuration + */ +export interface OrgIntegrationConfiguration { + service: OrganizationIntegrationServiceName; + toString(): string; +} + +/** + * Defines the structure for organization integration template + */ +export interface OrgIntegrationTemplate { + service: OrganizationIntegrationServiceName; + toString(): string; +} + +/** + * Builder class for creating organization integration configurations and templates + */ +export class OrgIntegrationBuilder { + static buildHecConfiguration( + uri: string, + token: string, + service: OrganizationIntegrationServiceName, + ): OrgIntegrationConfiguration { + return new HecConfiguration(uri, token, service); + } + + static buildHecTemplate( + index: string, + service: OrganizationIntegrationServiceName, + ): OrgIntegrationTemplate { + return new HecTemplate(index, service); + } + + static buildDataDogConfiguration(uri: string, apiKey: string): OrgIntegrationConfiguration { + return new DatadogConfiguration(uri, apiKey, OrganizationIntegrationServiceName.Datadog); + } + + static buildDataDogTemplate(service: OrganizationIntegrationServiceName): OrgIntegrationTemplate { + return new DatadogTemplate(service); + } + + static buildConfiguration( + type: OrganizationIntegrationType, + configuration: string, + ): OrgIntegrationConfiguration { + switch (type) { + case OrganizationIntegrationType.Hec: { + const hecConfig = this.convertToJson<HecConfiguration>(configuration); + return this.buildHecConfiguration(hecConfig.uri, hecConfig.token, hecConfig.service); + } + case OrganizationIntegrationType.Datadog: { + const datadogConfig = this.convertToJson<DatadogConfiguration>(configuration); + return this.buildDataDogConfiguration(datadogConfig.uri, datadogConfig.apiKey); + } + default: + throw new Error(`Unsupported integration type: ${type}`); + } + } + + static buildTemplate( + type: OrganizationIntegrationType, + template: string, + ): OrgIntegrationTemplate { + switch (type) { + case OrganizationIntegrationType.Hec: { + const hecTemplate = this.convertToJson<HecTemplate>(template); + return this.buildHecTemplate(hecTemplate.index, hecTemplate.service); + } + case OrganizationIntegrationType.Datadog: { + const datadogTemplate = this.convertToJson<DatadogTemplate>(template); + return this.buildDataDogTemplate(datadogTemplate.service); + } + default: + throw new Error(`Unsupported integration type: ${type}`); + } + } + + private static convertToJson<T>(jsonString?: string): T { + try { + return JSON.parse(jsonString || "{}") as T; + } catch { + throw new Error("Invalid integration configuration: JSON parse error"); + } + } +} 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 index 9aa6e34f478..d8e168aacbe 100644 --- 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 @@ -1,14 +1,15 @@ -import { OrganizationIntegrationServiceType } from "../../organization-integration-service-type"; +import { OrgIntegrationTemplate } from "../../integration-builder"; +import { OrganizationIntegrationServiceName } from "../../organization-integration-service-type"; -export class DatadogTemplate { +export class DatadogTemplate implements OrgIntegrationTemplate { 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; + service: OrganizationIntegrationServiceName; - constructor(service: string) { - this.service = service as OrganizationIntegrationServiceType; + constructor(service: OrganizationIntegrationServiceName) { + this.service = service; } toString(): string { diff --git a/bitwarden_license/bit-common/src/dirt/organization-integrations/models/integration-configuration-config/configuration-template/hec-template.ts b/bitwarden_license/bit-common/src/dirt/organization-integrations/models/integration-configuration-config/configuration-template/hec-template.ts index 7a841697fde..e1b474d0e77 100644 --- a/bitwarden_license/bit-common/src/dirt/organization-integrations/models/integration-configuration-config/configuration-template/hec-template.ts +++ b/bitwarden_license/bit-common/src/dirt/organization-integrations/models/integration-configuration-config/configuration-template/hec-template.ts @@ -1,14 +1,15 @@ -import { OrganizationIntegrationServiceType } from "../../organization-integration-service-type"; +import { OrgIntegrationTemplate } from "../../integration-builder"; +import { OrganizationIntegrationServiceName } from "../../organization-integration-service-type"; -export class HecTemplate { +export class HecTemplate implements OrgIntegrationTemplate { event = "#EventMessage#"; source = "Bitwarden"; index: string; - service: OrganizationIntegrationServiceType; + service: OrganizationIntegrationServiceName; - constructor(index: string, service: string) { + constructor(index: string, service: OrganizationIntegrationServiceName) { this.index = index; - this.service = service as OrganizationIntegrationServiceType; + this.service = service; } toString(): string { diff --git a/bitwarden_license/bit-common/src/dirt/organization-integrations/models/integration-configuration-config/configuration-template/webhook-template.ts b/bitwarden_license/bit-common/src/dirt/organization-integrations/models/integration-configuration-config/configuration-template/webhook-template.ts index 7c51e98282b..fb482d1f367 100644 --- a/bitwarden_license/bit-common/src/dirt/organization-integrations/models/integration-configuration-config/configuration-template/webhook-template.ts +++ b/bitwarden_license/bit-common/src/dirt/organization-integrations/models/integration-configuration-config/configuration-template/webhook-template.ts @@ -1,9 +1,14 @@ +import { OrgIntegrationTemplate } from "../../integration-builder"; +import { OrganizationIntegrationServiceName } from "../../organization-integration-service-type"; + // Added to reflect how future webhook integrations could be structured within the OrganizationIntegration -export class WebhookTemplate { +export class WebhookTemplate implements OrgIntegrationTemplate { + service: OrganizationIntegrationServiceName; propA: string; propB: string; - constructor(propA: string, propB: string) { + constructor(service: OrganizationIntegrationServiceName, propA: string, propB: string) { + this.service = service; this.propA = propA; this.propB = propB; } 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 0209460b630..5271dcd18da 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,31 +4,25 @@ 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"; +import { OrgIntegrationTemplate } from "./integration-builder"; export class OrganizationIntegrationConfiguration { id: OrganizationIntegrationConfigurationId; integrationId: OrganizationIntegrationId; eventType?: EventType | null; - configuration?: WebhookIntegrationConfigurationConfig | null; filters?: string; - template?: HecTemplate | WebhookTemplate | DatadogTemplate | null; + template?: OrgIntegrationTemplate | null; constructor( id: OrganizationIntegrationConfigurationId, integrationId: OrganizationIntegrationId, eventType?: EventType | null, - configuration?: WebhookIntegrationConfigurationConfig | null, filters?: string, - template?: HecTemplate | WebhookTemplate | DatadogTemplate | null, + template?: OrgIntegrationTemplate | null, ) { this.id = id; this.integrationId = integrationId; this.eventType = eventType; - this.configuration = configuration; this.filters = filters; this.template = template; } 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 e9e93adc0ff..9634ad7249a 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,7 +1,7 @@ -export const OrganizationIntegrationServiceType = Object.freeze({ +export const OrganizationIntegrationServiceName = Object.freeze({ CrowdStrike: "CrowdStrike", Datadog: "Datadog", } as const); -export type OrganizationIntegrationServiceType = - (typeof OrganizationIntegrationServiceType)[keyof typeof OrganizationIntegrationServiceType]; +export type OrganizationIntegrationServiceName = + (typeof OrganizationIntegrationServiceName)[keyof typeof OrganizationIntegrationServiceName]; 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 d32c92a460a..84b633a207c 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,29 +1,27 @@ 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 { OrgIntegrationConfiguration } from "./integration-builder"; import { OrganizationIntegrationConfiguration } from "./organization-integration-configuration"; -import { OrganizationIntegrationServiceType } from "./organization-integration-service-type"; +import { OrganizationIntegrationServiceName } from "./organization-integration-service-type"; import { OrganizationIntegrationType } from "./organization-integration-type"; export class OrganizationIntegration { id: OrganizationIntegrationId; type: OrganizationIntegrationType; - serviceType: OrganizationIntegrationServiceType; - configuration: HecConfiguration | WebhookConfiguration | DatadogConfiguration | null; + serviceName: OrganizationIntegrationServiceName; + configuration: OrgIntegrationConfiguration | null; integrationConfiguration: OrganizationIntegrationConfiguration[] = []; constructor( id: OrganizationIntegrationId, type: OrganizationIntegrationType, - serviceType: OrganizationIntegrationServiceType, - configuration: HecConfiguration | WebhookConfiguration | DatadogConfiguration | null, + serviceName: OrganizationIntegrationServiceName, + configuration: OrgIntegrationConfiguration | null, integrationConfiguration: OrganizationIntegrationConfiguration[] = [], ) { this.id = id; this.type = type; - this.serviceType = serviceType; + this.serviceName = serviceName; this.configuration = configuration; this.integrationConfiguration = integrationConfiguration; } 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 deleted file mode 100644 index 0545f95cb83..00000000000 --- a/bitwarden_license/bit-common/src/dirt/organization-integrations/services/datadog-organization-integration-service.spec.ts +++ /dev/null @@ -1,184 +0,0 @@ -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 deleted file mode 100644 index 1fd5e9f8c06..00000000000 --- a/bitwarden_license/bit-common/src/dirt/organization-integrations/services/datadog-organization-integration-service.ts +++ /dev/null @@ -1,350 +0,0 @@ -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.spec.ts b/bitwarden_license/bit-common/src/dirt/organization-integrations/services/hec-organization-integration-service.spec.ts deleted file mode 100644 index 556078ea862..00000000000 --- a/bitwarden_license/bit-common/src/dirt/organization-integrations/services/hec-organization-integration-service.spec.ts +++ /dev/null @@ -1,201 +0,0 @@ -import { mock } from "jest-mock-extended"; -import { firstValueFrom } from "rxjs"; - -import { - OrganizationId, - OrganizationIntegrationConfigurationId, - OrganizationIntegrationId, -} from "@bitwarden/common/types/guid"; - -import { HecConfiguration } from "../models/configuration/hec-configuration"; -import { HecTemplate } from "../models/integration-configuration-config/configuration-template/hec-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 { HecOrganizationIntegrationService } from "./hec-organization-integration-service"; -import { OrganizationIntegrationApiService } from "./organization-integration-api.service"; -import { OrganizationIntegrationConfigurationApiService } from "./organization-integration-configuration-api.service"; - -describe("HecOrganizationIntegrationService", () => { - let service: HecOrganizationIntegrationService; - 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 bearerToken = "token"; - const index = "main"; - - beforeEach(() => { - service = new HecOrganizationIntegrationService( - 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 Hec integration", async () => { - service.setOrganizationIntegrations(organizationId); - - const integrationResponse = { - id: integrationId, - type: OrganizationIntegrationType.Hec, - configuration: JSON.stringify({ url, bearerToken, service: serviceType }), - } as OrganizationIntegrationResponse; - - const configResponse = { - id: configId, - template: JSON.stringify({ index, service: serviceType }), - } as OrganizationIntegrationConfigurationResponse; - - mockIntegrationApiService.createOrganizationIntegration.mockResolvedValue(integrationResponse); - mockIntegrationConfigurationApiService.createOrganizationIntegrationConfiguration.mockResolvedValue( - configResponse, - ); - - await service.saveHec(organizationId, serviceType, url, bearerToken, index); - - 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 saveHec", async () => { - service.setOrganizationIntegrations("other-org" as OrganizationId); - await expect( - service.saveHec(organizationId, serviceType, url, bearerToken, index), - ).rejects.toThrow(Error("Organization ID mismatch")); - }); - - it("should update an existing Hec integration", async () => { - service.setOrganizationIntegrations(organizationId); - - const integrationResponse = { - id: integrationId, - type: OrganizationIntegrationType.Hec, - configuration: JSON.stringify({ url, bearerToken, service: serviceType }), - } as OrganizationIntegrationResponse; - - const configResponse = { - id: configId, - template: JSON.stringify({ index, service: serviceType }), - } as OrganizationIntegrationConfigurationResponse; - - mockIntegrationApiService.updateOrganizationIntegration.mockResolvedValue(integrationResponse); - mockIntegrationConfigurationApiService.updateOrganizationIntegrationConfiguration.mockResolvedValue( - configResponse, - ); - - await service.updateHec( - organizationId, - integrationId, - configId, - serviceType, - url, - bearerToken, - index, - ); - - 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 updateHec", async () => { - service.setOrganizationIntegrations("other-org" as OrganizationId); - await expect( - service.updateHec( - organizationId, - integrationId, - configId, - serviceType, - url, - bearerToken, - index, - ), - ).rejects.toThrow(Error("Organization ID mismatch")); - }); - - it("should get integration by id", async () => { - service["_integrations$"].next([ - new OrganizationIntegration( - integrationId, - OrganizationIntegrationType.Hec, - serviceType, - {} as HecConfiguration, - [], - ), - ]); - 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.Hec, - serviceType, - {} as HecConfiguration, - [], - ), - ]); - 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 HecTemplate, - ); - - service["_integrations$"].next([ - new OrganizationIntegration( - integrationId, - OrganizationIntegrationType.Hec, - serviceType, - {} as HecConfiguration, - [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/hec-organization-integration-service.ts b/bitwarden_license/bit-common/src/dirt/organization-integrations/services/hec-organization-integration-service.ts deleted file mode 100644 index b83ea26e166..00000000000 --- a/bitwarden_license/bit-common/src/dirt/organization-integrations/services/hec-organization-integration-service.ts +++ /dev/null @@ -1,353 +0,0 @@ -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 { HecConfiguration } from "../models/configuration/hec-configuration"; -import { HecTemplate } from "../models/integration-configuration-config/configuration-template/hec-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 HecModificationFailureReason = { - mustBeOwner: boolean; - success: boolean; -}; - -export class HecOrganizationIntegrationService { - 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 [] as OrganizationIntegration[]; - } - }), - 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) { - if (orgId == this.organizationId$.getValue()) { - return; - } - this._integrations$.next([]); - 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 bearerToken api token - * @param index index in service - */ - async saveHec( - organizationId: OrganizationId, - service: OrganizationIntegrationServiceType, - url: string, - bearerToken: string, - index: string, - ): Promise<HecModificationFailureReason> { - if (organizationId != this.organizationId$.getValue()) { - throw new Error("Organization ID mismatch"); - } - - try { - const hecConfig = new HecConfiguration(url, bearerToken, service); - const newIntegrationResponse = await this.integrationApiService.createOrganizationIntegration( - organizationId, - new OrganizationIntegrationRequest(OrganizationIntegrationType.Hec, hecConfig.toString()), - ); - - const newTemplate = new HecTemplate(index, 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 bearerToken api token - * @param index index in service - */ - async updateHec( - organizationId: OrganizationId, - OrganizationIntegrationId: OrganizationIntegrationId, - OrganizationIntegrationConfigurationId: OrganizationIntegrationConfigurationId, - service: OrganizationIntegrationServiceType, - url: string, - bearerToken: string, - index: string, - ): Promise<HecModificationFailureReason> { - if (organizationId != this.organizationId$.getValue()) { - throw new Error("Organization ID mismatch"); - } - - try { - const hecConfig = new HecConfiguration(url, bearerToken, service); - const updatedIntegrationResponse = - await this.integrationApiService.updateOrganizationIntegration( - organizationId, - OrganizationIntegrationId, - new OrganizationIntegrationRequest(OrganizationIntegrationType.Hec, hecConfig.toString()), - ); - - const updatedTemplate = new HecTemplate(index, 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) { - const unchangedIntegrations = this._integrations$ - .getValue() - .filter((i) => i.id !== OrganizationIntegrationId); - this._integrations$.next([...unchangedIntegrations, updatedIntegration]); - } - return { mustBeOwner: false, success: true }; - } catch (error) { - if (error instanceof ErrorResponse && error.statusCode === 404) { - return { mustBeOwner: true, success: false }; - } - throw error; - } - } - - async deleteHec( - organizationId: OrganizationId, - OrganizationIntegrationId: OrganizationIntegrationId, - OrganizationIntegrationConfigurationId: OrganizationIntegrationConfigurationId, - ): Promise<HecModificationFailureReason> { - 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 hecConfig = this.convertToJson<HecConfiguration>(integrationResponse.configuration); - const template = this.convertToJson<HecTemplate>(configurationResponse.template); - - if (!hecConfig || !template) { - return null; - } - - const integrationConfig = new OrganizationIntegrationConfiguration( - configurationResponse.id, - integrationResponse.id, - null, - null, - "", - template, - ); - - return new OrganizationIntegration( - integrationResponse.id, - integrationResponse.type, - hecConfig.service, - hecConfig, - [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 hec 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.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, - ); - - 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/organization-integration-api.service.spec.ts b/bitwarden_license/bit-common/src/dirt/organization-integrations/services/organization-integration-api.service.spec.ts index 10ea87486b4..a03b675868d 100644 --- a/bitwarden_license/bit-common/src/dirt/organization-integrations/services/organization-integration-api.service.spec.ts +++ b/bitwarden_license/bit-common/src/dirt/organization-integrations/services/organization-integration-api.service.spec.ts @@ -4,7 +4,7 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { OrganizationId, OrganizationIntegrationId } from "@bitwarden/common/types/guid"; import { OrganizationIntegrationRequest } from "../models/organization-integration-request"; -import { OrganizationIntegrationServiceType } from "../models/organization-integration-service-type"; +import { OrganizationIntegrationServiceName } from "../models/organization-integration-service-type"; import { OrganizationIntegrationType } from "../models/organization-integration-type"; import { OrganizationIntegrationApiService } from "./organization-integration-api.service"; @@ -56,7 +56,7 @@ describe("OrganizationIntegrationApiService", () => { it("should call apiService.send with correct parameters for createOrganizationIntegration", async () => { const request = new OrganizationIntegrationRequest( OrganizationIntegrationType.Hec, - `{ 'uri:' 'test.com', 'scheme:' 'bearer', 'token:' '123456789', 'service:' '${OrganizationIntegrationServiceType.CrowdStrike}' }`, + `{ 'uri:' 'test.com', 'scheme:' 'bearer', 'token:' '123456789', 'service:' '${OrganizationIntegrationServiceName.CrowdStrike}' }`, ); const orgId = "org1" as OrganizationId; @@ -76,7 +76,7 @@ describe("OrganizationIntegrationApiService", () => { it("should call apiService.send with the correct parameters for updateOrganizationIntegration", async () => { const request = new OrganizationIntegrationRequest( OrganizationIntegrationType.Hec, - `{ 'uri:' 'test.com', 'scheme:' 'bearer', 'token:' '123456789', 'service:' '${OrganizationIntegrationServiceType.CrowdStrike}' }`, + `{ 'uri:' 'test.com', 'scheme:' 'bearer', 'token:' '123456789', 'service:' '${OrganizationIntegrationServiceName.CrowdStrike}' }`, ); const orgId = "org1" as OrganizationId; const integrationId = "integration1" as OrganizationIntegrationId; diff --git a/bitwarden_license/bit-common/src/dirt/organization-integrations/services/organization-integration-service.spec.ts b/bitwarden_license/bit-common/src/dirt/organization-integrations/services/organization-integration-service.spec.ts new file mode 100644 index 00000000000..767c22e2014 --- /dev/null +++ b/bitwarden_license/bit-common/src/dirt/organization-integrations/services/organization-integration-service.spec.ts @@ -0,0 +1,633 @@ +import { mock, MockProxy } from "jest-mock-extended"; +import { firstValueFrom } from "rxjs"; + +import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; +import { + OrganizationId, + OrganizationIntegrationId, + OrganizationIntegrationConfigurationId, +} from "@bitwarden/common/types/guid"; + +import { OrgIntegrationBuilder } from "../models/integration-builder"; +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 { OrganizationIntegrationServiceName } 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"; +import { OrganizationIntegrationService } from "./organization-integration-service"; + +describe("OrganizationIntegrationService", () => { + let service: OrganizationIntegrationService; + let integrationApiService: MockProxy<OrganizationIntegrationApiService>; + let integrationConfigurationApiService: MockProxy<OrganizationIntegrationConfigurationApiService>; + + const orgId = "org-123" as OrganizationId; + const integrationId = "integration-456" as OrganizationIntegrationId; + const configurationId = "config-789" as OrganizationIntegrationConfigurationId; + + const mockIntegrationResponse = new OrganizationIntegrationResponse({ + Id: integrationId, + Type: OrganizationIntegrationType.Hec, + Configuration: JSON.stringify({ + uri: "https://test.splunk.com", + token: "test-token", + service: OrganizationIntegrationServiceName.CrowdStrike, + }), + }); + + const mockConfigurationResponse = new OrganizationIntegrationConfigurationResponse({ + Id: configurationId, + Template: JSON.stringify({ + index: "main", + service: OrganizationIntegrationServiceName.CrowdStrike, + }), + }); + + beforeEach(() => { + integrationApiService = mock<OrganizationIntegrationApiService>(); + integrationConfigurationApiService = mock<OrganizationIntegrationConfigurationApiService>(); + + service = new OrganizationIntegrationService( + integrationApiService, + integrationConfigurationApiService, + ); + }); + + describe("initialization", () => { + it("should be created", () => { + expect(service).toBeTruthy(); + }); + + it("should initialize with empty integrations", async () => { + const integrations = await firstValueFrom(service.integrations$); + expect(integrations).toEqual([]); + }); + }); + + describe("setOrganizationId", () => { + it("should fetch and set integrations for the organization", async () => { + integrationApiService.getOrganizationIntegrations.mockReturnValue( + Promise.resolve([mockIntegrationResponse]), + ); + integrationConfigurationApiService.getOrganizationIntegrationConfigurations.mockReturnValue( + Promise.resolve([mockConfigurationResponse]), + ); + + service.setOrganizationId(orgId).subscribe(); + + // Wait for the observable to emit + await new Promise((resolve) => setTimeout(resolve, 100)); + + const integrations = await firstValueFrom(service.integrations$); + expect(integrations).toHaveLength(1); + expect(integrations[0].id).toBe(integrationId); + expect(integrations[0].type).toBe(OrganizationIntegrationType.Hec); + expect(integrationApiService.getOrganizationIntegrations).toHaveBeenCalledWith(orgId); + expect( + integrationConfigurationApiService.getOrganizationIntegrationConfigurations, + ).toHaveBeenCalledWith(orgId, integrationId); + }); + + it("should skip fetching if organization ID is the same", async () => { + integrationApiService.getOrganizationIntegrations.mockReturnValue(Promise.resolve([])); + + service.setOrganizationId(orgId).subscribe(); + await new Promise((resolve) => setTimeout(resolve, 50)); + + integrationApiService.getOrganizationIntegrations.mockClear(); + + // Call again with the same org ID + service.setOrganizationId(orgId).subscribe(); + await new Promise((resolve) => setTimeout(resolve, 50)); + + expect(integrationApiService.getOrganizationIntegrations).not.toHaveBeenCalled(); + }); + + it("should clear existing integrations when switching organizations", async () => { + const orgId2 = "org-456" as OrganizationId; + + integrationApiService.getOrganizationIntegrations.mockReturnValue( + Promise.resolve([mockIntegrationResponse]), + ); + integrationConfigurationApiService.getOrganizationIntegrationConfigurations.mockReturnValue( + Promise.resolve([mockConfigurationResponse]), + ); + + service.setOrganizationId(orgId).subscribe(); + await new Promise((resolve) => setTimeout(resolve, 100)); + + let integrations = await firstValueFrom(service.integrations$); + expect(integrations).toHaveLength(1); + + // Switch to different org + integrationApiService.getOrganizationIntegrations.mockReturnValue(Promise.resolve([])); + service.setOrganizationId(orgId2).subscribe(); + + // Should immediately clear + integrations = await firstValueFrom(service.integrations$); + expect(integrations).toEqual([]); + }); + + it("should unsubscribe from previous fetch when setting new organization", async () => { + integrationApiService.getOrganizationIntegrations.mockReturnValue(Promise.resolve([])); + + service.setOrganizationId(orgId).subscribe(); + await new Promise((resolve) => setTimeout(resolve, 50)); + + const orgId2 = "org-456" as OrganizationId; + service.setOrganizationId(orgId2).subscribe(); + await new Promise((resolve) => setTimeout(resolve, 50)); + + // Should call the API for both organizations (no errors about duplicate subscriptions) + // The exact call count may vary based on observable behavior + expect(integrationApiService.getOrganizationIntegrations).toHaveBeenCalled(); + }); + + it("should handle multiple integrations", async () => { + const integration2Response = new OrganizationIntegrationResponse({ + Id: "integration-2" as OrganizationIntegrationId, + Type: OrganizationIntegrationType.Datadog, + Configuration: JSON.stringify({ + uri: "https://datadog.com", + apiKey: "test-api-key", + service: OrganizationIntegrationServiceName.Datadog, + }), + }); + + const configuration2Response = new OrganizationIntegrationConfigurationResponse({ + Id: "config-2" as OrganizationIntegrationConfigurationId, + Template: JSON.stringify({ + service: OrganizationIntegrationServiceName.Datadog, + }), + }); + + integrationApiService.getOrganizationIntegrations.mockReturnValue( + Promise.resolve([mockIntegrationResponse, integration2Response]), + ); + integrationConfigurationApiService.getOrganizationIntegrationConfigurations + .mockReturnValueOnce(Promise.resolve([mockConfigurationResponse])) + .mockReturnValueOnce(Promise.resolve([configuration2Response])); + + service.setOrganizationId(orgId).subscribe(); + await new Promise((resolve) => setTimeout(resolve, 100)); + + const integrations = await firstValueFrom(service.integrations$); + expect(integrations).toHaveLength(2); + }); + }); + + describe("save", () => { + const config = OrgIntegrationBuilder.buildHecConfiguration( + "https://test.splunk.com", + "test-token", + OrganizationIntegrationServiceName.CrowdStrike, + ); + const template = OrgIntegrationBuilder.buildHecTemplate( + "main", + OrganizationIntegrationServiceName.CrowdStrike, + ); + + beforeEach(() => { + // Set the organization first + integrationApiService.getOrganizationIntegrations.mockReturnValue(Promise.resolve([])); + service.setOrganizationId(orgId).subscribe(); + }); + + it("should save a new integration successfully", async () => { + integrationApiService.createOrganizationIntegration.mockResolvedValue( + mockIntegrationResponse, + ); + integrationConfigurationApiService.createOrganizationIntegrationConfiguration.mockResolvedValue( + mockConfigurationResponse, + ); + + const result = await service.save(orgId, OrganizationIntegrationType.Hec, config, template); + + expect(result).toEqual({ mustBeOwner: false, success: true }); + expect(integrationApiService.createOrganizationIntegration).toHaveBeenCalledWith( + orgId, + expect.any(OrganizationIntegrationRequest), + ); + expect( + integrationConfigurationApiService.createOrganizationIntegrationConfiguration, + ).toHaveBeenCalledWith( + orgId, + integrationId, + expect.any(OrganizationIntegrationConfigurationRequest), + ); + + const integrations = await firstValueFrom(service.integrations$); + expect(integrations).toHaveLength(1); + expect(integrations[0].id).toBe(integrationId); + }); + + it("should throw error when organization ID mismatch", async () => { + const differentOrgId = "different-org" as OrganizationId; + + await expect( + service.save(differentOrgId, OrganizationIntegrationType.Hec, config, template), + ).rejects.toThrow("Organization ID mismatch"); + }); + + it("should return mustBeOwner true when API returns 404", async () => { + const error = new ErrorResponse({}, 404); + integrationApiService.createOrganizationIntegration.mockRejectedValue(error); + + const result = await service.save(orgId, OrganizationIntegrationType.Hec, config, template); + + expect(result).toEqual({ mustBeOwner: true, success: false }); + }); + + it("should rethrow non-404 errors", async () => { + const error = new Error("Server error"); + integrationApiService.createOrganizationIntegration.mockRejectedValue(error); + + await expect( + service.save(orgId, OrganizationIntegrationType.Hec, config, template), + ).rejects.toThrow("Server error"); + }); + + it("should handle configuration creation failure with 404", async () => { + const error = new ErrorResponse({}, 404); + integrationApiService.createOrganizationIntegration.mockResolvedValue( + mockIntegrationResponse, + ); + integrationConfigurationApiService.createOrganizationIntegrationConfiguration.mockRejectedValue( + error, + ); + + const result = await service.save(orgId, OrganizationIntegrationType.Hec, config, template); + + expect(result).toEqual({ mustBeOwner: true, success: false }); + }); + }); + + describe("update", () => { + const config = OrgIntegrationBuilder.buildHecConfiguration( + "https://updated.splunk.com", + "updated-token", + OrganizationIntegrationServiceName.CrowdStrike, + ); + const template = OrgIntegrationBuilder.buildHecTemplate( + "updated-index", + OrganizationIntegrationServiceName.CrowdStrike, + ); + + beforeEach(() => { + // Set the organization and add an existing integration + integrationApiService.getOrganizationIntegrations.mockReturnValue( + Promise.resolve([mockIntegrationResponse]), + ); + integrationConfigurationApiService.getOrganizationIntegrationConfigurations.mockReturnValue( + Promise.resolve([mockConfigurationResponse]), + ); + service.setOrganizationId(orgId).subscribe(); + }); + + it("should update an integration successfully", async () => { + const updatedIntegrationResponse = new OrganizationIntegrationResponse({ + Id: integrationId, + Type: OrganizationIntegrationType.Hec, + Configuration: JSON.stringify({ + uri: "https://updated.splunk.com", + token: "updated-token", + service: OrganizationIntegrationServiceName.CrowdStrike, + }), + }); + + const updatedConfigurationResponse = new OrganizationIntegrationConfigurationResponse({ + Id: configurationId, + Template: JSON.stringify({ + index: "updated-index", + service: OrganizationIntegrationServiceName.CrowdStrike, + }), + }); + + integrationApiService.updateOrganizationIntegration.mockResolvedValue( + updatedIntegrationResponse, + ); + integrationConfigurationApiService.updateOrganizationIntegrationConfiguration.mockResolvedValue( + updatedConfigurationResponse, + ); + + await new Promise((resolve) => setTimeout(resolve, 100)); + + const result = await service.update( + orgId, + integrationId, + OrganizationIntegrationType.Hec, + configurationId, + config, + template, + ); + + expect(result).toEqual({ mustBeOwner: false, success: true }); + expect(integrationApiService.updateOrganizationIntegration).toHaveBeenCalledWith( + orgId, + integrationId, + expect.any(OrganizationIntegrationRequest), + ); + expect( + integrationConfigurationApiService.updateOrganizationIntegrationConfiguration, + ).toHaveBeenCalledWith( + orgId, + integrationId, + configurationId, + expect.any(OrganizationIntegrationConfigurationRequest), + ); + + const integrations = await firstValueFrom(service.integrations$); + expect(integrations).toHaveLength(1); + expect(integrations[0].id).toBe(integrationId); + }); + + it("should throw error when organization ID mismatch", async () => { + const differentOrgId = "different-org" as OrganizationId; + + await expect( + service.update( + differentOrgId, + integrationId, + OrganizationIntegrationType.Hec, + configurationId, + config, + template, + ), + ).rejects.toThrow("Organization ID mismatch"); + }); + + it("should return mustBeOwner true when API returns 404", async () => { + const error = new ErrorResponse({}, 404); + integrationApiService.updateOrganizationIntegration.mockRejectedValue(error); + + await new Promise((resolve) => setTimeout(resolve, 100)); + + const result = await service.update( + orgId, + integrationId, + OrganizationIntegrationType.Hec, + configurationId, + config, + template, + ); + + expect(result).toEqual({ mustBeOwner: true, success: false }); + }); + + it("should rethrow non-404 errors", async () => { + const error = new Error("Server error"); + integrationApiService.updateOrganizationIntegration.mockRejectedValue(error); + + await new Promise((resolve) => setTimeout(resolve, 100)); + + await expect( + service.update( + orgId, + integrationId, + OrganizationIntegrationType.Hec, + configurationId, + config, + template, + ), + ).rejects.toThrow("Server error"); + }); + + it("should replace old integration with updated one in the list", async () => { + // Add multiple integrations first + const integration2Response = new OrganizationIntegrationResponse({ + Id: "integration-2" as OrganizationIntegrationId, + Type: OrganizationIntegrationType.Hec, + Configuration: mockIntegrationResponse.configuration, + }); + const configuration2Response = new OrganizationIntegrationConfigurationResponse({ + Id: "config-2" as OrganizationIntegrationConfigurationId, + Template: mockConfigurationResponse.template, + }); + + const orgId2 = "org-456" as OrganizationId; + integrationApiService.getOrganizationIntegrations.mockReturnValue( + Promise.resolve([mockIntegrationResponse, integration2Response]), + ); + integrationConfigurationApiService.getOrganizationIntegrationConfigurations + .mockReturnValue(Promise.resolve([mockConfigurationResponse])) + .mockReturnValueOnce(Promise.resolve([mockConfigurationResponse])) + .mockReturnValueOnce(Promise.resolve([configuration2Response])); + + service.setOrganizationId(orgId2).subscribe(); + await new Promise((resolve) => setTimeout(resolve, 100)); + + let integrations = await firstValueFrom(service.integrations$); + expect(integrations).toHaveLength(2); + + // Now update the first integration + integrationApiService.updateOrganizationIntegration.mockResolvedValue( + mockIntegrationResponse, + ); + integrationConfigurationApiService.updateOrganizationIntegrationConfiguration.mockResolvedValue( + mockConfigurationResponse, + ); + + await service.update( + orgId2, + integrationId, + OrganizationIntegrationType.Hec, + configurationId, + config, + template, + ); + + integrations = await firstValueFrom(service.integrations$); + expect(integrations).toHaveLength(2); + expect(integrations.find((i) => i.id === integrationId)).toBeDefined(); + expect(integrations.find((i) => i.id === "integration-2")).toBeDefined(); + }); + }); + + describe("delete", () => { + beforeEach(() => { + // Set the organization and add an existing integration + integrationApiService.getOrganizationIntegrations.mockReturnValue( + Promise.resolve([mockIntegrationResponse]), + ); + integrationConfigurationApiService.getOrganizationIntegrationConfigurations.mockReturnValue( + Promise.resolve([mockConfigurationResponse]), + ); + service.setOrganizationId(orgId).subscribe(); + }); + + it("should delete an integration successfully", async () => { + integrationConfigurationApiService.deleteOrganizationIntegrationConfiguration.mockResolvedValue( + undefined, + ); + integrationApiService.deleteOrganizationIntegration.mockResolvedValue(undefined); + + await new Promise((resolve) => setTimeout(resolve, 100)); + + let integrations = await firstValueFrom(service.integrations$); + expect(integrations).toHaveLength(1); + + const result = await service.delete(orgId, integrationId, configurationId); + + expect(result).toEqual({ mustBeOwner: false, success: true }); + expect( + integrationConfigurationApiService.deleteOrganizationIntegrationConfiguration, + ).toHaveBeenCalledWith(orgId, integrationId, configurationId); + expect(integrationApiService.deleteOrganizationIntegration).toHaveBeenCalledWith( + orgId, + integrationId, + ); + + integrations = await firstValueFrom(service.integrations$); + expect(integrations).toHaveLength(0); + }); + + it("should delete configuration before integration", async () => { + const callOrder: string[] = []; + + integrationConfigurationApiService.deleteOrganizationIntegrationConfiguration.mockImplementation( + async () => { + callOrder.push("configuration"); + }, + ); + integrationApiService.deleteOrganizationIntegration.mockImplementation(async () => { + callOrder.push("integration"); + }); + + await new Promise((resolve) => setTimeout(resolve, 100)); + + await service.delete(orgId, integrationId, configurationId); + + expect(callOrder).toEqual(["configuration", "integration"]); + }); + + it("should throw error when organization ID mismatch", async () => { + const differentOrgId = "different-org" as OrganizationId; + + await expect(service.delete(differentOrgId, integrationId, configurationId)).rejects.toThrow( + "Organization ID mismatch", + ); + }); + + it("should return mustBeOwner true when API returns 404", async () => { + const error = new ErrorResponse({}, 404); + integrationConfigurationApiService.deleteOrganizationIntegrationConfiguration.mockRejectedValue( + error, + ); + + await new Promise((resolve) => setTimeout(resolve, 100)); + + const result = await service.delete(orgId, integrationId, configurationId); + + expect(result).toEqual({ mustBeOwner: true, success: false }); + }); + + it("should rethrow non-404 errors", async () => { + const error = new Error("Server error"); + integrationConfigurationApiService.deleteOrganizationIntegrationConfiguration.mockRejectedValue( + error, + ); + + await new Promise((resolve) => setTimeout(resolve, 100)); + + await expect(service.delete(orgId, integrationId, configurationId)).rejects.toThrow( + "Server error", + ); + }); + + it("should handle 404 error when deleting integration", async () => { + const error = new ErrorResponse({}, 404); + integrationConfigurationApiService.deleteOrganizationIntegrationConfiguration.mockResolvedValue( + undefined, + ); + integrationApiService.deleteOrganizationIntegration.mockRejectedValue(error); + + await new Promise((resolve) => setTimeout(resolve, 100)); + + const result = await service.delete(orgId, integrationId, configurationId); + + expect(result).toEqual({ mustBeOwner: true, success: false }); + }); + }); + + describe("mapResponsesToOrganizationIntegration", () => { + it("should return null if configuration cannot be built", () => { + const invalidIntegrationResponse = new OrganizationIntegrationResponse({ + Id: integrationId, + Type: 999 as OrganizationIntegrationType, // Invalid type + Configuration: "invalid-json", + }); + + // The buildConfiguration method throws for unsupported types + // In production, this error is caught in the setIntegrations pipeline + expect(() => + service["mapResponsesToOrganizationIntegration"]( + invalidIntegrationResponse, + mockConfigurationResponse, + ), + ).toThrow("Unsupported integration type: 999"); + }); + + it("should handle template with invalid data", () => { + const invalidConfigurationResponse = new OrganizationIntegrationConfigurationResponse({ + Id: configurationId, + Template: "{}", // Empty template, will have undefined values but won't return null + }); + + const result = service["mapResponsesToOrganizationIntegration"]( + mockIntegrationResponse, + invalidConfigurationResponse, + ); + + // The result won't be null, but will have a template with undefined/default values + expect(result).not.toBeNull(); + expect(result?.integrationConfiguration[0].template).toBeDefined(); + }); + + it("should successfully map valid responses to OrganizationIntegration", () => { + const result = service["mapResponsesToOrganizationIntegration"]( + mockIntegrationResponse, + mockConfigurationResponse, + ); + + expect(result).not.toBeNull(); + expect(result?.id).toBe(integrationId); + expect(result?.type).toBe(OrganizationIntegrationType.Hec); + expect(result?.integrationConfiguration).toHaveLength(1); + expect(result?.integrationConfiguration[0].id).toBe(configurationId); + }); + }); + + describe("edge cases", () => { + it("should handle empty integration list from API", async () => { + integrationApiService.getOrganizationIntegrations.mockReturnValue(Promise.resolve([])); + + service.setOrganizationId(orgId).subscribe(); + await new Promise((resolve) => setTimeout(resolve, 100)); + + const integrations = await firstValueFrom(service.integrations$); + expect(integrations).toEqual([]); + }); + + it("should handle errors when fetching integrations", async () => { + const validIntegration = mockIntegrationResponse; + + integrationApiService.getOrganizationIntegrations.mockReturnValue( + Promise.resolve([validIntegration]), + ); + integrationConfigurationApiService.getOrganizationIntegrationConfigurations.mockReturnValue( + Promise.resolve([mockConfigurationResponse]), + ); + + service.setOrganizationId(orgId).subscribe(); + await new Promise((resolve) => setTimeout(resolve, 100)); + + const integrations = await firstValueFrom(service.integrations$); + expect(integrations).toHaveLength(1); + expect(integrations[0].id).toBe(integrationId); + }); + }); +}); diff --git a/bitwarden_license/bit-common/src/dirt/organization-integrations/services/organization-integration-service.ts b/bitwarden_license/bit-common/src/dirt/organization-integrations/services/organization-integration-service.ts new file mode 100644 index 00000000000..cd153bc1133 --- /dev/null +++ b/bitwarden_license/bit-common/src/dirt/organization-integrations/services/organization-integration-service.ts @@ -0,0 +1,313 @@ +import { BehaviorSubject, map, Observable, of, switchMap, tap, zip } from "rxjs"; + +import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; +import { + OrganizationId, + OrganizationIntegrationId, + OrganizationIntegrationConfigurationId, +} from "@bitwarden/common/types/guid"; + +import { + OrgIntegrationBuilder, + OrgIntegrationConfiguration, + OrgIntegrationTemplate, +} from "../models/integration-builder"; +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 { OrganizationIntegrationType } from "../models/organization-integration-type"; + +import { OrganizationIntegrationApiService } from "./organization-integration-api.service"; +import { OrganizationIntegrationConfigurationApiService } from "./organization-integration-configuration-api.service"; +/** + * Common result type for integration modification operations (save, update, delete). + * was the server side failure due to insufficient permissions (must be owner)? + */ +export type IntegrationModificationResult = { + mustBeOwner: boolean; + success: boolean; +}; + +/** + * Provides common functionality for managing integrations with different external services. + */ +export class OrganizationIntegrationService { + private organizationId$ = new BehaviorSubject<OrganizationId | null>(null); + private _integrations$ = new BehaviorSubject<OrganizationIntegration[]>([]); + + integrations$: Observable<OrganizationIntegration[]> = this._integrations$.asObservable(); + + constructor( + protected integrationApiService: OrganizationIntegrationApiService, + protected integrationConfigurationApiService: OrganizationIntegrationConfigurationApiService, + ) {} + + /** + * Sets the organization Id and triggers the retrieval of integrations for the given organization. + * The integrations will be available via the integrations$ observable. + * If the organization ID is the same as the current one, no action is taken. + * Use this method to kick off loading integrations for a specific organization. + * Use integrations$ to subscribe to the loaded integrations. + * + * @param orgId - The organization ID to set + * @returns Observable<void> that completes when the operation is done. Subscribe to trigger the load. + */ + setOrganizationId(orgId: OrganizationId): Observable<void> { + if (orgId === this.organizationId$.getValue()) { + return of(void 0); + } + this._integrations$.next([]); + this.organizationId$.next(orgId); + + // subscribe to load and set integrations + // use integrations$ to get the loaded integrations + return this.setIntegrations(orgId).pipe( + tap((integrations) => { + this._integrations$.next(integrations); + }), + map((): void => void 0), + ); + } + + /** + * Saves a new organization integration and updates the integrations$ observable. + * + * @param organizationId - ID of the organization + * @param integrationType - Type of the organization integration + * @param config - The configuration object for this integration + * @param template - The template object for this integration + * @returns Promise with the result indicating success or failure reason + */ + async save( + organizationId: OrganizationId, + integrationType: OrganizationIntegrationType, + config: OrgIntegrationConfiguration, + template: OrgIntegrationTemplate, + ): Promise<IntegrationModificationResult> { + if (organizationId !== this.organizationId$.getValue()) { + throw new Error("Organization ID mismatch"); + } + + try { + const configString = config.toString(); + const newIntegrationResponse = await this.integrationApiService.createOrganizationIntegration( + organizationId, + new OrganizationIntegrationRequest(integrationType, configString), + ); + + const templateString = template.toString(); + const newIntegrationConfigResponse = + await this.integrationConfigurationApiService.createOrganizationIntegrationConfiguration( + organizationId, + newIntegrationResponse.id, + new OrganizationIntegrationConfigurationRequest(null, null, null, templateString), + ); + + 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 integrationId - ID of the organization integration + * @param integrationType - Type of the organization integration + * @param configurationId - ID of the organization integration configuration + * @param config - The updated configuration object + * @param template - The updated template object + * @returns Promise with the result indicating success or failure reason + */ + async update( + organizationId: OrganizationId, + integrationId: OrganizationIntegrationId, + integrationType: OrganizationIntegrationType, + configurationId: OrganizationIntegrationConfigurationId, + config: OrgIntegrationConfiguration, + template: OrgIntegrationTemplate, + ): Promise<IntegrationModificationResult> { + if (organizationId !== this.organizationId$.getValue()) { + throw new Error("Organization ID mismatch"); + } + + try { + const configString = config.toString(); + const updatedIntegrationResponse = + await this.integrationApiService.updateOrganizationIntegration( + organizationId, + integrationId, + new OrganizationIntegrationRequest(integrationType, configString), + ); + + const templateString = template.toString(); + const updatedIntegrationConfigResponse = + await this.integrationConfigurationApiService.updateOrganizationIntegrationConfiguration( + organizationId, + integrationId, + configurationId, + new OrganizationIntegrationConfigurationRequest(null, null, null, templateString), + ); + + const updatedIntegration = this.mapResponsesToOrganizationIntegration( + updatedIntegrationResponse, + updatedIntegrationConfigResponse, + ); + + if (updatedIntegration !== null) { + const integrations = this._integrations$.getValue(); + const index = integrations.findIndex((i) => i.id === integrationId); + if (index !== -1) { + integrations[index] = updatedIntegration; + } else { + integrations.push(updatedIntegration); + } + this._integrations$.next([...integrations]); + } + return { mustBeOwner: false, success: true }; + } catch (error) { + if (error instanceof ErrorResponse && error.statusCode === 404) { + return { mustBeOwner: true, success: false }; + } + throw error; + } + } + + /** + * Deletes an organization integration and updates the integrations$ observable. + * + * @param organizationId - ID of the organization + * @param integrationId - ID of the organization integration + * @param configurationId - ID of the organization integration configuration + * @returns Promise with the result indicating success or failure reason + */ + async delete( + organizationId: OrganizationId, + integrationId: OrganizationIntegrationId, + configurationId: OrganizationIntegrationConfigurationId, + ): Promise<IntegrationModificationResult> { + 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, + integrationId, + configurationId, + ); + + // delete the integration + await this.integrationApiService.deleteOrganizationIntegration(organizationId, integrationId); + + // update the local observable + const updatedIntegrations = this._integrations$ + .getValue() + .filter((i) => i.id !== integrationId); + 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; + } + } + + /** + * Maps API responses to an OrganizationIntegration domain model. + * + * @param integrationResponse - The integration response from the API + * @param configurationResponse - The configuration response from the API + * @returns OrganizationIntegration or null if mapping fails + */ + private mapResponsesToOrganizationIntegration( + integrationResponse: OrganizationIntegrationResponse, + configurationResponse: OrganizationIntegrationConfigurationResponse, + ): OrganizationIntegration | null { + const integrationType = integrationResponse.type; + const config = OrgIntegrationBuilder.buildConfiguration( + integrationType, + integrationResponse.configuration, + ); + const template = OrgIntegrationBuilder.buildTemplate( + integrationType, + configurationResponse.template ?? "{}", + ); + + if (!config || !template) { + return null; + } + + const integrationConfig = new OrganizationIntegrationConfiguration( + configurationResponse.id, + integrationResponse.id, + null, + "", + template, + ); + + return new OrganizationIntegration( + integrationResponse.id, + integrationResponse.type, + config.service, + config, + [integrationConfig], + ); + } + + /** + * Fetches integrations for the given organization from the API. + * + * @param orgId - Organization ID to fetch integrations for + * @returns Observable of OrganizationIntegration array + */ + private setIntegrations(orgId: OrganizationId): Observable<OrganizationIntegration[]> { + const results$ = zip(this.integrationApiService.getOrganizationIntegrations(orgId)).pipe( + switchMap(([responses]) => { + const integrations: OrganizationIntegration[] = []; + const promises: Promise<void>[] = []; + + responses.forEach((integration) => { + const promise = this.integrationConfigurationApiService + .getOrganizationIntegrationConfigurations(orgId, integration.id) + .then((response) => { + // Integration 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$; + } +} 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 8beaae7f10a..37bd504643c 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 @@ -4,9 +4,10 @@ import { mock } from "jest-mock-extended"; 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 { OrgIntegrationBuilder } from "@bitwarden/bit-common/dirt/organization-integrations/models/integration-builder"; +import { OrganizationIntegrationServiceName } 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 { OrganizationIntegrationService } from "@bitwarden/bit-common/dirt/organization-integrations/services/organization-integration-service"; import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { ThemeType } from "@bitwarden/common/platform/enums"; @@ -29,8 +30,7 @@ describe("IntegrationCardComponent", () => { let fixture: ComponentFixture<IntegrationCardComponent>; const mockI18nService = mock<I18nService>(); const activatedRoute = mock<ActivatedRoute>(); - const mockIntegrationService = mock<HecOrganizationIntegrationService>(); - const mockDatadogIntegrationService = mock<DatadogOrganizationIntegrationService>(); + const mockIntegrationService = mock<OrganizationIntegrationService>(); const dialogService = mock<DialogService>(); const toastService = mock<ToastService>(); @@ -54,8 +54,7 @@ describe("IntegrationCardComponent", () => { { provide: I18nPipe, useValue: mock<I18nPipe>() }, { provide: I18nService, useValue: mockI18nService }, { provide: ActivatedRoute, useValue: activatedRoute }, - { provide: HecOrganizationIntegrationService, useValue: mockIntegrationService }, - { provide: DatadogOrganizationIntegrationService, useValue: mockDatadogIntegrationService }, + { provide: OrganizationIntegrationService, useValue: mockIntegrationService }, { provide: ToastService, useValue: toastService }, { provide: DialogService, useValue: dialogService }, ], @@ -259,7 +258,7 @@ describe("IntegrationCardComponent", () => { configuration: {}, integrationConfiguration: [{ id: "config-id" }], }, - name: OrganizationIntegrationServiceType.CrowdStrike, + name: OrganizationIntegrationServiceName.CrowdStrike, } as any; component.organizationId = "org-id" as any; jest.resetAllMocks(); @@ -270,8 +269,8 @@ describe("IntegrationCardComponent", () => { closed: of({ success: false }), }); await component.setupConnection(); - expect(mockIntegrationService.updateHec).not.toHaveBeenCalled(); - expect(mockIntegrationService.saveHec).not.toHaveBeenCalled(); + expect(mockIntegrationService.update).not.toHaveBeenCalled(); + expect(mockIntegrationService.save).not.toHaveBeenCalled(); }); it("should call updateHec if isUpdateAvailable is true", async () => { @@ -284,26 +283,35 @@ describe("IntegrationCardComponent", () => { }), }); + const config = OrgIntegrationBuilder.buildHecConfiguration( + "test-url", + "token", + OrganizationIntegrationServiceName.CrowdStrike, + ); + const template = OrgIntegrationBuilder.buildHecTemplate( + "index", + OrganizationIntegrationServiceName.CrowdStrike, + ); + jest.spyOn(component, "isUpdateAvailable", "get").mockReturnValue(true); await component.setupConnection(); - expect(mockIntegrationService.updateHec).toHaveBeenCalledWith( + expect(mockIntegrationService.update).toHaveBeenCalledWith( "org-id", "integration-id", + OrganizationIntegrationType.Hec, "config-id", - OrganizationIntegrationServiceType.CrowdStrike, - "test-url", - "token", - "index", + config, + template, ); - expect(mockIntegrationService.saveHec).not.toHaveBeenCalled(); + expect(mockIntegrationService.save).not.toHaveBeenCalled(); }); it("should call saveHec if isUpdateAvailable is false", async () => { component.integrationSettings = { organizationIntegration: null, - name: OrganizationIntegrationServiceType.CrowdStrike, + name: OrganizationIntegrationServiceName.CrowdStrike, } as any; component.organizationId = "org-id" as any; @@ -316,23 +324,32 @@ describe("IntegrationCardComponent", () => { }), }); + const config = OrgIntegrationBuilder.buildHecConfiguration( + "test-url", + "token", + OrganizationIntegrationServiceName.CrowdStrike, + ); + const template = OrgIntegrationBuilder.buildHecTemplate( + "index", + OrganizationIntegrationServiceName.CrowdStrike, + ); + jest.spyOn(component, "isUpdateAvailable", "get").mockReturnValue(false); - mockIntegrationService.saveHec.mockResolvedValue({ mustBeOwner: false, success: true }); + mockIntegrationService.save.mockResolvedValue({ mustBeOwner: false, success: true }); await component.setupConnection(); - expect(mockIntegrationService.saveHec).toHaveBeenCalledWith( + expect(mockIntegrationService.save).toHaveBeenCalledWith( "org-id", - OrganizationIntegrationServiceType.CrowdStrike, - "test-url", - "token", - "index", + OrganizationIntegrationType.Hec, + config, + template, ); - expect(mockIntegrationService.updateHec).not.toHaveBeenCalled(); + expect(mockIntegrationService.update).not.toHaveBeenCalled(); }); - it("should call deleteHec when a delete is requested", async () => { + it("should call delete with Hec type when a delete is requested", async () => { component.organizationId = "org-id" as any; (openHecConnectDialog as jest.Mock).mockReturnValue({ @@ -344,22 +361,22 @@ describe("IntegrationCardComponent", () => { }), }); - mockIntegrationService.deleteHec.mockResolvedValue({ mustBeOwner: false, success: true }); + mockIntegrationService.delete.mockResolvedValue({ mustBeOwner: false, success: true }); await component.setupConnection(); - expect(mockIntegrationService.deleteHec).toHaveBeenCalledWith( + expect(mockIntegrationService.delete).toHaveBeenCalledWith( "org-id", "integration-id", "config-id", ); - expect(mockIntegrationService.saveHec).not.toHaveBeenCalled(); + expect(mockIntegrationService.save).not.toHaveBeenCalled(); }); - it("should not call deleteHec if no existing configuration", async () => { + it("should not call delete if no existing configuration", async () => { component.integrationSettings = { organizationIntegration: null, - name: OrganizationIntegrationServiceType.CrowdStrike, + name: OrganizationIntegrationServiceName.CrowdStrike, } as any; component.organizationId = "org-id" as any; @@ -372,20 +389,16 @@ describe("IntegrationCardComponent", () => { }), }); - mockIntegrationService.deleteHec.mockResolvedValue({ mustBeOwner: false, success: true }); + mockIntegrationService.delete.mockResolvedValue({ mustBeOwner: false, success: true }); await component.setupConnection(); - expect(mockIntegrationService.deleteHec).not.toHaveBeenCalledWith( + expect(mockIntegrationService.delete).not.toHaveBeenCalledWith( "org-id", "integration-id", "config-id", - OrganizationIntegrationServiceType.CrowdStrike, - "test-url", - "token", - "index", ); - expect(mockIntegrationService.updateHec).not.toHaveBeenCalled(); + expect(mockIntegrationService.update).not.toHaveBeenCalled(); }); it("should show toast on error while saving", async () => { @@ -399,11 +412,11 @@ describe("IntegrationCardComponent", () => { }); jest.spyOn(component, "isUpdateAvailable", "get").mockReturnValue(true); - mockIntegrationService.updateHec.mockRejectedValue(new Error("fail")); + mockIntegrationService.update.mockRejectedValue(new Error("fail")); await component.setupConnection(); - expect(mockIntegrationService.updateHec).toHaveBeenCalled(); + expect(mockIntegrationService.update).toHaveBeenCalled(); expect(toastService.showToast).toHaveBeenCalledWith({ variant: "error", title: "", @@ -422,11 +435,11 @@ describe("IntegrationCardComponent", () => { }); jest.spyOn(component, "isUpdateAvailable", "get").mockReturnValue(true); - mockIntegrationService.updateHec.mockRejectedValue(new ErrorResponse("Not Found", 404)); + mockIntegrationService.update.mockRejectedValue(new ErrorResponse("Not Found", 404)); await component.setupConnection(); - expect(mockIntegrationService.updateHec).toHaveBeenCalled(); + expect(mockIntegrationService.update).toHaveBeenCalled(); expect(toastService.showToast).toHaveBeenCalledWith({ variant: "error", title: "", @@ -445,11 +458,10 @@ describe("IntegrationCardComponent", () => { }); jest.spyOn(component, "isUpdateAvailable", "get").mockReturnValue(true); - mockIntegrationService.updateHec.mockRejectedValue(new ErrorResponse("Not Found", 404)); - + mockIntegrationService.update.mockRejectedValue(new ErrorResponse("Not Found", 404)); await component.setupConnection(); - expect(mockIntegrationService.updateHec).toHaveBeenCalled(); + expect(mockIntegrationService.update).toHaveBeenCalled(); expect(toastService.showToast).toHaveBeenCalledWith({ variant: "error", title: "", @@ -468,11 +480,11 @@ describe("IntegrationCardComponent", () => { }); jest.spyOn(component, "isUpdateAvailable", "get").mockReturnValue(true); - mockIntegrationService.deleteHec.mockRejectedValue(new Error("fail")); + mockIntegrationService.delete.mockRejectedValue(new Error("fail")); await component.setupConnection(); - expect(mockIntegrationService.deleteHec).toHaveBeenCalled(); + expect(mockIntegrationService.delete).toHaveBeenCalled(); expect(toastService.showToast).toHaveBeenCalledWith({ variant: "error", title: "", @@ -491,11 +503,10 @@ describe("IntegrationCardComponent", () => { }); jest.spyOn(component, "isUpdateAvailable", "get").mockReturnValue(true); - mockIntegrationService.deleteHec.mockRejectedValue(new ErrorResponse("Not Found", 404)); - + mockIntegrationService.delete.mockRejectedValue(new ErrorResponse("Not Found", 404)); await component.setupConnection(); - expect(mockIntegrationService.deleteHec).toHaveBeenCalled(); + expect(mockIntegrationService.delete).toHaveBeenCalled(); expect(toastService.showToast).toHaveBeenCalledWith({ variant: "error", title: "", 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 e6d4aff05fb..8026e14c2fc 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 @@ -12,10 +12,10 @@ 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 { OrgIntegrationBuilder } from "@bitwarden/bit-common/dirt/organization-integrations/models/integration-builder"; +import { OrganizationIntegrationServiceName } 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 { OrganizationIntegrationService } from "@bitwarden/bit-common/dirt/organization-integrations/services/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"; @@ -96,8 +96,7 @@ export class IntegrationCardComponent implements AfterViewInit, OnDestroy { private systemTheme$: Observable<ThemeType>, private dialogService: DialogService, private activatedRoute: ActivatedRoute, - private hecOrganizationIntegrationService: HecOrganizationIntegrationService, - private datadogOrganizationIntegrationService: DatadogOrganizationIntegrationService, + private organizationIntegrationService: OrganizationIntegrationService, private toastService: ToastService, private i18nService: I18nService, ) { @@ -250,7 +249,18 @@ export class IntegrationCardComponent implements AfterViewInit, OnDestroy { } async saveHec(result: HecConnectDialogResult) { - let saveResponse = { mustBeOwner: false, success: false }; + let response = { mustBeOwner: false, success: false }; + + const config = OrgIntegrationBuilder.buildHecConfiguration( + result.url, + result.bearerToken, + this.integrationSettings.name as OrganizationIntegrationServiceName, + ); + const template = OrgIntegrationBuilder.buildHecTemplate( + result.index, + this.integrationSettings.name as OrganizationIntegrationServiceName, + ); + if (this.isUpdateAvailable) { // retrieve org integration and configuration ids const orgIntegrationId = this.integrationSettings.organizationIntegration?.id; @@ -262,27 +272,25 @@ export class IntegrationCardComponent implements AfterViewInit, OnDestroy { } // update existing integration and configuration - saveResponse = await this.hecOrganizationIntegrationService.updateHec( + response = await this.organizationIntegrationService.update( this.organizationId, orgIntegrationId, + OrganizationIntegrationType.Hec, orgIntegrationConfigurationId, - this.integrationSettings.name as OrganizationIntegrationServiceType, - result.url, - result.bearerToken, - result.index, + config, + template, ); } else { // create new integration and configuration - saveResponse = await this.hecOrganizationIntegrationService.saveHec( + response = await this.organizationIntegrationService.save( this.organizationId, - this.integrationSettings.name as OrganizationIntegrationServiceType, - result.url, - result.bearerToken, - result.index, + OrganizationIntegrationType.Hec, + config, + template, ); } - if (saveResponse.mustBeOwner) { + if (response.mustBeOwner) { this.showMustBeOwnerToast(); return; } @@ -303,7 +311,7 @@ export class IntegrationCardComponent implements AfterViewInit, OnDestroy { throw Error("Organization Integration ID or Configuration ID is missing"); } - const response = await this.hecOrganizationIntegrationService.deleteHec( + const response = await this.organizationIntegrationService.delete( this.organizationId, orgIntegrationId, orgIntegrationConfigurationId, @@ -322,6 +330,13 @@ export class IntegrationCardComponent implements AfterViewInit, OnDestroy { } async saveDatadog(result: DatadogConnectDialogResult) { + let response = { mustBeOwner: false, success: false }; + + const config = OrgIntegrationBuilder.buildDataDogConfiguration(result.url, result.apiKey); + const template = OrgIntegrationBuilder.buildDataDogTemplate( + this.integrationSettings.name as OrganizationIntegrationServiceName, + ); + if (this.isUpdateAvailable) { // retrieve org integration and configuration ids const orgIntegrationId = this.integrationSettings.organizationIntegration?.id; @@ -333,23 +348,29 @@ export class IntegrationCardComponent implements AfterViewInit, OnDestroy { } // update existing integration and configuration - await this.datadogOrganizationIntegrationService.updateDatadog( + response = await this.organizationIntegrationService.update( this.organizationId, orgIntegrationId, + OrganizationIntegrationType.Datadog, orgIntegrationConfigurationId, - this.integrationSettings.name as OrganizationIntegrationServiceType, - result.url, - result.apiKey, + config, + template, ); } else { // create new integration and configuration - await this.datadogOrganizationIntegrationService.saveDatadog( + response = await this.organizationIntegrationService.save( this.organizationId, - this.integrationSettings.name as OrganizationIntegrationServiceType, - result.url, - result.apiKey, + OrganizationIntegrationType.Datadog, + config, + template, ); } + + if (response.mustBeOwner) { + this.showMustBeOwnerToast(); + return; + } + this.toastService.showToast({ variant: "success", title: "", @@ -366,7 +387,7 @@ export class IntegrationCardComponent implements AfterViewInit, OnDestroy { throw Error("Organization Integration ID or Configuration ID is missing"); } - const response = await this.datadogOrganizationIntegrationService.deleteDatadog( + const response = await this.organizationIntegrationService.delete( this.organizationId, orgIntegrationId, orgIntegrationConfigurationId, 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 2908fe0c089..3560a32fb40 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,8 +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 { OrganizationIntegrationService } from "@bitwarden/bit-common/dirt/organization-integrations/services/organization-integration-service"; import { IntegrationType } from "@bitwarden/common/enums"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { ThemeTypes } from "@bitwarden/common/platform/enums"; @@ -24,8 +23,7 @@ describe("IntegrationGridComponent", () => { let component: IntegrationGridComponent; let fixture: ComponentFixture<IntegrationGridComponent>; const mockActivatedRoute = mock<ActivatedRoute>(); - const mockIntegrationService = mock<HecOrganizationIntegrationService>(); - const mockDatadogIntegrationService = mock<DatadogOrganizationIntegrationService>(); + const mockIntegrationService = mock<OrganizationIntegrationService>(); const integrations: Integration[] = [ { name: "Integration 1", @@ -71,8 +69,7 @@ describe("IntegrationGridComponent", () => { provide: ActivatedRoute, useValue: mockActivatedRoute, }, - { provide: HecOrganizationIntegrationService, useValue: mockIntegrationService }, - { provide: DatadogOrganizationIntegrationService, useValue: mockDatadogIntegrationService }, + { provide: OrganizationIntegrationService, useValue: mockIntegrationService }, { provide: ToastService, useValue: mock<ToastService>(), diff --git a/bitwarden_license/bit-web/src/app/dirt/organization-integrations/integrations.component.html b/bitwarden_license/bit-web/src/app/dirt/organization-integrations/integrations.component.html index 58c52e4f40a..a35df3677bb 100644 --- a/bitwarden_license/bit-web/src/app/dirt/organization-integrations/integrations.component.html +++ b/bitwarden_license/bit-web/src/app/dirt/organization-integrations/integrations.component.html @@ -1,69 +1,78 @@ <app-header> </app-header> -<bit-tab-group [(selectedIndex)]="tabIndex" *ngIf="organization$ | async as organization"> - <bit-tab [label]="'singleSignOn' | i18n" *ngIf="organization.useSso"> - <section class="tw-mb-9"> - <h2 bitTypography="h2">{{ "singleSignOn" | i18n }}</h2> - <p bitTypography="body1"> - {{ "ssoDescStart" | i18n }} - <a bitLink routerLink="../settings/sso" class="tw-lowercase">{{ "singleSignOn" | i18n }}</a> - {{ "ssoDescEnd" | i18n }} - </p> - <app-integration-grid - [integrations]="integrationsList | filterIntegrations: IntegrationType.SSO" - ></app-integration-grid> - </section> - </bit-tab> +@let organization = organization$ | async; - <bit-tab - [label]="'userProvisioning' | i18n" - *ngIf="organization.useScim || organization.useDirectory" - > - <section class="tw-mb-9" *ngIf="organization.useScim"> - <h2 bitTypography="h2"> - {{ "scimIntegration" | i18n }} - </h2> - <p bitTypography="body1"> - {{ "scimIntegrationDescStart" | i18n }} - <a bitLink routerLink="../settings/scim">{{ "scimIntegration" | i18n }}</a> - {{ "scimIntegrationDescEnd" | i18n }} - </p> - <app-integration-grid - [integrations]="integrationsList | filterIntegrations: IntegrationType.SCIM" - ></app-integration-grid> - </section> - <section class="tw-mb-9" *ngIf="organization.useDirectory"> - <h2 bitTypography="h2"> - {{ "bwdc" | i18n }} - </h2> - <p bitTypography="body1">{{ "bwdcDesc" | i18n }}</p> - <app-integration-grid - [integrations]="integrationsList | filterIntegrations: IntegrationType.BWDC" - ></app-integration-grid> - </section> - </bit-tab> +@if (organization) { + <bit-tab-group [(selectedIndex)]="tabIndex"> + @if (organization?.useSso) { + <bit-tab [label]="'singleSignOn' | i18n"> + <section class="tw-mb-9"> + <h2 bitTypography="h2">{{ "singleSignOn" | i18n }}</h2> + <p bitTypography="body1"> + {{ "ssoDescStart" | i18n }} + <a bitLink routerLink="../settings/sso" class="tw-lowercase">{{ + "singleSignOn" | i18n + }}</a> + {{ "ssoDescEnd" | i18n }} + </p> + <app-integration-grid + [integrations]="integrationsList | filterIntegrations: IntegrationType.SSO" + ></app-integration-grid> + </section> + </bit-tab> + } - <bit-tab [label]="'eventManagement' | i18n" *ngIf="organization.useEvents"> - <section class="tw-mb-9"> - <h2 bitTypography="h2"> - {{ "eventManagement" | i18n }} - </h2> - <p bitTypography="body1">{{ "eventManagementDesc" | i18n }}</p> - <app-integration-grid - [integrations]="integrationsList | filterIntegrations: IntegrationType.EVENT" - ></app-integration-grid> - </section> - </bit-tab> + @if (organization?.useScim || organization?.useDirectory) { + <bit-tab [label]="'userProvisioning' | i18n"> + <section class="tw-mb-9" *ngIf="organization?.useScim"> + <h2 bitTypography="h2"> + {{ "scimIntegration" | i18n }} + </h2> + <p bitTypography="body1"> + {{ "scimIntegrationDescStart" | i18n }} + <a bitLink routerLink="../settings/scim">{{ "scimIntegration" | i18n }}</a> + {{ "scimIntegrationDescEnd" | i18n }} + </p> + <app-integration-grid + [integrations]="integrationsList | filterIntegrations: IntegrationType.SCIM" + ></app-integration-grid> + </section> + <section class="tw-mb-9" *ngIf="organization?.useDirectory"> + <h2 bitTypography="h2"> + {{ "bwdc" | i18n }} + </h2> + <p bitTypography="body1">{{ "bwdcDesc" | i18n }}</p> + <app-integration-grid + [integrations]="integrationsList | filterIntegrations: IntegrationType.BWDC" + ></app-integration-grid> + </section> + </bit-tab> + } - <bit-tab [label]="'deviceManagement' | i18n"> - <section class="tw-mb-9"> - <h2 bitTypography="h2"> - {{ "deviceManagement" | i18n }} - </h2> - <p bitTypography="body1">{{ "deviceManagementDesc" | i18n }}</p> - <app-integration-grid - [integrations]="integrationsList | filterIntegrations: IntegrationType.DEVICE" - ></app-integration-grid> - </section> - </bit-tab> -</bit-tab-group> + @if (organization?.useEvents) { + <bit-tab [label]="'eventManagement' | i18n"> + <section class="tw-mb-9"> + <h2 bitTypography="h2"> + {{ "eventManagement" | i18n }} + </h2> + <p bitTypography="body1">{{ "eventManagementDesc" | i18n }}</p> + <app-integration-grid + [integrations]="integrationsList | filterIntegrations: IntegrationType.EVENT" + ></app-integration-grid> + </section> + </bit-tab> + } + + <bit-tab [label]="'deviceManagement' | i18n"> + <section class="tw-mb-9"> + <h2 bitTypography="h2"> + {{ "deviceManagement" | i18n }} + </h2> + <p bitTypography="body1">{{ "deviceManagementDesc" | i18n }}</p> + <app-integration-grid + [integrations]="integrationsList | filterIntegrations: IntegrationType.DEVICE" + ></app-integration-grid> + </section> + </bit-tab> + </bit-tab-group> +} 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 894a8e9a25c..6517182b21e 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 @@ -3,10 +3,9 @@ import { ActivatedRoute } from "@angular/router"; import { firstValueFrom, Observable, Subject, switchMap, takeUntil, takeWhile } from "rxjs"; 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 { OrganizationIntegrationServiceName } 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 { OrganizationIntegrationService } from "@bitwarden/bit-common/dirt/organization-integrations/services/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"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; @@ -21,6 +20,7 @@ import { SharedModule } from "@bitwarden/web-vault/app/shared"; import { IntegrationGridComponent } from "./integration-grid/integration-grid.component"; import { FilterIntegrationsPipe } from "./integrations.pipe"; +// attempted, but because bit-tab-group is not OnPush, caused more issues than it solved // FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush // eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ @@ -236,10 +236,12 @@ 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); - }); + this.organization$ + .pipe( + switchMap((org) => this.organizationIntegrationService.setOrganizationId(org.id)), + takeUntil(this.destroy$), + ) + .subscribe(); } constructor( @@ -247,8 +249,7 @@ export class AdminConsoleIntegrationsComponent implements OnInit, OnDestroy { private organizationService: OrganizationService, private accountService: AccountService, private configService: ConfigService, - private hecOrganizationIntegrationService: HecOrganizationIntegrationService, - private datadogOrganizationIntegrationService: DatadogOrganizationIntegrationService, + private organizationIntegrationService: OrganizationIntegrationService, ) { this.configService .getFeatureFlag$(FeatureFlag.EventManagementForDataDogAndCrowdStrike) @@ -260,7 +261,7 @@ export class AdminConsoleIntegrationsComponent implements OnInit, OnDestroy { // Add the new event based items to the list if (this.isEventManagementForDataDogAndCrowdStrikeEnabled) { const crowdstrikeIntegration: Integration = { - name: OrganizationIntegrationServiceType.CrowdStrike, + name: OrganizationIntegrationServiceName.CrowdStrike, linkURL: "https://bitwarden.com/help/crowdstrike-siem/", image: "../../../../../../../images/integrations/logo-crowdstrike-black.svg", type: IntegrationType.EVENT, @@ -272,7 +273,7 @@ export class AdminConsoleIntegrationsComponent implements OnInit, OnDestroy { this.integrationsList.push(crowdstrikeIntegration); const datadogIntegration: Integration = { - name: OrganizationIntegrationServiceType.Datadog, + name: OrganizationIntegrationServiceName.Datadog, linkURL: "https://bitwarden.com/help/datadog-siem/", image: "../../../../../../../images/integrations/logo-datadog-color.svg", type: IntegrationType.EVENT, @@ -286,42 +287,23 @@ export class AdminConsoleIntegrationsComponent implements OnInit, OnDestroy { // For all existing event based configurations loop through and assign the // organizationIntegration for the correct services. - this.hecOrganizationIntegrationService.integrations$ + this.organizationIntegrationService.integrations$ .pipe(takeUntil(this.destroy$)) .subscribe((integrations) => { - // reset all integrations to null first - in case one was deleted + // reset all event based integrations to null first - in case one was deleted this.integrationsList.forEach((i) => { - if (i.integrationType === OrganizationIntegrationType.Hec) { - i.organizationIntegration = null; - } + 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); + integrations.forEach((integration) => { + const item = this.integrationsList.find((i) => i.name === integration.serviceName); if (item) { item.organizationIntegration = integration; } }); }); } + ngOnDestroy(): void { this.destroy$.next(); this.destroy$.complete(); 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 e3c37b4a42b..789ae548521 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,9 +1,8 @@ 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"; +import { OrganizationIntegrationService } from "@bitwarden/bit-common/dirt/organization-integrations/services/organization-integration-service"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { safeProvider } from "@bitwarden/ui-common"; @@ -14,13 +13,8 @@ import { OrganizationIntegrationsRoutingModule } from "./organization-integratio imports: [AdminConsoleIntegrationsComponent, OrganizationIntegrationsRoutingModule], providers: [ safeProvider({ - provide: DatadogOrganizationIntegrationService, - useClass: DatadogOrganizationIntegrationService, - deps: [OrganizationIntegrationApiService, OrganizationIntegrationConfigurationApiService], - }), - safeProvider({ - provide: HecOrganizationIntegrationService, - useClass: HecOrganizationIntegrationService, + provide: OrganizationIntegrationService, + useClass: OrganizationIntegrationService, deps: [OrganizationIntegrationApiService, OrganizationIntegrationConfigurationApiService], }), safeProvider({ 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 0e8c46c8864..7a02e3fb04e 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,8 +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 { OrganizationIntegrationService } from "@bitwarden/bit-common/dirt/organization-integrations/services/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"; @@ -41,8 +40,7 @@ class MockNewMenuComponent {} describe("IntegrationsComponent", () => { let fixture: ComponentFixture<IntegrationsComponent>; - const hecOrgIntegrationSvc = mock<HecOrganizationIntegrationService>(); - const datadogOrgIntegrationSvc = mock<DatadogOrganizationIntegrationService>(); + const orgIntegrationSvc = mock<OrganizationIntegrationService>(); const activatedRouteMock = { snapshot: { paramMap: { get: jest.fn() } }, @@ -60,8 +58,7 @@ describe("IntegrationsComponent", () => { { provide: ActivatedRoute, useValue: activatedRouteMock }, { provide: I18nPipe, useValue: mock<I18nPipe>() }, { provide: I18nService, useValue: mockI18nService }, - { provide: HecOrganizationIntegrationService, useValue: hecOrgIntegrationSvc }, - { provide: DatadogOrganizationIntegrationService, useValue: datadogOrgIntegrationSvc }, + { provide: OrganizationIntegrationService, useValue: orgIntegrationSvc }, ], }).compileComponents(); fixture = TestBed.createComponent(IntegrationsComponent); diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations.module.ts b/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations.module.ts index 04240da3176..bcfbb9b3f2c 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations.module.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations.module.ts @@ -1,9 +1,8 @@ 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"; +import { OrganizationIntegrationService } from "@bitwarden/bit-common/dirt/organization-integrations/services/organization-integration-service"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { safeProvider } from "@bitwarden/ui-common"; @@ -23,13 +22,8 @@ import { IntegrationsComponent } from "./integrations.component"; ], providers: [ safeProvider({ - provide: DatadogOrganizationIntegrationService, - useClass: DatadogOrganizationIntegrationService, - deps: [OrganizationIntegrationApiService, OrganizationIntegrationConfigurationApiService], - }), - safeProvider({ - provide: HecOrganizationIntegrationService, - useClass: HecOrganizationIntegrationService, + provide: OrganizationIntegrationService, + useClass: OrganizationIntegrationService, deps: [OrganizationIntegrationApiService, OrganizationIntegrationConfigurationApiService], }), safeProvider({ From 1edff74b30f7dcc928d7cf769e504c0d0d25227f Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann <mail@quexten.com> Date: Mon, 15 Dec 2025 18:36:16 +0100 Subject: [PATCH 073/188] Use proof of decryption (#17903) --- .../prompt-migration-password.component.ts | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/libs/angular/src/key-management/encrypted-migration/prompt-migration-password.component.ts b/libs/angular/src/key-management/encrypted-migration/prompt-migration-password.component.ts index 060901d68fb..59fec1a6f70 100644 --- a/libs/angular/src/key-management/encrypted-migration/prompt-migration-password.component.ts +++ b/libs/angular/src/key-management/encrypted-migration/prompt-migration-password.component.ts @@ -5,8 +5,7 @@ import { filter, firstValueFrom, map } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; -import { VerificationType } from "@bitwarden/common/auth/enums/verification-type"; +import { MasterPasswordUnlockService } from "@bitwarden/common/key-management/master-password/abstractions/master-password-unlock.service"; import { LinkModule, AsyncActionsModule, @@ -39,7 +38,7 @@ import { export class PromptMigrationPasswordComponent { private dialogRef = inject(DialogRef<string>); private formBuilder = inject(FormBuilder); - private uvService = inject(UserVerificationService); + private masterPasswordUnlockService = inject(MasterPasswordUnlockService); private accountService = inject(AccountService); migrationPasswordForm = this.formBuilder.group({ @@ -57,23 +56,21 @@ export class PromptMigrationPasswordComponent { return; } - const { userId, email } = await firstValueFrom( + const { userId } = await firstValueFrom( this.accountService.activeAccount$.pipe( filter((account) => account != null), map((account) => { return { userId: account!.id, - email: account!.email, }; }), ), ); if ( - !(await this.uvService.verifyUserByMasterPassword( - { type: VerificationType.MasterPassword, secret: masterPasswordControl.value }, + !(await this.masterPasswordUnlockService.proofOfDecryption( + masterPasswordControl.value, userId, - email, )) ) { return; From 1d1eca472eff9c639469e5772d61fe1eb75b1b42 Mon Sep 17 00:00:00 2001 From: "bw-ghapp[bot]" <178206702+bw-ghapp[bot]@users.noreply.github.com> Date: Mon, 15 Dec 2025 18:57:55 +0100 Subject: [PATCH 074/188] Autosync the updated translations (#17937) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/web/src/locales/af/messages.json | 229 +++++++++- apps/web/src/locales/ar/messages.json | 229 +++++++++- apps/web/src/locales/az/messages.json | 247 +++++++++- apps/web/src/locales/be/messages.json | 229 +++++++++- apps/web/src/locales/bg/messages.json | 227 +++++++++- apps/web/src/locales/bn/messages.json | 229 +++++++++- apps/web/src/locales/bs/messages.json | 229 +++++++++- apps/web/src/locales/ca/messages.json | 229 +++++++++- apps/web/src/locales/cs/messages.json | 227 +++++++++- apps/web/src/locales/cy/messages.json | 229 +++++++++- apps/web/src/locales/da/messages.json | 229 +++++++++- apps/web/src/locales/de/messages.json | 253 ++++++++++- apps/web/src/locales/el/messages.json | 229 +++++++++- apps/web/src/locales/en_GB/messages.json | 227 +++++++++- apps/web/src/locales/en_IN/messages.json | 229 +++++++++- apps/web/src/locales/eo/messages.json | 229 +++++++++- apps/web/src/locales/es/messages.json | 229 +++++++++- apps/web/src/locales/et/messages.json | 229 +++++++++- apps/web/src/locales/eu/messages.json | 229 +++++++++- apps/web/src/locales/fa/messages.json | 229 +++++++++- apps/web/src/locales/fi/messages.json | 229 +++++++++- apps/web/src/locales/fil/messages.json | 229 +++++++++- apps/web/src/locales/fr/messages.json | 231 +++++++++- apps/web/src/locales/gl/messages.json | 229 +++++++++- apps/web/src/locales/he/messages.json | 229 +++++++++- apps/web/src/locales/hi/messages.json | 229 +++++++++- apps/web/src/locales/hr/messages.json | 229 +++++++++- apps/web/src/locales/hu/messages.json | 229 +++++++++- apps/web/src/locales/id/messages.json | 229 +++++++++- apps/web/src/locales/it/messages.json | 247 +++++++++- apps/web/src/locales/ja/messages.json | 229 +++++++++- apps/web/src/locales/ka/messages.json | 229 +++++++++- apps/web/src/locales/km/messages.json | 229 +++++++++- apps/web/src/locales/kn/messages.json | 229 +++++++++- apps/web/src/locales/ko/messages.json | 229 +++++++++- apps/web/src/locales/lv/messages.json | 229 +++++++++- apps/web/src/locales/ml/messages.json | 229 +++++++++- apps/web/src/locales/mr/messages.json | 229 +++++++++- apps/web/src/locales/my/messages.json | 229 +++++++++- apps/web/src/locales/nb/messages.json | 229 +++++++++- apps/web/src/locales/ne/messages.json | 229 +++++++++- apps/web/src/locales/nl/messages.json | 229 +++++++++- apps/web/src/locales/nn/messages.json | 229 +++++++++- apps/web/src/locales/or/messages.json | 229 +++++++++- apps/web/src/locales/pl/messages.json | 229 +++++++++- apps/web/src/locales/pt_BR/messages.json | 253 ++++++++++- apps/web/src/locales/pt_PT/messages.json | 229 +++++++++- apps/web/src/locales/ro/messages.json | 229 +++++++++- apps/web/src/locales/ru/messages.json | 227 +++++++++- apps/web/src/locales/si/messages.json | 229 +++++++++- apps/web/src/locales/sk/messages.json | 297 ++++++++++-- apps/web/src/locales/sl/messages.json | 229 +++++++++- apps/web/src/locales/sr_CS/messages.json | 229 +++++++++- apps/web/src/locales/sr_CY/messages.json | 229 +++++++++- apps/web/src/locales/sv/messages.json | 313 ++++++++++--- apps/web/src/locales/ta/messages.json | 229 +++++++++- apps/web/src/locales/te/messages.json | 229 +++++++++- apps/web/src/locales/th/messages.json | 229 +++++++++- apps/web/src/locales/tr/messages.json | 549 ++++++++++++++++------- apps/web/src/locales/uk/messages.json | 229 +++++++++- apps/web/src/locales/vi/messages.json | 363 +++++++++++---- apps/web/src/locales/zh_CN/messages.json | 253 ++++++++++- apps/web/src/locales/zh_TW/messages.json | 247 +++++++++- 63 files changed, 14223 insertions(+), 930 deletions(-) diff --git a/apps/web/src/locales/af/messages.json b/apps/web/src/locales/af/messages.json index 923e65fbf99..b5701c01e86 100644 --- a/apps/web/src/locales/af/messages.json +++ b/apps/web/src/locales/af/messages.json @@ -5169,9 +5169,21 @@ "message": "Herstel", "description": "This is a verb. ex. 'Fix The Car'" }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "oldAttachmentsNeedFixDesc": { "message": "Daar is ou lêeraanhegsels in u kluis wat herstel moet word alvorens u u rekening se enkripsiesleutel kan roteer." }, + "itemsTransferred": { + "message": "Items transferred" + }, "yourAccountsFingerprint": { "message": "U rekening se vingerafdrukfrase", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -6453,6 +6465,32 @@ "bulkReinviteMessage": { "message": "Suksesvol heruitgenooi" }, + "bulkReinviteSuccessToast": { + "message": "$COUNT$ users re-invited", + "placeholders": { + "count": { + "content": "$1", + "example": "12" + } + } + }, + "bulkReinviteLimitedSuccessToast": { + "message": "$LIMIT$ of $SELECTEDCOUNT$ users re-invited. $EXCLUDEDCOUNT$ were not invited due to the $LIMIT$ invite limit.", + "placeholders": { + "limit": { + "content": "$1", + "example": "4,000" + }, + "selectedCount": { + "content": "$2", + "example": "4,005" + }, + "excludedCount": { + "content": "$3", + "example": "5" + } + } + }, "bulkRemovedMessage": { "message": "Suksesvol verwyder" }, @@ -6773,8 +6811,8 @@ "vaultTimeoutRangeError": { "message": "Kluisuittel is nie binne toegelate omvang nie." }, - "disablePersonalVaultExport": { - "message": "Deaktiveer uitstuur van persoonlike kluis" + "disableExport": { + "message": "Remove export" }, "disablePersonalVaultExportDescription": { "message": "Do not allow members to export data from their individual vault." @@ -7094,9 +7132,6 @@ "invalidVerificationCode": { "message": "Ongeldige bevestigingskode" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "keyConnectorDomain": { "message": "Key Connector domain" }, @@ -7154,8 +7189,8 @@ "enabledSso": { "message": "SSO geaktiveer" }, - "disabledSso": { - "message": "SSO gedeaktiveer" + "ssoTurnedOff": { + "message": "SSO turned off" }, "emailMustLoginWithSso": { "message": "$EMAIL$ must login with Single Sign-on", @@ -9448,6 +9483,9 @@ "ssoLoginIsRequired": { "message": "SSO login is required" }, + "emailRequiredForSsoLogin": { + "message": "Email is required for SSO" + }, "selectedRegionFlag": { "message": "Gekose streekvlag" }, @@ -11961,8 +11999,8 @@ "familiesMembership": { "message": "Families membership" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "planDescFamiliesV2": { "message": "Premium security for your family" @@ -12199,6 +12237,45 @@ } } }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "confirmNoSelectedCriticalApplicationsTitle": { "message": "No critical applications are selected" }, @@ -12207,5 +12284,139 @@ }, "userVerificationFailed": { "message": "User verification failed." + }, + "recoveryDeleteCiphersTitle": { + "message": "Delete unrecoverable vault items" + }, + "recoveryDeleteCiphersDesc": { + "message": "Some of your vault items could not be recovered. Do you want to delete these unrecoverable items from your vault?" + }, + "recoveryDeleteFoldersTitle": { + "message": "Delete unrecoverable folders" + }, + "recoveryDeleteFoldersDesc": { + "message": "Some of your folders could not be recovered. Do you want to delete these unrecoverable folders from your vault?" + }, + "recoveryReplacePrivateKeyTitle": { + "message": "Replace encryption key" + }, + "recoveryReplacePrivateKeyDesc": { + "message": "Your public-key encryption key pair could not be recovered. Do you want to replace your encryption key with a new key pair? This will require you to set up existing emergency-access and organization memberships again." + }, + "recoveryStepSyncTitle": { + "message": "Synchronizing data" + }, + "recoveryStepPrivateKeyTitle": { + "message": "Verifying encryption key integrity" + }, + "recoveryStepUserInfoTitle": { + "message": "Verifying user information" + }, + "recoveryStepCipherTitle": { + "message": "Verifying vault item integrity" + }, + "recoveryStepFoldersTitle": { + "message": "Verifying folder integrity" + }, + "dataRecoveryTitle": { + "message": "Data Recovery and Diagnostics" + }, + "dataRecoveryDescription": { + "message": "Use the data recovery tool to diagnose and repair issues with your account. After running diagnostics you have the option to save diagnostic logs for support and the option to repair any detected issues." + }, + "runDiagnostics": { + "message": "Run Diagnostics" + }, + "repairIssues": { + "message": "Repair Issues" + }, + "saveDiagnosticLogs": { + "message": "Save Diagnostic Logs" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On browser refresh." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On browser refresh" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/web/src/locales/ar/messages.json b/apps/web/src/locales/ar/messages.json index 4999534a5f6..807689cebae 100644 --- a/apps/web/src/locales/ar/messages.json +++ b/apps/web/src/locales/ar/messages.json @@ -5169,9 +5169,21 @@ "message": "اصلاح", "description": "This is a verb. ex. 'Fix The Car'" }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "oldAttachmentsNeedFixDesc": { "message": "There are old file attachments in your vault that need to be fixed before you can rotate your account's encryption key." }, + "itemsTransferred": { + "message": "Items transferred" + }, "yourAccountsFingerprint": { "message": "عبارة بصمة حسابك", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -6453,6 +6465,32 @@ "bulkReinviteMessage": { "message": "Reinvited successfully" }, + "bulkReinviteSuccessToast": { + "message": "$COUNT$ users re-invited", + "placeholders": { + "count": { + "content": "$1", + "example": "12" + } + } + }, + "bulkReinviteLimitedSuccessToast": { + "message": "$LIMIT$ of $SELECTEDCOUNT$ users re-invited. $EXCLUDEDCOUNT$ were not invited due to the $LIMIT$ invite limit.", + "placeholders": { + "limit": { + "content": "$1", + "example": "4,000" + }, + "selectedCount": { + "content": "$2", + "example": "4,005" + }, + "excludedCount": { + "content": "$3", + "example": "5" + } + } + }, "bulkRemovedMessage": { "message": "Removed successfully" }, @@ -6773,8 +6811,8 @@ "vaultTimeoutRangeError": { "message": "Vault timeout is not within allowed range." }, - "disablePersonalVaultExport": { - "message": "Remove individual vault export" + "disableExport": { + "message": "Remove export" }, "disablePersonalVaultExportDescription": { "message": "Do not allow members to export data from their individual vault." @@ -7094,9 +7132,6 @@ "invalidVerificationCode": { "message": "رمز التحقق غير صالح" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "keyConnectorDomain": { "message": "Key Connector domain" }, @@ -7154,8 +7189,8 @@ "enabledSso": { "message": "SSO turned on" }, - "disabledSso": { - "message": "SSO turned on" + "ssoTurnedOff": { + "message": "SSO turned off" }, "emailMustLoginWithSso": { "message": "$EMAIL$ must login with Single Sign-on", @@ -9448,6 +9483,9 @@ "ssoLoginIsRequired": { "message": "SSO login is required" }, + "emailRequiredForSsoLogin": { + "message": "Email is required for SSO" + }, "selectedRegionFlag": { "message": "Selected region flag" }, @@ -11961,8 +11999,8 @@ "familiesMembership": { "message": "Families membership" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "planDescFamiliesV2": { "message": "Premium security for your family" @@ -12199,6 +12237,45 @@ } } }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "confirmNoSelectedCriticalApplicationsTitle": { "message": "No critical applications are selected" }, @@ -12207,5 +12284,139 @@ }, "userVerificationFailed": { "message": "User verification failed." + }, + "recoveryDeleteCiphersTitle": { + "message": "Delete unrecoverable vault items" + }, + "recoveryDeleteCiphersDesc": { + "message": "Some of your vault items could not be recovered. Do you want to delete these unrecoverable items from your vault?" + }, + "recoveryDeleteFoldersTitle": { + "message": "Delete unrecoverable folders" + }, + "recoveryDeleteFoldersDesc": { + "message": "Some of your folders could not be recovered. Do you want to delete these unrecoverable folders from your vault?" + }, + "recoveryReplacePrivateKeyTitle": { + "message": "Replace encryption key" + }, + "recoveryReplacePrivateKeyDesc": { + "message": "Your public-key encryption key pair could not be recovered. Do you want to replace your encryption key with a new key pair? This will require you to set up existing emergency-access and organization memberships again." + }, + "recoveryStepSyncTitle": { + "message": "Synchronizing data" + }, + "recoveryStepPrivateKeyTitle": { + "message": "Verifying encryption key integrity" + }, + "recoveryStepUserInfoTitle": { + "message": "Verifying user information" + }, + "recoveryStepCipherTitle": { + "message": "Verifying vault item integrity" + }, + "recoveryStepFoldersTitle": { + "message": "Verifying folder integrity" + }, + "dataRecoveryTitle": { + "message": "Data Recovery and Diagnostics" + }, + "dataRecoveryDescription": { + "message": "Use the data recovery tool to diagnose and repair issues with your account. After running diagnostics you have the option to save diagnostic logs for support and the option to repair any detected issues." + }, + "runDiagnostics": { + "message": "Run Diagnostics" + }, + "repairIssues": { + "message": "Repair Issues" + }, + "saveDiagnosticLogs": { + "message": "Save Diagnostic Logs" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On browser refresh." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On browser refresh" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/web/src/locales/az/messages.json b/apps/web/src/locales/az/messages.json index 409d2a4edde..77df36f5c45 100644 --- a/apps/web/src/locales/az/messages.json +++ b/apps/web/src/locales/az/messages.json @@ -3063,7 +3063,7 @@ "message": "Fayl qoşmaları üçün 1 GB şifrələnmiş saxlama sahəsi." }, "premiumSignUpStorageV2": { - "message": "$SIZE$ encrypted storage for file attachments.", + "message": "Fayl qoşmaları üçün $SIZE$ şifrələnmiş anbar sahəsi.", "placeholders": { "size": { "content": "$1", @@ -3134,13 +3134,13 @@ } }, "premiumSubscriptionEnded": { - "message": "Your Premium subscription ended" + "message": "Premium abunəliyiniz bitdi" }, "premiumSubscriptionEndedDesc": { - "message": "To regain access to your archive, restart your Premium subscription. If you edit details for an archived item before restarting, it'll be moved back into your vault." + "message": "Arxivinizə təkrar erişmək üçün Premium abunəliyinizi yenidən başladın. Təkrar başlatmazdan əvvəl arxivlənmiş elementin detallarına düzəliş etsəniz, həmin element seyfinizə daşınacaq." }, "restartPremium": { - "message": "Restart Premium" + "message": "\"Premium\"u yenidən başlat" }, "additionalStorageGb": { "message": "Əlavə saxlama sahəsi (GB)" @@ -4627,19 +4627,19 @@ "message": "Daha ətraflı" }, "migrationsFailed": { - "message": "An error occurred updating the encryption settings." + "message": "Şifrələmə ayarlarını güncəlləyərkən bir xəta baş verdi." }, "updateEncryptionSettingsTitle": { - "message": "Update your encryption settings" + "message": "Şifrələmə ayarlarınızı güncəlləyin" }, "updateEncryptionSettingsDesc": { - "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + "message": "Tövsiyə edilən yeni şifrələmə ayarları, hesabınızın təhlükəsizliyini artıracaq. İndi güncəlləmək üçün ana parolunuzu daxil edin." }, "confirmIdentityToContinue": { - "message": "Confirm your identity to continue" + "message": "Davam etmək üçün kimliyinizi təsdiqləyin" }, "enterYourMasterPassword": { - "message": "Enter your master password" + "message": "Ana parolunuzu daxil edin" }, "updateSettings": { "message": "Güncəlləmə ayarları" @@ -5169,9 +5169,21 @@ "message": "Düzəlt", "description": "This is a verb. ex. 'Fix The Car'" }, + "fixEncryption": { + "message": "Şifrələməni düzəlt" + }, + "fixEncryptionTooltip": { + "message": "Bu fayl, köhnə bir şifrələmə üsulunu istifadə edir." + }, + "attachmentUpdated": { + "message": "Qoşma güncəllənib" + }, "oldAttachmentsNeedFixDesc": { "message": "Hesabınızın şifrələmə açarını döndərməzdən əvvəl, seyfinizdəki köhnə fayl qoşmalarını düzəltməlisiniz." }, + "itemsTransferred": { + "message": "Elementlər köçürüldü" + }, "yourAccountsFingerprint": { "message": "Hesabınızın barmaq izi ifadəsi", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -6453,6 +6465,32 @@ "bulkReinviteMessage": { "message": "Uğurla yenidən dəvət edildi." }, + "bulkReinviteSuccessToast": { + "message": "$COUNT$ istifadəçi təkrar dəvət edildi", + "placeholders": { + "count": { + "content": "$1", + "example": "12" + } + } + }, + "bulkReinviteLimitedSuccessToast": { + "message": "$LIMIT$/$SELECTEDCOUNT$ istifadəçi təkrar dəvət edildi. $LIMIT$ dəvət limitinə görə $EXCLUDEDCOUNT$ dəvət edilmədi.", + "placeholders": { + "limit": { + "content": "$1", + "example": "4,000" + }, + "selectedCount": { + "content": "$2", + "example": "4,005" + }, + "excludedCount": { + "content": "$3", + "example": "5" + } + } + }, "bulkRemovedMessage": { "message": "Uğurla çıxarıldı" }, @@ -6773,8 +6811,8 @@ "vaultTimeoutRangeError": { "message": "Seyf bitmə vaxtı, icazə verilən aralıqda deyil." }, - "disablePersonalVaultExport": { - "message": "Fərdi seyfi xaricə köçürməni ləğv et" + "disableExport": { + "message": "Xaricə köçürməni sil" }, "disablePersonalVaultExportDescription": { "message": "Üzvlərin fərdi seyflərindən veriləri xaricə köçürməsinə icazə verilməsin." @@ -7094,9 +7132,6 @@ "invalidVerificationCode": { "message": "Yararsız doğrulama kodu" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "Aşağıdakı təşkilatların üzvləri üçün artıq ana parol tələb olunmur. Lütfən aşağıdakı domeni təşkilatınızın inzibatçısı ilə təsdiqləyin." - }, "keyConnectorDomain": { "message": "Key Connector domeni" }, @@ -7154,8 +7189,8 @@ "enabledSso": { "message": "SSO fəaldır" }, - "disabledSso": { - "message": "SSO sıradan çıxarılıb" + "ssoTurnedOff": { + "message": "SSO söndürüldü" }, "emailMustLoginWithSso": { "message": "$EMAIL$, Vahid Daxil olma üsulu ilə giriş etməlidir", @@ -9448,6 +9483,9 @@ "ssoLoginIsRequired": { "message": "SSO girişi tələb olunur" }, + "emailRequiredForSsoLogin": { + "message": "SSO üçün e-poçt tələb olunur" + }, "selectedRegionFlag": { "message": "Seçilmiş bölgə bayrağı" }, @@ -11961,8 +11999,8 @@ "familiesMembership": { "message": "Ailə üzvlüyü" }, - "planDescPremium": { - "message": "Tam onlayn təhlükəsizlik" + "advancedOnlineSecurity": { + "message": "Qabaqcıl onlayn təhlükəsizlik" }, "planDescFamiliesV2": { "message": "Ailəniz üçün Premium təhlükəsizlik" @@ -12199,6 +12237,45 @@ } } }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "confirmNoSelectedCriticalApplicationsTitle": { "message": "Heç bir kritik tətbiq seçilməyib" }, @@ -12207,5 +12284,139 @@ }, "userVerificationFailed": { "message": "İstifadəçi doğrulaması uğursuz oldu." + }, + "recoveryDeleteCiphersTitle": { + "message": "Geri qaytarıla bilməyən seyf elementlərini sil" + }, + "recoveryDeleteCiphersDesc": { + "message": "Seyfinizdəki bəzi elementlər geri qaytarıla bilmədi. Bu geri qaytarıla bilməyən elementləri seyfinizdən silmək istəyirsiniz?" + }, + "recoveryDeleteFoldersTitle": { + "message": "Geri qaytarıla bilməyən qovluqları sil" + }, + "recoveryDeleteFoldersDesc": { + "message": "Qovluqlarınızdan bəziləri geri qaytarıla bilmədi. Bu geri qaytarıla bilməyən qovluqları seyfinizdən silmək istəyirsiniz?" + }, + "recoveryReplacePrivateKeyTitle": { + "message": "Şifrələmə açarını əvəz et" + }, + "recoveryReplacePrivateKeyDesc": { + "message": "Public-key şifrələmə açar cütü geri qaytarıla bilmədi. Şifrələmə açarınızı yeni bir açar cütü ilə əvəz etmək istəyirsiniz? Bu proses, mövcud fövqəladə hal və təşkilat üzvlüyünüzü yenidən qurmağınızı tələb edir." + }, + "recoveryStepSyncTitle": { + "message": "Verilər sinxronlaşdırılır" + }, + "recoveryStepPrivateKeyTitle": { + "message": "Şifrələmə açarı tamlığı doğrulanır" + }, + "recoveryStepUserInfoTitle": { + "message": "İstifadəçi məlumatı doğrulanır" + }, + "recoveryStepCipherTitle": { + "message": "Seyf elementinin tamlığı doğrulanır" + }, + "recoveryStepFoldersTitle": { + "message": "Qovluq tamlığı doğrulanır" + }, + "dataRecoveryTitle": { + "message": "Veri geri qaytarma və diaqnostika" + }, + "dataRecoveryDescription": { + "message": "Hesabınızla bağlı problemləri diaqnostika etmək və bərpa etmək üçün veri geri qaytarma alətini istifadə edin. Diaqnostika prosesini işə saldıqdan sonra, dəstək üçün diaqnostika log-larını saxlama və aşkarlanan problemləri həll etmə seçimləriniz olacaq." + }, + "runDiagnostics": { + "message": "Diaqnostikanı işə sal" + }, + "repairIssues": { + "message": "Problemləri həll et" + }, + "saveDiagnosticLogs": { + "message": "Diaqnostika log-larını saxla" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "Bu ayar, təşkilatınız tərəfindən idarə olunur." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Təşkilatınız, maksimum seyf bitmə vaxtını $HOURS$ saat $MINUTES$ dəqiqə olaraq ayarladı.", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Təşkilatınız, seansın ilkin bitmə vaxtını Brauzer təzələnəndə olaraq təyin etdi." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maksimum bitmə vaxtı $HOURS$ saat $MINUTES$ dəqiqə dəyərini aşa bilməz", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "Brauzer təzələnəndə" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Vaxt bitmə əməliyyatınızı dəyişdirmək üçün bir kilid açma üsulu qurun." + }, + "leaveConfirmationDialogTitle": { + "message": "Tərk etmək istədiyinizə əminsiniz?" + }, + "leaveConfirmationDialogContentOne": { + "message": "Rədd cavabı versəniz, fərdi elementləriniz hesabınızda qalacaq, paylaşılan elementlərə və təşkilat özəlliklərinə erişimi itirəcəksiniz." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Erişimi təkrar qazanmaq üçün admininizlə əlaqə saxlayın." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Tərk et: $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "Seyfimi necə idarə edim?" + }, + "transferItemsToOrganizationTitle": { + "message": "Elementləri bura köçür: $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$, təhlükəsizlik və riayətlilik üçün bütün elementlərin təşkilata aid olmasını tələb edir. Elementlərinizin sahibliyini transfer etmək üçün qəbul edin.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Transferi qəbul et" + }, + "declineAndLeave": { + "message": "Rədd et və tərk et" + }, + "whyAmISeeingThis": { + "message": "Bunu niyə görürəm?" } } diff --git a/apps/web/src/locales/be/messages.json b/apps/web/src/locales/be/messages.json index d0de973f30f..c670f82a00d 100644 --- a/apps/web/src/locales/be/messages.json +++ b/apps/web/src/locales/be/messages.json @@ -5169,9 +5169,21 @@ "message": "Выправіць", "description": "This is a verb. ex. 'Fix The Car'" }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "oldAttachmentsNeedFixDesc": { "message": "У вашым сховішчы ёсць старыя далучаныя файлы, якія неабходна выправіць перад тым, як змяніць ключ шыфравання ўліковага запісу." }, + "itemsTransferred": { + "message": "Items transferred" + }, "yourAccountsFingerprint": { "message": "Фраза адбітку пальца вашага ўліковага запісу", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -6453,6 +6465,32 @@ "bulkReinviteMessage": { "message": "Паспяхова паўторна запрошаны." }, + "bulkReinviteSuccessToast": { + "message": "$COUNT$ users re-invited", + "placeholders": { + "count": { + "content": "$1", + "example": "12" + } + } + }, + "bulkReinviteLimitedSuccessToast": { + "message": "$LIMIT$ of $SELECTEDCOUNT$ users re-invited. $EXCLUDEDCOUNT$ were not invited due to the $LIMIT$ invite limit.", + "placeholders": { + "limit": { + "content": "$1", + "example": "4,000" + }, + "selectedCount": { + "content": "$2", + "example": "4,005" + }, + "excludedCount": { + "content": "$3", + "example": "5" + } + } + }, "bulkRemovedMessage": { "message": "Паспяхова выдалена" }, @@ -6773,8 +6811,8 @@ "vaultTimeoutRangeError": { "message": "Час чакання сховішча па-за межамі дазволенага дыяпазону." }, - "disablePersonalVaultExport": { - "message": "Выдаліць экспартаванне асабістага сховішча" + "disableExport": { + "message": "Remove export" }, "disablePersonalVaultExportDescription": { "message": "Не дазваляць удзельнікам экспартаваць даныя з іх індывідуальнага сховішча." @@ -7094,9 +7132,6 @@ "invalidVerificationCode": { "message": "Памылковы праверачны код" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "keyConnectorDomain": { "message": "Key Connector domain" }, @@ -7154,8 +7189,8 @@ "enabledSso": { "message": "SSO уключана" }, - "disabledSso": { - "message": "SSO адключаны" + "ssoTurnedOff": { + "message": "SSO turned off" }, "emailMustLoginWithSso": { "message": "$EMAIL$ must login with Single Sign-on", @@ -9448,6 +9483,9 @@ "ssoLoginIsRequired": { "message": "SSO login is required" }, + "emailRequiredForSsoLogin": { + "message": "Email is required for SSO" + }, "selectedRegionFlag": { "message": "Сцяг выбранага рэгіёна" }, @@ -11961,8 +11999,8 @@ "familiesMembership": { "message": "Families membership" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "planDescFamiliesV2": { "message": "Premium security for your family" @@ -12199,6 +12237,45 @@ } } }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "confirmNoSelectedCriticalApplicationsTitle": { "message": "No critical applications are selected" }, @@ -12207,5 +12284,139 @@ }, "userVerificationFailed": { "message": "User verification failed." + }, + "recoveryDeleteCiphersTitle": { + "message": "Delete unrecoverable vault items" + }, + "recoveryDeleteCiphersDesc": { + "message": "Some of your vault items could not be recovered. Do you want to delete these unrecoverable items from your vault?" + }, + "recoveryDeleteFoldersTitle": { + "message": "Delete unrecoverable folders" + }, + "recoveryDeleteFoldersDesc": { + "message": "Some of your folders could not be recovered. Do you want to delete these unrecoverable folders from your vault?" + }, + "recoveryReplacePrivateKeyTitle": { + "message": "Replace encryption key" + }, + "recoveryReplacePrivateKeyDesc": { + "message": "Your public-key encryption key pair could not be recovered. Do you want to replace your encryption key with a new key pair? This will require you to set up existing emergency-access and organization memberships again." + }, + "recoveryStepSyncTitle": { + "message": "Synchronizing data" + }, + "recoveryStepPrivateKeyTitle": { + "message": "Verifying encryption key integrity" + }, + "recoveryStepUserInfoTitle": { + "message": "Verifying user information" + }, + "recoveryStepCipherTitle": { + "message": "Verifying vault item integrity" + }, + "recoveryStepFoldersTitle": { + "message": "Verifying folder integrity" + }, + "dataRecoveryTitle": { + "message": "Data Recovery and Diagnostics" + }, + "dataRecoveryDescription": { + "message": "Use the data recovery tool to diagnose and repair issues with your account. After running diagnostics you have the option to save diagnostic logs for support and the option to repair any detected issues." + }, + "runDiagnostics": { + "message": "Run Diagnostics" + }, + "repairIssues": { + "message": "Repair Issues" + }, + "saveDiagnosticLogs": { + "message": "Save Diagnostic Logs" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On browser refresh." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On browser refresh" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/web/src/locales/bg/messages.json b/apps/web/src/locales/bg/messages.json index e058bdee1bd..3e3451084fc 100644 --- a/apps/web/src/locales/bg/messages.json +++ b/apps/web/src/locales/bg/messages.json @@ -5169,9 +5169,21 @@ "message": "Поправяне", "description": "This is a verb. ex. 'Fix The Car'" }, + "fixEncryption": { + "message": "Поправяне на шифроването" + }, + "fixEncryptionTooltip": { + "message": "Този файл използва остарял метод на шифроване." + }, + "attachmentUpdated": { + "message": "Прикаченият файл е актуализиран" + }, "oldAttachmentsNeedFixDesc": { "message": "В трезора ви има стари прикачени файлове. Те трябва да бъдат поправени, за да можете да смените ключа за шифриране на абонамента." }, + "itemsTransferred": { + "message": "Елементите са прехвърлени" + }, "yourAccountsFingerprint": { "message": "Уникална фраза, идентифицираща абонамента ви", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -6453,6 +6465,32 @@ "bulkReinviteMessage": { "message": "Успешно изпратени наново покани." }, + "bulkReinviteSuccessToast": { + "message": "$COUNT$ повторно поканени потребители", + "placeholders": { + "count": { + "content": "$1", + "example": "12" + } + } + }, + "bulkReinviteLimitedSuccessToast": { + "message": "$LIMIT$ от $SELECTEDCOUNT$ повторно поканени потребители. $EXCLUDEDCOUNT$ не бяха поканени, поради ограничението от $LIMIT$.", + "placeholders": { + "limit": { + "content": "$1", + "example": "4,000" + }, + "selectedCount": { + "content": "$2", + "example": "4,005" + }, + "excludedCount": { + "content": "$3", + "example": "5" + } + } + }, "bulkRemovedMessage": { "message": "Успешно премахване" }, @@ -6773,8 +6811,8 @@ "vaultTimeoutRangeError": { "message": "Времето за изчакване на трезора не е в позволения интервал." }, - "disablePersonalVaultExport": { - "message": "Забраняване на изнасянето на личния трезор" + "disableExport": { + "message": "Премахване на изнесеното" }, "disablePersonalVaultExportDescription": { "message": "Да не се разрешава на членовете да изнасят данните от собствените си трезори." @@ -7094,9 +7132,6 @@ "invalidVerificationCode": { "message": "Грешен код за потвърждаване" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "За членовете на следната организация вече не се изисква главна парола. Потвърдете домейна по-долу с администратора на организацията си." - }, "keyConnectorDomain": { "message": "Домейн на конектора за ключове" }, @@ -7154,7 +7189,7 @@ "enabledSso": { "message": "Еднократното удостоверяване е включено" }, - "disabledSso": { + "ssoTurnedOff": { "message": "Еднократното удостоверяване е изключено" }, "emailMustLoginWithSso": { @@ -9448,6 +9483,9 @@ "ssoLoginIsRequired": { "message": "Необходимо е вписване чрез еднократно удостоверяване" }, + "emailRequiredForSsoLogin": { + "message": "За еднократно удостоверяване е необходима е-поща" + }, "selectedRegionFlag": { "message": "Знаме на избрания регион" }, @@ -11961,8 +11999,8 @@ "familiesMembership": { "message": "Членство за семейства" }, - "planDescPremium": { - "message": "Пълна сигурност в Интернет" + "advancedOnlineSecurity": { + "message": "Разширена сигурност в Интернет" }, "planDescFamiliesV2": { "message": "Допълнителна защита за Вашето семейство" @@ -12199,6 +12237,45 @@ } } }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Вашата организация вече не използва главни пароли за вписване в Битуорден. За да продължите, потвърдете организацията и домейна." + }, + "continueWithLogIn": { + "message": "Продължаване с вписването" + }, + "doNotContinue": { + "message": "Не продължавам" + }, + "domain": { + "message": "Домейн" + }, + "keyConnectorDomainTooltip": { + "message": "Този домейн ще съхранява ключовете за шифроване на акаунта Ви, така че се уверете, че му имате доверие. Ако имате съмнения, свържете се с администратора си." + }, + "verifyYourOrganization": { + "message": "Потвърдете организацията си, за да се впишете" + }, + "organizationVerified": { + "message": "Организацията е потвърдена" + }, + "domainVerified": { + "message": "Домейнът е потвърден" + }, + "leaveOrganizationContent": { + "message": "Ако не потвърдите организацията, достъпът Ви до нея ще бъде преустановен." + }, + "leaveNow": { + "message": "Напускане сега" + }, + "verifyYourDomainToLogin": { + "message": "Потвърдете домейна си, за да се впишете" + }, + "verifyYourDomainDescription": { + "message": "За да продължите с вписването, потвърдете този домейн." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "За да продължите с вписването, потвърдете организацията и домейна." + }, "confirmNoSelectedCriticalApplicationsTitle": { "message": "Няма избрани важни приложения" }, @@ -12207,5 +12284,139 @@ }, "userVerificationFailed": { "message": "Проверката на потребителя беше неуспешна." + }, + "recoveryDeleteCiphersTitle": { + "message": "Изтриване на невъзстановимите елементи от трезора" + }, + "recoveryDeleteCiphersDesc": { + "message": "Някои от елементите в трезора не могат да бъдат възстановени. Искате ли да ги изтриете от трезора си?" + }, + "recoveryDeleteFoldersTitle": { + "message": "Изтриване на невъзстановимите папки" + }, + "recoveryDeleteFoldersDesc": { + "message": "Някои от папките не могат да бъдат възстановени. Искате ли да ги изтриете от трезора си?" + }, + "recoveryReplacePrivateKeyTitle": { + "message": "Подмяна на ключа за шифроване" + }, + "recoveryReplacePrivateKeyDesc": { + "message": "Вашата двойка ключове за шифроване не може да бъде възстановена. Искате ли да замените ключа си за шифроване с нова двойка? Ще трябва да настроите аварийния достъп отново, както и членствата си в организации." + }, + "recoveryStepSyncTitle": { + "message": "Синхронизиране на данните…" + }, + "recoveryStepPrivateKeyTitle": { + "message": "Потвърждаване на целостта на ключа за шифроване" + }, + "recoveryStepUserInfoTitle": { + "message": "Потвърждаване на информацията за потребителя" + }, + "recoveryStepCipherTitle": { + "message": "Потвърждаване на целостта на данните в трезора" + }, + "recoveryStepFoldersTitle": { + "message": "Потвърждаване на целостта на папките" + }, + "dataRecoveryTitle": { + "message": "Възстановяване и диагностика на данните" + }, + "dataRecoveryDescription": { + "message": "Използвайте инструмента за възстановяване на данните, за да направите проверка и поправка, ако има проблеми с акаунта Ви. След изпълнението на диагностиката ще имате възможност да запазите нейния журнал, в случай, че са нужни на поддръжката, както и ще можете да опитате да поправите откритите проблеми." + }, + "runDiagnostics": { + "message": "Стартиране на диагностиката" + }, + "repairIssues": { + "message": "Поправяне на проблемите" + }, + "saveDiagnosticLogs": { + "message": "Запазване на журнала от диагностиката" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "Тази настройка се управлява от организацията Ви." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Организацията Ви е настроила максималното разрешено време за достъп на [%1$i] час(а) и [%2$i] минути.", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Организацията Ви е настроила стандартното разрешено време за достъп да бъде до презареждане на страницата в браузъра." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Максималното време на достъп не може да превишава $HOURS$ час(а) и $MINUTES$ минути", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "При презареждане на страницата в браузъра" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Задайте метод за отключване, за да може да промените действието при изтичане на времето за достъп" + }, + "leaveConfirmationDialogTitle": { + "message": "Наистина ли искате да напуснете?" + }, + "leaveConfirmationDialogContentOne": { + "message": "Ако откажете, Вашите собствени елементи ще останат в акаунта Ви, но ще загубите достъп до споделените елементи и функционалностите на организацията." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Свържете се с администратор, за да получите достъп отново." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Напускане на $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "Как да управлявам трезора си?" + }, + "transferItemsToOrganizationTitle": { + "message": "Прехвърляне на елементи към $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ изисква всички елементи да станат притежание на организацията, за по-добра сигурност и съвместимост. Изберете, че приемате, за да прехвърлите собствеността на елементите си към организацията.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Приемане на прехвърлянето" + }, + "declineAndLeave": { + "message": "Отказване и напускане" + }, + "whyAmISeeingThis": { + "message": "Защо виждам това?" } } diff --git a/apps/web/src/locales/bn/messages.json b/apps/web/src/locales/bn/messages.json index 915f2bff970..4e8c53b1598 100644 --- a/apps/web/src/locales/bn/messages.json +++ b/apps/web/src/locales/bn/messages.json @@ -5169,9 +5169,21 @@ "message": "Fix", "description": "This is a verb. ex. 'Fix The Car'" }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "oldAttachmentsNeedFixDesc": { "message": "There are old file attachments in your vault that need to be fixed before you can rotate your account's encryption key." }, + "itemsTransferred": { + "message": "Items transferred" + }, "yourAccountsFingerprint": { "message": "Your account's fingerprint phrase", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -6453,6 +6465,32 @@ "bulkReinviteMessage": { "message": "Reinvited successfully" }, + "bulkReinviteSuccessToast": { + "message": "$COUNT$ users re-invited", + "placeholders": { + "count": { + "content": "$1", + "example": "12" + } + } + }, + "bulkReinviteLimitedSuccessToast": { + "message": "$LIMIT$ of $SELECTEDCOUNT$ users re-invited. $EXCLUDEDCOUNT$ were not invited due to the $LIMIT$ invite limit.", + "placeholders": { + "limit": { + "content": "$1", + "example": "4,000" + }, + "selectedCount": { + "content": "$2", + "example": "4,005" + }, + "excludedCount": { + "content": "$3", + "example": "5" + } + } + }, "bulkRemovedMessage": { "message": "Removed successfully" }, @@ -6773,8 +6811,8 @@ "vaultTimeoutRangeError": { "message": "Vault timeout is not within allowed range." }, - "disablePersonalVaultExport": { - "message": "Remove individual vault export" + "disableExport": { + "message": "Remove export" }, "disablePersonalVaultExportDescription": { "message": "Do not allow members to export data from their individual vault." @@ -7094,9 +7132,6 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "keyConnectorDomain": { "message": "Key Connector domain" }, @@ -7154,8 +7189,8 @@ "enabledSso": { "message": "SSO turned on" }, - "disabledSso": { - "message": "SSO turned on" + "ssoTurnedOff": { + "message": "SSO turned off" }, "emailMustLoginWithSso": { "message": "$EMAIL$ must login with Single Sign-on", @@ -9448,6 +9483,9 @@ "ssoLoginIsRequired": { "message": "SSO login is required" }, + "emailRequiredForSsoLogin": { + "message": "Email is required for SSO" + }, "selectedRegionFlag": { "message": "Selected region flag" }, @@ -11961,8 +11999,8 @@ "familiesMembership": { "message": "Families membership" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "planDescFamiliesV2": { "message": "Premium security for your family" @@ -12199,6 +12237,45 @@ } } }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "confirmNoSelectedCriticalApplicationsTitle": { "message": "No critical applications are selected" }, @@ -12207,5 +12284,139 @@ }, "userVerificationFailed": { "message": "User verification failed." + }, + "recoveryDeleteCiphersTitle": { + "message": "Delete unrecoverable vault items" + }, + "recoveryDeleteCiphersDesc": { + "message": "Some of your vault items could not be recovered. Do you want to delete these unrecoverable items from your vault?" + }, + "recoveryDeleteFoldersTitle": { + "message": "Delete unrecoverable folders" + }, + "recoveryDeleteFoldersDesc": { + "message": "Some of your folders could not be recovered. Do you want to delete these unrecoverable folders from your vault?" + }, + "recoveryReplacePrivateKeyTitle": { + "message": "Replace encryption key" + }, + "recoveryReplacePrivateKeyDesc": { + "message": "Your public-key encryption key pair could not be recovered. Do you want to replace your encryption key with a new key pair? This will require you to set up existing emergency-access and organization memberships again." + }, + "recoveryStepSyncTitle": { + "message": "Synchronizing data" + }, + "recoveryStepPrivateKeyTitle": { + "message": "Verifying encryption key integrity" + }, + "recoveryStepUserInfoTitle": { + "message": "Verifying user information" + }, + "recoveryStepCipherTitle": { + "message": "Verifying vault item integrity" + }, + "recoveryStepFoldersTitle": { + "message": "Verifying folder integrity" + }, + "dataRecoveryTitle": { + "message": "Data Recovery and Diagnostics" + }, + "dataRecoveryDescription": { + "message": "Use the data recovery tool to diagnose and repair issues with your account. After running diagnostics you have the option to save diagnostic logs for support and the option to repair any detected issues." + }, + "runDiagnostics": { + "message": "Run Diagnostics" + }, + "repairIssues": { + "message": "Repair Issues" + }, + "saveDiagnosticLogs": { + "message": "Save Diagnostic Logs" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On browser refresh." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On browser refresh" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/web/src/locales/bs/messages.json b/apps/web/src/locales/bs/messages.json index 4c3005bda33..defb970c237 100644 --- a/apps/web/src/locales/bs/messages.json +++ b/apps/web/src/locales/bs/messages.json @@ -5169,9 +5169,21 @@ "message": "Fix", "description": "This is a verb. ex. 'Fix The Car'" }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "oldAttachmentsNeedFixDesc": { "message": "There are old file attachments in your vault that need to be fixed before you can rotate your account's encryption key." }, + "itemsTransferred": { + "message": "Items transferred" + }, "yourAccountsFingerprint": { "message": "Your account's fingerprint phrase", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -6453,6 +6465,32 @@ "bulkReinviteMessage": { "message": "Reinvited successfully" }, + "bulkReinviteSuccessToast": { + "message": "$COUNT$ users re-invited", + "placeholders": { + "count": { + "content": "$1", + "example": "12" + } + } + }, + "bulkReinviteLimitedSuccessToast": { + "message": "$LIMIT$ of $SELECTEDCOUNT$ users re-invited. $EXCLUDEDCOUNT$ were not invited due to the $LIMIT$ invite limit.", + "placeholders": { + "limit": { + "content": "$1", + "example": "4,000" + }, + "selectedCount": { + "content": "$2", + "example": "4,005" + }, + "excludedCount": { + "content": "$3", + "example": "5" + } + } + }, "bulkRemovedMessage": { "message": "Removed successfully" }, @@ -6773,8 +6811,8 @@ "vaultTimeoutRangeError": { "message": "Vault timeout is not within allowed range." }, - "disablePersonalVaultExport": { - "message": "Remove individual vault export" + "disableExport": { + "message": "Remove export" }, "disablePersonalVaultExportDescription": { "message": "Do not allow members to export data from their individual vault." @@ -7094,9 +7132,6 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "keyConnectorDomain": { "message": "Key Connector domain" }, @@ -7154,8 +7189,8 @@ "enabledSso": { "message": "SSO turned on" }, - "disabledSso": { - "message": "SSO turned on" + "ssoTurnedOff": { + "message": "SSO turned off" }, "emailMustLoginWithSso": { "message": "$EMAIL$ must login with Single Sign-on", @@ -9448,6 +9483,9 @@ "ssoLoginIsRequired": { "message": "SSO login is required" }, + "emailRequiredForSsoLogin": { + "message": "Email is required for SSO" + }, "selectedRegionFlag": { "message": "Selected region flag" }, @@ -11961,8 +11999,8 @@ "familiesMembership": { "message": "Families membership" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "planDescFamiliesV2": { "message": "Premium security for your family" @@ -12199,6 +12237,45 @@ } } }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "confirmNoSelectedCriticalApplicationsTitle": { "message": "No critical applications are selected" }, @@ -12207,5 +12284,139 @@ }, "userVerificationFailed": { "message": "User verification failed." + }, + "recoveryDeleteCiphersTitle": { + "message": "Delete unrecoverable vault items" + }, + "recoveryDeleteCiphersDesc": { + "message": "Some of your vault items could not be recovered. Do you want to delete these unrecoverable items from your vault?" + }, + "recoveryDeleteFoldersTitle": { + "message": "Delete unrecoverable folders" + }, + "recoveryDeleteFoldersDesc": { + "message": "Some of your folders could not be recovered. Do you want to delete these unrecoverable folders from your vault?" + }, + "recoveryReplacePrivateKeyTitle": { + "message": "Replace encryption key" + }, + "recoveryReplacePrivateKeyDesc": { + "message": "Your public-key encryption key pair could not be recovered. Do you want to replace your encryption key with a new key pair? This will require you to set up existing emergency-access and organization memberships again." + }, + "recoveryStepSyncTitle": { + "message": "Synchronizing data" + }, + "recoveryStepPrivateKeyTitle": { + "message": "Verifying encryption key integrity" + }, + "recoveryStepUserInfoTitle": { + "message": "Verifying user information" + }, + "recoveryStepCipherTitle": { + "message": "Verifying vault item integrity" + }, + "recoveryStepFoldersTitle": { + "message": "Verifying folder integrity" + }, + "dataRecoveryTitle": { + "message": "Data Recovery and Diagnostics" + }, + "dataRecoveryDescription": { + "message": "Use the data recovery tool to diagnose and repair issues with your account. After running diagnostics you have the option to save diagnostic logs for support and the option to repair any detected issues." + }, + "runDiagnostics": { + "message": "Run Diagnostics" + }, + "repairIssues": { + "message": "Repair Issues" + }, + "saveDiagnosticLogs": { + "message": "Save Diagnostic Logs" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On browser refresh." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On browser refresh" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/web/src/locales/ca/messages.json b/apps/web/src/locales/ca/messages.json index 8b3e85e936e..9b8bb1fce7d 100644 --- a/apps/web/src/locales/ca/messages.json +++ b/apps/web/src/locales/ca/messages.json @@ -5169,9 +5169,21 @@ "message": "Corregeix", "description": "This is a verb. ex. 'Fix The Car'" }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "oldAttachmentsNeedFixDesc": { "message": "Hi ha arxius adjunts antics a la vostra caixa forta que s'han de corregir abans de poder rotar la clau de xifratge del vostre compte." }, + "itemsTransferred": { + "message": "Items transferred" + }, "yourAccountsFingerprint": { "message": "Frase d'empremta digital del vostre compte", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -6453,6 +6465,32 @@ "bulkReinviteMessage": { "message": "Tornat a convidar correctament." }, + "bulkReinviteSuccessToast": { + "message": "$COUNT$ users re-invited", + "placeholders": { + "count": { + "content": "$1", + "example": "12" + } + } + }, + "bulkReinviteLimitedSuccessToast": { + "message": "$LIMIT$ of $SELECTEDCOUNT$ users re-invited. $EXCLUDEDCOUNT$ were not invited due to the $LIMIT$ invite limit.", + "placeholders": { + "limit": { + "content": "$1", + "example": "4,000" + }, + "selectedCount": { + "content": "$2", + "example": "4,005" + }, + "excludedCount": { + "content": "$3", + "example": "5" + } + } + }, "bulkRemovedMessage": { "message": "Suprimit correctament" }, @@ -6773,8 +6811,8 @@ "vaultTimeoutRangeError": { "message": "El temps d'espera de la caixa forta no es troba dins de l'interval permès." }, - "disablePersonalVaultExport": { - "message": "Inhabilita l'exportació de la caixa forta personal" + "disableExport": { + "message": "Remove export" }, "disablePersonalVaultExportDescription": { "message": "No permetes que els membres exporten dades des de la seua caixa forta individual." @@ -7094,9 +7132,6 @@ "invalidVerificationCode": { "message": "Codi de verificació no vàlid" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "keyConnectorDomain": { "message": "Key Connector domain" }, @@ -7154,8 +7189,8 @@ "enabledSso": { "message": "SSO habilitat" }, - "disabledSso": { - "message": "SSO inhabilitat" + "ssoTurnedOff": { + "message": "SSO turned off" }, "emailMustLoginWithSso": { "message": "$EMAIL$ must login with Single Sign-on", @@ -9448,6 +9483,9 @@ "ssoLoginIsRequired": { "message": "Inici de sessió SSO necessari" }, + "emailRequiredForSsoLogin": { + "message": "Email is required for SSO" + }, "selectedRegionFlag": { "message": "Bandera de la regió seleccionada" }, @@ -11961,8 +11999,8 @@ "familiesMembership": { "message": "Families membership" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "planDescFamiliesV2": { "message": "Premium security for your family" @@ -12199,6 +12237,45 @@ } } }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "confirmNoSelectedCriticalApplicationsTitle": { "message": "No critical applications are selected" }, @@ -12207,5 +12284,139 @@ }, "userVerificationFailed": { "message": "User verification failed." + }, + "recoveryDeleteCiphersTitle": { + "message": "Delete unrecoverable vault items" + }, + "recoveryDeleteCiphersDesc": { + "message": "Some of your vault items could not be recovered. Do you want to delete these unrecoverable items from your vault?" + }, + "recoveryDeleteFoldersTitle": { + "message": "Delete unrecoverable folders" + }, + "recoveryDeleteFoldersDesc": { + "message": "Some of your folders could not be recovered. Do you want to delete these unrecoverable folders from your vault?" + }, + "recoveryReplacePrivateKeyTitle": { + "message": "Replace encryption key" + }, + "recoveryReplacePrivateKeyDesc": { + "message": "Your public-key encryption key pair could not be recovered. Do you want to replace your encryption key with a new key pair? This will require you to set up existing emergency-access and organization memberships again." + }, + "recoveryStepSyncTitle": { + "message": "Synchronizing data" + }, + "recoveryStepPrivateKeyTitle": { + "message": "Verifying encryption key integrity" + }, + "recoveryStepUserInfoTitle": { + "message": "Verifying user information" + }, + "recoveryStepCipherTitle": { + "message": "Verifying vault item integrity" + }, + "recoveryStepFoldersTitle": { + "message": "Verifying folder integrity" + }, + "dataRecoveryTitle": { + "message": "Data Recovery and Diagnostics" + }, + "dataRecoveryDescription": { + "message": "Use the data recovery tool to diagnose and repair issues with your account. After running diagnostics you have the option to save diagnostic logs for support and the option to repair any detected issues." + }, + "runDiagnostics": { + "message": "Run Diagnostics" + }, + "repairIssues": { + "message": "Repair Issues" + }, + "saveDiagnosticLogs": { + "message": "Save Diagnostic Logs" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On browser refresh." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On browser refresh" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/web/src/locales/cs/messages.json b/apps/web/src/locales/cs/messages.json index aceb40291df..4eacb212138 100644 --- a/apps/web/src/locales/cs/messages.json +++ b/apps/web/src/locales/cs/messages.json @@ -5169,9 +5169,21 @@ "message": "Opravit", "description": "This is a verb. ex. 'Fix The Car'" }, + "fixEncryption": { + "message": "Opravit šifrování" + }, + "fixEncryptionTooltip": { + "message": "Tento soubor používá zastaralou šifrovací metodu." + }, + "attachmentUpdated": { + "message": "Příloha byla aktualizována" + }, "oldAttachmentsNeedFixDesc": { "message": "Ve Vašem trezoru jsou staré přílohy vyžadující opravu před změnou šifrovacího klíče k Vašemu účtu." }, + "itemsTransferred": { + "message": "Převedené položky" + }, "yourAccountsFingerprint": { "message": "Fráze otisku prstu Vašeho účtu", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -6453,6 +6465,32 @@ "bulkReinviteMessage": { "message": "Znovu úspěšně pozváno" }, + "bulkReinviteSuccessToast": { + "message": "$COUNT$ uživatelů bylo znovu pozváno", + "placeholders": { + "count": { + "content": "$1", + "example": "12" + } + } + }, + "bulkReinviteLimitedSuccessToast": { + "message": "$LIMIT$ z $SELECTEDCOUNT$ uživatelů bylo znovu pozváno. $EXCLUDEDCOUNT$ nebylo pozváno z důvodu limitu pozvánky: $LIMIT$.", + "placeholders": { + "limit": { + "content": "$1", + "example": "4,000" + }, + "selectedCount": { + "content": "$2", + "example": "4,005" + }, + "excludedCount": { + "content": "$3", + "example": "5" + } + } + }, "bulkRemovedMessage": { "message": "Úspěšně odebráno" }, @@ -6773,8 +6811,8 @@ "vaultTimeoutRangeError": { "message": "Časový limit trezoru není v rámci povoleného rozsahu." }, - "disablePersonalVaultExport": { - "message": "Odebrat osobní export trezoru" + "disableExport": { + "message": "Odebrat export" }, "disablePersonalVaultExportDescription": { "message": "Nedovolí, aby členové exportovali svá osobní data trezoru." @@ -7094,9 +7132,6 @@ "invalidVerificationCode": { "message": "Neplatný ověřovací kód" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "Hlavní heslo již není vyžadováno pro členy následující organizace. Potvrďte níže uvedenou doménu u správce Vaší organizace." - }, "keyConnectorDomain": { "message": "Doména Key Connectoru" }, @@ -7154,7 +7189,7 @@ "enabledSso": { "message": "SSO je zapnuto" }, - "disabledSso": { + "ssoTurnedOff": { "message": "SSO je vypnuto" }, "emailMustLoginWithSso": { @@ -9448,6 +9483,9 @@ "ssoLoginIsRequired": { "message": "Přihlášení SSO je povinné" }, + "emailRequiredForSsoLogin": { + "message": "Pro SSO je vyžadován e-mail" + }, "selectedRegionFlag": { "message": "Vlajka zvoleného regionu" }, @@ -11961,8 +11999,8 @@ "familiesMembership": { "message": "Členství v rodinách" }, - "planDescPremium": { - "message": "Dokončit online zabezpečení" + "advancedOnlineSecurity": { + "message": "Pokročilé zabezpečení online" }, "planDescFamiliesV2": { "message": "Prémiové zabezpečení pro Vaši rodinu" @@ -12199,6 +12237,45 @@ } } }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Vaše organizace již k přihlášení do Bitwardenu nepoužívá hlavní hesla. Chcete-li pokračovat, ověřte organizaci a doménu." + }, + "continueWithLogIn": { + "message": "Pokračovat s přihlášením" + }, + "doNotContinue": { + "message": "Nepokračovat" + }, + "domain": { + "message": "Doména" + }, + "keyConnectorDomainTooltip": { + "message": "Tato doména uloží šifrovací klíče Vašeho účtu, takže se ujistěte, že jí věříte. Pokud si nejste jisti, kontaktujte Vašeho správce." + }, + "verifyYourOrganization": { + "message": "Ověřte svou organizaci pro přihlášení" + }, + "organizationVerified": { + "message": "Organizace byla ověřena" + }, + "domainVerified": { + "message": "Doména byla ověřena" + }, + "leaveOrganizationContent": { + "message": "Pokud neověříte svou organizaci, Váš přístup k organizaci bude zrušen." + }, + "leaveNow": { + "message": "Opustit hned" + }, + "verifyYourDomainToLogin": { + "message": "Ověřte svou doménu pro přihlášení" + }, + "verifyYourDomainDescription": { + "message": "Chcete-li pokračovat v přihlášení, ověřte tuto doménu." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "Chcete-li pokračovat v přihlášení, ověřte organizaci a doménu." + }, "confirmNoSelectedCriticalApplicationsTitle": { "message": "Nejsou vybrány žádné kritické aplikace" }, @@ -12207,5 +12284,139 @@ }, "userVerificationFailed": { "message": "Ověření uživatele se nezdařilo." + }, + "recoveryDeleteCiphersTitle": { + "message": "Smazat neobnovitelné položky trezoru" + }, + "recoveryDeleteCiphersDesc": { + "message": "Některé z Vašich trezorů nelze obnovit. Chcete smazat tyto neobnovitelné položky z Vašeho trezoru?" + }, + "recoveryDeleteFoldersTitle": { + "message": "Smazat neobnovitelné složky" + }, + "recoveryDeleteFoldersDesc": { + "message": "Některé z Vašich složek nelze obnovit. Chcete smazat tyto neobnovitelné složky z Vašeho trezoru?" + }, + "recoveryReplacePrivateKeyTitle": { + "message": "Nahradit šifrovací klíč" + }, + "recoveryReplacePrivateKeyDesc": { + "message": "Nelze obnovit Váš pár šifrovacích klíčů veřejného klíče. Chcete nahradit Váš šifrovací klíč novým párem klíčů? To bude vyžadovat, abyste znovu nastavili stávající nouzový přístup a členství v organizaci." + }, + "recoveryStepSyncTitle": { + "message": "Synchronizování dat" + }, + "recoveryStepPrivateKeyTitle": { + "message": "Ověřování integrity šifrovacího klíče" + }, + "recoveryStepUserInfoTitle": { + "message": "Ověřování informací o uživateli" + }, + "recoveryStepCipherTitle": { + "message": "Ověřování integrity položky trezoru" + }, + "recoveryStepFoldersTitle": { + "message": "Ověřování integrity složky" + }, + "dataRecoveryTitle": { + "message": "Obnova dat a diagnostika" + }, + "dataRecoveryDescription": { + "message": "Použijte nástroj pro obnovení dat k diagnostice a opravě problémů s Vaším účtem. Po spuštění diagnostiky máte možnost uložit diagnostické protokoly pro podporu a možnost opravit všechny zjištěné problémy." + }, + "runDiagnostics": { + "message": "Spustit diagnostiku" + }, + "repairIssues": { + "message": "Problémy s opravou" + }, + "saveDiagnosticLogs": { + "message": "Uložit diagnostické protokoly" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "Tato nastavení je spravováno Vaší organizací." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Vaše organizace nastavila maximální časový limit relace na $HOURS$ hodin a $MINUTES$ minut.", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Vaše organizace nastavila výchozí časový limit relace na Při obnovení prohlížeče." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximální časový limit nesmí překročit $HOURS$ hodin a $MINUTES$ minut", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "Při obnovení prohlížeče" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Nastavte metodu odemknutí, abyste změnili akci při vypršení časového limitu" + }, + "leaveConfirmationDialogTitle": { + "message": "Opravdu chcete odejít?" + }, + "leaveConfirmationDialogContentOne": { + "message": "Odmítnutím zůstanou Vaše osobní položky ve Vašem účtu, ale ztratíte přístup ke sdíleným položkám a funkcím organizace." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Obraťte se na svého správce, abyste znovu získali přístup." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Opustit $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "Jak mohu spravovat svůj trezor?" + }, + "transferItemsToOrganizationTitle": { + "message": "Přenést položky do $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ vyžaduje, aby byly všechny položky vlastněny organizací z důvodu bezpečnosti a shody. Klepnutím na tlačítko pro převod vlastnictví Vašich položek.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Přijmout převod" + }, + "declineAndLeave": { + "message": "Odmítnout a opustit" + }, + "whyAmISeeingThis": { + "message": "Proč se mi toto zobrazuje?" } } diff --git a/apps/web/src/locales/cy/messages.json b/apps/web/src/locales/cy/messages.json index 108b1ebe23a..f8839744e98 100644 --- a/apps/web/src/locales/cy/messages.json +++ b/apps/web/src/locales/cy/messages.json @@ -5169,9 +5169,21 @@ "message": "Fix", "description": "This is a verb. ex. 'Fix The Car'" }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "oldAttachmentsNeedFixDesc": { "message": "There are old file attachments in your vault that need to be fixed before you can rotate your account's encryption key." }, + "itemsTransferred": { + "message": "Items transferred" + }, "yourAccountsFingerprint": { "message": "Your account's fingerprint phrase", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -6453,6 +6465,32 @@ "bulkReinviteMessage": { "message": "Reinvited successfully" }, + "bulkReinviteSuccessToast": { + "message": "$COUNT$ users re-invited", + "placeholders": { + "count": { + "content": "$1", + "example": "12" + } + } + }, + "bulkReinviteLimitedSuccessToast": { + "message": "$LIMIT$ of $SELECTEDCOUNT$ users re-invited. $EXCLUDEDCOUNT$ were not invited due to the $LIMIT$ invite limit.", + "placeholders": { + "limit": { + "content": "$1", + "example": "4,000" + }, + "selectedCount": { + "content": "$2", + "example": "4,005" + }, + "excludedCount": { + "content": "$3", + "example": "5" + } + } + }, "bulkRemovedMessage": { "message": "Removed successfully" }, @@ -6773,8 +6811,8 @@ "vaultTimeoutRangeError": { "message": "Vault timeout is not within allowed range." }, - "disablePersonalVaultExport": { - "message": "Remove individual vault export" + "disableExport": { + "message": "Remove export" }, "disablePersonalVaultExportDescription": { "message": "Do not allow members to export data from their individual vault." @@ -7094,9 +7132,6 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "keyConnectorDomain": { "message": "Key Connector domain" }, @@ -7154,8 +7189,8 @@ "enabledSso": { "message": "SSO turned on" }, - "disabledSso": { - "message": "SSO turned on" + "ssoTurnedOff": { + "message": "SSO turned off" }, "emailMustLoginWithSso": { "message": "$EMAIL$ must login with Single Sign-on", @@ -9448,6 +9483,9 @@ "ssoLoginIsRequired": { "message": "SSO login is required" }, + "emailRequiredForSsoLogin": { + "message": "Email is required for SSO" + }, "selectedRegionFlag": { "message": "Selected region flag" }, @@ -11961,8 +11999,8 @@ "familiesMembership": { "message": "Families membership" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "planDescFamiliesV2": { "message": "Premium security for your family" @@ -12199,6 +12237,45 @@ } } }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "confirmNoSelectedCriticalApplicationsTitle": { "message": "No critical applications are selected" }, @@ -12207,5 +12284,139 @@ }, "userVerificationFailed": { "message": "User verification failed." + }, + "recoveryDeleteCiphersTitle": { + "message": "Delete unrecoverable vault items" + }, + "recoveryDeleteCiphersDesc": { + "message": "Some of your vault items could not be recovered. Do you want to delete these unrecoverable items from your vault?" + }, + "recoveryDeleteFoldersTitle": { + "message": "Delete unrecoverable folders" + }, + "recoveryDeleteFoldersDesc": { + "message": "Some of your folders could not be recovered. Do you want to delete these unrecoverable folders from your vault?" + }, + "recoveryReplacePrivateKeyTitle": { + "message": "Replace encryption key" + }, + "recoveryReplacePrivateKeyDesc": { + "message": "Your public-key encryption key pair could not be recovered. Do you want to replace your encryption key with a new key pair? This will require you to set up existing emergency-access and organization memberships again." + }, + "recoveryStepSyncTitle": { + "message": "Synchronizing data" + }, + "recoveryStepPrivateKeyTitle": { + "message": "Verifying encryption key integrity" + }, + "recoveryStepUserInfoTitle": { + "message": "Verifying user information" + }, + "recoveryStepCipherTitle": { + "message": "Verifying vault item integrity" + }, + "recoveryStepFoldersTitle": { + "message": "Verifying folder integrity" + }, + "dataRecoveryTitle": { + "message": "Data Recovery and Diagnostics" + }, + "dataRecoveryDescription": { + "message": "Use the data recovery tool to diagnose and repair issues with your account. After running diagnostics you have the option to save diagnostic logs for support and the option to repair any detected issues." + }, + "runDiagnostics": { + "message": "Run Diagnostics" + }, + "repairIssues": { + "message": "Repair Issues" + }, + "saveDiagnosticLogs": { + "message": "Save Diagnostic Logs" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On browser refresh." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On browser refresh" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/web/src/locales/da/messages.json b/apps/web/src/locales/da/messages.json index 904dc69a0e3..3b583416b25 100644 --- a/apps/web/src/locales/da/messages.json +++ b/apps/web/src/locales/da/messages.json @@ -5169,9 +5169,21 @@ "message": "Reparér", "description": "This is a verb. ex. 'Fix The Car'" }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "oldAttachmentsNeedFixDesc": { "message": "Der er gamle filvedhæftninger i din boks, der skal repareres, før du kan rotere din kontos krypteringsnøgle." }, + "itemsTransferred": { + "message": "Items transferred" + }, "yourAccountsFingerprint": { "message": "Din kontos fingeraftrykssætning", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -6453,6 +6465,32 @@ "bulkReinviteMessage": { "message": "Hermed geninviteret." }, + "bulkReinviteSuccessToast": { + "message": "$COUNT$ users re-invited", + "placeholders": { + "count": { + "content": "$1", + "example": "12" + } + } + }, + "bulkReinviteLimitedSuccessToast": { + "message": "$LIMIT$ of $SELECTEDCOUNT$ users re-invited. $EXCLUDEDCOUNT$ were not invited due to the $LIMIT$ invite limit.", + "placeholders": { + "limit": { + "content": "$1", + "example": "4,000" + }, + "selectedCount": { + "content": "$2", + "example": "4,005" + }, + "excludedCount": { + "content": "$3", + "example": "5" + } + } + }, "bulkRemovedMessage": { "message": "Hermed fjernet" }, @@ -6773,8 +6811,8 @@ "vaultTimeoutRangeError": { "message": "Boks-timeout er ikke inden for det tilladte interval." }, - "disablePersonalVaultExport": { - "message": "Fjern individuel bokseksport" + "disableExport": { + "message": "Remove export" }, "disablePersonalVaultExportDescription": { "message": "Tillad ikke medlemmer at eksportere data fra deres individuelle bokse." @@ -7094,9 +7132,6 @@ "invalidVerificationCode": { "message": "Ugyldig bekræftelseskode" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "keyConnectorDomain": { "message": "Key Connector domain" }, @@ -7154,8 +7189,8 @@ "enabledSso": { "message": "SSO aktiveret" }, - "disabledSso": { - "message": "SSO deaktiveret" + "ssoTurnedOff": { + "message": "SSO turned off" }, "emailMustLoginWithSso": { "message": "$EMAIL$ must login with Single Sign-on", @@ -9448,6 +9483,9 @@ "ssoLoginIsRequired": { "message": "SSO-login er obligatorisk" }, + "emailRequiredForSsoLogin": { + "message": "Email is required for SSO" + }, "selectedRegionFlag": { "message": "Valgte områdeflag" }, @@ -11961,8 +11999,8 @@ "familiesMembership": { "message": "Families membership" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "planDescFamiliesV2": { "message": "Premium security for your family" @@ -12199,6 +12237,45 @@ } } }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "confirmNoSelectedCriticalApplicationsTitle": { "message": "No critical applications are selected" }, @@ -12207,5 +12284,139 @@ }, "userVerificationFailed": { "message": "User verification failed." + }, + "recoveryDeleteCiphersTitle": { + "message": "Delete unrecoverable vault items" + }, + "recoveryDeleteCiphersDesc": { + "message": "Some of your vault items could not be recovered. Do you want to delete these unrecoverable items from your vault?" + }, + "recoveryDeleteFoldersTitle": { + "message": "Delete unrecoverable folders" + }, + "recoveryDeleteFoldersDesc": { + "message": "Some of your folders could not be recovered. Do you want to delete these unrecoverable folders from your vault?" + }, + "recoveryReplacePrivateKeyTitle": { + "message": "Replace encryption key" + }, + "recoveryReplacePrivateKeyDesc": { + "message": "Your public-key encryption key pair could not be recovered. Do you want to replace your encryption key with a new key pair? This will require you to set up existing emergency-access and organization memberships again." + }, + "recoveryStepSyncTitle": { + "message": "Synchronizing data" + }, + "recoveryStepPrivateKeyTitle": { + "message": "Verifying encryption key integrity" + }, + "recoveryStepUserInfoTitle": { + "message": "Verifying user information" + }, + "recoveryStepCipherTitle": { + "message": "Verifying vault item integrity" + }, + "recoveryStepFoldersTitle": { + "message": "Verifying folder integrity" + }, + "dataRecoveryTitle": { + "message": "Data Recovery and Diagnostics" + }, + "dataRecoveryDescription": { + "message": "Use the data recovery tool to diagnose and repair issues with your account. After running diagnostics you have the option to save diagnostic logs for support and the option to repair any detected issues." + }, + "runDiagnostics": { + "message": "Run Diagnostics" + }, + "repairIssues": { + "message": "Repair Issues" + }, + "saveDiagnosticLogs": { + "message": "Save Diagnostic Logs" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On browser refresh." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On browser refresh" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/web/src/locales/de/messages.json b/apps/web/src/locales/de/messages.json index 6c354cd9064..6eb0b1d5dc7 100644 --- a/apps/web/src/locales/de/messages.json +++ b/apps/web/src/locales/de/messages.json @@ -15,13 +15,13 @@ "message": "Keine kritischen Anwendungen gefährdet" }, "accessIntelligence": { - "message": "Zugriff auf Informationen" + "message": "Access Intelligence" }, "passwordRisk": { "message": "Passwort-Risiko" }, "noEditPermissions": { - "message": "Du hast keine Berechtigung, diesen Eintrag zu bearbeiten" + "message": "Du bist nicht berechtigt, diesen Eintrag zu bearbeiten" }, "reviewAtRiskPasswords": { "message": "Überprüfe gefährdete Passwörter (schwach, kompromittiert oder wiederverwendet) in allen Anwendungen. Wähle deine kritischsten Anwendungen aus, um die Sicherheitsmaßnahmen für deine Benutzer zu priorisieren, um gefährdete Passwörter zu beseitigen." @@ -182,7 +182,7 @@ "message": "Keine Daten gefunden" }, "noDataInOrgDescription": { - "message": "Importiere die Zugangsdaten deiner Organisation, um mit der Zugangsintelligenz zu beginnen. Sobald du dies getan hast, kannst du:" + "message": "Importiere die Zugangsdaten deiner Organisation, um mit Access Intelligence zu beginnen. Sobald du dies getan hast, kannst du:" }, "feature1Title": { "message": "Anwendungen als kritisch markieren" @@ -3063,7 +3063,7 @@ "message": "1 GB verschlüsselter Speicherplatz für Dateianhänge." }, "premiumSignUpStorageV2": { - "message": "$SIZE$ encrypted storage for file attachments.", + "message": "$SIZE$ verschlüsselter Speicher für Dateianhänge.", "placeholders": { "size": { "content": "$1", @@ -3134,13 +3134,13 @@ } }, "premiumSubscriptionEnded": { - "message": "Your Premium subscription ended" + "message": "Dein Premium-Abonnement ist abgelaufen" }, "premiumSubscriptionEndedDesc": { - "message": "To regain access to your archive, restart your Premium subscription. If you edit details for an archived item before restarting, it'll be moved back into your vault." + "message": "Starte dein Premium-Abonnement neu, um den Zugriff auf dein Archiv wiederherzustellen. Wenn du die Details für einen archivierten Eintrag vor dem Neustart bearbeitest, wird er wieder zurück in deinen Tresor verschoben." }, "restartPremium": { - "message": "Restart Premium" + "message": "Premium neu starten" }, "additionalStorageGb": { "message": "Zusätzlicher Speicher (GB)" @@ -4479,7 +4479,7 @@ "message": "Browser aktualisieren" }, "generatingYourAccessIntelligence": { - "message": "Deine Zugangsintelligenz wird generiert..." + "message": "Deine Access Intelligence wird generiert..." }, "fetchingMemberData": { "message": "Mitgliedsdaten werden abgerufen..." @@ -4627,19 +4627,19 @@ "message": "Erfahre mehr" }, "migrationsFailed": { - "message": "An error occurred updating the encryption settings." + "message": "Beim Aktualisieren der Verschlüsselungseinstellungen ist ein Fehler aufgetreten." }, "updateEncryptionSettingsTitle": { - "message": "Update your encryption settings" + "message": "Aktualisiere deine Verschlüsselungseinstellungen" }, "updateEncryptionSettingsDesc": { - "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + "message": "Die neuen empfohlenen Verschlüsselungseinstellungen verbessern deine Kontosicherheit. Gib dein Master-Passwort ein, um sie zu aktualisieren." }, "confirmIdentityToContinue": { - "message": "Confirm your identity to continue" + "message": "Bestätige deine Identität, um fortzufahren" }, "enterYourMasterPassword": { - "message": "Enter your master password" + "message": "Gib dein Master-Passwort ein" }, "updateSettings": { "message": "Einstellungen aktualisieren" @@ -5169,9 +5169,21 @@ "message": "Reparieren", "description": "This is a verb. ex. 'Fix The Car'" }, + "fixEncryption": { + "message": "Verschlüsselung reparieren" + }, + "fixEncryptionTooltip": { + "message": "Diese Datei verwendet eine veraltete Verschlüsselungsmethode." + }, + "attachmentUpdated": { + "message": "Anhang aktualisiert" + }, "oldAttachmentsNeedFixDesc": { "message": "Es gibt alte Dateianhänge in deinem Tresor, die repariert werden müssen, bevor du deinen Verschlüsselungsschlüssel erneuern kannst." }, + "itemsTransferred": { + "message": "Einträge übertragen" + }, "yourAccountsFingerprint": { "message": "Fingerabdruck-Phrase deines Kontos", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -6453,6 +6465,32 @@ "bulkReinviteMessage": { "message": "Erfolgreich erneut eingeladen" }, + "bulkReinviteSuccessToast": { + "message": "$COUNT$ users re-invited", + "placeholders": { + "count": { + "content": "$1", + "example": "12" + } + } + }, + "bulkReinviteLimitedSuccessToast": { + "message": "$LIMIT$ of $SELECTEDCOUNT$ users re-invited. $EXCLUDEDCOUNT$ were not invited due to the $LIMIT$ invite limit.", + "placeholders": { + "limit": { + "content": "$1", + "example": "4,000" + }, + "selectedCount": { + "content": "$2", + "example": "4,005" + }, + "excludedCount": { + "content": "$3", + "example": "5" + } + } + }, "bulkRemovedMessage": { "message": "Erfolgreich entfernt" }, @@ -6773,8 +6811,8 @@ "vaultTimeoutRangeError": { "message": "Tresor-Timeout ist außerhalb des zulässigen Bereichs." }, - "disablePersonalVaultExport": { - "message": "Persönlichen Tresor-Export deaktivieren" + "disableExport": { + "message": "Export entfernen" }, "disablePersonalVaultExportDescription": { "message": "Mitgliedern nicht erlauben, Daten aus ihrem persönlichen Tresor zu exportieren." @@ -7094,9 +7132,6 @@ "invalidVerificationCode": { "message": "Ungültiger Verifizierungscode" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "Für Mitglieder der folgenden Organisation ist kein Master-Passwort mehr erforderlich. Bitte bestätige die folgende Domain bei deinem Organisations-Administrator." - }, "keyConnectorDomain": { "message": "Key Connector-Domain" }, @@ -7154,7 +7189,7 @@ "enabledSso": { "message": "SSO aktiviert" }, - "disabledSso": { + "ssoTurnedOff": { "message": "SSO deaktiviert" }, "emailMustLoginWithSso": { @@ -9448,6 +9483,9 @@ "ssoLoginIsRequired": { "message": "SSO-Anmeldung ist erforderlich" }, + "emailRequiredForSsoLogin": { + "message": "Für SSO ist eine E-Mail-Adresse erforderlich" + }, "selectedRegionFlag": { "message": "Flagge der ausgewählten Region" }, @@ -11961,8 +11999,8 @@ "familiesMembership": { "message": "Families-Mitgliedschaft" }, - "planDescPremium": { - "message": "Umfassende Online-Sicherheit" + "advancedOnlineSecurity": { + "message": "Erweiterte Online-Sicherheit" }, "planDescFamiliesV2": { "message": "Premium-Sicherheit für deine Familie" @@ -12199,6 +12237,45 @@ } } }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Deine Organisation verwendet keine Master-Passwörter mehr, um sich bei Bitwarden anzumelden. Verifiziere die Organisation und Domain, um fortzufahren." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organisation verifiziert" + }, + "domainVerified": { + "message": "Domain verifiziert" + }, + "leaveOrganizationContent": { + "message": "Wenn du deine Organisation nicht verifizierst, wird dein Zugriff auf die Organisation widerrufen." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "confirmNoSelectedCriticalApplicationsTitle": { "message": "Es sind keine kritischen Anwendungen ausgewählt" }, @@ -12207,5 +12284,139 @@ }, "userVerificationFailed": { "message": "Benutzerverifizierung fehlgeschlagen." + }, + "recoveryDeleteCiphersTitle": { + "message": "Delete unrecoverable vault items" + }, + "recoveryDeleteCiphersDesc": { + "message": "Some of your vault items could not be recovered. Do you want to delete these unrecoverable items from your vault?" + }, + "recoveryDeleteFoldersTitle": { + "message": "Delete unrecoverable folders" + }, + "recoveryDeleteFoldersDesc": { + "message": "Some of your folders could not be recovered. Do you want to delete these unrecoverable folders from your vault?" + }, + "recoveryReplacePrivateKeyTitle": { + "message": "Verschlüsselungsschlüssel ersetzen" + }, + "recoveryReplacePrivateKeyDesc": { + "message": "Your public-key encryption key pair could not be recovered. Do you want to replace your encryption key with a new key pair? This will require you to set up existing emergency-access and organization memberships again." + }, + "recoveryStepSyncTitle": { + "message": "Synchronizing data" + }, + "recoveryStepPrivateKeyTitle": { + "message": "Verifying encryption key integrity" + }, + "recoveryStepUserInfoTitle": { + "message": "Verifying user information" + }, + "recoveryStepCipherTitle": { + "message": "Verifying vault item integrity" + }, + "recoveryStepFoldersTitle": { + "message": "Verifying folder integrity" + }, + "dataRecoveryTitle": { + "message": "Data Recovery and Diagnostics" + }, + "dataRecoveryDescription": { + "message": "Use the data recovery tool to diagnose and repair issues with your account. After running diagnostics you have the option to save diagnostic logs for support and the option to repair any detected issues." + }, + "runDiagnostics": { + "message": "Run Diagnostics" + }, + "repairIssues": { + "message": "Repair Issues" + }, + "saveDiagnosticLogs": { + "message": "Save Diagnostic Logs" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "Diese Einstellung wird von deiner Organisation verwaltet." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Deine Organisation hat das maximale Sitzungs-Timeout auf $HOURS$ Stunde(n) und $MINUTES$ Minute(n) festgelegt.", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Deine Organisation hat das Standard-Sitzungs-Timeout auf \"Bei Browser-Aktualisierung\" gesetzt." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Das maximale Timeout darf $HOURS$ Stunde(n) und $MINUTES$ Minute(n) nicht überschreiten", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "Bei Browser-Aktualisierung" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Stell eine Entsperrmethode ein, um deine Timeout-Aktion zu ändern" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "Wenn du ablehnst, bleiben deine persönlichen Einträge in deinem Konto erhalten, aber du wirst den Zugriff auf geteilte Einträge und Organisationsfunktionen verlieren." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Übertragung annehmen" + }, + "declineAndLeave": { + "message": "Ablehnen und verlassen" + }, + "whyAmISeeingThis": { + "message": "Warum wird mir das angezeigt?" } } diff --git a/apps/web/src/locales/el/messages.json b/apps/web/src/locales/el/messages.json index f6e8abfd612..f8efb8f78f8 100644 --- a/apps/web/src/locales/el/messages.json +++ b/apps/web/src/locales/el/messages.json @@ -5169,9 +5169,21 @@ "message": "Επιδιόρθωση", "description": "This is a verb. ex. 'Fix The Car'" }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "oldAttachmentsNeedFixDesc": { "message": "Υπάρχουν στο παρελθόν παλιά συνημμένα αρχεία που πρέπει να διορθωθούν πριν την περιστροφή κλειδιού κρυπτογράφησης του λογαριασμού σας." }, + "itemsTransferred": { + "message": "Items transferred" + }, "yourAccountsFingerprint": { "message": "Φράση δακτυλικών αποτυπωμάτων λογαριασμού", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -6453,6 +6465,32 @@ "bulkReinviteMessage": { "message": "Επιτυχής ανάκληση." }, + "bulkReinviteSuccessToast": { + "message": "$COUNT$ users re-invited", + "placeholders": { + "count": { + "content": "$1", + "example": "12" + } + } + }, + "bulkReinviteLimitedSuccessToast": { + "message": "$LIMIT$ of $SELECTEDCOUNT$ users re-invited. $EXCLUDEDCOUNT$ were not invited due to the $LIMIT$ invite limit.", + "placeholders": { + "limit": { + "content": "$1", + "example": "4,000" + }, + "selectedCount": { + "content": "$2", + "example": "4,005" + }, + "excludedCount": { + "content": "$3", + "example": "5" + } + } + }, "bulkRemovedMessage": { "message": "Καταργήθηκε με επιτυχία" }, @@ -6773,8 +6811,8 @@ "vaultTimeoutRangeError": { "message": "Το χρονικό όριο λήξης του θησαυ/κίου δεν είναι εντός του επιτρεπόμενου εύρους." }, - "disablePersonalVaultExport": { - "message": "Απενεργοποίηση Εξαγωγής Προσωπικών Vault" + "disableExport": { + "message": "Remove export" }, "disablePersonalVaultExportDescription": { "message": "Do not allow members to export data from their individual vault." @@ -7094,9 +7132,6 @@ "invalidVerificationCode": { "message": "Μη έγκυρος κωδικός επαλήθευσης" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "keyConnectorDomain": { "message": "Key Connector domain" }, @@ -7154,8 +7189,8 @@ "enabledSso": { "message": "Ενεργοποιημένο SSO" }, - "disabledSso": { - "message": "Απενεργοποιημένο SSO" + "ssoTurnedOff": { + "message": "SSO turned off" }, "emailMustLoginWithSso": { "message": "$EMAIL$ must login with Single Sign-on", @@ -9448,6 +9483,9 @@ "ssoLoginIsRequired": { "message": "Απαιτείται σύνδεση SSO" }, + "emailRequiredForSsoLogin": { + "message": "Email is required for SSO" + }, "selectedRegionFlag": { "message": "Selected region flag" }, @@ -11961,8 +11999,8 @@ "familiesMembership": { "message": "Families membership" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "planDescFamiliesV2": { "message": "Premium security for your family" @@ -12199,6 +12237,45 @@ } } }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "confirmNoSelectedCriticalApplicationsTitle": { "message": "No critical applications are selected" }, @@ -12207,5 +12284,139 @@ }, "userVerificationFailed": { "message": "User verification failed." + }, + "recoveryDeleteCiphersTitle": { + "message": "Delete unrecoverable vault items" + }, + "recoveryDeleteCiphersDesc": { + "message": "Some of your vault items could not be recovered. Do you want to delete these unrecoverable items from your vault?" + }, + "recoveryDeleteFoldersTitle": { + "message": "Delete unrecoverable folders" + }, + "recoveryDeleteFoldersDesc": { + "message": "Some of your folders could not be recovered. Do you want to delete these unrecoverable folders from your vault?" + }, + "recoveryReplacePrivateKeyTitle": { + "message": "Replace encryption key" + }, + "recoveryReplacePrivateKeyDesc": { + "message": "Your public-key encryption key pair could not be recovered. Do you want to replace your encryption key with a new key pair? This will require you to set up existing emergency-access and organization memberships again." + }, + "recoveryStepSyncTitle": { + "message": "Synchronizing data" + }, + "recoveryStepPrivateKeyTitle": { + "message": "Verifying encryption key integrity" + }, + "recoveryStepUserInfoTitle": { + "message": "Verifying user information" + }, + "recoveryStepCipherTitle": { + "message": "Verifying vault item integrity" + }, + "recoveryStepFoldersTitle": { + "message": "Verifying folder integrity" + }, + "dataRecoveryTitle": { + "message": "Data Recovery and Diagnostics" + }, + "dataRecoveryDescription": { + "message": "Use the data recovery tool to diagnose and repair issues with your account. After running diagnostics you have the option to save diagnostic logs for support and the option to repair any detected issues." + }, + "runDiagnostics": { + "message": "Run Diagnostics" + }, + "repairIssues": { + "message": "Repair Issues" + }, + "saveDiagnosticLogs": { + "message": "Save Diagnostic Logs" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On browser refresh." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On browser refresh" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/web/src/locales/en_GB/messages.json b/apps/web/src/locales/en_GB/messages.json index be621369c77..cdcf727df78 100644 --- a/apps/web/src/locales/en_GB/messages.json +++ b/apps/web/src/locales/en_GB/messages.json @@ -5169,9 +5169,21 @@ "message": "Fix", "description": "This is a verb. ex. 'Fix The Car'" }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "oldAttachmentsNeedFixDesc": { "message": "There are old file attachments in your vault that need to be fixed before you can rotate your account's encryption key." }, + "itemsTransferred": { + "message": "Items transferred" + }, "yourAccountsFingerprint": { "message": "Your account's fingerprint phrase", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -6453,6 +6465,32 @@ "bulkReinviteMessage": { "message": "Reinvited successfully" }, + "bulkReinviteSuccessToast": { + "message": "$COUNT$ users re-invited", + "placeholders": { + "count": { + "content": "$1", + "example": "12" + } + } + }, + "bulkReinviteLimitedSuccessToast": { + "message": "$LIMIT$ of $SELECTEDCOUNT$ users re-invited. $EXCLUDEDCOUNT$ were not invited due to the $LIMIT$ invite limit.", + "placeholders": { + "limit": { + "content": "$1", + "example": "4,000" + }, + "selectedCount": { + "content": "$2", + "example": "4,005" + }, + "excludedCount": { + "content": "$3", + "example": "5" + } + } + }, "bulkRemovedMessage": { "message": "Removed successfully" }, @@ -6773,8 +6811,8 @@ "vaultTimeoutRangeError": { "message": "Vault timeout is not within allowed range." }, - "disablePersonalVaultExport": { - "message": "Remove individual vault export" + "disableExport": { + "message": "Remove export" }, "disablePersonalVaultExportDescription": { "message": "Do not allow members to export data from their individual vault." @@ -7094,9 +7132,6 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organisation. Please confirm the domain below with your organisation administrator." - }, "keyConnectorDomain": { "message": "Key Connector domain" }, @@ -7154,7 +7189,7 @@ "enabledSso": { "message": "SSO turned on" }, - "disabledSso": { + "ssoTurnedOff": { "message": "SSO turned off" }, "emailMustLoginWithSso": { @@ -9448,6 +9483,9 @@ "ssoLoginIsRequired": { "message": "SSO login is required" }, + "emailRequiredForSsoLogin": { + "message": "Email is required for SSO" + }, "selectedRegionFlag": { "message": "Selected region flag" }, @@ -11961,8 +11999,8 @@ "familiesMembership": { "message": "Families membership" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "planDescFamiliesV2": { "message": "Premium security for your family" @@ -12199,6 +12237,45 @@ } } }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organisation is no longer using master passwords to log into Bitwarden. To continue, verify the organisation and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organisation to log in" + }, + "organizationVerified": { + "message": "Organisation verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organisation, your access to the organisation will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organisation and domain." + }, "confirmNoSelectedCriticalApplicationsTitle": { "message": "No critical applications are selected" }, @@ -12207,5 +12284,139 @@ }, "userVerificationFailed": { "message": "User verification failed." + }, + "recoveryDeleteCiphersTitle": { + "message": "Delete unrecoverable vault items" + }, + "recoveryDeleteCiphersDesc": { + "message": "Some of your vault items could not be recovered. Do you want to delete these unrecoverable items from your vault?" + }, + "recoveryDeleteFoldersTitle": { + "message": "Delete unrecoverable folders" + }, + "recoveryDeleteFoldersDesc": { + "message": "Some of your folders could not be recovered. Do you want to delete these unrecoverable folders from your vault?" + }, + "recoveryReplacePrivateKeyTitle": { + "message": "Replace encryption key" + }, + "recoveryReplacePrivateKeyDesc": { + "message": "Your public-key encryption key pair could not be recovered. Do you want to replace your encryption key with a new key pair? This will require you to set up existing emergency-access and organisation memberships again." + }, + "recoveryStepSyncTitle": { + "message": "Synchronising data" + }, + "recoveryStepPrivateKeyTitle": { + "message": "Verifying encryption key integrity" + }, + "recoveryStepUserInfoTitle": { + "message": "Verifying user information" + }, + "recoveryStepCipherTitle": { + "message": "Verifying vault item integrity" + }, + "recoveryStepFoldersTitle": { + "message": "Verifying folder integrity" + }, + "dataRecoveryTitle": { + "message": "Data Recovery and Diagnostics" + }, + "dataRecoveryDescription": { + "message": "Use the data recovery tool to diagnose and repair issues with your account. After running diagnostics you have the option to save diagnostic logs for support and the option to repair any detected issues." + }, + "runDiagnostics": { + "message": "Run Diagnostics" + }, + "repairIssues": { + "message": "Repair Issues" + }, + "saveDiagnosticLogs": { + "message": "Save Diagnostic Logs" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organisation." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organisation has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organisation has set the default session timeout to On browser refresh." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On browser refresh" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organisation features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organisation for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/web/src/locales/en_IN/messages.json b/apps/web/src/locales/en_IN/messages.json index aafb9a01f3e..ba0927f73fc 100644 --- a/apps/web/src/locales/en_IN/messages.json +++ b/apps/web/src/locales/en_IN/messages.json @@ -5169,9 +5169,21 @@ "message": "Fix", "description": "This is a verb. ex. 'Fix The Car'" }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "oldAttachmentsNeedFixDesc": { "message": "There are old file attachments in your vault that need to be fixed before you can rotate your account's encryption key." }, + "itemsTransferred": { + "message": "Items transferred" + }, "yourAccountsFingerprint": { "message": "Your account's fingerprint phrase", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -6453,6 +6465,32 @@ "bulkReinviteMessage": { "message": "Reinvited successfully." }, + "bulkReinviteSuccessToast": { + "message": "$COUNT$ users re-invited", + "placeholders": { + "count": { + "content": "$1", + "example": "12" + } + } + }, + "bulkReinviteLimitedSuccessToast": { + "message": "$LIMIT$ of $SELECTEDCOUNT$ users re-invited. $EXCLUDEDCOUNT$ were not invited due to the $LIMIT$ invite limit.", + "placeholders": { + "limit": { + "content": "$1", + "example": "4,000" + }, + "selectedCount": { + "content": "$2", + "example": "4,005" + }, + "excludedCount": { + "content": "$3", + "example": "5" + } + } + }, "bulkRemovedMessage": { "message": "Removed successfully" }, @@ -6773,8 +6811,8 @@ "vaultTimeoutRangeError": { "message": "Vault timeout is not within allowed range." }, - "disablePersonalVaultExport": { - "message": "Remove individual vault export" + "disableExport": { + "message": "Remove export" }, "disablePersonalVaultExportDescription": { "message": "Do not allow members to export data from their individual vault." @@ -7094,9 +7132,6 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organisation. Please confirm the domain below with your organisation administrator." - }, "keyConnectorDomain": { "message": "Key Connector domain" }, @@ -7154,8 +7189,8 @@ "enabledSso": { "message": "SSO turned on" }, - "disabledSso": { - "message": "SSO turned on" + "ssoTurnedOff": { + "message": "SSO turned off" }, "emailMustLoginWithSso": { "message": "$EMAIL$ must login with Single Sign-on", @@ -9448,6 +9483,9 @@ "ssoLoginIsRequired": { "message": "SSO login is required" }, + "emailRequiredForSsoLogin": { + "message": "Email is required for SSO" + }, "selectedRegionFlag": { "message": "Selected region flag" }, @@ -11961,8 +11999,8 @@ "familiesMembership": { "message": "Families membership" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "planDescFamiliesV2": { "message": "Premium security for your family" @@ -12199,6 +12237,45 @@ } } }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organisation is no longer using master passwords to log into Bitwarden. To continue, verify the organisation and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organisation to log in" + }, + "organizationVerified": { + "message": "Organisation verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organisation, your access to the organisation will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organisation and domain." + }, "confirmNoSelectedCriticalApplicationsTitle": { "message": "No critical applications are selected" }, @@ -12207,5 +12284,139 @@ }, "userVerificationFailed": { "message": "User verification failed." + }, + "recoveryDeleteCiphersTitle": { + "message": "Delete unrecoverable vault items" + }, + "recoveryDeleteCiphersDesc": { + "message": "Some of your vault items could not be recovered. Do you want to delete these unrecoverable items from your vault?" + }, + "recoveryDeleteFoldersTitle": { + "message": "Delete unrecoverable folders" + }, + "recoveryDeleteFoldersDesc": { + "message": "Some of your folders could not be recovered. Do you want to delete these unrecoverable folders from your vault?" + }, + "recoveryReplacePrivateKeyTitle": { + "message": "Replace encryption key" + }, + "recoveryReplacePrivateKeyDesc": { + "message": "Your public-key encryption key pair could not be recovered. Do you want to replace your encryption key with a new key pair? This will require you to set up existing emergency-access and organisation memberships again." + }, + "recoveryStepSyncTitle": { + "message": "Synchronising data" + }, + "recoveryStepPrivateKeyTitle": { + "message": "Verifying encryption key integrity" + }, + "recoveryStepUserInfoTitle": { + "message": "Verifying user information" + }, + "recoveryStepCipherTitle": { + "message": "Verifying vault item integrity" + }, + "recoveryStepFoldersTitle": { + "message": "Verifying folder integrity" + }, + "dataRecoveryTitle": { + "message": "Data Recovery and Diagnostics" + }, + "dataRecoveryDescription": { + "message": "Use the data recovery tool to diagnose and repair issues with your account. After running diagnostics you have the option to save diagnostic logs for support and the option to repair any detected issues." + }, + "runDiagnostics": { + "message": "Run Diagnostics" + }, + "repairIssues": { + "message": "Repair Issues" + }, + "saveDiagnosticLogs": { + "message": "Save Diagnostic Logs" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organisation." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organisation has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organisation has set the default session timeout to On browser restart." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On browser refresh" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organisation features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organisation for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/web/src/locales/eo/messages.json b/apps/web/src/locales/eo/messages.json index 220d9492c67..dbccabfe6d7 100644 --- a/apps/web/src/locales/eo/messages.json +++ b/apps/web/src/locales/eo/messages.json @@ -5169,9 +5169,21 @@ "message": "Ripari", "description": "This is a verb. ex. 'Fix The Car'" }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "oldAttachmentsNeedFixDesc": { "message": "Estas malnovaj dosieraj aldonaĵoj en via trezorejo, kiuj devas esti riparitaj antaŭ ol vi povas turni la ĉifran ŝlosilon de via konto." }, + "itemsTransferred": { + "message": "Items transferred" + }, "yourAccountsFingerprint": { "message": "Fingrospuro de via konto", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -6453,6 +6465,32 @@ "bulkReinviteMessage": { "message": "Reinvited successfully" }, + "bulkReinviteSuccessToast": { + "message": "$COUNT$ users re-invited", + "placeholders": { + "count": { + "content": "$1", + "example": "12" + } + } + }, + "bulkReinviteLimitedSuccessToast": { + "message": "$LIMIT$ of $SELECTEDCOUNT$ users re-invited. $EXCLUDEDCOUNT$ were not invited due to the $LIMIT$ invite limit.", + "placeholders": { + "limit": { + "content": "$1", + "example": "4,000" + }, + "selectedCount": { + "content": "$2", + "example": "4,005" + }, + "excludedCount": { + "content": "$3", + "example": "5" + } + } + }, "bulkRemovedMessage": { "message": "Removed successfully" }, @@ -6773,8 +6811,8 @@ "vaultTimeoutRangeError": { "message": "Vault timeout is not within allowed range." }, - "disablePersonalVaultExport": { - "message": "Remove individual vault export" + "disableExport": { + "message": "Remove export" }, "disablePersonalVaultExportDescription": { "message": "Do not allow members to export data from their individual vault." @@ -7094,9 +7132,6 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "keyConnectorDomain": { "message": "Key Connector domain" }, @@ -7154,8 +7189,8 @@ "enabledSso": { "message": "SSO turned on" }, - "disabledSso": { - "message": "SSO turned on" + "ssoTurnedOff": { + "message": "SSO turned off" }, "emailMustLoginWithSso": { "message": "$EMAIL$ must login with Single Sign-on", @@ -9448,6 +9483,9 @@ "ssoLoginIsRequired": { "message": "SSO-salutado estas bezonata" }, + "emailRequiredForSsoLogin": { + "message": "Email is required for SSO" + }, "selectedRegionFlag": { "message": "Elektis flagon de regiono" }, @@ -11961,8 +11999,8 @@ "familiesMembership": { "message": "Families membership" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "planDescFamiliesV2": { "message": "Premium security for your family" @@ -12199,6 +12237,45 @@ } } }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "confirmNoSelectedCriticalApplicationsTitle": { "message": "No critical applications are selected" }, @@ -12207,5 +12284,139 @@ }, "userVerificationFailed": { "message": "User verification failed." + }, + "recoveryDeleteCiphersTitle": { + "message": "Delete unrecoverable vault items" + }, + "recoveryDeleteCiphersDesc": { + "message": "Some of your vault items could not be recovered. Do you want to delete these unrecoverable items from your vault?" + }, + "recoveryDeleteFoldersTitle": { + "message": "Delete unrecoverable folders" + }, + "recoveryDeleteFoldersDesc": { + "message": "Some of your folders could not be recovered. Do you want to delete these unrecoverable folders from your vault?" + }, + "recoveryReplacePrivateKeyTitle": { + "message": "Replace encryption key" + }, + "recoveryReplacePrivateKeyDesc": { + "message": "Your public-key encryption key pair could not be recovered. Do you want to replace your encryption key with a new key pair? This will require you to set up existing emergency-access and organization memberships again." + }, + "recoveryStepSyncTitle": { + "message": "Synchronizing data" + }, + "recoveryStepPrivateKeyTitle": { + "message": "Verifying encryption key integrity" + }, + "recoveryStepUserInfoTitle": { + "message": "Verifying user information" + }, + "recoveryStepCipherTitle": { + "message": "Verifying vault item integrity" + }, + "recoveryStepFoldersTitle": { + "message": "Verifying folder integrity" + }, + "dataRecoveryTitle": { + "message": "Data Recovery and Diagnostics" + }, + "dataRecoveryDescription": { + "message": "Use the data recovery tool to diagnose and repair issues with your account. After running diagnostics you have the option to save diagnostic logs for support and the option to repair any detected issues." + }, + "runDiagnostics": { + "message": "Run Diagnostics" + }, + "repairIssues": { + "message": "Repair Issues" + }, + "saveDiagnosticLogs": { + "message": "Save Diagnostic Logs" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On browser refresh." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On browser refresh" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/web/src/locales/es/messages.json b/apps/web/src/locales/es/messages.json index b0a3716cf60..218cfea5617 100644 --- a/apps/web/src/locales/es/messages.json +++ b/apps/web/src/locales/es/messages.json @@ -5169,9 +5169,21 @@ "message": "Arreglar", "description": "This is a verb. ex. 'Fix The Car'" }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "oldAttachmentsNeedFixDesc": { "message": "Hay archivos adjuntos antiguos en la caja fuerte que necesitan ser corregidos antes de poder rotar la clave de encriptación de su cuenta." }, + "itemsTransferred": { + "message": "Items transferred" + }, "yourAccountsFingerprint": { "message": "Frase de la huella digital de su cuenta", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -6453,6 +6465,32 @@ "bulkReinviteMessage": { "message": "Reinvitado con éxito." }, + "bulkReinviteSuccessToast": { + "message": "$COUNT$ users re-invited", + "placeholders": { + "count": { + "content": "$1", + "example": "12" + } + } + }, + "bulkReinviteLimitedSuccessToast": { + "message": "$LIMIT$ of $SELECTEDCOUNT$ users re-invited. $EXCLUDEDCOUNT$ were not invited due to the $LIMIT$ invite limit.", + "placeholders": { + "limit": { + "content": "$1", + "example": "4,000" + }, + "selectedCount": { + "content": "$2", + "example": "4,005" + }, + "excludedCount": { + "content": "$3", + "example": "5" + } + } + }, "bulkRemovedMessage": { "message": "Eliminado con éxito" }, @@ -6773,8 +6811,8 @@ "vaultTimeoutRangeError": { "message": "El tiempo de espera de la caja fuerte no está dentro del rango permitido." }, - "disablePersonalVaultExport": { - "message": "Desactivar exportación de la caja fuerte personal" + "disableExport": { + "message": "Remove export" }, "disablePersonalVaultExportDescription": { "message": "No permita a los miembros exportar datos de su caja fuerte individual." @@ -7094,9 +7132,6 @@ "invalidVerificationCode": { "message": "Código de verificación no válido" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "keyConnectorDomain": { "message": "Key Connector domain" }, @@ -7154,8 +7189,8 @@ "enabledSso": { "message": "SSO habilitado" }, - "disabledSso": { - "message": "SSO desactivado" + "ssoTurnedOff": { + "message": "SSO turned off" }, "emailMustLoginWithSso": { "message": "$EMAIL$ must login with Single Sign-on", @@ -9448,6 +9483,9 @@ "ssoLoginIsRequired": { "message": "SSO login is required" }, + "emailRequiredForSsoLogin": { + "message": "Email is required for SSO" + }, "selectedRegionFlag": { "message": "Región seleccionada" }, @@ -11961,8 +11999,8 @@ "familiesMembership": { "message": "Families membership" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "planDescFamiliesV2": { "message": "Premium security for your family" @@ -12199,6 +12237,45 @@ } } }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "confirmNoSelectedCriticalApplicationsTitle": { "message": "No critical applications are selected" }, @@ -12207,5 +12284,139 @@ }, "userVerificationFailed": { "message": "User verification failed." + }, + "recoveryDeleteCiphersTitle": { + "message": "Delete unrecoverable vault items" + }, + "recoveryDeleteCiphersDesc": { + "message": "Some of your vault items could not be recovered. Do you want to delete these unrecoverable items from your vault?" + }, + "recoveryDeleteFoldersTitle": { + "message": "Delete unrecoverable folders" + }, + "recoveryDeleteFoldersDesc": { + "message": "Some of your folders could not be recovered. Do you want to delete these unrecoverable folders from your vault?" + }, + "recoveryReplacePrivateKeyTitle": { + "message": "Replace encryption key" + }, + "recoveryReplacePrivateKeyDesc": { + "message": "Your public-key encryption key pair could not be recovered. Do you want to replace your encryption key with a new key pair? This will require you to set up existing emergency-access and organization memberships again." + }, + "recoveryStepSyncTitle": { + "message": "Synchronizing data" + }, + "recoveryStepPrivateKeyTitle": { + "message": "Verifying encryption key integrity" + }, + "recoveryStepUserInfoTitle": { + "message": "Verifying user information" + }, + "recoveryStepCipherTitle": { + "message": "Verifying vault item integrity" + }, + "recoveryStepFoldersTitle": { + "message": "Verifying folder integrity" + }, + "dataRecoveryTitle": { + "message": "Data Recovery and Diagnostics" + }, + "dataRecoveryDescription": { + "message": "Use the data recovery tool to diagnose and repair issues with your account. After running diagnostics you have the option to save diagnostic logs for support and the option to repair any detected issues." + }, + "runDiagnostics": { + "message": "Run Diagnostics" + }, + "repairIssues": { + "message": "Repair Issues" + }, + "saveDiagnosticLogs": { + "message": "Save Diagnostic Logs" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On browser refresh." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On browser refresh" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/web/src/locales/et/messages.json b/apps/web/src/locales/et/messages.json index 1388e25bc13..df1baf98bc2 100644 --- a/apps/web/src/locales/et/messages.json +++ b/apps/web/src/locales/et/messages.json @@ -5169,9 +5169,21 @@ "message": "Paranda", "description": "This is a verb. ex. 'Fix The Car'" }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "oldAttachmentsNeedFixDesc": { "message": "Hoidlas on vanu failimanuseid, mida peab enne konto krüpteerimise võtme roteerimist parandama." }, + "itemsTransferred": { + "message": "Items transferred" + }, "yourAccountsFingerprint": { "message": "Sinu konto unikaalne sõnajada", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -6453,6 +6465,32 @@ "bulkReinviteMessage": { "message": "Edukalt uuesti kutsutud." }, + "bulkReinviteSuccessToast": { + "message": "$COUNT$ users re-invited", + "placeholders": { + "count": { + "content": "$1", + "example": "12" + } + } + }, + "bulkReinviteLimitedSuccessToast": { + "message": "$LIMIT$ of $SELECTEDCOUNT$ users re-invited. $EXCLUDEDCOUNT$ were not invited due to the $LIMIT$ invite limit.", + "placeholders": { + "limit": { + "content": "$1", + "example": "4,000" + }, + "selectedCount": { + "content": "$2", + "example": "4,005" + }, + "excludedCount": { + "content": "$3", + "example": "5" + } + } + }, "bulkRemovedMessage": { "message": "Edukalt eemaldatud" }, @@ -6773,8 +6811,8 @@ "vaultTimeoutRangeError": { "message": "Hoidla ajalõpp pole lubatud piirides." }, - "disablePersonalVaultExport": { - "message": "Remove individual vault export" + "disableExport": { + "message": "Remove export" }, "disablePersonalVaultExportDescription": { "message": "Do not allow members to export data from their individual vault." @@ -7094,9 +7132,6 @@ "invalidVerificationCode": { "message": "Vale kinnituskood" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "keyConnectorDomain": { "message": "Key Connector domain" }, @@ -7154,8 +7189,8 @@ "enabledSso": { "message": "SSO sisse lülitatud" }, - "disabledSso": { - "message": "SSO välja lülitatud" + "ssoTurnedOff": { + "message": "SSO turned off" }, "emailMustLoginWithSso": { "message": "$EMAIL$ must login with Single Sign-on", @@ -9448,6 +9483,9 @@ "ssoLoginIsRequired": { "message": "SSO login is required" }, + "emailRequiredForSsoLogin": { + "message": "Email is required for SSO" + }, "selectedRegionFlag": { "message": "Selected region flag" }, @@ -11961,8 +11999,8 @@ "familiesMembership": { "message": "Families membership" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "planDescFamiliesV2": { "message": "Premium security for your family" @@ -12199,6 +12237,45 @@ } } }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "confirmNoSelectedCriticalApplicationsTitle": { "message": "No critical applications are selected" }, @@ -12207,5 +12284,139 @@ }, "userVerificationFailed": { "message": "User verification failed." + }, + "recoveryDeleteCiphersTitle": { + "message": "Delete unrecoverable vault items" + }, + "recoveryDeleteCiphersDesc": { + "message": "Some of your vault items could not be recovered. Do you want to delete these unrecoverable items from your vault?" + }, + "recoveryDeleteFoldersTitle": { + "message": "Delete unrecoverable folders" + }, + "recoveryDeleteFoldersDesc": { + "message": "Some of your folders could not be recovered. Do you want to delete these unrecoverable folders from your vault?" + }, + "recoveryReplacePrivateKeyTitle": { + "message": "Replace encryption key" + }, + "recoveryReplacePrivateKeyDesc": { + "message": "Your public-key encryption key pair could not be recovered. Do you want to replace your encryption key with a new key pair? This will require you to set up existing emergency-access and organization memberships again." + }, + "recoveryStepSyncTitle": { + "message": "Synchronizing data" + }, + "recoveryStepPrivateKeyTitle": { + "message": "Verifying encryption key integrity" + }, + "recoveryStepUserInfoTitle": { + "message": "Verifying user information" + }, + "recoveryStepCipherTitle": { + "message": "Verifying vault item integrity" + }, + "recoveryStepFoldersTitle": { + "message": "Verifying folder integrity" + }, + "dataRecoveryTitle": { + "message": "Data Recovery and Diagnostics" + }, + "dataRecoveryDescription": { + "message": "Use the data recovery tool to diagnose and repair issues with your account. After running diagnostics you have the option to save diagnostic logs for support and the option to repair any detected issues." + }, + "runDiagnostics": { + "message": "Run Diagnostics" + }, + "repairIssues": { + "message": "Repair Issues" + }, + "saveDiagnosticLogs": { + "message": "Save Diagnostic Logs" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On browser refresh." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On browser refresh" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/web/src/locales/eu/messages.json b/apps/web/src/locales/eu/messages.json index c2fa69d56b8..e633cefdbf0 100644 --- a/apps/web/src/locales/eu/messages.json +++ b/apps/web/src/locales/eu/messages.json @@ -5169,9 +5169,21 @@ "message": "Konpondu", "description": "This is a verb. ex. 'Fix The Car'" }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "oldAttachmentsNeedFixDesc": { "message": "Kutxa gotorrean eranskin zaharrak daude, eta konpondu beharra dute zure kontuko zifratze-gakoa berritu ahal izateko." }, + "itemsTransferred": { + "message": "Items transferred" + }, "yourAccountsFingerprint": { "message": "Zure kontuaren hatz-marka esaldia", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -6453,6 +6465,32 @@ "bulkReinviteMessage": { "message": "Behar bezala bergonbidatua." }, + "bulkReinviteSuccessToast": { + "message": "$COUNT$ users re-invited", + "placeholders": { + "count": { + "content": "$1", + "example": "12" + } + } + }, + "bulkReinviteLimitedSuccessToast": { + "message": "$LIMIT$ of $SELECTEDCOUNT$ users re-invited. $EXCLUDEDCOUNT$ were not invited due to the $LIMIT$ invite limit.", + "placeholders": { + "limit": { + "content": "$1", + "example": "4,000" + }, + "selectedCount": { + "content": "$2", + "example": "4,005" + }, + "excludedCount": { + "content": "$3", + "example": "5" + } + } + }, "bulkRemovedMessage": { "message": "Behar bezala kendua" }, @@ -6773,8 +6811,8 @@ "vaultTimeoutRangeError": { "message": "Kutxa gotorreko itxaronaldia ez dago baimendutako tartean." }, - "disablePersonalVaultExport": { - "message": "Desgaitu kutxa gotor pertsonalaren esportazioa" + "disableExport": { + "message": "Remove export" }, "disablePersonalVaultExportDescription": { "message": "Do not allow members to export data from their individual vault." @@ -7094,9 +7132,6 @@ "invalidVerificationCode": { "message": "Egiaztatze-kodea ez da baliozkoa" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "keyConnectorDomain": { "message": "Key Connector domain" }, @@ -7154,8 +7189,8 @@ "enabledSso": { "message": "SSO gaituta" }, - "disabledSso": { - "message": "SSO desgaituta" + "ssoTurnedOff": { + "message": "SSO turned off" }, "emailMustLoginWithSso": { "message": "$EMAIL$ must login with Single Sign-on", @@ -9448,6 +9483,9 @@ "ssoLoginIsRequired": { "message": "SSO login is required" }, + "emailRequiredForSsoLogin": { + "message": "Email is required for SSO" + }, "selectedRegionFlag": { "message": "Selected region flag" }, @@ -11961,8 +11999,8 @@ "familiesMembership": { "message": "Families membership" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "planDescFamiliesV2": { "message": "Premium security for your family" @@ -12199,6 +12237,45 @@ } } }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "confirmNoSelectedCriticalApplicationsTitle": { "message": "No critical applications are selected" }, @@ -12207,5 +12284,139 @@ }, "userVerificationFailed": { "message": "User verification failed." + }, + "recoveryDeleteCiphersTitle": { + "message": "Delete unrecoverable vault items" + }, + "recoveryDeleteCiphersDesc": { + "message": "Some of your vault items could not be recovered. Do you want to delete these unrecoverable items from your vault?" + }, + "recoveryDeleteFoldersTitle": { + "message": "Delete unrecoverable folders" + }, + "recoveryDeleteFoldersDesc": { + "message": "Some of your folders could not be recovered. Do you want to delete these unrecoverable folders from your vault?" + }, + "recoveryReplacePrivateKeyTitle": { + "message": "Replace encryption key" + }, + "recoveryReplacePrivateKeyDesc": { + "message": "Your public-key encryption key pair could not be recovered. Do you want to replace your encryption key with a new key pair? This will require you to set up existing emergency-access and organization memberships again." + }, + "recoveryStepSyncTitle": { + "message": "Synchronizing data" + }, + "recoveryStepPrivateKeyTitle": { + "message": "Verifying encryption key integrity" + }, + "recoveryStepUserInfoTitle": { + "message": "Verifying user information" + }, + "recoveryStepCipherTitle": { + "message": "Verifying vault item integrity" + }, + "recoveryStepFoldersTitle": { + "message": "Verifying folder integrity" + }, + "dataRecoveryTitle": { + "message": "Data Recovery and Diagnostics" + }, + "dataRecoveryDescription": { + "message": "Use the data recovery tool to diagnose and repair issues with your account. After running diagnostics you have the option to save diagnostic logs for support and the option to repair any detected issues." + }, + "runDiagnostics": { + "message": "Run Diagnostics" + }, + "repairIssues": { + "message": "Repair Issues" + }, + "saveDiagnosticLogs": { + "message": "Save Diagnostic Logs" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On browser refresh." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On browser refresh" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/web/src/locales/fa/messages.json b/apps/web/src/locales/fa/messages.json index c8623050394..5039584050d 100644 --- a/apps/web/src/locales/fa/messages.json +++ b/apps/web/src/locales/fa/messages.json @@ -5169,9 +5169,21 @@ "message": "اصلاح", "description": "This is a verb. ex. 'Fix The Car'" }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "oldAttachmentsNeedFixDesc": { "message": "پرونده‌های پیوستی قدیمی در گاوصندوق شما وجود دارد که قبل از اینکه بتوانید کلید رمزگذاری حساب خود را بچرخانید باید اصلاح شوند." }, + "itemsTransferred": { + "message": "Items transferred" + }, "yourAccountsFingerprint": { "message": "عبارت اثر انگشت حساب شما", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -6453,6 +6465,32 @@ "bulkReinviteMessage": { "message": "با موفقیت مجدد دعوت شد" }, + "bulkReinviteSuccessToast": { + "message": "$COUNT$ users re-invited", + "placeholders": { + "count": { + "content": "$1", + "example": "12" + } + } + }, + "bulkReinviteLimitedSuccessToast": { + "message": "$LIMIT$ of $SELECTEDCOUNT$ users re-invited. $EXCLUDEDCOUNT$ were not invited due to the $LIMIT$ invite limit.", + "placeholders": { + "limit": { + "content": "$1", + "example": "4,000" + }, + "selectedCount": { + "content": "$2", + "example": "4,005" + }, + "excludedCount": { + "content": "$3", + "example": "5" + } + } + }, "bulkRemovedMessage": { "message": "با موفقیت حذف شد" }, @@ -6773,8 +6811,8 @@ "vaultTimeoutRangeError": { "message": "مهلت زمانی گاوصندوق در محدوده مجاز نیست." }, - "disablePersonalVaultExport": { - "message": "حذف برون ریزی گاوصندوق شخصی" + "disableExport": { + "message": "Remove export" }, "disablePersonalVaultExportDescription": { "message": "به اعضا اجازه ندهید که داده‌های گاوصندوق شخصی خود را برون ریزی کنند." @@ -7094,9 +7132,6 @@ "invalidVerificationCode": { "message": "کد تأیید نامعتبر است" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "برای اعضای سازمان زیر، کلمه عبور اصلی دیگر لازم نیست. لطفاً دامنه زیر را با مدیر سازمان خود تأیید کنید." - }, "keyConnectorDomain": { "message": "دامنه رابط کلید" }, @@ -7154,8 +7189,8 @@ "enabledSso": { "message": "SSO روشن شد" }, - "disabledSso": { - "message": "SSO روشن شد" + "ssoTurnedOff": { + "message": "SSO turned off" }, "emailMustLoginWithSso": { "message": "$EMAIL$ must login with Single Sign-on", @@ -9448,6 +9483,9 @@ "ssoLoginIsRequired": { "message": "ورود از طریق SSO الزامی است" }, + "emailRequiredForSsoLogin": { + "message": "Email is required for SSO" + }, "selectedRegionFlag": { "message": "پرچم منطقه انتخاب شد" }, @@ -11961,8 +11999,8 @@ "familiesMembership": { "message": "Families membership" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "planDescFamiliesV2": { "message": "Premium security for your family" @@ -12199,6 +12237,45 @@ } } }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "confirmNoSelectedCriticalApplicationsTitle": { "message": "No critical applications are selected" }, @@ -12207,5 +12284,139 @@ }, "userVerificationFailed": { "message": "User verification failed." + }, + "recoveryDeleteCiphersTitle": { + "message": "Delete unrecoverable vault items" + }, + "recoveryDeleteCiphersDesc": { + "message": "Some of your vault items could not be recovered. Do you want to delete these unrecoverable items from your vault?" + }, + "recoveryDeleteFoldersTitle": { + "message": "Delete unrecoverable folders" + }, + "recoveryDeleteFoldersDesc": { + "message": "Some of your folders could not be recovered. Do you want to delete these unrecoverable folders from your vault?" + }, + "recoveryReplacePrivateKeyTitle": { + "message": "Replace encryption key" + }, + "recoveryReplacePrivateKeyDesc": { + "message": "Your public-key encryption key pair could not be recovered. Do you want to replace your encryption key with a new key pair? This will require you to set up existing emergency-access and organization memberships again." + }, + "recoveryStepSyncTitle": { + "message": "Synchronizing data" + }, + "recoveryStepPrivateKeyTitle": { + "message": "Verifying encryption key integrity" + }, + "recoveryStepUserInfoTitle": { + "message": "Verifying user information" + }, + "recoveryStepCipherTitle": { + "message": "Verifying vault item integrity" + }, + "recoveryStepFoldersTitle": { + "message": "Verifying folder integrity" + }, + "dataRecoveryTitle": { + "message": "Data Recovery and Diagnostics" + }, + "dataRecoveryDescription": { + "message": "Use the data recovery tool to diagnose and repair issues with your account. After running diagnostics you have the option to save diagnostic logs for support and the option to repair any detected issues." + }, + "runDiagnostics": { + "message": "Run Diagnostics" + }, + "repairIssues": { + "message": "Repair Issues" + }, + "saveDiagnosticLogs": { + "message": "Save Diagnostic Logs" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On browser refresh." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On browser refresh" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/web/src/locales/fi/messages.json b/apps/web/src/locales/fi/messages.json index 181c8d4f125..8a70ab58fde 100644 --- a/apps/web/src/locales/fi/messages.json +++ b/apps/web/src/locales/fi/messages.json @@ -5169,9 +5169,21 @@ "message": "Korjaa", "description": "This is a verb. ex. 'Fix The Car'" }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "oldAttachmentsNeedFixDesc": { "message": "Holvissasi on vanhoja tiedostoliitteitä, jotka on korjattava ennen kuin voit uudistaa tilisi salausavaimen." }, + "itemsTransferred": { + "message": "Items transferred" + }, "yourAccountsFingerprint": { "message": "Tilisi tunnistelause", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -6453,6 +6465,32 @@ "bulkReinviteMessage": { "message": "Uuudelleenkutsu onnistui" }, + "bulkReinviteSuccessToast": { + "message": "$COUNT$ users re-invited", + "placeholders": { + "count": { + "content": "$1", + "example": "12" + } + } + }, + "bulkReinviteLimitedSuccessToast": { + "message": "$LIMIT$ of $SELECTEDCOUNT$ users re-invited. $EXCLUDEDCOUNT$ were not invited due to the $LIMIT$ invite limit.", + "placeholders": { + "limit": { + "content": "$1", + "example": "4,000" + }, + "selectedCount": { + "content": "$2", + "example": "4,005" + }, + "excludedCount": { + "content": "$3", + "example": "5" + } + } + }, "bulkRemovedMessage": { "message": "Poisto onnistui." }, @@ -6773,8 +6811,8 @@ "vaultTimeoutRangeError": { "message": "Holvin aikakatkaisu ei ole sallitun alueen sisällä." }, - "disablePersonalVaultExport": { - "message": "Estä yksityisen holvin vienti" + "disableExport": { + "message": "Remove export" }, "disablePersonalVaultExportDescription": { "message": "Älä salli jäsenten viedä henkilökohtaisten holviensa tietoja." @@ -7094,9 +7132,6 @@ "invalidVerificationCode": { "message": "Virheellinen todennuskoodi" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "keyConnectorDomain": { "message": "Key Connector domain" }, @@ -7154,8 +7189,8 @@ "enabledSso": { "message": "Kertakirjautuminen otettiin käyttöön" }, - "disabledSso": { - "message": "Kertakirjautuminen poistettiin käytöstä" + "ssoTurnedOff": { + "message": "SSO turned off" }, "emailMustLoginWithSso": { "message": "$EMAIL$ must login with Single Sign-on", @@ -9448,6 +9483,9 @@ "ssoLoginIsRequired": { "message": "Kertakirjautuminen vaaditaan" }, + "emailRequiredForSsoLogin": { + "message": "Email is required for SSO" + }, "selectedRegionFlag": { "message": "Valitun alueen lippu" }, @@ -11961,8 +11999,8 @@ "familiesMembership": { "message": "Families membership" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "planDescFamiliesV2": { "message": "Premium security for your family" @@ -12199,6 +12237,45 @@ } } }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "confirmNoSelectedCriticalApplicationsTitle": { "message": "No critical applications are selected" }, @@ -12207,5 +12284,139 @@ }, "userVerificationFailed": { "message": "User verification failed." + }, + "recoveryDeleteCiphersTitle": { + "message": "Delete unrecoverable vault items" + }, + "recoveryDeleteCiphersDesc": { + "message": "Some of your vault items could not be recovered. Do you want to delete these unrecoverable items from your vault?" + }, + "recoveryDeleteFoldersTitle": { + "message": "Delete unrecoverable folders" + }, + "recoveryDeleteFoldersDesc": { + "message": "Some of your folders could not be recovered. Do you want to delete these unrecoverable folders from your vault?" + }, + "recoveryReplacePrivateKeyTitle": { + "message": "Replace encryption key" + }, + "recoveryReplacePrivateKeyDesc": { + "message": "Your public-key encryption key pair could not be recovered. Do you want to replace your encryption key with a new key pair? This will require you to set up existing emergency-access and organization memberships again." + }, + "recoveryStepSyncTitle": { + "message": "Synchronizing data" + }, + "recoveryStepPrivateKeyTitle": { + "message": "Verifying encryption key integrity" + }, + "recoveryStepUserInfoTitle": { + "message": "Verifying user information" + }, + "recoveryStepCipherTitle": { + "message": "Verifying vault item integrity" + }, + "recoveryStepFoldersTitle": { + "message": "Verifying folder integrity" + }, + "dataRecoveryTitle": { + "message": "Data Recovery and Diagnostics" + }, + "dataRecoveryDescription": { + "message": "Use the data recovery tool to diagnose and repair issues with your account. After running diagnostics you have the option to save diagnostic logs for support and the option to repair any detected issues." + }, + "runDiagnostics": { + "message": "Run Diagnostics" + }, + "repairIssues": { + "message": "Repair Issues" + }, + "saveDiagnosticLogs": { + "message": "Save Diagnostic Logs" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On browser refresh." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On browser refresh" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/web/src/locales/fil/messages.json b/apps/web/src/locales/fil/messages.json index f3542368044..cd201f852ef 100644 --- a/apps/web/src/locales/fil/messages.json +++ b/apps/web/src/locales/fil/messages.json @@ -5169,9 +5169,21 @@ "message": "Ayusin ang", "description": "This is a verb. ex. 'Fix The Car'" }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "oldAttachmentsNeedFixDesc": { "message": "May mga lumang file attachment sa iyong vault na kailangang ayusin bago mo maiikot ang encryption key ng iyong account." }, + "itemsTransferred": { + "message": "Items transferred" + }, "yourAccountsFingerprint": { "message": "Ang fingerprint pala ng iyong account", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -6453,6 +6465,32 @@ "bulkReinviteMessage": { "message": "Matagumpay na naimbitahan muli" }, + "bulkReinviteSuccessToast": { + "message": "$COUNT$ users re-invited", + "placeholders": { + "count": { + "content": "$1", + "example": "12" + } + } + }, + "bulkReinviteLimitedSuccessToast": { + "message": "$LIMIT$ of $SELECTEDCOUNT$ users re-invited. $EXCLUDEDCOUNT$ were not invited due to the $LIMIT$ invite limit.", + "placeholders": { + "limit": { + "content": "$1", + "example": "4,000" + }, + "selectedCount": { + "content": "$2", + "example": "4,005" + }, + "excludedCount": { + "content": "$3", + "example": "5" + } + } + }, "bulkRemovedMessage": { "message": "Matagumpay na tinanggal" }, @@ -6773,8 +6811,8 @@ "vaultTimeoutRangeError": { "message": "Hindi pinapayagan ang vault timeout." }, - "disablePersonalVaultExport": { - "message": "Alisin ang indibidwal na pag export ng vault" + "disableExport": { + "message": "Remove export" }, "disablePersonalVaultExportDescription": { "message": "Do not allow members to export data from their individual vault." @@ -7094,9 +7132,6 @@ "invalidVerificationCode": { "message": "Maling verification code" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "keyConnectorDomain": { "message": "Key Connector domain" }, @@ -7154,8 +7189,8 @@ "enabledSso": { "message": "Nakabukas ang SSO" }, - "disabledSso": { - "message": "Nakabukas ang SSO" + "ssoTurnedOff": { + "message": "SSO turned off" }, "emailMustLoginWithSso": { "message": "$EMAIL$ must login with Single Sign-on", @@ -9448,6 +9483,9 @@ "ssoLoginIsRequired": { "message": "SSO login is required" }, + "emailRequiredForSsoLogin": { + "message": "Email is required for SSO" + }, "selectedRegionFlag": { "message": "Selected region flag" }, @@ -11961,8 +11999,8 @@ "familiesMembership": { "message": "Families membership" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "planDescFamiliesV2": { "message": "Premium security for your family" @@ -12199,6 +12237,45 @@ } } }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "confirmNoSelectedCriticalApplicationsTitle": { "message": "No critical applications are selected" }, @@ -12207,5 +12284,139 @@ }, "userVerificationFailed": { "message": "User verification failed." + }, + "recoveryDeleteCiphersTitle": { + "message": "Delete unrecoverable vault items" + }, + "recoveryDeleteCiphersDesc": { + "message": "Some of your vault items could not be recovered. Do you want to delete these unrecoverable items from your vault?" + }, + "recoveryDeleteFoldersTitle": { + "message": "Delete unrecoverable folders" + }, + "recoveryDeleteFoldersDesc": { + "message": "Some of your folders could not be recovered. Do you want to delete these unrecoverable folders from your vault?" + }, + "recoveryReplacePrivateKeyTitle": { + "message": "Replace encryption key" + }, + "recoveryReplacePrivateKeyDesc": { + "message": "Your public-key encryption key pair could not be recovered. Do you want to replace your encryption key with a new key pair? This will require you to set up existing emergency-access and organization memberships again." + }, + "recoveryStepSyncTitle": { + "message": "Synchronizing data" + }, + "recoveryStepPrivateKeyTitle": { + "message": "Verifying encryption key integrity" + }, + "recoveryStepUserInfoTitle": { + "message": "Verifying user information" + }, + "recoveryStepCipherTitle": { + "message": "Verifying vault item integrity" + }, + "recoveryStepFoldersTitle": { + "message": "Verifying folder integrity" + }, + "dataRecoveryTitle": { + "message": "Data Recovery and Diagnostics" + }, + "dataRecoveryDescription": { + "message": "Use the data recovery tool to diagnose and repair issues with your account. After running diagnostics you have the option to save diagnostic logs for support and the option to repair any detected issues." + }, + "runDiagnostics": { + "message": "Run Diagnostics" + }, + "repairIssues": { + "message": "Repair Issues" + }, + "saveDiagnosticLogs": { + "message": "Save Diagnostic Logs" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On browser refresh." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On browser refresh" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/web/src/locales/fr/messages.json b/apps/web/src/locales/fr/messages.json index e5fcf929f0d..311f8912137 100644 --- a/apps/web/src/locales/fr/messages.json +++ b/apps/web/src/locales/fr/messages.json @@ -5075,7 +5075,7 @@ "message": "4 heures" }, "onRefresh": { - "message": "Au rechargement de la page" + "message": "À l'actualisation du navigateur" }, "dateUpdated": { "message": "Mis à jour", @@ -5169,9 +5169,21 @@ "message": "Réparer", "description": "This is a verb. ex. 'Fix The Car'" }, + "fixEncryption": { + "message": "Corriger le chiffrement" + }, + "fixEncryptionTooltip": { + "message": "Ce fichier utilise une méthode de chiffrement obsolète." + }, + "attachmentUpdated": { + "message": "Pièce jointe mise à jour" + }, "oldAttachmentsNeedFixDesc": { "message": "Il y a d'anciennes pièces jointes dans votre coffre qui doivent être réparées avant que vous ne puissiez régénérer la clé de chiffrement de votre compte." }, + "itemsTransferred": { + "message": "Éléments transférés" + }, "yourAccountsFingerprint": { "message": "Phrase d'empreinte de votre compte", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -6453,6 +6465,32 @@ "bulkReinviteMessage": { "message": "Réinvité avec succès." }, + "bulkReinviteSuccessToast": { + "message": "$COUNT$ ont été réinvités", + "placeholders": { + "count": { + "content": "$1", + "example": "12" + } + } + }, + "bulkReinviteLimitedSuccessToast": { + "message": "$LIMIT$ des utilisateurs de $SELECTEDCOUNT$ ont été ré-invités. $EXCLUDEDCOUNT$ n'ont pas été invités en raison de la limite d'invitation de $LIMIT$.", + "placeholders": { + "limit": { + "content": "$1", + "example": "4,000" + }, + "selectedCount": { + "content": "$2", + "example": "4,005" + }, + "excludedCount": { + "content": "$3", + "example": "5" + } + } + }, "bulkRemovedMessage": { "message": "Supprimé avec succès" }, @@ -6773,8 +6811,8 @@ "vaultTimeoutRangeError": { "message": "Le délai de mise en veille du coffre n'est pas dans l'intervalle de temps autorisé." }, - "disablePersonalVaultExport": { - "message": "Supprimer l'exportation individuelle du coffre" + "disableExport": { + "message": "Supprimer l'exportation" }, "disablePersonalVaultExportDescription": { "message": "Ne pas autoriser les membres à exporter des données de leur coffre individuel." @@ -7094,9 +7132,6 @@ "invalidVerificationCode": { "message": "Code de vérification invalide" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "Un mot de passe maître n'est plus requis pour les membres de l'organisation suivante. Veuillez confirmer le domaine ci-dessous avec l'administrateur de votre organisation." - }, "keyConnectorDomain": { "message": "Domaine Key Connector" }, @@ -7154,8 +7189,8 @@ "enabledSso": { "message": "SSO activé" }, - "disabledSso": { - "message": "SSO désactivé" + "ssoTurnedOff": { + "message": "SSO éteint" }, "emailMustLoginWithSso": { "message": "$EMAIL$ doit se connecter avec une Authentification Unique (SSO)", @@ -9448,6 +9483,9 @@ "ssoLoginIsRequired": { "message": "La connexion SSO est requise" }, + "emailRequiredForSsoLogin": { + "message": "Le courriel est requis pour le SSO" + }, "selectedRegionFlag": { "message": "Drapeau de la région sélectionnée" }, @@ -11961,8 +11999,8 @@ "familiesMembership": { "message": "Abonnement à Familles" }, - "planDescPremium": { - "message": "Sécurité en ligne complète" + "advancedOnlineSecurity": { + "message": "Sécurité en ligne avancée" }, "planDescFamiliesV2": { "message": "Sécurité Premium pour votre famille" @@ -12199,6 +12237,45 @@ } } }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Votre organisation n'utilise plus les mots de passe principaux pour se connecter à Bitwarden. Pour continuer, vérifiez l'organisation et le domaine." + }, + "continueWithLogIn": { + "message": "Continuer avec la connexion" + }, + "doNotContinue": { + "message": "Ne pas continuer" + }, + "domain": { + "message": "Domaine" + }, + "keyConnectorDomainTooltip": { + "message": "Ce domaine stockera les clés de chiffrement de votre compte, alors assurez-vous que vous lui faites confiance. Si vous n'êtes pas sûr, vérifiez auprès de votre administrateur." + }, + "verifyYourOrganization": { + "message": "Vérifiez votre organisation pour vous connecter" + }, + "organizationVerified": { + "message": "Organisation vérifiée" + }, + "domainVerified": { + "message": "Domaine vérifié" + }, + "leaveOrganizationContent": { + "message": "Si vous ne vérifiez pas votre organisation, votre accès à l'organisation sera révoqué." + }, + "leaveNow": { + "message": "Quitter maintenant" + }, + "verifyYourDomainToLogin": { + "message": "Vérifiez votre domaine pour vous connecter" + }, + "verifyYourDomainDescription": { + "message": "Pour continuer à vous connecter, vérifiez ce domaine." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "Pour continuer à vous connecter, vérifiez l'organisation et le domaine." + }, "confirmNoSelectedCriticalApplicationsTitle": { "message": "Aucune application critique n'est sélectionnée" }, @@ -12207,5 +12284,139 @@ }, "userVerificationFailed": { "message": "La vérification de l'utilisateur a échoué." + }, + "recoveryDeleteCiphersTitle": { + "message": "Supprimer les éléments non récupérables du coffre" + }, + "recoveryDeleteCiphersDesc": { + "message": "Certains éléments de votre coffre n'ont pu être récupérés. Voulez-vous supprimer ces éléments non récupérables de votre coffre ?" + }, + "recoveryDeleteFoldersTitle": { + "message": "Supprimer les dossiers non récupérables" + }, + "recoveryDeleteFoldersDesc": { + "message": "Certains de vos dossiers n'ont pu être récupérés. Voulez-vous supprimer ces dossiers irrécupérables de votre coffre ?" + }, + "recoveryReplacePrivateKeyTitle": { + "message": "Remplacer la clé de chiffrement" + }, + "recoveryReplacePrivateKeyDesc": { + "message": "Votre paire de clés de chiffrement à clé publique n'a pu être récupérée. Voulez-vous remplacer votre clé de chiffrement par une nouvelle paire de clés ? Cela vous demandera de reconfigurer les accès d'urgence existants et les adhésions à l'organisation." + }, + "recoveryStepSyncTitle": { + "message": "Synchronisation des données" + }, + "recoveryStepPrivateKeyTitle": { + "message": "Vérification de l'intégrité de la clé de chiffrement" + }, + "recoveryStepUserInfoTitle": { + "message": "Vérification des informations de l'utilisateur" + }, + "recoveryStepCipherTitle": { + "message": "Vérification de l'intégrité de l'élément du coffre" + }, + "recoveryStepFoldersTitle": { + "message": "Vérification de l'intégrité du dossier" + }, + "dataRecoveryTitle": { + "message": "Récupération des données et Diagnostics" + }, + "dataRecoveryDescription": { + "message": "Utilisez l'outil de récupération de données pour diagnostiquer et réparer les problèmes avec votre compte. Après avoir exécuté les diagnostics, vous avez la possibilité d'enregistrer les journaux de diagnostic pour la prise en charge et l'option de réparer tous les problèmes détectés." + }, + "runDiagnostics": { + "message": "Lancer le diagnostique" + }, + "repairIssues": { + "message": "Réparer les problèmes" + }, + "saveDiagnosticLogs": { + "message": "Enregistrer les journaux de diagnostic" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "Ce paramètre est géré par votre organisation." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Votre organisation a réglé le délai d'expiration de session maximal à $HOURS$ heure(s) et $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Votre organisation a défini le délai d'expiration de session par défaut sur À l'actualisation du navigateur." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Le délai d'expiration de session maximal ne peut pas dépasser $HOURS$ heure(s) et $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "À l'actualisation du navigateur" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Configurez une méthode de déverrouillage pour changer le délai d'expiration de votre coffre" + }, + "leaveConfirmationDialogTitle": { + "message": "Êtes-vous sûr de vouloir quitter ?" + }, + "leaveConfirmationDialogContentOne": { + "message": "En refusant, vos éléments personnels resteront dans votre compte, mais vous perdrez l'accès aux éléments partagés et aux fonctionnalités de l'organisation." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contactez votre administrateur pour regagner l'accès." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Quitter $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "Comment gérer mon coffre ?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transférer les éléments vers $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ exige que tous les éléments soient détenus par l’organisation pour des raisons de sécurité et de conformité. Cliquez sur Accepter pour transférer la propriété de vos éléments.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accepter le transfert" + }, + "declineAndLeave": { + "message": "Refuser et quitter" + }, + "whyAmISeeingThis": { + "message": "Pourquoi est-ce que je vois ça ?" } } diff --git a/apps/web/src/locales/gl/messages.json b/apps/web/src/locales/gl/messages.json index f7a848bd025..f882247b331 100644 --- a/apps/web/src/locales/gl/messages.json +++ b/apps/web/src/locales/gl/messages.json @@ -5169,9 +5169,21 @@ "message": "Fix", "description": "This is a verb. ex. 'Fix The Car'" }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "oldAttachmentsNeedFixDesc": { "message": "There are old file attachments in your vault that need to be fixed before you can rotate your account's encryption key." }, + "itemsTransferred": { + "message": "Items transferred" + }, "yourAccountsFingerprint": { "message": "Your account's fingerprint phrase", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -6453,6 +6465,32 @@ "bulkReinviteMessage": { "message": "Reinvited successfully" }, + "bulkReinviteSuccessToast": { + "message": "$COUNT$ users re-invited", + "placeholders": { + "count": { + "content": "$1", + "example": "12" + } + } + }, + "bulkReinviteLimitedSuccessToast": { + "message": "$LIMIT$ of $SELECTEDCOUNT$ users re-invited. $EXCLUDEDCOUNT$ were not invited due to the $LIMIT$ invite limit.", + "placeholders": { + "limit": { + "content": "$1", + "example": "4,000" + }, + "selectedCount": { + "content": "$2", + "example": "4,005" + }, + "excludedCount": { + "content": "$3", + "example": "5" + } + } + }, "bulkRemovedMessage": { "message": "Removed successfully" }, @@ -6773,8 +6811,8 @@ "vaultTimeoutRangeError": { "message": "Vault timeout is not within allowed range." }, - "disablePersonalVaultExport": { - "message": "Remove individual vault export" + "disableExport": { + "message": "Remove export" }, "disablePersonalVaultExportDescription": { "message": "Do not allow members to export data from their individual vault." @@ -7094,9 +7132,6 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "keyConnectorDomain": { "message": "Key Connector domain" }, @@ -7154,8 +7189,8 @@ "enabledSso": { "message": "SSO turned on" }, - "disabledSso": { - "message": "SSO turned on" + "ssoTurnedOff": { + "message": "SSO turned off" }, "emailMustLoginWithSso": { "message": "$EMAIL$ must login with Single Sign-on", @@ -9448,6 +9483,9 @@ "ssoLoginIsRequired": { "message": "SSO login is required" }, + "emailRequiredForSsoLogin": { + "message": "Email is required for SSO" + }, "selectedRegionFlag": { "message": "Selected region flag" }, @@ -11961,8 +11999,8 @@ "familiesMembership": { "message": "Families membership" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "planDescFamiliesV2": { "message": "Premium security for your family" @@ -12199,6 +12237,45 @@ } } }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "confirmNoSelectedCriticalApplicationsTitle": { "message": "No critical applications are selected" }, @@ -12207,5 +12284,139 @@ }, "userVerificationFailed": { "message": "User verification failed." + }, + "recoveryDeleteCiphersTitle": { + "message": "Delete unrecoverable vault items" + }, + "recoveryDeleteCiphersDesc": { + "message": "Some of your vault items could not be recovered. Do you want to delete these unrecoverable items from your vault?" + }, + "recoveryDeleteFoldersTitle": { + "message": "Delete unrecoverable folders" + }, + "recoveryDeleteFoldersDesc": { + "message": "Some of your folders could not be recovered. Do you want to delete these unrecoverable folders from your vault?" + }, + "recoveryReplacePrivateKeyTitle": { + "message": "Replace encryption key" + }, + "recoveryReplacePrivateKeyDesc": { + "message": "Your public-key encryption key pair could not be recovered. Do you want to replace your encryption key with a new key pair? This will require you to set up existing emergency-access and organization memberships again." + }, + "recoveryStepSyncTitle": { + "message": "Synchronizing data" + }, + "recoveryStepPrivateKeyTitle": { + "message": "Verifying encryption key integrity" + }, + "recoveryStepUserInfoTitle": { + "message": "Verifying user information" + }, + "recoveryStepCipherTitle": { + "message": "Verifying vault item integrity" + }, + "recoveryStepFoldersTitle": { + "message": "Verifying folder integrity" + }, + "dataRecoveryTitle": { + "message": "Data Recovery and Diagnostics" + }, + "dataRecoveryDescription": { + "message": "Use the data recovery tool to diagnose and repair issues with your account. After running diagnostics you have the option to save diagnostic logs for support and the option to repair any detected issues." + }, + "runDiagnostics": { + "message": "Run Diagnostics" + }, + "repairIssues": { + "message": "Repair Issues" + }, + "saveDiagnosticLogs": { + "message": "Save Diagnostic Logs" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On browser refresh." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On browser refresh" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/web/src/locales/he/messages.json b/apps/web/src/locales/he/messages.json index d723cb42140..3a56db568a7 100644 --- a/apps/web/src/locales/he/messages.json +++ b/apps/web/src/locales/he/messages.json @@ -5169,9 +5169,21 @@ "message": "תקן", "description": "This is a verb. ex. 'Fix The Car'" }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "oldAttachmentsNeedFixDesc": { "message": "בכספת שלך קיים קובץ מצורף ישן שצריך לעבור תיקון לפני שתוכל להחליף את מפתחות ההצפנה של החשבון שלך." }, + "itemsTransferred": { + "message": "Items transferred" + }, "yourAccountsFingerprint": { "message": "הסיסמה של טביעת האצבעות בחשבון שלך", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -6453,6 +6465,32 @@ "bulkReinviteMessage": { "message": "הוזמנו בהצלחה" }, + "bulkReinviteSuccessToast": { + "message": "$COUNT$ users re-invited", + "placeholders": { + "count": { + "content": "$1", + "example": "12" + } + } + }, + "bulkReinviteLimitedSuccessToast": { + "message": "$LIMIT$ of $SELECTEDCOUNT$ users re-invited. $EXCLUDEDCOUNT$ were not invited due to the $LIMIT$ invite limit.", + "placeholders": { + "limit": { + "content": "$1", + "example": "4,000" + }, + "selectedCount": { + "content": "$2", + "example": "4,005" + }, + "excludedCount": { + "content": "$3", + "example": "5" + } + } + }, "bulkRemovedMessage": { "message": "הוסרו בהצלחה" }, @@ -6773,8 +6811,8 @@ "vaultTimeoutRangeError": { "message": "פסק זמן לכספת אינו בטווח המותר." }, - "disablePersonalVaultExport": { - "message": "הסר ייצוא כספת אישית" + "disableExport": { + "message": "Remove export" }, "disablePersonalVaultExportDescription": { "message": "אל תאפשר לחברים לייצא נתונים מהכספת האישית שלהם." @@ -7094,9 +7132,6 @@ "invalidVerificationCode": { "message": "קוד אימות שגוי" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "סיסמה ראשית אינה נדרשת עוד עבור חברים בארגון הבא. נא לאשר את הדומיין שלהלן עם מנהל הארגון שלך." - }, "keyConnectorDomain": { "message": "דומיין של Key Connector" }, @@ -7154,8 +7189,8 @@ "enabledSso": { "message": "SSO מופעל" }, - "disabledSso": { - "message": "SSO כבוי" + "ssoTurnedOff": { + "message": "SSO turned off" }, "emailMustLoginWithSso": { "message": "$EMAIL$ מוכרח להיכנס עם כניסה יחידה", @@ -9448,6 +9483,9 @@ "ssoLoginIsRequired": { "message": "נדרשת כניסת SSO" }, + "emailRequiredForSsoLogin": { + "message": "Email is required for SSO" + }, "selectedRegionFlag": { "message": "דגל האזור שנבחר" }, @@ -11961,8 +11999,8 @@ "familiesMembership": { "message": "חברות למשפחות" }, - "planDescPremium": { - "message": "השלם אבטחה מקוונת" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "planDescFamiliesV2": { "message": "אבטחת פרימיום למשפחה שלך" @@ -12199,6 +12237,45 @@ } } }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "confirmNoSelectedCriticalApplicationsTitle": { "message": "לא סומנו יישומים קריטים" }, @@ -12207,5 +12284,139 @@ }, "userVerificationFailed": { "message": "User verification failed." + }, + "recoveryDeleteCiphersTitle": { + "message": "Delete unrecoverable vault items" + }, + "recoveryDeleteCiphersDesc": { + "message": "Some of your vault items could not be recovered. Do you want to delete these unrecoverable items from your vault?" + }, + "recoveryDeleteFoldersTitle": { + "message": "Delete unrecoverable folders" + }, + "recoveryDeleteFoldersDesc": { + "message": "Some of your folders could not be recovered. Do you want to delete these unrecoverable folders from your vault?" + }, + "recoveryReplacePrivateKeyTitle": { + "message": "Replace encryption key" + }, + "recoveryReplacePrivateKeyDesc": { + "message": "Your public-key encryption key pair could not be recovered. Do you want to replace your encryption key with a new key pair? This will require you to set up existing emergency-access and organization memberships again." + }, + "recoveryStepSyncTitle": { + "message": "Synchronizing data" + }, + "recoveryStepPrivateKeyTitle": { + "message": "Verifying encryption key integrity" + }, + "recoveryStepUserInfoTitle": { + "message": "Verifying user information" + }, + "recoveryStepCipherTitle": { + "message": "Verifying vault item integrity" + }, + "recoveryStepFoldersTitle": { + "message": "Verifying folder integrity" + }, + "dataRecoveryTitle": { + "message": "Data Recovery and Diagnostics" + }, + "dataRecoveryDescription": { + "message": "Use the data recovery tool to diagnose and repair issues with your account. After running diagnostics you have the option to save diagnostic logs for support and the option to repair any detected issues." + }, + "runDiagnostics": { + "message": "Run Diagnostics" + }, + "repairIssues": { + "message": "Repair Issues" + }, + "saveDiagnosticLogs": { + "message": "Save Diagnostic Logs" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On browser refresh." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On browser refresh" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/web/src/locales/hi/messages.json b/apps/web/src/locales/hi/messages.json index 1c5a211aaca..5909b46ff33 100644 --- a/apps/web/src/locales/hi/messages.json +++ b/apps/web/src/locales/hi/messages.json @@ -5169,9 +5169,21 @@ "message": "Fix", "description": "This is a verb. ex. 'Fix The Car'" }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "oldAttachmentsNeedFixDesc": { "message": "There are old file attachments in your vault that need to be fixed before you can rotate your account's encryption key." }, + "itemsTransferred": { + "message": "Items transferred" + }, "yourAccountsFingerprint": { "message": "Your account's fingerprint phrase", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -6453,6 +6465,32 @@ "bulkReinviteMessage": { "message": "Reinvited successfully" }, + "bulkReinviteSuccessToast": { + "message": "$COUNT$ users re-invited", + "placeholders": { + "count": { + "content": "$1", + "example": "12" + } + } + }, + "bulkReinviteLimitedSuccessToast": { + "message": "$LIMIT$ of $SELECTEDCOUNT$ users re-invited. $EXCLUDEDCOUNT$ were not invited due to the $LIMIT$ invite limit.", + "placeholders": { + "limit": { + "content": "$1", + "example": "4,000" + }, + "selectedCount": { + "content": "$2", + "example": "4,005" + }, + "excludedCount": { + "content": "$3", + "example": "5" + } + } + }, "bulkRemovedMessage": { "message": "Removed successfully" }, @@ -6773,8 +6811,8 @@ "vaultTimeoutRangeError": { "message": "Vault timeout is not within allowed range." }, - "disablePersonalVaultExport": { - "message": "Remove individual vault export" + "disableExport": { + "message": "Remove export" }, "disablePersonalVaultExportDescription": { "message": "Do not allow members to export data from their individual vault." @@ -7094,9 +7132,6 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "keyConnectorDomain": { "message": "Key Connector domain" }, @@ -7154,8 +7189,8 @@ "enabledSso": { "message": "SSO turned on" }, - "disabledSso": { - "message": "SSO turned on" + "ssoTurnedOff": { + "message": "SSO turned off" }, "emailMustLoginWithSso": { "message": "$EMAIL$ must login with Single Sign-on", @@ -9448,6 +9483,9 @@ "ssoLoginIsRequired": { "message": "SSO login is required" }, + "emailRequiredForSsoLogin": { + "message": "Email is required for SSO" + }, "selectedRegionFlag": { "message": "Selected region flag" }, @@ -11961,8 +11999,8 @@ "familiesMembership": { "message": "Families membership" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "planDescFamiliesV2": { "message": "Premium security for your family" @@ -12199,6 +12237,45 @@ } } }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "confirmNoSelectedCriticalApplicationsTitle": { "message": "No critical applications are selected" }, @@ -12207,5 +12284,139 @@ }, "userVerificationFailed": { "message": "User verification failed." + }, + "recoveryDeleteCiphersTitle": { + "message": "Delete unrecoverable vault items" + }, + "recoveryDeleteCiphersDesc": { + "message": "Some of your vault items could not be recovered. Do you want to delete these unrecoverable items from your vault?" + }, + "recoveryDeleteFoldersTitle": { + "message": "Delete unrecoverable folders" + }, + "recoveryDeleteFoldersDesc": { + "message": "Some of your folders could not be recovered. Do you want to delete these unrecoverable folders from your vault?" + }, + "recoveryReplacePrivateKeyTitle": { + "message": "Replace encryption key" + }, + "recoveryReplacePrivateKeyDesc": { + "message": "Your public-key encryption key pair could not be recovered. Do you want to replace your encryption key with a new key pair? This will require you to set up existing emergency-access and organization memberships again." + }, + "recoveryStepSyncTitle": { + "message": "Synchronizing data" + }, + "recoveryStepPrivateKeyTitle": { + "message": "Verifying encryption key integrity" + }, + "recoveryStepUserInfoTitle": { + "message": "Verifying user information" + }, + "recoveryStepCipherTitle": { + "message": "Verifying vault item integrity" + }, + "recoveryStepFoldersTitle": { + "message": "Verifying folder integrity" + }, + "dataRecoveryTitle": { + "message": "Data Recovery and Diagnostics" + }, + "dataRecoveryDescription": { + "message": "Use the data recovery tool to diagnose and repair issues with your account. After running diagnostics you have the option to save diagnostic logs for support and the option to repair any detected issues." + }, + "runDiagnostics": { + "message": "Run Diagnostics" + }, + "repairIssues": { + "message": "Repair Issues" + }, + "saveDiagnosticLogs": { + "message": "Save Diagnostic Logs" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On browser refresh." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On browser refresh" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/web/src/locales/hr/messages.json b/apps/web/src/locales/hr/messages.json index f357718bab0..ff7cf0be88e 100644 --- a/apps/web/src/locales/hr/messages.json +++ b/apps/web/src/locales/hr/messages.json @@ -5169,9 +5169,21 @@ "message": "Popravi", "description": "This is a verb. ex. 'Fix The Car'" }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "oldAttachmentsNeedFixDesc": { "message": "Postoje stari privitci u tvom trezoru koje je potrebno popraviti prije rotacije ključa za šifriranje." }, + "itemsTransferred": { + "message": "Items transferred" + }, "yourAccountsFingerprint": { "message": "Jedinstvena fraza tvog računa", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -6453,6 +6465,32 @@ "bulkReinviteMessage": { "message": "Uspješno ponovno pozvano" }, + "bulkReinviteSuccessToast": { + "message": "$COUNT$ users re-invited", + "placeholders": { + "count": { + "content": "$1", + "example": "12" + } + } + }, + "bulkReinviteLimitedSuccessToast": { + "message": "$LIMIT$ of $SELECTEDCOUNT$ users re-invited. $EXCLUDEDCOUNT$ were not invited due to the $LIMIT$ invite limit.", + "placeholders": { + "limit": { + "content": "$1", + "example": "4,000" + }, + "selectedCount": { + "content": "$2", + "example": "4,005" + }, + "excludedCount": { + "content": "$3", + "example": "5" + } + } + }, "bulkRemovedMessage": { "message": "Uspješno uklonjeno" }, @@ -6773,8 +6811,8 @@ "vaultTimeoutRangeError": { "message": "Istek trezora nije unutar zadanog vremena." }, - "disablePersonalVaultExport": { - "message": "Onemogući izvoz osobnog trezora" + "disableExport": { + "message": "Remove export" }, "disablePersonalVaultExportDescription": { "message": "Onemogućuje korisnicima izvoz osobnog trezora." @@ -7094,9 +7132,6 @@ "invalidVerificationCode": { "message": "Nevažeći kôd za provjeru" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "Glavna lozinka više nije obavezna za članove ove organizacije. Provjeri prikazanu domenu sa svojim administratorom." - }, "keyConnectorDomain": { "message": "Domena konektora ključa" }, @@ -7154,8 +7189,8 @@ "enabledSso": { "message": "SSO omogućen" }, - "disabledSso": { - "message": "SSO onemogućen" + "ssoTurnedOff": { + "message": "SSO turned off" }, "emailMustLoginWithSso": { "message": "$EMAIL$ se mora prijavljivati sa SSO", @@ -9448,6 +9483,9 @@ "ssoLoginIsRequired": { "message": "Potrebna je SSO prijava" }, + "emailRequiredForSsoLogin": { + "message": "Email is required for SSO" + }, "selectedRegionFlag": { "message": "Zastava odabrane regije" }, @@ -11961,8 +11999,8 @@ "familiesMembership": { "message": "Obiteljsko članstvo" }, - "planDescPremium": { - "message": "Dovrši online sigurnost" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "planDescFamiliesV2": { "message": "Premium sigurnost za tvoju obitelj" @@ -12199,6 +12237,45 @@ } } }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "confirmNoSelectedCriticalApplicationsTitle": { "message": "Nisu odabrane kritične aplikacije" }, @@ -12207,5 +12284,139 @@ }, "userVerificationFailed": { "message": "User verification failed." + }, + "recoveryDeleteCiphersTitle": { + "message": "Delete unrecoverable vault items" + }, + "recoveryDeleteCiphersDesc": { + "message": "Some of your vault items could not be recovered. Do you want to delete these unrecoverable items from your vault?" + }, + "recoveryDeleteFoldersTitle": { + "message": "Delete unrecoverable folders" + }, + "recoveryDeleteFoldersDesc": { + "message": "Some of your folders could not be recovered. Do you want to delete these unrecoverable folders from your vault?" + }, + "recoveryReplacePrivateKeyTitle": { + "message": "Replace encryption key" + }, + "recoveryReplacePrivateKeyDesc": { + "message": "Your public-key encryption key pair could not be recovered. Do you want to replace your encryption key with a new key pair? This will require you to set up existing emergency-access and organization memberships again." + }, + "recoveryStepSyncTitle": { + "message": "Synchronizing data" + }, + "recoveryStepPrivateKeyTitle": { + "message": "Verifying encryption key integrity" + }, + "recoveryStepUserInfoTitle": { + "message": "Verifying user information" + }, + "recoveryStepCipherTitle": { + "message": "Verifying vault item integrity" + }, + "recoveryStepFoldersTitle": { + "message": "Verifying folder integrity" + }, + "dataRecoveryTitle": { + "message": "Data Recovery and Diagnostics" + }, + "dataRecoveryDescription": { + "message": "Use the data recovery tool to diagnose and repair issues with your account. After running diagnostics you have the option to save diagnostic logs for support and the option to repair any detected issues." + }, + "runDiagnostics": { + "message": "Run Diagnostics" + }, + "repairIssues": { + "message": "Repair Issues" + }, + "saveDiagnosticLogs": { + "message": "Save Diagnostic Logs" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On browser refresh." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On browser refresh" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/web/src/locales/hu/messages.json b/apps/web/src/locales/hu/messages.json index 3d360541b93..eb437dcc678 100644 --- a/apps/web/src/locales/hu/messages.json +++ b/apps/web/src/locales/hu/messages.json @@ -5169,9 +5169,21 @@ "message": "Javítás", "description": "This is a verb. ex. 'Fix The Car'" }, + "fixEncryption": { + "message": "Titkosítás javítása" + }, + "fixEncryptionTooltip": { + "message": "Ez a fájl elavult titkosítási módszert használ." + }, + "attachmentUpdated": { + "message": "A melléklet frissítésre került." + }, "oldAttachmentsNeedFixDesc": { "message": "A széfben régi mellékletek vannak, amelyeket javítani kell a fiók titkosító kulcsának fordítása előtt." }, + "itemsTransferred": { + "message": "Az elemek átvitelre kerültek." + }, "yourAccountsFingerprint": { "message": "Fiók ujjlenyomat kifejezés", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -6453,6 +6465,32 @@ "bulkReinviteMessage": { "message": "Az ismételt meghívás sikeres volt." }, + "bulkReinviteSuccessToast": { + "message": "$COUNT$ felhasználó ismételten meghívásra került.", + "placeholders": { + "count": { + "content": "$1", + "example": "12" + } + } + }, + "bulkReinviteLimitedSuccessToast": { + "message": "$LIMIT$ / $SELECTEDCOUNT$ felhasználó ismételten meghívásra került. $EXCLUDEDCOUNT$ nem kapott meghívást $LIMIT$ meghívási korlát miatt.", + "placeholders": { + "limit": { + "content": "$1", + "example": "4,000" + }, + "selectedCount": { + "content": "$2", + "example": "4,005" + }, + "excludedCount": { + "content": "$3", + "example": "5" + } + } + }, "bulkRemovedMessage": { "message": "Az eltávolítás sikeres volt." }, @@ -6773,8 +6811,8 @@ "vaultTimeoutRangeError": { "message": "A széf időkifutás nincs az engedélyezett intervallunban." }, - "disablePersonalVaultExport": { - "message": "A személyes széf exportálás eltávolítása" + "disableExport": { + "message": "Exportálás eltávolítása" }, "disablePersonalVaultExportDescription": { "message": "Ne engedjük meg a tagoknak, hogy adatokat exportáljanak a saját széfből." @@ -7094,9 +7132,6 @@ "invalidVerificationCode": { "message": "Érvénytelen ellenőrző kód" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A következő szervezet tagjai számára már nincs szükség mesterjelszóra. Erősítsük meg az alábbi tartományt a szervezet adminisztrátorával." - }, "keyConnectorDomain": { "message": "Key Connector tartomány" }, @@ -7154,8 +7189,8 @@ "enabledSso": { "message": "Az SSO bekapcsolásra került." }, - "disabledSso": { - "message": "Az SSO bekapcsolásra került." + "ssoTurnedOff": { + "message": "Az SSO kikapcsolásra került." }, "emailMustLoginWithSso": { "message": "$EMAIL$ segítségével be kell jelentkezni egyszeri bejelentkezéssel.", @@ -9448,6 +9483,9 @@ "ssoLoginIsRequired": { "message": "SSO bejelentkezés szükséges" }, + "emailRequiredForSsoLogin": { + "message": "Email cím szükséges az SSO esetén." + }, "selectedRegionFlag": { "message": "Kiválasztott régió zászló" }, @@ -11961,8 +11999,8 @@ "familiesMembership": { "message": "Családi tagság" }, - "planDescPremium": { - "message": "Teljes körű online biztonság" + "advancedOnlineSecurity": { + "message": "Bővített online biztonság" }, "planDescFamiliesV2": { "message": "Prémium biztonság a család számára" @@ -12199,6 +12237,45 @@ } } }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "A szervezet már nem használ mesterjelszavakat a Bitwardenbe bejelentkezéshez. A folytatáshoz ellenőrizzük a szervezetet és a tartományt." + }, + "continueWithLogIn": { + "message": "Folytatás bejelentkezéssel" + }, + "doNotContinue": { + "message": "Nincs folytatás" + }, + "domain": { + "message": "Tartomány" + }, + "keyConnectorDomainTooltip": { + "message": "Ez a tartomány tárolja a fiók titkosítási kulcsait, ezért győződjünk meg róla, hogy megbízunk-e benne. Ha nem vagyunk biztos benne, érdeklődjünk adminisztrátornál." + }, + "verifyYourOrganization": { + "message": "Szervezet ellenőrzése a bejelentkezéshez" + }, + "organizationVerified": { + "message": "A szervezet ellenőrzésre került." + }, + "domainVerified": { + "message": "A tartomány ellenőrzésre került." + }, + "leaveOrganizationContent": { + "message": "Ha nem ellenőrizzük a szervezetet, a szervezethez hozzáférés visszavonásra kerül." + }, + "leaveNow": { + "message": "Elhagyás most" + }, + "verifyYourDomainToLogin": { + "message": "Tartomány ellenőrzése a bejelentkezéshez" + }, + "verifyYourDomainDescription": { + "message": "A bejelentkezés folytatásához ellenőrizzük ezt a tartományt." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "A bejelentkezés folytatásához ellenőrizzük a szervezetet és a tartományt." + }, "confirmNoSelectedCriticalApplicationsTitle": { "message": "Nincsenek veszélyben levő kritikus alkalmazások." }, @@ -12207,5 +12284,139 @@ }, "userVerificationFailed": { "message": "A felhasználó ellenőrzése sikertelen volt." + }, + "recoveryDeleteCiphersTitle": { + "message": "Helyreállíthatatlan széf elemek törlése" + }, + "recoveryDeleteCiphersDesc": { + "message": "Néhány széf elemet nem sikerült helyreállítani. Törölni szeretnénk ezeket a helyreállíthatatlan elemeket a széfből?" + }, + "recoveryDeleteFoldersTitle": { + "message": "Helyreállíthatatlan mappák törlése" + }, + "recoveryDeleteFoldersDesc": { + "message": "Néhány mappát nem sikerült helyreállítani. Törölni szeretnénk ezeket a helyreállíthatatlan nappákat a széfből?" + }, + "recoveryReplacePrivateKeyTitle": { + "message": "Titkosító kulcs cseréje" + }, + "recoveryReplacePrivateKeyDesc": { + "message": "A nyilvános kulcsú titkosítás kulcspárját nem sikerült helyreállítani. Szeretnénk lecserélni a titkosítási kulcsot egy új kulcspárra? Ehhez újra be kell üzemelni a meglévő vészhelyzeti hozzáférési és szervezeti tagságokat." + }, + "recoveryStepSyncTitle": { + "message": "Adatok szinkronizálása" + }, + "recoveryStepPrivateKeyTitle": { + "message": "Titkosítási kulcs integritás ellenőrzése" + }, + "recoveryStepUserInfoTitle": { + "message": "Felhasználói információk ellenőrzése" + }, + "recoveryStepCipherTitle": { + "message": "Széf elem integritás ellenőrzése" + }, + "recoveryStepFoldersTitle": { + "message": "Mappa integritás ellenőrzése" + }, + "dataRecoveryTitle": { + "message": "Adat helyreállítás és diagnosztika" + }, + "dataRecoveryDescription": { + "message": "Használjuk az adat helyreállítási eszközt a fiókkal kapcsolatos problémák diagnosztizálásához és javításához. A diagnosztika futtatása után lehetőség van a diagnosztikai naplók mentésére támogatáshoz, valamint az észlelt problémák kijavításához." + }, + "runDiagnostics": { + "message": "Diagnosztika futtatása" + }, + "repairIssues": { + "message": "Hibák javítása" + }, + "saveDiagnosticLogs": { + "message": "Diagnosztikai naplók mentése" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "Ezt a beállítást a szervezet lezeli." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "A szervezet a munkamenet maximális munkamenet időkifutását $HOURS$ órára és $MINUTES$ percre állította be.", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "A szervezet az alapértelmezett munkamenet időkifutást a Böngésző frissítésekor értékre állította." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "A maximális időtúllépés nem haladhatja meg a $HOURS$ óra és $MINUTES$ perc értéket.", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "Böngésző frissítésekor" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Állítsunk be egy feloldási módot a széf időkifutási műveletének módosításához." + }, + "leaveConfirmationDialogTitle": { + "message": "Biztosan szeretnénk kilépni?" + }, + "leaveConfirmationDialogContentOne": { + "message": "Az elutasítással a személyes elemek a fiókban maradnak, de elveszítjük hozzáférést a megosztott elemekhez és a szervezeti funkciókhoz." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Lépjünk kapcsolatba az adminisztrátorral a hozzáférés visszaszerzéséért." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "$ORGANIZATION$ elhagyása", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "Hogyan kezeljem a széfet?" + }, + "transferItemsToOrganizationTitle": { + "message": "Elemek átvitele $ORGANIZATION$ szervezethez", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ megköveteli, hogy minden elem a szervezet tulajdonában legyen a biztonság és a megfelelőség érdekében. Kattintás az elfogadásra az elemek tulajdonjogának átruházásához.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Átvitel elfogadása" + }, + "declineAndLeave": { + "message": "Elutasítás és kilépés" + }, + "whyAmISeeingThis": { + "message": "Miért látható ez?" } } diff --git a/apps/web/src/locales/id/messages.json b/apps/web/src/locales/id/messages.json index 4dc41e8f928..74804d5db8e 100644 --- a/apps/web/src/locales/id/messages.json +++ b/apps/web/src/locales/id/messages.json @@ -5169,9 +5169,21 @@ "message": "Perbaiki", "description": "This is a verb. ex. 'Fix The Car'" }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "oldAttachmentsNeedFixDesc": { "message": "Ada lampiran berkas lama di brankas Anda yang perlu diperbaiki sebelum Anda dapat memutar kunci enkripsi akun Anda." }, + "itemsTransferred": { + "message": "Items transferred" + }, "yourAccountsFingerprint": { "message": "Frase sidik jari akun Anda", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -6453,6 +6465,32 @@ "bulkReinviteMessage": { "message": "Berhasil mengundang kembali." }, + "bulkReinviteSuccessToast": { + "message": "$COUNT$ users re-invited", + "placeholders": { + "count": { + "content": "$1", + "example": "12" + } + } + }, + "bulkReinviteLimitedSuccessToast": { + "message": "$LIMIT$ of $SELECTEDCOUNT$ users re-invited. $EXCLUDEDCOUNT$ were not invited due to the $LIMIT$ invite limit.", + "placeholders": { + "limit": { + "content": "$1", + "example": "4,000" + }, + "selectedCount": { + "content": "$2", + "example": "4,005" + }, + "excludedCount": { + "content": "$3", + "example": "5" + } + } + }, "bulkRemovedMessage": { "message": "Penghapusan sukses" }, @@ -6773,8 +6811,8 @@ "vaultTimeoutRangeError": { "message": "Vault timeout is not within allowed range." }, - "disablePersonalVaultExport": { - "message": "Remove individual vault export" + "disableExport": { + "message": "Remove export" }, "disablePersonalVaultExportDescription": { "message": "Do not allow members to export data from their individual vault." @@ -7094,9 +7132,6 @@ "invalidVerificationCode": { "message": "Kode verifikasi tidak valid" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "keyConnectorDomain": { "message": "Key Connector domain" }, @@ -7154,8 +7189,8 @@ "enabledSso": { "message": "SSO aktif" }, - "disabledSso": { - "message": "SSO tidak aktif" + "ssoTurnedOff": { + "message": "SSO turned off" }, "emailMustLoginWithSso": { "message": "$EMAIL$ must login with Single Sign-on", @@ -9448,6 +9483,9 @@ "ssoLoginIsRequired": { "message": "SSO login is required" }, + "emailRequiredForSsoLogin": { + "message": "Email is required for SSO" + }, "selectedRegionFlag": { "message": "Selected region flag" }, @@ -11961,8 +11999,8 @@ "familiesMembership": { "message": "Families membership" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "planDescFamiliesV2": { "message": "Premium security for your family" @@ -12199,6 +12237,45 @@ } } }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "confirmNoSelectedCriticalApplicationsTitle": { "message": "No critical applications are selected" }, @@ -12207,5 +12284,139 @@ }, "userVerificationFailed": { "message": "User verification failed." + }, + "recoveryDeleteCiphersTitle": { + "message": "Delete unrecoverable vault items" + }, + "recoveryDeleteCiphersDesc": { + "message": "Some of your vault items could not be recovered. Do you want to delete these unrecoverable items from your vault?" + }, + "recoveryDeleteFoldersTitle": { + "message": "Delete unrecoverable folders" + }, + "recoveryDeleteFoldersDesc": { + "message": "Some of your folders could not be recovered. Do you want to delete these unrecoverable folders from your vault?" + }, + "recoveryReplacePrivateKeyTitle": { + "message": "Replace encryption key" + }, + "recoveryReplacePrivateKeyDesc": { + "message": "Your public-key encryption key pair could not be recovered. Do you want to replace your encryption key with a new key pair? This will require you to set up existing emergency-access and organization memberships again." + }, + "recoveryStepSyncTitle": { + "message": "Synchronizing data" + }, + "recoveryStepPrivateKeyTitle": { + "message": "Verifying encryption key integrity" + }, + "recoveryStepUserInfoTitle": { + "message": "Verifying user information" + }, + "recoveryStepCipherTitle": { + "message": "Verifying vault item integrity" + }, + "recoveryStepFoldersTitle": { + "message": "Verifying folder integrity" + }, + "dataRecoveryTitle": { + "message": "Data Recovery and Diagnostics" + }, + "dataRecoveryDescription": { + "message": "Use the data recovery tool to diagnose and repair issues with your account. After running diagnostics you have the option to save diagnostic logs for support and the option to repair any detected issues." + }, + "runDiagnostics": { + "message": "Run Diagnostics" + }, + "repairIssues": { + "message": "Repair Issues" + }, + "saveDiagnosticLogs": { + "message": "Save Diagnostic Logs" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On browser refresh." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On browser refresh" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/web/src/locales/it/messages.json b/apps/web/src/locales/it/messages.json index 459f1dfd3a4..3814df96b74 100644 --- a/apps/web/src/locales/it/messages.json +++ b/apps/web/src/locales/it/messages.json @@ -3063,7 +3063,7 @@ "message": "1 GB di spazio di archiviazione crittografato per gli allegati." }, "premiumSignUpStorageV2": { - "message": "$SIZE$ encrypted storage for file attachments.", + "message": "Archivio crittografato di $SIZE$ per allegati.", "placeholders": { "size": { "content": "$1", @@ -3134,13 +3134,13 @@ } }, "premiumSubscriptionEnded": { - "message": "Your Premium subscription ended" + "message": "Il tuo abbonamento Premium è scaduto" }, "premiumSubscriptionEndedDesc": { - "message": "To regain access to your archive, restart your Premium subscription. If you edit details for an archived item before restarting, it'll be moved back into your vault." + "message": "Per recuperare l'accesso al tuo archivio, riavvia il tuo abbonamento Premium. Se modifichi i dettagli di un elemento archiviato prima del riavvio, sarà spostato nella tua cassaforte." }, "restartPremium": { - "message": "Restart Premium" + "message": "Riavvia Premium" }, "additionalStorageGb": { "message": "Spazio di archiviazione aggiuntivo (GB)" @@ -4627,19 +4627,19 @@ "message": "Ulteriori informazioni" }, "migrationsFailed": { - "message": "An error occurred updating the encryption settings." + "message": "S'è verificato un errore durante l'aggiornamento delle impostazioni di crittografia." }, "updateEncryptionSettingsTitle": { - "message": "Update your encryption settings" + "message": "Aggiorna le impostazioni di crittografia" }, "updateEncryptionSettingsDesc": { - "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + "message": "Le impostazioni di crittografia consigliate miglioreranno la sicurezza del tuo account. Inserisci la password principale per aggiornare." }, "confirmIdentityToContinue": { - "message": "Confirm your identity to continue" + "message": "Conferma la tua identità per continuare" }, "enterYourMasterPassword": { - "message": "Enter your master password" + "message": "Inserisci la password principale" }, "updateSettings": { "message": "Aggiorna le impostazioni" @@ -5169,9 +5169,21 @@ "message": "Correggi", "description": "This is a verb. ex. 'Fix The Car'" }, + "fixEncryption": { + "message": "Correggi la crittografia" + }, + "fixEncryptionTooltip": { + "message": "Questo file usa un metodo di crittografia obsoleto." + }, + "attachmentUpdated": { + "message": "Allegato aggiornato" + }, "oldAttachmentsNeedFixDesc": { "message": "Ci sono vecchi file allegati nella tua cassaforte che devono essere corretti prima di poter ruotare la chiave di crittografia del tuo account." }, + "itemsTransferred": { + "message": "Elementi trasferiti" + }, "yourAccountsFingerprint": { "message": "Frase impronta del tuo account", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -6453,6 +6465,32 @@ "bulkReinviteMessage": { "message": "Invitato di nuovo" }, + "bulkReinviteSuccessToast": { + "message": "$COUNT$ utente/i ri-invitato/i", + "placeholders": { + "count": { + "content": "$1", + "example": "12" + } + } + }, + "bulkReinviteLimitedSuccessToast": { + "message": "$LIMIT$ utenti su $SELECTEDCOUNT$ ri-invitati. $EXCLUDEDCOUNT$ non hanno ricevuto l'invito a causa del limite di $LIMIT$ inviti.", + "placeholders": { + "limit": { + "content": "$1", + "example": "4,000" + }, + "selectedCount": { + "content": "$2", + "example": "4,005" + }, + "excludedCount": { + "content": "$3", + "example": "5" + } + } + }, "bulkRemovedMessage": { "message": "Rimosso" }, @@ -6773,8 +6811,8 @@ "vaultTimeoutRangeError": { "message": "Il timeout della cassaforte non è all'interno dell'intervallo consentito." }, - "disablePersonalVaultExport": { - "message": "Rimuovi esportazione cassaforte individuale" + "disableExport": { + "message": "Rimuovi esportazione" }, "disablePersonalVaultExportDescription": { "message": "Non consentire ai membri di esportare i dati dalla loro cassaforte individuale." @@ -7094,9 +7132,6 @@ "invalidVerificationCode": { "message": "Codice di verifica non valido" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "La password principale non è più richiesta per i membri dell'organizzazione. Per favore, conferma il dominio qui sotto con l'amministratore." - }, "keyConnectorDomain": { "message": "Dominio Key Connector" }, @@ -7154,8 +7189,8 @@ "enabledSso": { "message": "SSO attivato" }, - "disabledSso": { - "message": "SSO disattivato" + "ssoTurnedOff": { + "message": "Single Sign-On (SSO) disattivato" }, "emailMustLoginWithSso": { "message": "$EMAIL$ deve accedere con Single Sign-on", @@ -9448,6 +9483,9 @@ "ssoLoginIsRequired": { "message": "Il login SSO è obbligatorio" }, + "emailRequiredForSsoLogin": { + "message": "L'email è richiesta per SSO (Single Sign-On)" + }, "selectedRegionFlag": { "message": "Bandiera della regione selezionata" }, @@ -11961,8 +11999,8 @@ "familiesMembership": { "message": "Abbonamento famiglie" }, - "planDescPremium": { - "message": "Sicurezza online completa" + "advancedOnlineSecurity": { + "message": "Sicurezza online avanzata" }, "planDescFamiliesV2": { "message": "Sicurezza Premium per la tua famiglia" @@ -12199,6 +12237,45 @@ } } }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "confirmNoSelectedCriticalApplicationsTitle": { "message": "Non ci sono applicazioni contrassegnate come critiche" }, @@ -12207,5 +12284,139 @@ }, "userVerificationFailed": { "message": "Verifica dell'utente non riuscita." + }, + "recoveryDeleteCiphersTitle": { + "message": "Elimina gli oggetti della cassaforte non recuperabili" + }, + "recoveryDeleteCiphersDesc": { + "message": "Alcuni oggetti della tua cassaforte non possono essere recuperati. Vuoi eliminarli?" + }, + "recoveryDeleteFoldersTitle": { + "message": "Elimina le cartelle non recuperabili" + }, + "recoveryDeleteFoldersDesc": { + "message": "Alcune cartelle della tua cassaforte non possono essere recuperate. Vuoi eliminarle?" + }, + "recoveryReplacePrivateKeyTitle": { + "message": "Cambia chiave crittografica" + }, + "recoveryReplacePrivateKeyDesc": { + "message": "La tua coppia di chiavi crittografiche non può essere recuperata. Vuoi sostituirla con una nuova coppia di chiavi? Attenzione: dovrai impostare nuovamente i membri abilitati all'accesso d'emergenza." + }, + "recoveryStepSyncTitle": { + "message": "Sincronizzazione dati…" + }, + "recoveryStepPrivateKeyTitle": { + "message": "Verifica integrità della chiave crittografica" + }, + "recoveryStepUserInfoTitle": { + "message": "Verifica delle informazioni dell'utente" + }, + "recoveryStepCipherTitle": { + "message": "Verifica integrità dell'oggetto…" + }, + "recoveryStepFoldersTitle": { + "message": "Verifica integrità della cartella…" + }, + "dataRecoveryTitle": { + "message": "Recupero dati e diagnostica" + }, + "dataRecoveryDescription": { + "message": "Usa lo strumento di recupero dati per diagnosticare e riparare i problemi dell'account. Dopo la scansione della diagnostica, potrai salvare i registri diagnostici per il supporto e avrai la possibilità di riparare eventuali problemi rilevati." + }, + "runDiagnostics": { + "message": "Esegui la diagnostica" + }, + "repairIssues": { + "message": "Ripara i problemi rilevati" + }, + "saveDiagnosticLogs": { + "message": "Salva registri diagnostici" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "Questa impostazione è gestita dalla tua organizzazione." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "La tua organizzazione ha impostato $HOURS$ ora/e e $MINUTES$ minuto/i come durata massima della sessione.", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "In base alle impostazioni della tua organizzazione, la sessione terminerà ogni volta che la pagina sarà chiusa o ricaricata." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "La durata della sessione non può superare $HOURS$ ora/e e $MINUTES$ minuto/i", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "Al refresh o riavvio del browser" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Imposta un metodo di sblocco per modificare l'azione al timeout" + }, + "leaveConfirmationDialogTitle": { + "message": "Vuoi davvero abbandonare?" + }, + "leaveConfirmationDialogContentOne": { + "message": "Se rifiuti, tutti gli elementi esistenti resteranno nel tuo account, ma perderai l'accesso agli oggetti condivisi e alle funzioni organizzative." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contatta il tuo amministratore per recuperare l'accesso." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Abbandona $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "Come si gestisce la cassaforte?" + }, + "transferItemsToOrganizationTitle": { + "message": "Trasferisci gli elementi in $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ richiede che tutti gli elementi siano di proprietà dell'organizzazione per motivi di conformità e sicurezza. Clicca su 'Accetta' per trasferire la proprietà.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accetta il trasferimento" + }, + "declineAndLeave": { + "message": "Rifiuta e abbandona" + }, + "whyAmISeeingThis": { + "message": "Perché vedo questo avviso?" } } diff --git a/apps/web/src/locales/ja/messages.json b/apps/web/src/locales/ja/messages.json index 33535634bc8..930b2ddd81d 100644 --- a/apps/web/src/locales/ja/messages.json +++ b/apps/web/src/locales/ja/messages.json @@ -5169,9 +5169,21 @@ "message": "修正", "description": "This is a verb. ex. 'Fix The Car'" }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "oldAttachmentsNeedFixDesc": { "message": "暗号化キーのローテーションを行う前に、保管庫内の古い添付ファイルを修正する必要があります。" }, + "itemsTransferred": { + "message": "Items transferred" + }, "yourAccountsFingerprint": { "message": "アカウントのパスフレーズ", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -6453,6 +6465,32 @@ "bulkReinviteMessage": { "message": "再招待に成功しました" }, + "bulkReinviteSuccessToast": { + "message": "$COUNT$ users re-invited", + "placeholders": { + "count": { + "content": "$1", + "example": "12" + } + } + }, + "bulkReinviteLimitedSuccessToast": { + "message": "$LIMIT$ of $SELECTEDCOUNT$ users re-invited. $EXCLUDEDCOUNT$ were not invited due to the $LIMIT$ invite limit.", + "placeholders": { + "limit": { + "content": "$1", + "example": "4,000" + }, + "selectedCount": { + "content": "$2", + "example": "4,005" + }, + "excludedCount": { + "content": "$3", + "example": "5" + } + } + }, "bulkRemovedMessage": { "message": "削除しました " }, @@ -6773,8 +6811,8 @@ "vaultTimeoutRangeError": { "message": "保管庫のタイムアウトは許可された範囲内にありません。" }, - "disablePersonalVaultExport": { - "message": "個別の保管庫のエクスポートを削除" + "disableExport": { + "message": "Remove export" }, "disablePersonalVaultExportDescription": { "message": "メンバーが個人の保管庫からデータをエクスポートすることを許可しません。" @@ -7094,9 +7132,6 @@ "invalidVerificationCode": { "message": "認証コードが間違っています" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "keyConnectorDomain": { "message": "Key Connector domain" }, @@ -7154,8 +7189,8 @@ "enabledSso": { "message": "SSO を有効にしました" }, - "disabledSso": { - "message": "SSOを無効にしました" + "ssoTurnedOff": { + "message": "SSO turned off" }, "emailMustLoginWithSso": { "message": "$EMAIL$ must login with Single Sign-on", @@ -9448,6 +9483,9 @@ "ssoLoginIsRequired": { "message": "SSO ログインが必要です" }, + "emailRequiredForSsoLogin": { + "message": "Email is required for SSO" + }, "selectedRegionFlag": { "message": "リージョン選択" }, @@ -11961,8 +11999,8 @@ "familiesMembership": { "message": "Families membership" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "planDescFamiliesV2": { "message": "Premium security for your family" @@ -12199,6 +12237,45 @@ } } }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "confirmNoSelectedCriticalApplicationsTitle": { "message": "No critical applications are selected" }, @@ -12207,5 +12284,139 @@ }, "userVerificationFailed": { "message": "User verification failed." + }, + "recoveryDeleteCiphersTitle": { + "message": "Delete unrecoverable vault items" + }, + "recoveryDeleteCiphersDesc": { + "message": "Some of your vault items could not be recovered. Do you want to delete these unrecoverable items from your vault?" + }, + "recoveryDeleteFoldersTitle": { + "message": "Delete unrecoverable folders" + }, + "recoveryDeleteFoldersDesc": { + "message": "Some of your folders could not be recovered. Do you want to delete these unrecoverable folders from your vault?" + }, + "recoveryReplacePrivateKeyTitle": { + "message": "Replace encryption key" + }, + "recoveryReplacePrivateKeyDesc": { + "message": "Your public-key encryption key pair could not be recovered. Do you want to replace your encryption key with a new key pair? This will require you to set up existing emergency-access and organization memberships again." + }, + "recoveryStepSyncTitle": { + "message": "Synchronizing data" + }, + "recoveryStepPrivateKeyTitle": { + "message": "Verifying encryption key integrity" + }, + "recoveryStepUserInfoTitle": { + "message": "Verifying user information" + }, + "recoveryStepCipherTitle": { + "message": "Verifying vault item integrity" + }, + "recoveryStepFoldersTitle": { + "message": "Verifying folder integrity" + }, + "dataRecoveryTitle": { + "message": "Data Recovery and Diagnostics" + }, + "dataRecoveryDescription": { + "message": "Use the data recovery tool to diagnose and repair issues with your account. After running diagnostics you have the option to save diagnostic logs for support and the option to repair any detected issues." + }, + "runDiagnostics": { + "message": "Run Diagnostics" + }, + "repairIssues": { + "message": "Repair Issues" + }, + "saveDiagnosticLogs": { + "message": "Save Diagnostic Logs" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On browser refresh." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On browser refresh" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/web/src/locales/ka/messages.json b/apps/web/src/locales/ka/messages.json index 47a46367feb..d1f614c61d7 100644 --- a/apps/web/src/locales/ka/messages.json +++ b/apps/web/src/locales/ka/messages.json @@ -5169,9 +5169,21 @@ "message": "Fix", "description": "This is a verb. ex. 'Fix The Car'" }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "oldAttachmentsNeedFixDesc": { "message": "There are old file attachments in your vault that need to be fixed before you can rotate your account's encryption key." }, + "itemsTransferred": { + "message": "Items transferred" + }, "yourAccountsFingerprint": { "message": "Your account's fingerprint phrase", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -6453,6 +6465,32 @@ "bulkReinviteMessage": { "message": "Reinvited successfully" }, + "bulkReinviteSuccessToast": { + "message": "$COUNT$ users re-invited", + "placeholders": { + "count": { + "content": "$1", + "example": "12" + } + } + }, + "bulkReinviteLimitedSuccessToast": { + "message": "$LIMIT$ of $SELECTEDCOUNT$ users re-invited. $EXCLUDEDCOUNT$ were not invited due to the $LIMIT$ invite limit.", + "placeholders": { + "limit": { + "content": "$1", + "example": "4,000" + }, + "selectedCount": { + "content": "$2", + "example": "4,005" + }, + "excludedCount": { + "content": "$3", + "example": "5" + } + } + }, "bulkRemovedMessage": { "message": "Removed successfully" }, @@ -6773,8 +6811,8 @@ "vaultTimeoutRangeError": { "message": "Vault timeout is not within allowed range." }, - "disablePersonalVaultExport": { - "message": "Remove individual vault export" + "disableExport": { + "message": "Remove export" }, "disablePersonalVaultExportDescription": { "message": "Do not allow members to export data from their individual vault." @@ -7094,9 +7132,6 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "keyConnectorDomain": { "message": "Key Connector domain" }, @@ -7154,8 +7189,8 @@ "enabledSso": { "message": "SSO turned on" }, - "disabledSso": { - "message": "SSO turned on" + "ssoTurnedOff": { + "message": "SSO turned off" }, "emailMustLoginWithSso": { "message": "$EMAIL$ must login with Single Sign-on", @@ -9448,6 +9483,9 @@ "ssoLoginIsRequired": { "message": "SSO login is required" }, + "emailRequiredForSsoLogin": { + "message": "Email is required for SSO" + }, "selectedRegionFlag": { "message": "Selected region flag" }, @@ -11961,8 +11999,8 @@ "familiesMembership": { "message": "Families membership" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "planDescFamiliesV2": { "message": "Premium security for your family" @@ -12199,6 +12237,45 @@ } } }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "confirmNoSelectedCriticalApplicationsTitle": { "message": "No critical applications are selected" }, @@ -12207,5 +12284,139 @@ }, "userVerificationFailed": { "message": "User verification failed." + }, + "recoveryDeleteCiphersTitle": { + "message": "Delete unrecoverable vault items" + }, + "recoveryDeleteCiphersDesc": { + "message": "Some of your vault items could not be recovered. Do you want to delete these unrecoverable items from your vault?" + }, + "recoveryDeleteFoldersTitle": { + "message": "Delete unrecoverable folders" + }, + "recoveryDeleteFoldersDesc": { + "message": "Some of your folders could not be recovered. Do you want to delete these unrecoverable folders from your vault?" + }, + "recoveryReplacePrivateKeyTitle": { + "message": "Replace encryption key" + }, + "recoveryReplacePrivateKeyDesc": { + "message": "Your public-key encryption key pair could not be recovered. Do you want to replace your encryption key with a new key pair? This will require you to set up existing emergency-access and organization memberships again." + }, + "recoveryStepSyncTitle": { + "message": "Synchronizing data" + }, + "recoveryStepPrivateKeyTitle": { + "message": "Verifying encryption key integrity" + }, + "recoveryStepUserInfoTitle": { + "message": "Verifying user information" + }, + "recoveryStepCipherTitle": { + "message": "Verifying vault item integrity" + }, + "recoveryStepFoldersTitle": { + "message": "Verifying folder integrity" + }, + "dataRecoveryTitle": { + "message": "Data Recovery and Diagnostics" + }, + "dataRecoveryDescription": { + "message": "Use the data recovery tool to diagnose and repair issues with your account. After running diagnostics you have the option to save diagnostic logs for support and the option to repair any detected issues." + }, + "runDiagnostics": { + "message": "Run Diagnostics" + }, + "repairIssues": { + "message": "Repair Issues" + }, + "saveDiagnosticLogs": { + "message": "Save Diagnostic Logs" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On browser refresh." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On browser refresh" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/web/src/locales/km/messages.json b/apps/web/src/locales/km/messages.json index 608215f8155..fd419fe7397 100644 --- a/apps/web/src/locales/km/messages.json +++ b/apps/web/src/locales/km/messages.json @@ -5169,9 +5169,21 @@ "message": "Fix", "description": "This is a verb. ex. 'Fix The Car'" }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "oldAttachmentsNeedFixDesc": { "message": "There are old file attachments in your vault that need to be fixed before you can rotate your account's encryption key." }, + "itemsTransferred": { + "message": "Items transferred" + }, "yourAccountsFingerprint": { "message": "Your account's fingerprint phrase", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -6453,6 +6465,32 @@ "bulkReinviteMessage": { "message": "Reinvited successfully" }, + "bulkReinviteSuccessToast": { + "message": "$COUNT$ users re-invited", + "placeholders": { + "count": { + "content": "$1", + "example": "12" + } + } + }, + "bulkReinviteLimitedSuccessToast": { + "message": "$LIMIT$ of $SELECTEDCOUNT$ users re-invited. $EXCLUDEDCOUNT$ were not invited due to the $LIMIT$ invite limit.", + "placeholders": { + "limit": { + "content": "$1", + "example": "4,000" + }, + "selectedCount": { + "content": "$2", + "example": "4,005" + }, + "excludedCount": { + "content": "$3", + "example": "5" + } + } + }, "bulkRemovedMessage": { "message": "Removed successfully" }, @@ -6773,8 +6811,8 @@ "vaultTimeoutRangeError": { "message": "Vault timeout is not within allowed range." }, - "disablePersonalVaultExport": { - "message": "Remove individual vault export" + "disableExport": { + "message": "Remove export" }, "disablePersonalVaultExportDescription": { "message": "Do not allow members to export data from their individual vault." @@ -7094,9 +7132,6 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "keyConnectorDomain": { "message": "Key Connector domain" }, @@ -7154,8 +7189,8 @@ "enabledSso": { "message": "SSO turned on" }, - "disabledSso": { - "message": "SSO turned on" + "ssoTurnedOff": { + "message": "SSO turned off" }, "emailMustLoginWithSso": { "message": "$EMAIL$ must login with Single Sign-on", @@ -9448,6 +9483,9 @@ "ssoLoginIsRequired": { "message": "SSO login is required" }, + "emailRequiredForSsoLogin": { + "message": "Email is required for SSO" + }, "selectedRegionFlag": { "message": "Selected region flag" }, @@ -11961,8 +11999,8 @@ "familiesMembership": { "message": "Families membership" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "planDescFamiliesV2": { "message": "Premium security for your family" @@ -12199,6 +12237,45 @@ } } }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "confirmNoSelectedCriticalApplicationsTitle": { "message": "No critical applications are selected" }, @@ -12207,5 +12284,139 @@ }, "userVerificationFailed": { "message": "User verification failed." + }, + "recoveryDeleteCiphersTitle": { + "message": "Delete unrecoverable vault items" + }, + "recoveryDeleteCiphersDesc": { + "message": "Some of your vault items could not be recovered. Do you want to delete these unrecoverable items from your vault?" + }, + "recoveryDeleteFoldersTitle": { + "message": "Delete unrecoverable folders" + }, + "recoveryDeleteFoldersDesc": { + "message": "Some of your folders could not be recovered. Do you want to delete these unrecoverable folders from your vault?" + }, + "recoveryReplacePrivateKeyTitle": { + "message": "Replace encryption key" + }, + "recoveryReplacePrivateKeyDesc": { + "message": "Your public-key encryption key pair could not be recovered. Do you want to replace your encryption key with a new key pair? This will require you to set up existing emergency-access and organization memberships again." + }, + "recoveryStepSyncTitle": { + "message": "Synchronizing data" + }, + "recoveryStepPrivateKeyTitle": { + "message": "Verifying encryption key integrity" + }, + "recoveryStepUserInfoTitle": { + "message": "Verifying user information" + }, + "recoveryStepCipherTitle": { + "message": "Verifying vault item integrity" + }, + "recoveryStepFoldersTitle": { + "message": "Verifying folder integrity" + }, + "dataRecoveryTitle": { + "message": "Data Recovery and Diagnostics" + }, + "dataRecoveryDescription": { + "message": "Use the data recovery tool to diagnose and repair issues with your account. After running diagnostics you have the option to save diagnostic logs for support and the option to repair any detected issues." + }, + "runDiagnostics": { + "message": "Run Diagnostics" + }, + "repairIssues": { + "message": "Repair Issues" + }, + "saveDiagnosticLogs": { + "message": "Save Diagnostic Logs" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On browser refresh." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On browser refresh" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/web/src/locales/kn/messages.json b/apps/web/src/locales/kn/messages.json index 2f0759ff939..5d5c07f2baa 100644 --- a/apps/web/src/locales/kn/messages.json +++ b/apps/web/src/locales/kn/messages.json @@ -5169,9 +5169,21 @@ "message": "ಹೊಂದಿಸು", "description": "This is a verb. ex. 'Fix The Car'" }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "oldAttachmentsNeedFixDesc": { "message": "ನಿಮ್ಮ ವಾಲ್ಟ್‌ನಲ್ಲಿ ಹಳೆಯ ಫೈಲ್ ಲಗತ್ತುಗಳಿವೆ, ಅದನ್ನು ನಿಮ್ಮ ಖಾತೆಯ ಎನ್‌ಕ್ರಿಪ್ಶನ್ ಕೀಲಿಯನ್ನು ತಿರುಗಿಸುವ ಮೊದಲು ಸರಿಪಡಿಸಬೇಕಾಗಿದೆ." }, + "itemsTransferred": { + "message": "Items transferred" + }, "yourAccountsFingerprint": { "message": "ನಿಮ್ಮ ಖಾತೆಯ ಫಿಂಗರ್‌ಪ್ರಿಂಟ್ ನುಡಿಗಟ್ಟು", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -6453,6 +6465,32 @@ "bulkReinviteMessage": { "message": "ಯಶಸ್ವಿಯಾಗಿ ಪುನಃ ಆಹ್ವಾನಿಸಲಾಗಿದೆ." }, + "bulkReinviteSuccessToast": { + "message": "$COUNT$ users re-invited", + "placeholders": { + "count": { + "content": "$1", + "example": "12" + } + } + }, + "bulkReinviteLimitedSuccessToast": { + "message": "$LIMIT$ of $SELECTEDCOUNT$ users re-invited. $EXCLUDEDCOUNT$ were not invited due to the $LIMIT$ invite limit.", + "placeholders": { + "limit": { + "content": "$1", + "example": "4,000" + }, + "selectedCount": { + "content": "$2", + "example": "4,005" + }, + "excludedCount": { + "content": "$3", + "example": "5" + } + } + }, "bulkRemovedMessage": { "message": "ಯಶಸ್ವಿಯಾಗಿ ತೆಗೆದುಹಾಕಲಾಗಿದೆ" }, @@ -6773,8 +6811,8 @@ "vaultTimeoutRangeError": { "message": "Vault timeout is not within allowed range." }, - "disablePersonalVaultExport": { - "message": "Remove individual vault export" + "disableExport": { + "message": "Remove export" }, "disablePersonalVaultExportDescription": { "message": "Do not allow members to export data from their individual vault." @@ -7094,9 +7132,6 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "keyConnectorDomain": { "message": "Key Connector domain" }, @@ -7154,8 +7189,8 @@ "enabledSso": { "message": "SSO turned on" }, - "disabledSso": { - "message": "SSO turned on" + "ssoTurnedOff": { + "message": "SSO turned off" }, "emailMustLoginWithSso": { "message": "$EMAIL$ must login with Single Sign-on", @@ -9448,6 +9483,9 @@ "ssoLoginIsRequired": { "message": "SSO login is required" }, + "emailRequiredForSsoLogin": { + "message": "Email is required for SSO" + }, "selectedRegionFlag": { "message": "Selected region flag" }, @@ -11961,8 +11999,8 @@ "familiesMembership": { "message": "Families membership" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "planDescFamiliesV2": { "message": "Premium security for your family" @@ -12199,6 +12237,45 @@ } } }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "confirmNoSelectedCriticalApplicationsTitle": { "message": "No critical applications are selected" }, @@ -12207,5 +12284,139 @@ }, "userVerificationFailed": { "message": "User verification failed." + }, + "recoveryDeleteCiphersTitle": { + "message": "Delete unrecoverable vault items" + }, + "recoveryDeleteCiphersDesc": { + "message": "Some of your vault items could not be recovered. Do you want to delete these unrecoverable items from your vault?" + }, + "recoveryDeleteFoldersTitle": { + "message": "Delete unrecoverable folders" + }, + "recoveryDeleteFoldersDesc": { + "message": "Some of your folders could not be recovered. Do you want to delete these unrecoverable folders from your vault?" + }, + "recoveryReplacePrivateKeyTitle": { + "message": "Replace encryption key" + }, + "recoveryReplacePrivateKeyDesc": { + "message": "Your public-key encryption key pair could not be recovered. Do you want to replace your encryption key with a new key pair? This will require you to set up existing emergency-access and organization memberships again." + }, + "recoveryStepSyncTitle": { + "message": "Synchronizing data" + }, + "recoveryStepPrivateKeyTitle": { + "message": "Verifying encryption key integrity" + }, + "recoveryStepUserInfoTitle": { + "message": "Verifying user information" + }, + "recoveryStepCipherTitle": { + "message": "Verifying vault item integrity" + }, + "recoveryStepFoldersTitle": { + "message": "Verifying folder integrity" + }, + "dataRecoveryTitle": { + "message": "Data Recovery and Diagnostics" + }, + "dataRecoveryDescription": { + "message": "Use the data recovery tool to diagnose and repair issues with your account. After running diagnostics you have the option to save diagnostic logs for support and the option to repair any detected issues." + }, + "runDiagnostics": { + "message": "Run Diagnostics" + }, + "repairIssues": { + "message": "Repair Issues" + }, + "saveDiagnosticLogs": { + "message": "Save Diagnostic Logs" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On browser refresh." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On browser refresh" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/web/src/locales/ko/messages.json b/apps/web/src/locales/ko/messages.json index aaef9d378e9..a3ea2982a4a 100644 --- a/apps/web/src/locales/ko/messages.json +++ b/apps/web/src/locales/ko/messages.json @@ -5169,9 +5169,21 @@ "message": "수정", "description": "This is a verb. ex. 'Fix The Car'" }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "oldAttachmentsNeedFixDesc": { "message": "계정의 암호화 키를 교체하기 전에 보관함 내 오래된 파일 수정이 필요합니다." }, + "itemsTransferred": { + "message": "Items transferred" + }, "yourAccountsFingerprint": { "message": "계정 지문 구절", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -6453,6 +6465,32 @@ "bulkReinviteMessage": { "message": "성공적으로 재초대되었습니다." }, + "bulkReinviteSuccessToast": { + "message": "$COUNT$ users re-invited", + "placeholders": { + "count": { + "content": "$1", + "example": "12" + } + } + }, + "bulkReinviteLimitedSuccessToast": { + "message": "$LIMIT$ of $SELECTEDCOUNT$ users re-invited. $EXCLUDEDCOUNT$ were not invited due to the $LIMIT$ invite limit.", + "placeholders": { + "limit": { + "content": "$1", + "example": "4,000" + }, + "selectedCount": { + "content": "$2", + "example": "4,005" + }, + "excludedCount": { + "content": "$3", + "example": "5" + } + } + }, "bulkRemovedMessage": { "message": "성공적으로 제거됨" }, @@ -6773,8 +6811,8 @@ "vaultTimeoutRangeError": { "message": "Vault timeout is not within allowed range." }, - "disablePersonalVaultExport": { - "message": "개인 보관함 내보내기 비활성화" + "disableExport": { + "message": "Remove export" }, "disablePersonalVaultExportDescription": { "message": "Do not allow members to export data from their individual vault." @@ -7094,9 +7132,6 @@ "invalidVerificationCode": { "message": "유효하지 않은 확인 코드" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "keyConnectorDomain": { "message": "Key Connector domain" }, @@ -7154,8 +7189,8 @@ "enabledSso": { "message": "SSO 활성화됨" }, - "disabledSso": { - "message": "SSO 비활성화됨" + "ssoTurnedOff": { + "message": "SSO turned off" }, "emailMustLoginWithSso": { "message": "$EMAIL$ must login with Single Sign-on", @@ -9448,6 +9483,9 @@ "ssoLoginIsRequired": { "message": "SSO login is required" }, + "emailRequiredForSsoLogin": { + "message": "Email is required for SSO" + }, "selectedRegionFlag": { "message": "Selected region flag" }, @@ -11961,8 +11999,8 @@ "familiesMembership": { "message": "Families membership" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "planDescFamiliesV2": { "message": "Premium security for your family" @@ -12199,6 +12237,45 @@ } } }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "confirmNoSelectedCriticalApplicationsTitle": { "message": "No critical applications are selected" }, @@ -12207,5 +12284,139 @@ }, "userVerificationFailed": { "message": "User verification failed." + }, + "recoveryDeleteCiphersTitle": { + "message": "Delete unrecoverable vault items" + }, + "recoveryDeleteCiphersDesc": { + "message": "Some of your vault items could not be recovered. Do you want to delete these unrecoverable items from your vault?" + }, + "recoveryDeleteFoldersTitle": { + "message": "Delete unrecoverable folders" + }, + "recoveryDeleteFoldersDesc": { + "message": "Some of your folders could not be recovered. Do you want to delete these unrecoverable folders from your vault?" + }, + "recoveryReplacePrivateKeyTitle": { + "message": "Replace encryption key" + }, + "recoveryReplacePrivateKeyDesc": { + "message": "Your public-key encryption key pair could not be recovered. Do you want to replace your encryption key with a new key pair? This will require you to set up existing emergency-access and organization memberships again." + }, + "recoveryStepSyncTitle": { + "message": "Synchronizing data" + }, + "recoveryStepPrivateKeyTitle": { + "message": "Verifying encryption key integrity" + }, + "recoveryStepUserInfoTitle": { + "message": "Verifying user information" + }, + "recoveryStepCipherTitle": { + "message": "Verifying vault item integrity" + }, + "recoveryStepFoldersTitle": { + "message": "Verifying folder integrity" + }, + "dataRecoveryTitle": { + "message": "Data Recovery and Diagnostics" + }, + "dataRecoveryDescription": { + "message": "Use the data recovery tool to diagnose and repair issues with your account. After running diagnostics you have the option to save diagnostic logs for support and the option to repair any detected issues." + }, + "runDiagnostics": { + "message": "Run Diagnostics" + }, + "repairIssues": { + "message": "Repair Issues" + }, + "saveDiagnosticLogs": { + "message": "Save Diagnostic Logs" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On browser refresh." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On browser refresh" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/web/src/locales/lv/messages.json b/apps/web/src/locales/lv/messages.json index d627835b1f4..42d8dc15f3c 100644 --- a/apps/web/src/locales/lv/messages.json +++ b/apps/web/src/locales/lv/messages.json @@ -5169,9 +5169,21 @@ "message": "Salabot", "description": "This is a verb. ex. 'Fix The Car'" }, + "fixEncryption": { + "message": "Salabot šifrēšanu" + }, + "fixEncryptionTooltip": { + "message": "Šī datne izmanto novecojušu šifrēšanas veidu." + }, + "attachmentUpdated": { + "message": "Pielikums atjaunināts" + }, "oldAttachmentsNeedFixDesc": { "message": "Glabātavā atrodas veci datņu pielikumi, kas ir jāsalabo, pirms tiek veikta konta šifrēšanas atslēgu maiņa." }, + "itemsTransferred": { + "message": "Vienumi pārcelti" + }, "yourAccountsFingerprint": { "message": "Konta atpazīšanas vārdkopa", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -6453,6 +6465,32 @@ "bulkReinviteMessage": { "message": "Uzaicinājums veiksmīgi nosūtīts atkārtoti." }, + "bulkReinviteSuccessToast": { + "message": "$COUNT$ lietotāji uzaicināti atkārtoti", + "placeholders": { + "count": { + "content": "$1", + "example": "12" + } + } + }, + "bulkReinviteLimitedSuccessToast": { + "message": "Atkārtoti uzaicināti $LIMIT$ no $SELECTEDCOUNT$ lieotājiem. $EXCLUDEDCOUNT$ netika uzaicināt uzaicinājumu ierobežojuma ($LIMIT$) dēļ.", + "placeholders": { + "limit": { + "content": "$1", + "example": "4,000" + }, + "selectedCount": { + "content": "$2", + "example": "4,005" + }, + "excludedCount": { + "content": "$3", + "example": "5" + } + } + }, "bulkRemovedMessage": { "message": "Veiksmīgi noņemts" }, @@ -6773,8 +6811,8 @@ "vaultTimeoutRangeError": { "message": "Glabātavas noildze nav pieļaujamajās robežvērtībās." }, - "disablePersonalVaultExport": { - "message": "Atspējot personīgās glabātavas izgūšanu" + "disableExport": { + "message": "Noņemt izguvi" }, "disablePersonalVaultExportDescription": { "message": "Neļaut dalībniekiem izgūt datus no viņu glabātavas." @@ -7094,9 +7132,6 @@ "invalidVerificationCode": { "message": "Nederīgs apliecinājuma kods" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "Galvenā parole vairs nav nepieciešama turpmāk minētās apvienības dalībniekiem. Lūgums saskaņot zemāk esošo domēnu ar savas apvienības pārvaldītāju." - }, "keyConnectorDomain": { "message": "Key Connector domēns" }, @@ -7154,8 +7189,8 @@ "enabledSso": { "message": "Iespējota vienotā pieteikšanās" }, - "disabledSso": { - "message": "Atspējota vienotā pieteikšanās" + "ssoTurnedOff": { + "message": "Vienotā pieteikšanās izslēgta" }, "emailMustLoginWithSso": { "message": "$EMAIL$ jāpiesakās ar vienoto pieteikšanos", @@ -9448,6 +9483,9 @@ "ssoLoginIsRequired": { "message": "Vienotā pieteikšanās ir nepieciešama" }, + "emailRequiredForSsoLogin": { + "message": "SSO ir nepieciešama e-pasta adrese" + }, "selectedRegionFlag": { "message": "Atlasītā apgabala karogs" }, @@ -11961,8 +11999,8 @@ "familiesMembership": { "message": "Dalība ģimeņu plānā" }, - "planDescPremium": { - "message": "Pilnīga drošība tiešsaistē" + "advancedOnlineSecurity": { + "message": "Izvērsta tiešsaistes drošība" }, "planDescFamiliesV2": { "message": "Augstākā labuma drošība ģimenei" @@ -12199,6 +12237,45 @@ } } }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Apvienība vairs neizmanto galvenās paroles, lai pieteiktos Bitwarden. Lai turpinātu, jāapliecina apvienība un domēns." + }, + "continueWithLogIn": { + "message": "Turpināt ar pieteikšanos" + }, + "doNotContinue": { + "message": "Neturpināt" + }, + "domain": { + "message": "Domēns" + }, + "keyConnectorDomainTooltip": { + "message": "Šajā domēnā tiks glabātas konta šifrēšanas atslēgas, tādēļ jāpārliecinās par uzticamību. Ja nav pārliecības, jāsazinās ar savu pārvaldītāju." + }, + "verifyYourOrganization": { + "message": "Jāapliecina apvienība, lai pieteiktos" + }, + "organizationVerified": { + "message": "Apvienība apliecināta" + }, + "domainVerified": { + "message": "Domēns ir apliecināts" + }, + "leaveOrganizationContent": { + "message": "Ja neapliecināsi apvienību, tiks atsaukta piekļuve tai." + }, + "leaveNow": { + "message": "Pamest tagad" + }, + "verifyYourDomainToLogin": { + "message": "Jāapliecina domēns, lai pieteiktos" + }, + "verifyYourDomainDescription": { + "message": "Lai turpinātu pieteikšanos, jāapliecina šis domēns." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "Lai turpinātu pieteikšanos, jāapliecina apvienība un domēns." + }, "confirmNoSelectedCriticalApplicationsTitle": { "message": "Neviena būtiska lietotne nav atlasīta" }, @@ -12207,5 +12284,139 @@ }, "userVerificationFailed": { "message": "Lietotāja apliecināšana neizdevās." + }, + "recoveryDeleteCiphersTitle": { + "message": "Izdzēst neatkopjamos glabātavas vienumus" + }, + "recoveryDeleteCiphersDesc": { + "message": "Dažus no glabātavas vienumiem nevarēja atkopt. Vai izdzēst tos?" + }, + "recoveryDeleteFoldersTitle": { + "message": "Izdzēst neatkopjamās mapes" + }, + "recoveryDeleteFoldersDesc": { + "message": "Dažus no mapēm nevarēja atkopt. Vai izdzēst neatkopjamās mapes no glabātavas?" + }, + "recoveryReplacePrivateKeyTitle": { + "message": "Aizvietot šifrēšanas atslēgu" + }, + "recoveryReplacePrivateKeyDesc": { + "message": "Your public-key encryption key pair could not be recovered. Do you want to replace your encryption key with a new key pair? This will require you to set up existing emergency-access and organization memberships again." + }, + "recoveryStepSyncTitle": { + "message": "Synchronizing data" + }, + "recoveryStepPrivateKeyTitle": { + "message": "Verifying encryption key integrity" + }, + "recoveryStepUserInfoTitle": { + "message": "Verifying user information" + }, + "recoveryStepCipherTitle": { + "message": "Verifying vault item integrity" + }, + "recoveryStepFoldersTitle": { + "message": "Verifying folder integrity" + }, + "dataRecoveryTitle": { + "message": "Data Recovery and Diagnostics" + }, + "dataRecoveryDescription": { + "message": "Use the data recovery tool to diagnose and repair issues with your account. After running diagnostics you have the option to save diagnostic logs for support and the option to repair any detected issues." + }, + "runDiagnostics": { + "message": "Run Diagnostics" + }, + "repairIssues": { + "message": "Repair Issues" + }, + "saveDiagnosticLogs": { + "message": "Save Diagnostic Logs" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On browser refresh." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On browser refresh" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "leaveConfirmationDialogTitle": { + "message": "Vai tiešām pamest?" + }, + "leaveConfirmationDialogContentOne": { + "message": "Pēc noraidīšanas personīgie vienumi paliks Tavā kontā, bet Tu zaudēsi piekļvuvi kopīgotajiem vienumiem un apvienību iespējām." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Jāsazinās ar savu pārvaldītāju, lai atgūtu piekļuvi." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Pamest $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "Kā es varu pārvaldīt savu glabātavu?" + }, + "transferItemsToOrganizationTitle": { + "message": "Pārcelt vienumus uz $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/web/src/locales/ml/messages.json b/apps/web/src/locales/ml/messages.json index 8d49e35690e..907dd90fa2e 100644 --- a/apps/web/src/locales/ml/messages.json +++ b/apps/web/src/locales/ml/messages.json @@ -5169,9 +5169,21 @@ "message": "പരിഹരിക്കുക", "description": "This is a verb. ex. 'Fix The Car'" }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "oldAttachmentsNeedFixDesc": { "message": "നിങ്ങളുടെ അക്കൗണ്ടിന്റെ എൻ‌ക്രിപ്ഷൻ കീ തിരിക്കുന്നതിന് മുമ്പ് ശരിയാക്കേണ്ട പഴയ ഫയൽ അറ്റാച്ചുമെന്റുകൾ നിങ്ങളുടെ നിലവറയിൽ ഉണ്ട്." }, + "itemsTransferred": { + "message": "Items transferred" + }, "yourAccountsFingerprint": { "message": "നിങ്ങളുടെ അക്കൗണ്ടിൻ്റെ ഫിംഗർപ്രിന്റ് ഫ്രേസ്‌", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -6453,6 +6465,32 @@ "bulkReinviteMessage": { "message": "Reinvited successfully" }, + "bulkReinviteSuccessToast": { + "message": "$COUNT$ users re-invited", + "placeholders": { + "count": { + "content": "$1", + "example": "12" + } + } + }, + "bulkReinviteLimitedSuccessToast": { + "message": "$LIMIT$ of $SELECTEDCOUNT$ users re-invited. $EXCLUDEDCOUNT$ were not invited due to the $LIMIT$ invite limit.", + "placeholders": { + "limit": { + "content": "$1", + "example": "4,000" + }, + "selectedCount": { + "content": "$2", + "example": "4,005" + }, + "excludedCount": { + "content": "$3", + "example": "5" + } + } + }, "bulkRemovedMessage": { "message": "Removed successfully" }, @@ -6773,8 +6811,8 @@ "vaultTimeoutRangeError": { "message": "Vault timeout is not within allowed range." }, - "disablePersonalVaultExport": { - "message": "Remove individual vault export" + "disableExport": { + "message": "Remove export" }, "disablePersonalVaultExportDescription": { "message": "Do not allow members to export data from their individual vault." @@ -7094,9 +7132,6 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "keyConnectorDomain": { "message": "Key Connector domain" }, @@ -7154,8 +7189,8 @@ "enabledSso": { "message": "SSO turned on" }, - "disabledSso": { - "message": "SSO turned on" + "ssoTurnedOff": { + "message": "SSO turned off" }, "emailMustLoginWithSso": { "message": "$EMAIL$ must login with Single Sign-on", @@ -9448,6 +9483,9 @@ "ssoLoginIsRequired": { "message": "SSO login is required" }, + "emailRequiredForSsoLogin": { + "message": "Email is required for SSO" + }, "selectedRegionFlag": { "message": "Selected region flag" }, @@ -11961,8 +11999,8 @@ "familiesMembership": { "message": "Families membership" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "planDescFamiliesV2": { "message": "Premium security for your family" @@ -12199,6 +12237,45 @@ } } }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "confirmNoSelectedCriticalApplicationsTitle": { "message": "No critical applications are selected" }, @@ -12207,5 +12284,139 @@ }, "userVerificationFailed": { "message": "User verification failed." + }, + "recoveryDeleteCiphersTitle": { + "message": "Delete unrecoverable vault items" + }, + "recoveryDeleteCiphersDesc": { + "message": "Some of your vault items could not be recovered. Do you want to delete these unrecoverable items from your vault?" + }, + "recoveryDeleteFoldersTitle": { + "message": "Delete unrecoverable folders" + }, + "recoveryDeleteFoldersDesc": { + "message": "Some of your folders could not be recovered. Do you want to delete these unrecoverable folders from your vault?" + }, + "recoveryReplacePrivateKeyTitle": { + "message": "Replace encryption key" + }, + "recoveryReplacePrivateKeyDesc": { + "message": "Your public-key encryption key pair could not be recovered. Do you want to replace your encryption key with a new key pair? This will require you to set up existing emergency-access and organization memberships again." + }, + "recoveryStepSyncTitle": { + "message": "Synchronizing data" + }, + "recoveryStepPrivateKeyTitle": { + "message": "Verifying encryption key integrity" + }, + "recoveryStepUserInfoTitle": { + "message": "Verifying user information" + }, + "recoveryStepCipherTitle": { + "message": "Verifying vault item integrity" + }, + "recoveryStepFoldersTitle": { + "message": "Verifying folder integrity" + }, + "dataRecoveryTitle": { + "message": "Data Recovery and Diagnostics" + }, + "dataRecoveryDescription": { + "message": "Use the data recovery tool to diagnose and repair issues with your account. After running diagnostics you have the option to save diagnostic logs for support and the option to repair any detected issues." + }, + "runDiagnostics": { + "message": "Run Diagnostics" + }, + "repairIssues": { + "message": "Repair Issues" + }, + "saveDiagnosticLogs": { + "message": "Save Diagnostic Logs" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On browser refresh." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On browser refresh" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/web/src/locales/mr/messages.json b/apps/web/src/locales/mr/messages.json index 6ba6661c478..d8d7999dbe2 100644 --- a/apps/web/src/locales/mr/messages.json +++ b/apps/web/src/locales/mr/messages.json @@ -5169,9 +5169,21 @@ "message": "Fix", "description": "This is a verb. ex. 'Fix The Car'" }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "oldAttachmentsNeedFixDesc": { "message": "There are old file attachments in your vault that need to be fixed before you can rotate your account's encryption key." }, + "itemsTransferred": { + "message": "Items transferred" + }, "yourAccountsFingerprint": { "message": "Your account's fingerprint phrase", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -6453,6 +6465,32 @@ "bulkReinviteMessage": { "message": "Reinvited successfully" }, + "bulkReinviteSuccessToast": { + "message": "$COUNT$ users re-invited", + "placeholders": { + "count": { + "content": "$1", + "example": "12" + } + } + }, + "bulkReinviteLimitedSuccessToast": { + "message": "$LIMIT$ of $SELECTEDCOUNT$ users re-invited. $EXCLUDEDCOUNT$ were not invited due to the $LIMIT$ invite limit.", + "placeholders": { + "limit": { + "content": "$1", + "example": "4,000" + }, + "selectedCount": { + "content": "$2", + "example": "4,005" + }, + "excludedCount": { + "content": "$3", + "example": "5" + } + } + }, "bulkRemovedMessage": { "message": "Removed successfully" }, @@ -6773,8 +6811,8 @@ "vaultTimeoutRangeError": { "message": "Vault timeout is not within allowed range." }, - "disablePersonalVaultExport": { - "message": "Remove individual vault export" + "disableExport": { + "message": "Remove export" }, "disablePersonalVaultExportDescription": { "message": "Do not allow members to export data from their individual vault." @@ -7094,9 +7132,6 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "keyConnectorDomain": { "message": "Key Connector domain" }, @@ -7154,8 +7189,8 @@ "enabledSso": { "message": "SSO turned on" }, - "disabledSso": { - "message": "SSO turned on" + "ssoTurnedOff": { + "message": "SSO turned off" }, "emailMustLoginWithSso": { "message": "$EMAIL$ must login with Single Sign-on", @@ -9448,6 +9483,9 @@ "ssoLoginIsRequired": { "message": "SSO login is required" }, + "emailRequiredForSsoLogin": { + "message": "Email is required for SSO" + }, "selectedRegionFlag": { "message": "Selected region flag" }, @@ -11961,8 +11999,8 @@ "familiesMembership": { "message": "Families membership" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "planDescFamiliesV2": { "message": "Premium security for your family" @@ -12199,6 +12237,45 @@ } } }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "confirmNoSelectedCriticalApplicationsTitle": { "message": "No critical applications are selected" }, @@ -12207,5 +12284,139 @@ }, "userVerificationFailed": { "message": "User verification failed." + }, + "recoveryDeleteCiphersTitle": { + "message": "Delete unrecoverable vault items" + }, + "recoveryDeleteCiphersDesc": { + "message": "Some of your vault items could not be recovered. Do you want to delete these unrecoverable items from your vault?" + }, + "recoveryDeleteFoldersTitle": { + "message": "Delete unrecoverable folders" + }, + "recoveryDeleteFoldersDesc": { + "message": "Some of your folders could not be recovered. Do you want to delete these unrecoverable folders from your vault?" + }, + "recoveryReplacePrivateKeyTitle": { + "message": "Replace encryption key" + }, + "recoveryReplacePrivateKeyDesc": { + "message": "Your public-key encryption key pair could not be recovered. Do you want to replace your encryption key with a new key pair? This will require you to set up existing emergency-access and organization memberships again." + }, + "recoveryStepSyncTitle": { + "message": "Synchronizing data" + }, + "recoveryStepPrivateKeyTitle": { + "message": "Verifying encryption key integrity" + }, + "recoveryStepUserInfoTitle": { + "message": "Verifying user information" + }, + "recoveryStepCipherTitle": { + "message": "Verifying vault item integrity" + }, + "recoveryStepFoldersTitle": { + "message": "Verifying folder integrity" + }, + "dataRecoveryTitle": { + "message": "Data Recovery and Diagnostics" + }, + "dataRecoveryDescription": { + "message": "Use the data recovery tool to diagnose and repair issues with your account. After running diagnostics you have the option to save diagnostic logs for support and the option to repair any detected issues." + }, + "runDiagnostics": { + "message": "Run Diagnostics" + }, + "repairIssues": { + "message": "Repair Issues" + }, + "saveDiagnosticLogs": { + "message": "Save Diagnostic Logs" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On browser refresh." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On browser refresh" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/web/src/locales/my/messages.json b/apps/web/src/locales/my/messages.json index 608215f8155..fd419fe7397 100644 --- a/apps/web/src/locales/my/messages.json +++ b/apps/web/src/locales/my/messages.json @@ -5169,9 +5169,21 @@ "message": "Fix", "description": "This is a verb. ex. 'Fix The Car'" }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "oldAttachmentsNeedFixDesc": { "message": "There are old file attachments in your vault that need to be fixed before you can rotate your account's encryption key." }, + "itemsTransferred": { + "message": "Items transferred" + }, "yourAccountsFingerprint": { "message": "Your account's fingerprint phrase", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -6453,6 +6465,32 @@ "bulkReinviteMessage": { "message": "Reinvited successfully" }, + "bulkReinviteSuccessToast": { + "message": "$COUNT$ users re-invited", + "placeholders": { + "count": { + "content": "$1", + "example": "12" + } + } + }, + "bulkReinviteLimitedSuccessToast": { + "message": "$LIMIT$ of $SELECTEDCOUNT$ users re-invited. $EXCLUDEDCOUNT$ were not invited due to the $LIMIT$ invite limit.", + "placeholders": { + "limit": { + "content": "$1", + "example": "4,000" + }, + "selectedCount": { + "content": "$2", + "example": "4,005" + }, + "excludedCount": { + "content": "$3", + "example": "5" + } + } + }, "bulkRemovedMessage": { "message": "Removed successfully" }, @@ -6773,8 +6811,8 @@ "vaultTimeoutRangeError": { "message": "Vault timeout is not within allowed range." }, - "disablePersonalVaultExport": { - "message": "Remove individual vault export" + "disableExport": { + "message": "Remove export" }, "disablePersonalVaultExportDescription": { "message": "Do not allow members to export data from their individual vault." @@ -7094,9 +7132,6 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "keyConnectorDomain": { "message": "Key Connector domain" }, @@ -7154,8 +7189,8 @@ "enabledSso": { "message": "SSO turned on" }, - "disabledSso": { - "message": "SSO turned on" + "ssoTurnedOff": { + "message": "SSO turned off" }, "emailMustLoginWithSso": { "message": "$EMAIL$ must login with Single Sign-on", @@ -9448,6 +9483,9 @@ "ssoLoginIsRequired": { "message": "SSO login is required" }, + "emailRequiredForSsoLogin": { + "message": "Email is required for SSO" + }, "selectedRegionFlag": { "message": "Selected region flag" }, @@ -11961,8 +11999,8 @@ "familiesMembership": { "message": "Families membership" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "planDescFamiliesV2": { "message": "Premium security for your family" @@ -12199,6 +12237,45 @@ } } }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "confirmNoSelectedCriticalApplicationsTitle": { "message": "No critical applications are selected" }, @@ -12207,5 +12284,139 @@ }, "userVerificationFailed": { "message": "User verification failed." + }, + "recoveryDeleteCiphersTitle": { + "message": "Delete unrecoverable vault items" + }, + "recoveryDeleteCiphersDesc": { + "message": "Some of your vault items could not be recovered. Do you want to delete these unrecoverable items from your vault?" + }, + "recoveryDeleteFoldersTitle": { + "message": "Delete unrecoverable folders" + }, + "recoveryDeleteFoldersDesc": { + "message": "Some of your folders could not be recovered. Do you want to delete these unrecoverable folders from your vault?" + }, + "recoveryReplacePrivateKeyTitle": { + "message": "Replace encryption key" + }, + "recoveryReplacePrivateKeyDesc": { + "message": "Your public-key encryption key pair could not be recovered. Do you want to replace your encryption key with a new key pair? This will require you to set up existing emergency-access and organization memberships again." + }, + "recoveryStepSyncTitle": { + "message": "Synchronizing data" + }, + "recoveryStepPrivateKeyTitle": { + "message": "Verifying encryption key integrity" + }, + "recoveryStepUserInfoTitle": { + "message": "Verifying user information" + }, + "recoveryStepCipherTitle": { + "message": "Verifying vault item integrity" + }, + "recoveryStepFoldersTitle": { + "message": "Verifying folder integrity" + }, + "dataRecoveryTitle": { + "message": "Data Recovery and Diagnostics" + }, + "dataRecoveryDescription": { + "message": "Use the data recovery tool to diagnose and repair issues with your account. After running diagnostics you have the option to save diagnostic logs for support and the option to repair any detected issues." + }, + "runDiagnostics": { + "message": "Run Diagnostics" + }, + "repairIssues": { + "message": "Repair Issues" + }, + "saveDiagnosticLogs": { + "message": "Save Diagnostic Logs" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On browser refresh." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On browser refresh" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/web/src/locales/nb/messages.json b/apps/web/src/locales/nb/messages.json index 042444814e6..444a13a68e1 100644 --- a/apps/web/src/locales/nb/messages.json +++ b/apps/web/src/locales/nb/messages.json @@ -5169,9 +5169,21 @@ "message": "Reparer", "description": "This is a verb. ex. 'Fix The Car'" }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "oldAttachmentsNeedFixDesc": { "message": "Hvelvet ditt har gamle fil-vedlegg som må repareres før du kan oppdatere krypteringsnøkkelen til kontoen din." }, + "itemsTransferred": { + "message": "Items transferred" + }, "yourAccountsFingerprint": { "message": "Din kontos fingeravtrykksfrase", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -6453,6 +6465,32 @@ "bulkReinviteMessage": { "message": "Reinvitasjon vellykket." }, + "bulkReinviteSuccessToast": { + "message": "$COUNT$ users re-invited", + "placeholders": { + "count": { + "content": "$1", + "example": "12" + } + } + }, + "bulkReinviteLimitedSuccessToast": { + "message": "$LIMIT$ of $SELECTEDCOUNT$ users re-invited. $EXCLUDEDCOUNT$ were not invited due to the $LIMIT$ invite limit.", + "placeholders": { + "limit": { + "content": "$1", + "example": "4,000" + }, + "selectedCount": { + "content": "$2", + "example": "4,005" + }, + "excludedCount": { + "content": "$3", + "example": "5" + } + } + }, "bulkRemovedMessage": { "message": "Fjernet" }, @@ -6773,8 +6811,8 @@ "vaultTimeoutRangeError": { "message": "Hvelv-timeout er utenfor gyldig intervall." }, - "disablePersonalVaultExport": { - "message": "Deaktivere personlig hvelv eksport" + "disableExport": { + "message": "Remove export" }, "disablePersonalVaultExportDescription": { "message": "Do not allow members to export data from their individual vault." @@ -7094,9 +7132,6 @@ "invalidVerificationCode": { "message": "Ugyldig bekreftelseskode" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "keyConnectorDomain": { "message": "Key Connector domain" }, @@ -7154,8 +7189,8 @@ "enabledSso": { "message": "Skrudde på SSO" }, - "disabledSso": { - "message": "Skrudde av SSO" + "ssoTurnedOff": { + "message": "SSO turned off" }, "emailMustLoginWithSso": { "message": "$EMAIL$ must login with Single Sign-on", @@ -9448,6 +9483,9 @@ "ssoLoginIsRequired": { "message": "SSO login is required" }, + "emailRequiredForSsoLogin": { + "message": "Email is required for SSO" + }, "selectedRegionFlag": { "message": "Selected region flag" }, @@ -11961,8 +11999,8 @@ "familiesMembership": { "message": "Families membership" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "planDescFamiliesV2": { "message": "Premium security for your family" @@ -12199,6 +12237,45 @@ } } }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "confirmNoSelectedCriticalApplicationsTitle": { "message": "No critical applications are selected" }, @@ -12207,5 +12284,139 @@ }, "userVerificationFailed": { "message": "User verification failed." + }, + "recoveryDeleteCiphersTitle": { + "message": "Delete unrecoverable vault items" + }, + "recoveryDeleteCiphersDesc": { + "message": "Some of your vault items could not be recovered. Do you want to delete these unrecoverable items from your vault?" + }, + "recoveryDeleteFoldersTitle": { + "message": "Delete unrecoverable folders" + }, + "recoveryDeleteFoldersDesc": { + "message": "Some of your folders could not be recovered. Do you want to delete these unrecoverable folders from your vault?" + }, + "recoveryReplacePrivateKeyTitle": { + "message": "Replace encryption key" + }, + "recoveryReplacePrivateKeyDesc": { + "message": "Your public-key encryption key pair could not be recovered. Do you want to replace your encryption key with a new key pair? This will require you to set up existing emergency-access and organization memberships again." + }, + "recoveryStepSyncTitle": { + "message": "Synchronizing data" + }, + "recoveryStepPrivateKeyTitle": { + "message": "Verifying encryption key integrity" + }, + "recoveryStepUserInfoTitle": { + "message": "Verifying user information" + }, + "recoveryStepCipherTitle": { + "message": "Verifying vault item integrity" + }, + "recoveryStepFoldersTitle": { + "message": "Verifying folder integrity" + }, + "dataRecoveryTitle": { + "message": "Data Recovery and Diagnostics" + }, + "dataRecoveryDescription": { + "message": "Use the data recovery tool to diagnose and repair issues with your account. After running diagnostics you have the option to save diagnostic logs for support and the option to repair any detected issues." + }, + "runDiagnostics": { + "message": "Run Diagnostics" + }, + "repairIssues": { + "message": "Repair Issues" + }, + "saveDiagnosticLogs": { + "message": "Save Diagnostic Logs" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On browser refresh." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On browser refresh" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/web/src/locales/ne/messages.json b/apps/web/src/locales/ne/messages.json index 31367740728..99f87c4fb75 100644 --- a/apps/web/src/locales/ne/messages.json +++ b/apps/web/src/locales/ne/messages.json @@ -5169,9 +5169,21 @@ "message": "Fix", "description": "This is a verb. ex. 'Fix The Car'" }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "oldAttachmentsNeedFixDesc": { "message": "There are old file attachments in your vault that need to be fixed before you can rotate your account's encryption key." }, + "itemsTransferred": { + "message": "Items transferred" + }, "yourAccountsFingerprint": { "message": "Your account's fingerprint phrase", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -6453,6 +6465,32 @@ "bulkReinviteMessage": { "message": "Reinvited successfully" }, + "bulkReinviteSuccessToast": { + "message": "$COUNT$ users re-invited", + "placeholders": { + "count": { + "content": "$1", + "example": "12" + } + } + }, + "bulkReinviteLimitedSuccessToast": { + "message": "$LIMIT$ of $SELECTEDCOUNT$ users re-invited. $EXCLUDEDCOUNT$ were not invited due to the $LIMIT$ invite limit.", + "placeholders": { + "limit": { + "content": "$1", + "example": "4,000" + }, + "selectedCount": { + "content": "$2", + "example": "4,005" + }, + "excludedCount": { + "content": "$3", + "example": "5" + } + } + }, "bulkRemovedMessage": { "message": "Removed successfully" }, @@ -6773,8 +6811,8 @@ "vaultTimeoutRangeError": { "message": "Vault timeout is not within allowed range." }, - "disablePersonalVaultExport": { - "message": "Remove individual vault export" + "disableExport": { + "message": "Remove export" }, "disablePersonalVaultExportDescription": { "message": "Do not allow members to export data from their individual vault." @@ -7094,9 +7132,6 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "keyConnectorDomain": { "message": "Key Connector domain" }, @@ -7154,8 +7189,8 @@ "enabledSso": { "message": "SSO turned on" }, - "disabledSso": { - "message": "SSO turned on" + "ssoTurnedOff": { + "message": "SSO turned off" }, "emailMustLoginWithSso": { "message": "$EMAIL$ must login with Single Sign-on", @@ -9448,6 +9483,9 @@ "ssoLoginIsRequired": { "message": "SSO login is required" }, + "emailRequiredForSsoLogin": { + "message": "Email is required for SSO" + }, "selectedRegionFlag": { "message": "Selected region flag" }, @@ -11961,8 +11999,8 @@ "familiesMembership": { "message": "Families membership" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "planDescFamiliesV2": { "message": "Premium security for your family" @@ -12199,6 +12237,45 @@ } } }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "confirmNoSelectedCriticalApplicationsTitle": { "message": "No critical applications are selected" }, @@ -12207,5 +12284,139 @@ }, "userVerificationFailed": { "message": "User verification failed." + }, + "recoveryDeleteCiphersTitle": { + "message": "Delete unrecoverable vault items" + }, + "recoveryDeleteCiphersDesc": { + "message": "Some of your vault items could not be recovered. Do you want to delete these unrecoverable items from your vault?" + }, + "recoveryDeleteFoldersTitle": { + "message": "Delete unrecoverable folders" + }, + "recoveryDeleteFoldersDesc": { + "message": "Some of your folders could not be recovered. Do you want to delete these unrecoverable folders from your vault?" + }, + "recoveryReplacePrivateKeyTitle": { + "message": "Replace encryption key" + }, + "recoveryReplacePrivateKeyDesc": { + "message": "Your public-key encryption key pair could not be recovered. Do you want to replace your encryption key with a new key pair? This will require you to set up existing emergency-access and organization memberships again." + }, + "recoveryStepSyncTitle": { + "message": "Synchronizing data" + }, + "recoveryStepPrivateKeyTitle": { + "message": "Verifying encryption key integrity" + }, + "recoveryStepUserInfoTitle": { + "message": "Verifying user information" + }, + "recoveryStepCipherTitle": { + "message": "Verifying vault item integrity" + }, + "recoveryStepFoldersTitle": { + "message": "Verifying folder integrity" + }, + "dataRecoveryTitle": { + "message": "Data Recovery and Diagnostics" + }, + "dataRecoveryDescription": { + "message": "Use the data recovery tool to diagnose and repair issues with your account. After running diagnostics you have the option to save diagnostic logs for support and the option to repair any detected issues." + }, + "runDiagnostics": { + "message": "Run Diagnostics" + }, + "repairIssues": { + "message": "Repair Issues" + }, + "saveDiagnosticLogs": { + "message": "Save Diagnostic Logs" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On browser refresh." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On browser refresh" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/web/src/locales/nl/messages.json b/apps/web/src/locales/nl/messages.json index 333df664f77..0b648eaf204 100644 --- a/apps/web/src/locales/nl/messages.json +++ b/apps/web/src/locales/nl/messages.json @@ -5169,9 +5169,21 @@ "message": "Oplossen", "description": "This is a verb. ex. 'Fix The Car'" }, + "fixEncryption": { + "message": "Versleuteling repareren" + }, + "fixEncryptionTooltip": { + "message": "Dit bestand gebruikt een verouderde versleutelingsmethode." + }, + "attachmentUpdated": { + "message": "Bijlagen bijgewerkt" + }, "oldAttachmentsNeedFixDesc": { "message": "Er zijn oude bestandsbijlagen in je kluis die aangepast moeten worden voordat je je encryptiesleutels kunt roteren." }, + "itemsTransferred": { + "message": "Items overgedragen" + }, "yourAccountsFingerprint": { "message": "Vingerafdrukzin van je account", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -6453,6 +6465,32 @@ "bulkReinviteMessage": { "message": "Succesvol opnieuw uitgenodigd" }, + "bulkReinviteSuccessToast": { + "message": "$COUNT$ gebruikers opnieuw uitgenodigd", + "placeholders": { + "count": { + "content": "$1", + "example": "12" + } + } + }, + "bulkReinviteLimitedSuccessToast": { + "message": "$LIMIT$ van $SELECTEDCOUNT$ gebruikers opnieuw uitgenodigd. $EXCLUDEDCOUNT$ werden niet uitgenodigd vanwege de $LIMIT$ uitnodigingslimiet.", + "placeholders": { + "limit": { + "content": "$1", + "example": "4,000" + }, + "selectedCount": { + "content": "$2", + "example": "4,005" + }, + "excludedCount": { + "content": "$3", + "example": "5" + } + } + }, "bulkRemovedMessage": { "message": "Succesvol verwijderd" }, @@ -6773,8 +6811,8 @@ "vaultTimeoutRangeError": { "message": "Kluis time-out ligt niet binnen het toegestane bereik." }, - "disablePersonalVaultExport": { - "message": "Persoonlijke kluis exporteren uitschakelen" + "disableExport": { + "message": "Export verwijderen" }, "disablePersonalVaultExportDescription": { "message": "Exporteren van gegevens uit hun individuele kluis niet toestaan." @@ -7094,9 +7132,6 @@ "invalidVerificationCode": { "message": "Ongeldige verificatiecode" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "Voor leden van de volgende organisatie is een hoofdwachtwoord niet langer nodig. Bevestig het domein hieronder met de beheerder van je organisatie." - }, "keyConnectorDomain": { "message": "Key Connector-domein" }, @@ -7154,8 +7189,8 @@ "enabledSso": { "message": "SSO ingeschakeld" }, - "disabledSso": { - "message": "SSO uitgeschakeld" + "ssoTurnedOff": { + "message": "SSO turned off" }, "emailMustLoginWithSso": { "message": "$EMAIL$ moet met Single Sign-on inloggen", @@ -9448,6 +9483,9 @@ "ssoLoginIsRequired": { "message": "SSO login is vereist" }, + "emailRequiredForSsoLogin": { + "message": "Email is required for SSO" + }, "selectedRegionFlag": { "message": "Geselecteerde regionale vlag" }, @@ -11961,8 +11999,8 @@ "familiesMembership": { "message": "Lidmaatschap families" }, - "planDescPremium": { - "message": "Online beveiliging voltooien" + "advancedOnlineSecurity": { + "message": "Geavanceerde online beveiliging" }, "planDescFamiliesV2": { "message": "Premium beveiliging voor je familie" @@ -12199,6 +12237,45 @@ } } }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Je organisatie maakt niet langer gebruik van hoofdwachtwoorden om in te loggen op Bitwarden. Controleer de organisatie en het domein om door te gaan." + }, + "continueWithLogIn": { + "message": "Doorgaan met inloggen" + }, + "doNotContinue": { + "message": "Niet verder gaan" + }, + "domain": { + "message": "Domein" + }, + "keyConnectorDomainTooltip": { + "message": "Dit domein zal de encryptiesleutels van je account opslaan, dus zorg ervoor dat je het vertrouwt. Als je het niet zeker weet, controleer dan bij je beheerder." + }, + "verifyYourOrganization": { + "message": "Verifieer je organisatie om in te loggen" + }, + "organizationVerified": { + "message": "Organisatie geverifieerd" + }, + "domainVerified": { + "message": "Domein geverifieerd" + }, + "leaveOrganizationContent": { + "message": "Als je je organisatie niet verifieert, wordt je toegang tot de organisatie ingetrokken." + }, + "leaveNow": { + "message": "Nu verlaten" + }, + "verifyYourDomainToLogin": { + "message": "Verifieer je domein om in te loggen" + }, + "verifyYourDomainDescription": { + "message": "Bevestig dit domein om verder te gaan met inloggen." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "Bevestig organisatie en domein om verder te gaan met inloggen." + }, "confirmNoSelectedCriticalApplicationsTitle": { "message": "Geen belangrijke applicaties geselecteerd" }, @@ -12207,5 +12284,139 @@ }, "userVerificationFailed": { "message": "User verification failed." + }, + "recoveryDeleteCiphersTitle": { + "message": "Onherstelbare kluisitems verwijderen" + }, + "recoveryDeleteCiphersDesc": { + "message": "Sommige kluisitems konden niet worden hersteld. Wilt je deze onherstelbare items uit je kluis verwijderen?" + }, + "recoveryDeleteFoldersTitle": { + "message": "Onherstelbare mappen verwijderen" + }, + "recoveryDeleteFoldersDesc": { + "message": "Sommige mappen konden niet worden hersteld. Wilt je deze onherstelbare mappen uit je kluis verwijderen?" + }, + "recoveryReplacePrivateKeyTitle": { + "message": "Encryptiesleutel vervangen" + }, + "recoveryReplacePrivateKeyDesc": { + "message": "Je sleutelpaar voor versleuteling met publieke sleutel kon niet worden hersteld. Wilt je je encryptiesleutel vervangen door een nieuwe sleutel? Hiervoor zal je bestaande noodtoegang en organisatielidmaatschappen opnieuw moeten instellen." + }, + "recoveryStepSyncTitle": { + "message": "Data synchroniseren" + }, + "recoveryStepPrivateKeyTitle": { + "message": "Integriteit encryptiesleutel verifiëren" + }, + "recoveryStepUserInfoTitle": { + "message": "Gebruikersinformatie verifiëren" + }, + "recoveryStepCipherTitle": { + "message": "Integriteit kluisitem verifiëren" + }, + "recoveryStepFoldersTitle": { + "message": "Integriteit map verifiëren" + }, + "dataRecoveryTitle": { + "message": "Gegevensherstel en diagnostiek" + }, + "dataRecoveryDescription": { + "message": "Gebruik het hulpmiddelen voor gegevensherstel om problemen met je account te diagnosticeren en repareren. Na het uitvoeren van de diagnose heb je de optie om diagnostische logboeken op te slaan voor ondersteuning en de optie om gedetecteerde problemen op te lossen." + }, + "runDiagnostics": { + "message": "Diagnose uitvoeren" + }, + "repairIssues": { + "message": "Problemen oplossen" + }, + "saveDiagnosticLogs": { + "message": "Diagnostische logs opslaan" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "Deze instelling wordt beheerd door je organisatie." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Je organisatie heeft de maximale sessietime-out ingesteld op $HOURS$ uur en $MINUTES$ minuten.", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Je organisatie heeft de standaard sessietime-out ingesteld op Browser verversen." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximale time-out kan niet langer zijn dan $HOURS$ uur en $MINUTES$ minu(u) t(en)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "Browser verversen" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Stel een ontgrendelingsmethode in om je kluis time-out actie te wijzigen" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/web/src/locales/nn/messages.json b/apps/web/src/locales/nn/messages.json index 32c452cf657..8d1c2d1b394 100644 --- a/apps/web/src/locales/nn/messages.json +++ b/apps/web/src/locales/nn/messages.json @@ -5169,9 +5169,21 @@ "message": "Fix", "description": "This is a verb. ex. 'Fix The Car'" }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "oldAttachmentsNeedFixDesc": { "message": "There are old file attachments in your vault that need to be fixed before you can rotate your account's encryption key." }, + "itemsTransferred": { + "message": "Items transferred" + }, "yourAccountsFingerprint": { "message": "Your account's fingerprint phrase", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -6453,6 +6465,32 @@ "bulkReinviteMessage": { "message": "Reinvited successfully" }, + "bulkReinviteSuccessToast": { + "message": "$COUNT$ users re-invited", + "placeholders": { + "count": { + "content": "$1", + "example": "12" + } + } + }, + "bulkReinviteLimitedSuccessToast": { + "message": "$LIMIT$ of $SELECTEDCOUNT$ users re-invited. $EXCLUDEDCOUNT$ were not invited due to the $LIMIT$ invite limit.", + "placeholders": { + "limit": { + "content": "$1", + "example": "4,000" + }, + "selectedCount": { + "content": "$2", + "example": "4,005" + }, + "excludedCount": { + "content": "$3", + "example": "5" + } + } + }, "bulkRemovedMessage": { "message": "Removed successfully" }, @@ -6773,8 +6811,8 @@ "vaultTimeoutRangeError": { "message": "Vault timeout is not within allowed range." }, - "disablePersonalVaultExport": { - "message": "Remove individual vault export" + "disableExport": { + "message": "Remove export" }, "disablePersonalVaultExportDescription": { "message": "Do not allow members to export data from their individual vault." @@ -7094,9 +7132,6 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "keyConnectorDomain": { "message": "Key Connector domain" }, @@ -7154,8 +7189,8 @@ "enabledSso": { "message": "SSO turned on" }, - "disabledSso": { - "message": "SSO turned on" + "ssoTurnedOff": { + "message": "SSO turned off" }, "emailMustLoginWithSso": { "message": "$EMAIL$ must login with Single Sign-on", @@ -9448,6 +9483,9 @@ "ssoLoginIsRequired": { "message": "SSO login is required" }, + "emailRequiredForSsoLogin": { + "message": "Email is required for SSO" + }, "selectedRegionFlag": { "message": "Selected region flag" }, @@ -11961,8 +11999,8 @@ "familiesMembership": { "message": "Families membership" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "planDescFamiliesV2": { "message": "Premium security for your family" @@ -12199,6 +12237,45 @@ } } }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "confirmNoSelectedCriticalApplicationsTitle": { "message": "No critical applications are selected" }, @@ -12207,5 +12284,139 @@ }, "userVerificationFailed": { "message": "User verification failed." + }, + "recoveryDeleteCiphersTitle": { + "message": "Delete unrecoverable vault items" + }, + "recoveryDeleteCiphersDesc": { + "message": "Some of your vault items could not be recovered. Do you want to delete these unrecoverable items from your vault?" + }, + "recoveryDeleteFoldersTitle": { + "message": "Delete unrecoverable folders" + }, + "recoveryDeleteFoldersDesc": { + "message": "Some of your folders could not be recovered. Do you want to delete these unrecoverable folders from your vault?" + }, + "recoveryReplacePrivateKeyTitle": { + "message": "Replace encryption key" + }, + "recoveryReplacePrivateKeyDesc": { + "message": "Your public-key encryption key pair could not be recovered. Do you want to replace your encryption key with a new key pair? This will require you to set up existing emergency-access and organization memberships again." + }, + "recoveryStepSyncTitle": { + "message": "Synchronizing data" + }, + "recoveryStepPrivateKeyTitle": { + "message": "Verifying encryption key integrity" + }, + "recoveryStepUserInfoTitle": { + "message": "Verifying user information" + }, + "recoveryStepCipherTitle": { + "message": "Verifying vault item integrity" + }, + "recoveryStepFoldersTitle": { + "message": "Verifying folder integrity" + }, + "dataRecoveryTitle": { + "message": "Data Recovery and Diagnostics" + }, + "dataRecoveryDescription": { + "message": "Use the data recovery tool to diagnose and repair issues with your account. After running diagnostics you have the option to save diagnostic logs for support and the option to repair any detected issues." + }, + "runDiagnostics": { + "message": "Run Diagnostics" + }, + "repairIssues": { + "message": "Repair Issues" + }, + "saveDiagnosticLogs": { + "message": "Save Diagnostic Logs" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On browser refresh." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On browser refresh" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/web/src/locales/or/messages.json b/apps/web/src/locales/or/messages.json index 608215f8155..fd419fe7397 100644 --- a/apps/web/src/locales/or/messages.json +++ b/apps/web/src/locales/or/messages.json @@ -5169,9 +5169,21 @@ "message": "Fix", "description": "This is a verb. ex. 'Fix The Car'" }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "oldAttachmentsNeedFixDesc": { "message": "There are old file attachments in your vault that need to be fixed before you can rotate your account's encryption key." }, + "itemsTransferred": { + "message": "Items transferred" + }, "yourAccountsFingerprint": { "message": "Your account's fingerprint phrase", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -6453,6 +6465,32 @@ "bulkReinviteMessage": { "message": "Reinvited successfully" }, + "bulkReinviteSuccessToast": { + "message": "$COUNT$ users re-invited", + "placeholders": { + "count": { + "content": "$1", + "example": "12" + } + } + }, + "bulkReinviteLimitedSuccessToast": { + "message": "$LIMIT$ of $SELECTEDCOUNT$ users re-invited. $EXCLUDEDCOUNT$ were not invited due to the $LIMIT$ invite limit.", + "placeholders": { + "limit": { + "content": "$1", + "example": "4,000" + }, + "selectedCount": { + "content": "$2", + "example": "4,005" + }, + "excludedCount": { + "content": "$3", + "example": "5" + } + } + }, "bulkRemovedMessage": { "message": "Removed successfully" }, @@ -6773,8 +6811,8 @@ "vaultTimeoutRangeError": { "message": "Vault timeout is not within allowed range." }, - "disablePersonalVaultExport": { - "message": "Remove individual vault export" + "disableExport": { + "message": "Remove export" }, "disablePersonalVaultExportDescription": { "message": "Do not allow members to export data from their individual vault." @@ -7094,9 +7132,6 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "keyConnectorDomain": { "message": "Key Connector domain" }, @@ -7154,8 +7189,8 @@ "enabledSso": { "message": "SSO turned on" }, - "disabledSso": { - "message": "SSO turned on" + "ssoTurnedOff": { + "message": "SSO turned off" }, "emailMustLoginWithSso": { "message": "$EMAIL$ must login with Single Sign-on", @@ -9448,6 +9483,9 @@ "ssoLoginIsRequired": { "message": "SSO login is required" }, + "emailRequiredForSsoLogin": { + "message": "Email is required for SSO" + }, "selectedRegionFlag": { "message": "Selected region flag" }, @@ -11961,8 +11999,8 @@ "familiesMembership": { "message": "Families membership" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "planDescFamiliesV2": { "message": "Premium security for your family" @@ -12199,6 +12237,45 @@ } } }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "confirmNoSelectedCriticalApplicationsTitle": { "message": "No critical applications are selected" }, @@ -12207,5 +12284,139 @@ }, "userVerificationFailed": { "message": "User verification failed." + }, + "recoveryDeleteCiphersTitle": { + "message": "Delete unrecoverable vault items" + }, + "recoveryDeleteCiphersDesc": { + "message": "Some of your vault items could not be recovered. Do you want to delete these unrecoverable items from your vault?" + }, + "recoveryDeleteFoldersTitle": { + "message": "Delete unrecoverable folders" + }, + "recoveryDeleteFoldersDesc": { + "message": "Some of your folders could not be recovered. Do you want to delete these unrecoverable folders from your vault?" + }, + "recoveryReplacePrivateKeyTitle": { + "message": "Replace encryption key" + }, + "recoveryReplacePrivateKeyDesc": { + "message": "Your public-key encryption key pair could not be recovered. Do you want to replace your encryption key with a new key pair? This will require you to set up existing emergency-access and organization memberships again." + }, + "recoveryStepSyncTitle": { + "message": "Synchronizing data" + }, + "recoveryStepPrivateKeyTitle": { + "message": "Verifying encryption key integrity" + }, + "recoveryStepUserInfoTitle": { + "message": "Verifying user information" + }, + "recoveryStepCipherTitle": { + "message": "Verifying vault item integrity" + }, + "recoveryStepFoldersTitle": { + "message": "Verifying folder integrity" + }, + "dataRecoveryTitle": { + "message": "Data Recovery and Diagnostics" + }, + "dataRecoveryDescription": { + "message": "Use the data recovery tool to diagnose and repair issues with your account. After running diagnostics you have the option to save diagnostic logs for support and the option to repair any detected issues." + }, + "runDiagnostics": { + "message": "Run Diagnostics" + }, + "repairIssues": { + "message": "Repair Issues" + }, + "saveDiagnosticLogs": { + "message": "Save Diagnostic Logs" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On browser refresh." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On browser refresh" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/web/src/locales/pl/messages.json b/apps/web/src/locales/pl/messages.json index f5e6b898bff..e90d3e1ab47 100644 --- a/apps/web/src/locales/pl/messages.json +++ b/apps/web/src/locales/pl/messages.json @@ -5169,9 +5169,21 @@ "message": "Napraw", "description": "This is a verb. ex. 'Fix The Car'" }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "oldAttachmentsNeedFixDesc": { "message": "W Twoim sejfie istnieją stare załączniki, które muszą zostać naprawione, zanim będziesz mógł zmienić klucz szyfrowania Twojego konta." }, + "itemsTransferred": { + "message": "Items transferred" + }, "yourAccountsFingerprint": { "message": "Unikalny identyfikator Twojego konta", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -6453,6 +6465,32 @@ "bulkReinviteMessage": { "message": "Zaproszony ponownie" }, + "bulkReinviteSuccessToast": { + "message": "$COUNT$ users re-invited", + "placeholders": { + "count": { + "content": "$1", + "example": "12" + } + } + }, + "bulkReinviteLimitedSuccessToast": { + "message": "$LIMIT$ of $SELECTEDCOUNT$ users re-invited. $EXCLUDEDCOUNT$ were not invited due to the $LIMIT$ invite limit.", + "placeholders": { + "limit": { + "content": "$1", + "example": "4,000" + }, + "selectedCount": { + "content": "$2", + "example": "4,005" + }, + "excludedCount": { + "content": "$3", + "example": "5" + } + } + }, "bulkRemovedMessage": { "message": "Usunięto" }, @@ -6773,8 +6811,8 @@ "vaultTimeoutRangeError": { "message": "Czas blokowania sejfu nie jest w dozwolonym zakresie." }, - "disablePersonalVaultExport": { - "message": "Wyłącz eksportowanie osobistego sejfu" + "disableExport": { + "message": "Remove export" }, "disablePersonalVaultExportDescription": { "message": "Nie zezwalaj użytkownikom na eksport danych z ich indywidualnego sejfu." @@ -7094,9 +7132,6 @@ "invalidVerificationCode": { "message": "Kod weryfikacyjny jest nieprawidłowy" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "Hasło główne nie jest już wymagane dla członków następującej organizacji. Proszę potwierdzić poniższą domenę u administratora organizacji." - }, "keyConnectorDomain": { "message": "Domena Key Connector'a" }, @@ -7154,8 +7189,8 @@ "enabledSso": { "message": "Logowanie jednokrotne SSO zostało włączone" }, - "disabledSso": { - "message": "Logowanie jednokrotne SSO zostało wyłączone" + "ssoTurnedOff": { + "message": "SSO turned off" }, "emailMustLoginWithSso": { "message": "$EMAIL$ must login with Single Sign-on", @@ -9448,6 +9483,9 @@ "ssoLoginIsRequired": { "message": "Logowaniez użyciem SSO jest wymagane" }, + "emailRequiredForSsoLogin": { + "message": "Email is required for SSO" + }, "selectedRegionFlag": { "message": "Flaga wybranego regionu" }, @@ -11961,8 +11999,8 @@ "familiesMembership": { "message": "Families membership" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "planDescFamiliesV2": { "message": "Premium security for your family" @@ -12199,6 +12237,45 @@ } } }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "confirmNoSelectedCriticalApplicationsTitle": { "message": "No critical applications are selected" }, @@ -12207,5 +12284,139 @@ }, "userVerificationFailed": { "message": "User verification failed." + }, + "recoveryDeleteCiphersTitle": { + "message": "Delete unrecoverable vault items" + }, + "recoveryDeleteCiphersDesc": { + "message": "Some of your vault items could not be recovered. Do you want to delete these unrecoverable items from your vault?" + }, + "recoveryDeleteFoldersTitle": { + "message": "Delete unrecoverable folders" + }, + "recoveryDeleteFoldersDesc": { + "message": "Some of your folders could not be recovered. Do you want to delete these unrecoverable folders from your vault?" + }, + "recoveryReplacePrivateKeyTitle": { + "message": "Replace encryption key" + }, + "recoveryReplacePrivateKeyDesc": { + "message": "Your public-key encryption key pair could not be recovered. Do you want to replace your encryption key with a new key pair? This will require you to set up existing emergency-access and organization memberships again." + }, + "recoveryStepSyncTitle": { + "message": "Synchronizing data" + }, + "recoveryStepPrivateKeyTitle": { + "message": "Verifying encryption key integrity" + }, + "recoveryStepUserInfoTitle": { + "message": "Verifying user information" + }, + "recoveryStepCipherTitle": { + "message": "Verifying vault item integrity" + }, + "recoveryStepFoldersTitle": { + "message": "Verifying folder integrity" + }, + "dataRecoveryTitle": { + "message": "Data Recovery and Diagnostics" + }, + "dataRecoveryDescription": { + "message": "Use the data recovery tool to diagnose and repair issues with your account. After running diagnostics you have the option to save diagnostic logs for support and the option to repair any detected issues." + }, + "runDiagnostics": { + "message": "Run Diagnostics" + }, + "repairIssues": { + "message": "Repair Issues" + }, + "saveDiagnosticLogs": { + "message": "Save Diagnostic Logs" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On browser refresh." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On browser refresh" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/web/src/locales/pt_BR/messages.json b/apps/web/src/locales/pt_BR/messages.json index ff5686cbba8..4c79ec5ecc4 100644 --- a/apps/web/src/locales/pt_BR/messages.json +++ b/apps/web/src/locales/pt_BR/messages.json @@ -1958,10 +1958,10 @@ "message": "Confirmar exportação de segredos" }, "exportWarningDesc": { - "message": "Esta exportação contém os dados do seu cofre em um formato não criptografado. Você não deve armazenar ou enviar o arquivo exportado por canais inseguros (como e-mail). Apague o arquivo imediatamente após terminar de usá-lo." + "message": "Esta exportação contém os dados do seu cofre em um formato sem criptografia. Você não deve armazenar ou enviar o arquivo exportado por canais inseguros (como e-mail). Apague o arquivo imediatamente após terminar de usá-lo." }, "exportSecretsWarningDesc": { - "message": "Esta exportação contém seus dados de segredos em um formato não criptografado. Você não deve armazenar ou enviar o arquivo exportado por canais inseguros (como e-mail). Apague-o imediatamente após terminar de usá-lo." + "message": "Esta exportação contém seus dados de segredos em um formato sem criptografia. Você não deve armazenar ou enviar o arquivo exportado por canais inseguros (como e-mail). Apague-o imediatamente após terminar de usá-lo." }, "encExportKeyWarningDesc": { "message": "Esta exportação criptografa seus dados usando a chave de criptografia da sua conta. Se você rotacionar a chave de criptografia da sua conta, você deve exportar novamente, já que você não será capaz de descriptografar este arquivo de exportação." @@ -4331,7 +4331,7 @@ "message": "Esta solicitação não é mais válida." }, "loginRequestApprovedForEmailOnDevice": { - "message": "Solicitação de autenticação aprovada para $EMAIL$ em $DEVICE$", + "message": "Solicitação de acesso aprovada para $EMAIL$ em $DEVICE$", "placeholders": { "email": { "content": "$1", @@ -4344,10 +4344,10 @@ } }, "youDeniedLoginAttemptFromAnotherDevice": { - "message": "Você negou uma tentativa de autenticação de outro dispositivo. Se era você, tente se conectar com o dispositivo novamente." + "message": "Você negou uma tentativa de acesso de outro dispositivo. Se era você, tente se conectar com o dispositivo novamente." }, "loginRequestHasAlreadyExpired": { - "message": "A solicitação de autenticação já expirou." + "message": "A solicitação de acesso já expirou." }, "justNow": { "message": "Agora há pouco" @@ -4512,13 +4512,13 @@ "message": "Você está usando um navegador da web não suportado. O cofre web pode não funcionar corretamente." }, "youHaveAPendingLoginRequest": { - "message": "Você tem uma solicitação de autenticação pendente de outro dispositivo." + "message": "Você tem uma solicitação de acesso pendente de outro dispositivo." }, "reviewLoginRequest": { - "message": "Revisar solicitação de autenticação" + "message": "Revisar solicitação de acesso" }, "loginRequest": { - "message": "Solicitação de autenticação" + "message": "Solicitação de acesso" }, "freeTrialEndPromptCount": { "message": "Seu teste grátis termina em $COUNT$ dias.", @@ -5169,9 +5169,21 @@ "message": "Corrigir", "description": "This is a verb. ex. 'Fix The Car'" }, + "fixEncryption": { + "message": "Corrigir criptografia" + }, + "fixEncryptionTooltip": { + "message": "Este arquivo está usando um método de criptografia desatualizado." + }, + "attachmentUpdated": { + "message": "Anexo atualizado" + }, "oldAttachmentsNeedFixDesc": { "message": "Há anexos de arquivos antigos no seu cofre que precisam ser corrigidos antes que você possa rotacionar a chave de criptografia da sua conta." }, + "itemsTransferred": { + "message": "Itens transferidos" + }, "yourAccountsFingerprint": { "message": "A frase biométrica da sua conta", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -6453,6 +6465,32 @@ "bulkReinviteMessage": { "message": "Re-convidado com sucesso" }, + "bulkReinviteSuccessToast": { + "message": "$COUNT$ usuários re-convidados", + "placeholders": { + "count": { + "content": "$1", + "example": "12" + } + } + }, + "bulkReinviteLimitedSuccessToast": { + "message": "$LIMIT$ dos $SELECTEDCOUNT$ usuários foram re-convidados. $EXCLUDEDCOUNT$ não foram convidados devido ao limite de convite de $LIMIT$.", + "placeholders": { + "limit": { + "content": "$1", + "example": "4,000" + }, + "selectedCount": { + "content": "$2", + "example": "4,005" + }, + "excludedCount": { + "content": "$3", + "example": "5" + } + } + }, "bulkRemovedMessage": { "message": "Removido com sucesso" }, @@ -6773,8 +6811,8 @@ "vaultTimeoutRangeError": { "message": "Tempo limite do cofre não está dentro do intervalo permitido." }, - "disablePersonalVaultExport": { - "message": "Remover exportação do cofre individual" + "disableExport": { + "message": "Remover exportação" }, "disablePersonalVaultExportDescription": { "message": "Não permita que os membros exportem dados do seu cofre individual." @@ -6840,7 +6878,7 @@ "message": "Escopos personalizados" }, "additionalUserIdClaimTypes": { - "message": "Tipos de reivindicação de ID de usuário personalizado" + "message": "Tipos personalizados de reivindicação de ID de usuário" }, "additionalEmailClaimTypes": { "message": "Tipos de reivindicação de e-mail" @@ -7094,9 +7132,6 @@ "invalidVerificationCode": { "message": "Código de verificação inválido" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "Uma senha principal não é mais necessária para membros da seguinte organização. Confirme o domínio abaixo com o administrador da sua organização." - }, "keyConnectorDomain": { "message": "Domínio do Key Connector" }, @@ -7154,8 +7189,8 @@ "enabledSso": { "message": "SSO foi ativado" }, - "disabledSso": { - "message": "SSO foi desativado" + "ssoTurnedOff": { + "message": "SSO desativado" }, "emailMustLoginWithSso": { "message": "$EMAIL$ deve se conectar com a autenticação única", @@ -9095,7 +9130,7 @@ "message": "Importar segredos" }, "getStarted": { - "message": "Vamos começar" + "message": "Introdução" }, "complete": { "message": "$COMPLETED$ concluídos dos $TOTAL$", @@ -9377,7 +9412,7 @@ "message": "Solicitação de acesso negada" }, "allLoginRequestsDenied": { - "message": "Todas as solicitações de autenticação foram negadas" + "message": "Todas as solicitações de acesso foram negadas" }, "loginRequestApproved": { "message": "Solicitação de acesso aprovada" @@ -9448,6 +9483,9 @@ "ssoLoginIsRequired": { "message": "Autenticação com SSO é necessário" }, + "emailRequiredForSsoLogin": { + "message": "O e-mail é necessário para o SSO" + }, "selectedRegionFlag": { "message": "Bandeira da região selecionada" }, @@ -9464,7 +9502,7 @@ "message": "Problemas para acessar?" }, "loginApproved": { - "message": "Autenticação aprovada" + "message": "Acesso aprovado" }, "userEmailMissing": { "message": "E-mail do usuário ausente" @@ -11961,8 +11999,8 @@ "familiesMembership": { "message": "Assinatura do Famílias" }, - "planDescPremium": { - "message": "Segurança on-line completa" + "advancedOnlineSecurity": { + "message": "Segurança on-line avançada" }, "planDescFamiliesV2": { "message": "Segurança Premium para a sua família" @@ -12199,6 +12237,45 @@ } } }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "A sua organização não está mais usando senhas principais para se conectar ao Bitwarden. Para continuar, verifique a organização e o domínio." + }, + "continueWithLogIn": { + "message": "Continuar acessando" + }, + "doNotContinue": { + "message": "Não continuar" + }, + "domain": { + "message": "Domínio" + }, + "keyConnectorDomainTooltip": { + "message": "Este domínio armazenará as chaves de criptografia da sua conta, então certifique-se que confia nele. Se não tiver certeza, verifique com o seu administrador." + }, + "verifyYourOrganization": { + "message": "Verifique sua organização para se conectar" + }, + "organizationVerified": { + "message": "Organização verificada" + }, + "domainVerified": { + "message": "Domínio verificado" + }, + "leaveOrganizationContent": { + "message": "Se você não verificar a sua organização, o seu acesso à organização será revogado." + }, + "leaveNow": { + "message": "Sair agora" + }, + "verifyYourDomainToLogin": { + "message": "Verifique seu domínio para se conectar" + }, + "verifyYourDomainDescription": { + "message": "Para continuar se conectando, verifique este domínio." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "Para continuar se conectando, verifique a organização e o domínio." + }, "confirmNoSelectedCriticalApplicationsTitle": { "message": "Nenhum aplicativo crítico foi selecionado" }, @@ -12207,5 +12284,139 @@ }, "userVerificationFailed": { "message": "Falha na verificação do usuário." + }, + "recoveryDeleteCiphersTitle": { + "message": "Apagar itens não recuperáveis do cofre" + }, + "recoveryDeleteCiphersDesc": { + "message": "Alguns dos itens do seu cofre não puderam ser recuperados. Gostaria de apagar estes itens não recuperáveis do seu cofre?" + }, + "recoveryDeleteFoldersTitle": { + "message": "Apagar pastas não recuperáveis" + }, + "recoveryDeleteFoldersDesc": { + "message": "Algumas das suas pastas não puderam ser recuperadas. Gostaria de apagar estas pastas não recuperáveis do seu cofre?" + }, + "recoveryReplacePrivateKeyTitle": { + "message": "Substituir chave de criptografia" + }, + "recoveryReplacePrivateKeyDesc": { + "message": "O seu par de chaves de criptografia de chave pública não pôde ser recuperado. Gostaria de substituir sua chave de criptografia com um novo par? Ao fazer isso, será necessário reconfigurar o acesso de emergência existente e participações de organizações." + }, + "recoveryStepSyncTitle": { + "message": "Sincronizando dados" + }, + "recoveryStepPrivateKeyTitle": { + "message": "Verificando integridade da chave de criptografia" + }, + "recoveryStepUserInfoTitle": { + "message": "Verificando informações do usuário" + }, + "recoveryStepCipherTitle": { + "message": "Verificando integridade do item do cofre" + }, + "recoveryStepFoldersTitle": { + "message": "Verificando integridade da pasta" + }, + "dataRecoveryTitle": { + "message": "Diagnósticos e recuperação de dados" + }, + "dataRecoveryDescription": { + "message": "Use a ferramenta de recuperação de dados para diagnosticar e resolver problemas com a sua conta. Ao executar os diagnósticos, você tem a opção de salvar os registros de diagnóstico para suporte, e a opção de resolver quaisquer problemas detectados." + }, + "runDiagnostics": { + "message": "Executar diagnósticos" + }, + "repairIssues": { + "message": "Resolver problemas" + }, + "saveDiagnosticLogs": { + "message": "Salvar registros de diagnóstico" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "Esta configuração é gerenciada pela sua organização." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "A sua organização configurou o tempo de limite máximo da sessão para $HOURS$ hora(s) e $MINUTES$ minuto(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "A sua organização configurou o tempo de limite máximo padrão da sessão para ser no recarregar do navegador." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "O tempo limite máximo não pode exceder $HOURS$ hora(s) e $MINUTES$ minuto(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "No recarregar do navegador" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Configure um método de desbloqueio para alterar a ação do tempo limite" + }, + "leaveConfirmationDialogTitle": { + "message": "Tem certeza de que quer sair?" + }, + "leaveConfirmationDialogContentOne": { + "message": "Se recusar, seus itens pessoais continuarão na sua conta, mas você perderá o acesso aos itens compartilhados e os recursos de organização." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Entre em contato com o seu administrador para recuperar o acesso." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Sair de $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "Como gerencio meu cofre?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transferir itens para $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ exige que todos os itens sejam propriedade da organização por segurança e conformidade. Clique em aceitar para transferir a propriedade dos seus itens.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Aceitar transferência" + }, + "declineAndLeave": { + "message": "Recusar e sair" + }, + "whyAmISeeingThis": { + "message": "Por que estou vendo isso?" } } diff --git a/apps/web/src/locales/pt_PT/messages.json b/apps/web/src/locales/pt_PT/messages.json index 73ba923b416..7b0e9e8a197 100644 --- a/apps/web/src/locales/pt_PT/messages.json +++ b/apps/web/src/locales/pt_PT/messages.json @@ -5169,9 +5169,21 @@ "message": "Corrigir", "description": "This is a verb. ex. 'Fix The Car'" }, + "fixEncryption": { + "message": "Corrigir encriptação" + }, + "fixEncryptionTooltip": { + "message": "Este ficheiro está a utilizar um método de encriptação desatualizado." + }, + "attachmentUpdated": { + "message": "Anexo atualizado" + }, "oldAttachmentsNeedFixDesc": { "message": "Existem anexos de ficheiros antigos no seu cofre que têm de ser corrigidos antes de poder regenerar a chave de encriptação da sua conta." }, + "itemsTransferred": { + "message": "Itens transferidos" + }, "yourAccountsFingerprint": { "message": "Frase de impressão digital da sua conta", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -5854,7 +5866,7 @@ "description": "This will be used as a hyperlink" }, "organizationDataOwnershipWarningTitle": { - "message": "Tens a certeza de que queres continuar?" + "message": "Tem a certeza de que pretende continuar?" }, "organizationDataOwnershipWarning1": { "message": "continuará a ser acessível aos membros" @@ -6453,6 +6465,32 @@ "bulkReinviteMessage": { "message": "Convidado novamente com sucesso" }, + "bulkReinviteSuccessToast": { + "message": "$COUNT$ utilizadores convidados novamente", + "placeholders": { + "count": { + "content": "$1", + "example": "12" + } + } + }, + "bulkReinviteLimitedSuccessToast": { + "message": "$LIMIT$ de $SELECTEDCOUNT$ utilizadores convidados novamente. $EXCLUDEDCOUNT$ não foram convidados devido ao limite de $LIMIT$ convites.", + "placeholders": { + "limit": { + "content": "$1", + "example": "4,000" + }, + "selectedCount": { + "content": "$2", + "example": "4,005" + }, + "excludedCount": { + "content": "$3", + "example": "5" + } + } + }, "bulkRemovedMessage": { "message": "Removido com sucesso" }, @@ -6773,8 +6811,8 @@ "vaultTimeoutRangeError": { "message": "O tempo limite do cofre não está dentro do intervalo permitido." }, - "disablePersonalVaultExport": { - "message": "Remover a exportação do cofre individual" + "disableExport": { + "message": "Remover exportação" }, "disablePersonalVaultExportDescription": { "message": "Não permitir que os membros exportem dados do seu cofre individual." @@ -7094,9 +7132,6 @@ "invalidVerificationCode": { "message": "Código de verificação inválido" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "Já não é necessária uma palavra-passe mestra para os membros da seguinte organização. Por favor, confirme o domínio abaixo com o administrador da sua organização." - }, "keyConnectorDomain": { "message": "Domínio do Key Connector" }, @@ -7154,7 +7189,7 @@ "enabledSso": { "message": "SSO ativado" }, - "disabledSso": { + "ssoTurnedOff": { "message": "SSO desativado" }, "emailMustLoginWithSso": { @@ -9448,6 +9483,9 @@ "ssoLoginIsRequired": { "message": "É necessário um início de sessão SSO" }, + "emailRequiredForSsoLogin": { + "message": "O e-mail é necessário para o SSO" + }, "selectedRegionFlag": { "message": "Bandeira da região selecionada" }, @@ -11961,8 +11999,8 @@ "familiesMembership": { "message": "Adesão familiar" }, - "planDescPremium": { - "message": "Segurança total online" + "advancedOnlineSecurity": { + "message": "Segurança online avançada" }, "planDescFamiliesV2": { "message": "Segurança de topo para a sua família" @@ -12199,6 +12237,45 @@ } } }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "confirmNoSelectedCriticalApplicationsTitle": { "message": "Não foram selecionadas aplicações críticas" }, @@ -12207,5 +12284,139 @@ }, "userVerificationFailed": { "message": "Falha na verificação do utilizador." + }, + "recoveryDeleteCiphersTitle": { + "message": "Eliminar itens irrecuperáveis do cofre" + }, + "recoveryDeleteCiphersDesc": { + "message": "Alguns dos seus itens do cofre não puderam ser recuperados. Pretende eliminar esses itens irrecuperáveis do seu cofre?" + }, + "recoveryDeleteFoldersTitle": { + "message": "Eliminar pastas irrecuperáveis" + }, + "recoveryDeleteFoldersDesc": { + "message": "Alguns das suas pastas não puderam ser recuperados. Pretende eliminar essas pastas irrecuperáveis do seu cofre?" + }, + "recoveryReplacePrivateKeyTitle": { + "message": "Substituir chave de encriptação" + }, + "recoveryReplacePrivateKeyDesc": { + "message": "Não foi possível recuperar o seu par de chaves de encriptação de chave pública. Pretende substituir a sua chave de encriptação por um novo par de chaves? Isto exigirá que configure novamente os acessos de emergência existentes e as associações à organização." + }, + "recoveryStepSyncTitle": { + "message": "A sincronizar dados" + }, + "recoveryStepPrivateKeyTitle": { + "message": "A verificar a integridade da chave de encriptação" + }, + "recoveryStepUserInfoTitle": { + "message": "A verificar informações do utilizador" + }, + "recoveryStepCipherTitle": { + "message": "A verificar a integridade dos itens do cofre" + }, + "recoveryStepFoldersTitle": { + "message": "A verificar a integridade da pasta" + }, + "dataRecoveryTitle": { + "message": "Recuperação de Dados e Diagnóstico" + }, + "dataRecoveryDescription": { + "message": "Utilize a ferramenta de recuperação de dados para diagnosticar e resolver problemas na sua conta. Após executar os diagnósticos, terá a opção de guardar os registos de diagnóstico para suporte e a opção de reparar quaisquer problemas detetados." + }, + "runDiagnostics": { + "message": "Executar diagnóstico" + }, + "repairIssues": { + "message": "Resolver problemas" + }, + "saveDiagnosticLogs": { + "message": "Guardar registos de diagnóstico" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "Esta configuração é gerida pela sua organização." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "A sua organização definiu o tempo limite máximo da sessão para $HOURS$ hora(s) e $MINUTES$ minuto(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "A sua organização definiu o tempo limite de sessão predefinido para Ao atualizar o navegador." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "O tempo limite máximo não pode ser superior a $HOURS$ hora(s) e $MINUTES$ minuto(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "Ao atualizar o navegador" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Configure um método de desbloqueio para alterar a sua ação de tempo limite" + }, + "leaveConfirmationDialogTitle": { + "message": "Tem a certeza de que pretende sair?" + }, + "leaveConfirmationDialogContentOne": { + "message": "Ao recusar, os seus itens pessoais permanecerão na sua conta, mas perderá o acesso aos itens partilhados e às funcionalidades da organização." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Entre em contacto com o seu administrador para recuperar o acesso." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Sair de $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "Como posso gerir o meu cofre?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transferir itens para $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ exige que todos os itens sejam propriedade da organização por motivos de segurança e conformidade. Clique em Aceitar para transferir a propriedade dos seus itens.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Aceitar transferência" + }, + "declineAndLeave": { + "message": "Recusar e sair" + }, + "whyAmISeeingThis": { + "message": "Porque é que estou a ver isto?" } } diff --git a/apps/web/src/locales/ro/messages.json b/apps/web/src/locales/ro/messages.json index 8efd88e49aa..9ae83220ebd 100644 --- a/apps/web/src/locales/ro/messages.json +++ b/apps/web/src/locales/ro/messages.json @@ -5169,9 +5169,21 @@ "message": "Repară", "description": "This is a verb. ex. 'Fix The Car'" }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "oldAttachmentsNeedFixDesc": { "message": "Există atașamente de fișiere vechi în seiful dvs. care trebuie reparate înainte de a putea revoca cheia de criptare a contului." }, + "itemsTransferred": { + "message": "Items transferred" + }, "yourAccountsFingerprint": { "message": "Fraza amprentă a contului dvs.", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -6453,6 +6465,32 @@ "bulkReinviteMessage": { "message": "Reinvitat cu succes" }, + "bulkReinviteSuccessToast": { + "message": "$COUNT$ users re-invited", + "placeholders": { + "count": { + "content": "$1", + "example": "12" + } + } + }, + "bulkReinviteLimitedSuccessToast": { + "message": "$LIMIT$ of $SELECTEDCOUNT$ users re-invited. $EXCLUDEDCOUNT$ were not invited due to the $LIMIT$ invite limit.", + "placeholders": { + "limit": { + "content": "$1", + "example": "4,000" + }, + "selectedCount": { + "content": "$2", + "example": "4,005" + }, + "excludedCount": { + "content": "$3", + "example": "5" + } + } + }, "bulkRemovedMessage": { "message": "Eliminat cu succes" }, @@ -6773,8 +6811,8 @@ "vaultTimeoutRangeError": { "message": "Timpul de expirare al seifului nu este în intervalul permis." }, - "disablePersonalVaultExport": { - "message": "Înlăturați exportul de seif individual" + "disableExport": { + "message": "Remove export" }, "disablePersonalVaultExportDescription": { "message": "Do not allow members to export data from their individual vault." @@ -7094,9 +7132,6 @@ "invalidVerificationCode": { "message": "Cod de verificare nevalid" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "keyConnectorDomain": { "message": "Key Connector domain" }, @@ -7154,8 +7189,8 @@ "enabledSso": { "message": "SSO a fost activat" }, - "disabledSso": { - "message": "SSO a fost activat" + "ssoTurnedOff": { + "message": "SSO turned off" }, "emailMustLoginWithSso": { "message": "$EMAIL$ must login with Single Sign-on", @@ -9448,6 +9483,9 @@ "ssoLoginIsRequired": { "message": "SSO login is required" }, + "emailRequiredForSsoLogin": { + "message": "Email is required for SSO" + }, "selectedRegionFlag": { "message": "Selected region flag" }, @@ -11961,8 +11999,8 @@ "familiesMembership": { "message": "Families membership" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "planDescFamiliesV2": { "message": "Premium security for your family" @@ -12199,6 +12237,45 @@ } } }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "confirmNoSelectedCriticalApplicationsTitle": { "message": "No critical applications are selected" }, @@ -12207,5 +12284,139 @@ }, "userVerificationFailed": { "message": "User verification failed." + }, + "recoveryDeleteCiphersTitle": { + "message": "Delete unrecoverable vault items" + }, + "recoveryDeleteCiphersDesc": { + "message": "Some of your vault items could not be recovered. Do you want to delete these unrecoverable items from your vault?" + }, + "recoveryDeleteFoldersTitle": { + "message": "Delete unrecoverable folders" + }, + "recoveryDeleteFoldersDesc": { + "message": "Some of your folders could not be recovered. Do you want to delete these unrecoverable folders from your vault?" + }, + "recoveryReplacePrivateKeyTitle": { + "message": "Replace encryption key" + }, + "recoveryReplacePrivateKeyDesc": { + "message": "Your public-key encryption key pair could not be recovered. Do you want to replace your encryption key with a new key pair? This will require you to set up existing emergency-access and organization memberships again." + }, + "recoveryStepSyncTitle": { + "message": "Synchronizing data" + }, + "recoveryStepPrivateKeyTitle": { + "message": "Verifying encryption key integrity" + }, + "recoveryStepUserInfoTitle": { + "message": "Verifying user information" + }, + "recoveryStepCipherTitle": { + "message": "Verifying vault item integrity" + }, + "recoveryStepFoldersTitle": { + "message": "Verifying folder integrity" + }, + "dataRecoveryTitle": { + "message": "Data Recovery and Diagnostics" + }, + "dataRecoveryDescription": { + "message": "Use the data recovery tool to diagnose and repair issues with your account. After running diagnostics you have the option to save diagnostic logs for support and the option to repair any detected issues." + }, + "runDiagnostics": { + "message": "Run Diagnostics" + }, + "repairIssues": { + "message": "Repair Issues" + }, + "saveDiagnosticLogs": { + "message": "Save Diagnostic Logs" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On browser refresh." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On browser refresh" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/web/src/locales/ru/messages.json b/apps/web/src/locales/ru/messages.json index 0691b4e654b..7637c2baab5 100644 --- a/apps/web/src/locales/ru/messages.json +++ b/apps/web/src/locales/ru/messages.json @@ -5169,9 +5169,21 @@ "message": "Исправить", "description": "This is a verb. ex. 'Fix The Car'" }, + "fixEncryption": { + "message": "Исправить шифрование" + }, + "fixEncryptionTooltip": { + "message": "Этот файл использует устаревший метод шифрования." + }, + "attachmentUpdated": { + "message": "Вложение обновлено" + }, "oldAttachmentsNeedFixDesc": { "message": "В вашем хранилище есть старые вложения файлов, которые необходимо исправить, прежде чем вы сможете сменить ключ шифрования вашего аккаунта." }, + "itemsTransferred": { + "message": "Элементы переданы" + }, "yourAccountsFingerprint": { "message": "Фраза отпечатка вашего аккаунта", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -6453,6 +6465,32 @@ "bulkReinviteMessage": { "message": "Повторно приглашен успешно" }, + "bulkReinviteSuccessToast": { + "message": "$COUNT$ users re-invited", + "placeholders": { + "count": { + "content": "$1", + "example": "12" + } + } + }, + "bulkReinviteLimitedSuccessToast": { + "message": "$LIMIT$ of $SELECTEDCOUNT$ users re-invited. $EXCLUDEDCOUNT$ were not invited due to the $LIMIT$ invite limit.", + "placeholders": { + "limit": { + "content": "$1", + "example": "4,000" + }, + "selectedCount": { + "content": "$2", + "example": "4,005" + }, + "excludedCount": { + "content": "$3", + "example": "5" + } + } + }, "bulkRemovedMessage": { "message": "Удален(-о) успешно" }, @@ -6773,8 +6811,8 @@ "vaultTimeoutRangeError": { "message": "Тайм-аут хранилища находится за пределами допустимого диапазона." }, - "disablePersonalVaultExport": { - "message": "Удалить экспорт личного хранилища" + "disableExport": { + "message": "Удалить экспорт" }, "disablePersonalVaultExportDescription": { "message": "Запретить пользователям экспортировать данные из их собственного хранилища." @@ -7094,9 +7132,6 @@ "invalidVerificationCode": { "message": "Неверный код подтверждения" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "Мастер-пароль больше не требуется для членов следующей организации. Пожалуйста, подтвердите указанный ниже домен у администратора вашей организации." - }, "keyConnectorDomain": { "message": "Домен соединителя ключей" }, @@ -7154,7 +7189,7 @@ "enabledSso": { "message": "SSO включен" }, - "disabledSso": { + "ssoTurnedOff": { "message": "SSO отключен" }, "emailMustLoginWithSso": { @@ -9448,6 +9483,9 @@ "ssoLoginIsRequired": { "message": "Требуется логин SSO" }, + "emailRequiredForSsoLogin": { + "message": "Для SSO необходим email" + }, "selectedRegionFlag": { "message": "Флаг выбранного региона" }, @@ -11961,8 +11999,8 @@ "familiesMembership": { "message": "Членство Families" }, - "planDescPremium": { - "message": "Полная онлайн-защищенность" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "planDescFamiliesV2": { "message": "Премиальная защищенность \n для вашей семьи" @@ -12199,6 +12237,45 @@ } } }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Ваша организация больше не использует мастер-пароли для входа в Bitwarden. Чтобы продолжить, подтвердите организацию и домен." + }, + "continueWithLogIn": { + "message": "Продолжить с логином" + }, + "doNotContinue": { + "message": "Не продолжать" + }, + "domain": { + "message": "Домен" + }, + "keyConnectorDomainTooltip": { + "message": "В этом домене будут храниться ключи шифрования вашего аккаунта, поэтому убедитесь, что вы ему доверяете. Если вы не уверены, обратитесь к своему администратору." + }, + "verifyYourOrganization": { + "message": "Подтвердите свою организацию для входа" + }, + "organizationVerified": { + "message": "Организация подтверждена" + }, + "domainVerified": { + "message": "Домен верифицирован" + }, + "leaveOrganizationContent": { + "message": "Если вы не подтвердите свою организацию, ваш доступ к ней будет аннулирован." + }, + "leaveNow": { + "message": "Покинуть" + }, + "verifyYourDomainToLogin": { + "message": "Подтвердите свой домен для входа" + }, + "verifyYourDomainDescription": { + "message": "Чтобы продолжить с логином, подтвердите этот домен." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "Чтобы продолжить с логином, подтвердите организацию и домен." + }, "confirmNoSelectedCriticalApplicationsTitle": { "message": "Критичные приложения не выбраны" }, @@ -12207,5 +12284,139 @@ }, "userVerificationFailed": { "message": "Проверка пользователя не удалась." + }, + "recoveryDeleteCiphersTitle": { + "message": "Удалить невосстановимые элементы хранилища" + }, + "recoveryDeleteCiphersDesc": { + "message": "Некоторые элементы вашего хранилища восстановить не удалось. Вы хотите удалить элементы, которые невозможно восстановить из вашего хранилища?" + }, + "recoveryDeleteFoldersTitle": { + "message": "Удалить невосстановимые папки" + }, + "recoveryDeleteFoldersDesc": { + "message": "Некоторые папки вашего хранилища восстановить не удалось. Вы хотите удалить папки, которые невозможно восстановить из вашего хранилища?" + }, + "recoveryReplacePrivateKeyTitle": { + "message": "Сменить ключ шифрования" + }, + "recoveryReplacePrivateKeyDesc": { + "message": "Не удалось восстановить вашу пару ключей шифрования с публичным ключом. Вы хотите заменить свой ключ шифрования на новую пару ключей? Для этого вам потребуется заново настроить существующий экстренный доступ и членство в организации." + }, + "recoveryStepSyncTitle": { + "message": "Синхронизация данных" + }, + "recoveryStepPrivateKeyTitle": { + "message": "Проверка целостности ключа шифрования" + }, + "recoveryStepUserInfoTitle": { + "message": "Проверка информации о пользователе" + }, + "recoveryStepCipherTitle": { + "message": "Проверка целостности хранилища" + }, + "recoveryStepFoldersTitle": { + "message": "Проверка целостности папки" + }, + "dataRecoveryTitle": { + "message": "Восстановление и диагностика данных" + }, + "dataRecoveryDescription": { + "message": "Используйте инструмент восстановления данных для диагностики и устранения неполадок с вашим аккаунтом. После запуска диагностики у вас будет возможность сохранить журналы диагностики для поддержки и устранить любые обнаруженные неполадки." + }, + "runDiagnostics": { + "message": "Запустить диагностику" + }, + "repairIssues": { + "message": "Решить проблемы" + }, + "saveDiagnosticLogs": { + "message": "Сохранить журналы диагностики" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On browser refresh." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On browser refresh" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "leaveConfirmationDialogTitle": { + "message": "Вы уверены, что хотите покинуть?" + }, + "leaveConfirmationDialogContentOne": { + "message": "В случае отказа ваши личные данные останутся в вашем аккаунте, но вы потеряете доступ к общим элементам и возможностям организации." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Свяжитесь с вашим администратором для восстановления доступа." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Покинуть $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "Как я могу управлять своим хранилищем?" + }, + "transferItemsToOrganizationTitle": { + "message": "Перенести элементы в $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ требует, чтобы все элементы принадлежали организации для обеспечения безопасности и соответствия требованиям. Нажмите Принять, чтобы передать собственность на ваши элементы.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Принять передачу" + }, + "declineAndLeave": { + "message": "Отклонить и покинуть" + }, + "whyAmISeeingThis": { + "message": "Почему я это вижу?" } } diff --git a/apps/web/src/locales/si/messages.json b/apps/web/src/locales/si/messages.json index d4c4d143600..432a61608b5 100644 --- a/apps/web/src/locales/si/messages.json +++ b/apps/web/src/locales/si/messages.json @@ -5169,9 +5169,21 @@ "message": "Fix", "description": "This is a verb. ex. 'Fix The Car'" }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "oldAttachmentsNeedFixDesc": { "message": "There are old file attachments in your vault that need to be fixed before you can rotate your account's encryption key." }, + "itemsTransferred": { + "message": "Items transferred" + }, "yourAccountsFingerprint": { "message": "Your account's fingerprint phrase", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -6453,6 +6465,32 @@ "bulkReinviteMessage": { "message": "Reinvited successfully" }, + "bulkReinviteSuccessToast": { + "message": "$COUNT$ users re-invited", + "placeholders": { + "count": { + "content": "$1", + "example": "12" + } + } + }, + "bulkReinviteLimitedSuccessToast": { + "message": "$LIMIT$ of $SELECTEDCOUNT$ users re-invited. $EXCLUDEDCOUNT$ were not invited due to the $LIMIT$ invite limit.", + "placeholders": { + "limit": { + "content": "$1", + "example": "4,000" + }, + "selectedCount": { + "content": "$2", + "example": "4,005" + }, + "excludedCount": { + "content": "$3", + "example": "5" + } + } + }, "bulkRemovedMessage": { "message": "Removed successfully" }, @@ -6773,8 +6811,8 @@ "vaultTimeoutRangeError": { "message": "Vault timeout is not within allowed range." }, - "disablePersonalVaultExport": { - "message": "Remove individual vault export" + "disableExport": { + "message": "Remove export" }, "disablePersonalVaultExportDescription": { "message": "Do not allow members to export data from their individual vault." @@ -7094,9 +7132,6 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "keyConnectorDomain": { "message": "Key Connector domain" }, @@ -7154,8 +7189,8 @@ "enabledSso": { "message": "SSO turned on" }, - "disabledSso": { - "message": "SSO turned on" + "ssoTurnedOff": { + "message": "SSO turned off" }, "emailMustLoginWithSso": { "message": "$EMAIL$ must login with Single Sign-on", @@ -9448,6 +9483,9 @@ "ssoLoginIsRequired": { "message": "SSO login is required" }, + "emailRequiredForSsoLogin": { + "message": "Email is required for SSO" + }, "selectedRegionFlag": { "message": "Selected region flag" }, @@ -11961,8 +11999,8 @@ "familiesMembership": { "message": "Families membership" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "planDescFamiliesV2": { "message": "Premium security for your family" @@ -12199,6 +12237,45 @@ } } }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "confirmNoSelectedCriticalApplicationsTitle": { "message": "No critical applications are selected" }, @@ -12207,5 +12284,139 @@ }, "userVerificationFailed": { "message": "User verification failed." + }, + "recoveryDeleteCiphersTitle": { + "message": "Delete unrecoverable vault items" + }, + "recoveryDeleteCiphersDesc": { + "message": "Some of your vault items could not be recovered. Do you want to delete these unrecoverable items from your vault?" + }, + "recoveryDeleteFoldersTitle": { + "message": "Delete unrecoverable folders" + }, + "recoveryDeleteFoldersDesc": { + "message": "Some of your folders could not be recovered. Do you want to delete these unrecoverable folders from your vault?" + }, + "recoveryReplacePrivateKeyTitle": { + "message": "Replace encryption key" + }, + "recoveryReplacePrivateKeyDesc": { + "message": "Your public-key encryption key pair could not be recovered. Do you want to replace your encryption key with a new key pair? This will require you to set up existing emergency-access and organization memberships again." + }, + "recoveryStepSyncTitle": { + "message": "Synchronizing data" + }, + "recoveryStepPrivateKeyTitle": { + "message": "Verifying encryption key integrity" + }, + "recoveryStepUserInfoTitle": { + "message": "Verifying user information" + }, + "recoveryStepCipherTitle": { + "message": "Verifying vault item integrity" + }, + "recoveryStepFoldersTitle": { + "message": "Verifying folder integrity" + }, + "dataRecoveryTitle": { + "message": "Data Recovery and Diagnostics" + }, + "dataRecoveryDescription": { + "message": "Use the data recovery tool to diagnose and repair issues with your account. After running diagnostics you have the option to save diagnostic logs for support and the option to repair any detected issues." + }, + "runDiagnostics": { + "message": "Run Diagnostics" + }, + "repairIssues": { + "message": "Repair Issues" + }, + "saveDiagnosticLogs": { + "message": "Save Diagnostic Logs" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On browser refresh." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On browser refresh" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/web/src/locales/sk/messages.json b/apps/web/src/locales/sk/messages.json index 70578e5bf59..698c71f458f 100644 --- a/apps/web/src/locales/sk/messages.json +++ b/apps/web/src/locales/sk/messages.json @@ -94,7 +94,7 @@ "message": "Pre sledovanie progresu, priraďte členom úlohy" }, "onceYouReviewApplications": { - "message": "Once you review applications and mark them as critical, assign tasks to your members to change their passwords." + "message": "Po kontrole aplikácií a ich označení za kritické, predeľte členom úlohu na zmenu svojho hesla." }, "sendReminders": { "message": "Poslať upomienky" @@ -179,34 +179,34 @@ } }, "noDataInOrgTitle": { - "message": "No data found" + "message": "Nenašli sa žiadne údaje" }, "noDataInOrgDescription": { - "message": "Import your organization's login data to get started with Access Intelligence. Once you do that, you'll be able to:" + "message": "Ak chcete začať s prehľadom o prístupe, importujete prihlasovacie dáta vašej organizácie. Keď to spravíte, budete môcť:" }, "feature1Title": { - "message": "Mark applications as critical" + "message": "Označiť aplikácie za kritické" }, "feature1Description": { - "message": "This will help you remove risks to your most important applications first." + "message": "Toto vám pomôže prioritne odstrániť ohrozenia vo vašich najdôležitejších aplikáciach." }, "feature2Title": { - "message": "Help members improve their security" + "message": "Pomôcť členom zlepšiť si zabezpečenie" }, "feature2Description": { - "message": "Assign at-risk members guided security tasks to update credentials." + "message": "Prideľte ohrozeným členom bezpečnostné úlohy pre aktualizáciu prihlasovacích údajov." }, "feature3Title": { - "message": "Monitor progress" + "message": "Sledovať progres" }, "feature3Description": { - "message": "Track changes over time to show security improvements." + "message": "Zobrazte zlepšenie zabezpečenia sledovaním zmien v priebehu času." }, "noReportsRunTitle": { - "message": "Generate report" + "message": "Generovať report" }, "noReportsRunDescription": { - "message": "You’re ready to start generating reports. Once you generate, you’ll be able to:" + "message": "Môžete začať generovať reporty. Po generovaní budete môcť:" }, "noCriticalApplicationsTitle": { "message": "Neoznačili ste žiadne aplikácie ako kritické" @@ -266,13 +266,13 @@ "message": "Ohrozených členov" }, "membersWithAccessToAtRiskItemsForCriticalApplications": { - "message": "These members have access to vulnerable items for critical applications." + "message": "Títo členovia majú prístup k ohrozeným položkám kritických aplikácii." }, "membersWithAtRiskPasswords": { "message": "Členovia s ohrozenými heslami" }, "membersWillReceiveSecurityTask": { - "message": "Members of your organization will be assigned a task to change vulnerable passwords. They’ll receive a notification within their Bitwarden browser extension." + "message": "Členom vašej organizácie budú pridelené úlohy na zmenu ohrozených hesiel. Upozornenie obdržia prostredníctvom rozšírenia Bitwarden pre prehliadače." }, "membersAtRiskCount": { "message": "$COUNT$ ohrozených členov", @@ -302,7 +302,7 @@ } }, "atRiskMemberDescription": { - "message": "These members are logging into critical applications with weak, exposed, or reused passwords." + "message": "Títo členovia sa prihlasujú do kritických aplikácií so slabým, uniknutým alebo viacnásobne použitým heslom." }, "atRiskMembersDescriptionNone": { "message": "These are no members logging into applications with weak, exposed, or reused passwords." @@ -386,13 +386,13 @@ "message": "Uprednostniť kritické aplikácie" }, "selectCriticalAppsDescription": { - "message": "Select which applications are most critical to your organization. Then, you’ll be able to assign security tasks to members to remove risks." + "message": "Vyberte ktoré aplikácie sú pre vašu organizáciu najkritickejšie. Potom budete môcť členom prideliť bezpečnostné úlohy pre vyriešenie ohrozených hesiel." }, "reviewNewApplications": { "message": "Review new applications" }, "reviewNewAppsDescription": { - "message": "Review new applications with vulnerable items and mark those you’d like to monitor closely as critical. Then, you’ll be able to assign security tasks to members to remove risks." + "message": "Skontrolujte nové aplikácie s ohrozenými položkami a označte tie, ktoré chcete podrobne sledovať za kritické. Potom budete môcť prideliť členom bezpečnostné úlohy pre odstránenie ohrození." }, "clickIconToMarkAppAsCritical": { "message": "Aplikáciu označíte za kritickú kliknutím na ikonu hviezdičky" @@ -3063,7 +3063,7 @@ "message": "1 GB šifrovaného úložiska pre prílohy." }, "premiumSignUpStorageV2": { - "message": "$SIZE$ encrypted storage for file attachments.", + "message": "$SIZE$ šifrovaného úložiska na prílohy.", "placeholders": { "size": { "content": "$1", @@ -4627,19 +4627,19 @@ "message": "Zistiť viac" }, "migrationsFailed": { - "message": "An error occurred updating the encryption settings." + "message": "Pri aktualizácii nastavení šifrovania došlo k chybe." }, "updateEncryptionSettingsTitle": { - "message": "Update your encryption settings" + "message": "Aktualizujte nastavenie šifrovania" }, "updateEncryptionSettingsDesc": { - "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + "message": "Nové odporúčané nastavenia šifrovania zlepšia bezpečnosť vášho účtu. Ak ich chcete aktualizovať teraz, zadajte hlavné heslo." }, "confirmIdentityToContinue": { - "message": "Confirm your identity to continue" + "message": "Ak chcete pokračovať, potvrďte svoju identitu" }, "enterYourMasterPassword": { - "message": "Enter your master password" + "message": "Zadajte hlavné heslo" }, "updateSettings": { "message": "Update settings" @@ -5169,9 +5169,21 @@ "message": "Opraviť", "description": "This is a verb. ex. 'Fix The Car'" }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "oldAttachmentsNeedFixDesc": { "message": "V trezore máte staré prílohy ktoré musia byť opravené pred tým, než budete môcť obnoviť šifrovací kľúč k účtu." }, + "itemsTransferred": { + "message": "Items transferred" + }, "yourAccountsFingerprint": { "message": "Fráza odtlačku vašeho účtu", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -5846,7 +5858,7 @@ "description": "This is the policy description shown in the policy list." }, "organizationDataOwnershipDescContent": { - "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the ", + "message": "Všetky položky budú vo vlastníctve organizácie a uložené v nej, čo umožní kontrolu, prehľadnosť a vykazovanie v rámci celej organizácie. Po zapnutí bude každému členovi k dispozícii predvolená zbierka na ukladanie položiek. Dozvedieť sa viac o správe ", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the credential lifecycle.'" }, "organizationDataOwnershipContentAnchor": { @@ -6453,6 +6465,32 @@ "bulkReinviteMessage": { "message": "Opätovné pozvanie úspešné." }, + "bulkReinviteSuccessToast": { + "message": "$COUNT$ používateľov opätovne pozvaných", + "placeholders": { + "count": { + "content": "$1", + "example": "12" + } + } + }, + "bulkReinviteLimitedSuccessToast": { + "message": "$LIMIT$ z $SELECTEDCOUNT$ používateľov opätovne pozvaných. $EXCLUDEDCOUNT$ nebolo pozvaných kvôli limitu $LIMIT$ pozvánok.", + "placeholders": { + "limit": { + "content": "$1", + "example": "4,000" + }, + "selectedCount": { + "content": "$2", + "example": "4,005" + }, + "excludedCount": { + "content": "$3", + "example": "5" + } + } + }, "bulkRemovedMessage": { "message": "Odstránenie úspešné" }, @@ -6686,7 +6724,7 @@ "message": "Okamžite" }, "onSystemLock": { - "message": "Keď je systém uzamknutý" + "message": "Pri uzamknutí systému" }, "onAppRestart": { "message": "Po reštarte aplikácie" @@ -6773,8 +6811,8 @@ "vaultTimeoutRangeError": { "message": "Časový limit trezoru nie je v povolenom rozsahu." }, - "disablePersonalVaultExport": { - "message": "Zakázať export osobného trezora" + "disableExport": { + "message": "Remove export" }, "disablePersonalVaultExportDescription": { "message": "Zakázať používateľom exportovať údaje zo súkromného trezora." @@ -7094,9 +7132,6 @@ "invalidVerificationCode": { "message": "Neplatný verifikačný kód" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "Hlavné heslo sa už nevyžaduje pre členov tejto organizácie. Nižšie uvedenú doménu potvrďte u správcu organizácie." - }, "keyConnectorDomain": { "message": "Doména Key Connectora" }, @@ -7154,8 +7189,8 @@ "enabledSso": { "message": "SSO povolené" }, - "disabledSso": { - "message": "SSO zakázané" + "ssoTurnedOff": { + "message": "Jednotné prihlásenie vypnuté" }, "emailMustLoginWithSso": { "message": "$EMAIL$ sa musí prihlásiť cez prihlasovací formulár spoločnosti ", @@ -9448,6 +9483,9 @@ "ssoLoginIsRequired": { "message": "Vyžaduje sa prihlásenie cez SSO" }, + "emailRequiredForSsoLogin": { + "message": "Email is required for SSO" + }, "selectedRegionFlag": { "message": "Selected region flag" }, @@ -9857,11 +9895,11 @@ "description": "An option for the offboarding survey shown when a user cancels their subscription." }, "switchToFreePlan": { - "message": "Switching to free plan", + "message": "Prechod na bezplatný plán", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, "switchToFreeOrg": { - "message": "Switching to free organization", + "message": "Prechod na bezplatnú organizáciu", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, "freeForOneYear": { @@ -9895,7 +9933,7 @@ "message": "Priradiť úlohy" }, "assignSecurityTasksToMembers": { - "message": "Send notifications to change passwords" + "message": "Odoslať upozornenia na zmenu hesla" }, "assignToCollections": { "message": "Prideliť k zbierkam" @@ -11704,7 +11742,7 @@ "message": "Rozšírenie Bitwarden nainštalované!" }, "bitwardenExtensionIsInstalled": { - "message": "Bitwarden extension is installed!" + "message": "Rozšírenie Bitwarden je nainštalované!" }, "openExtensionToAutofill": { "message": "Otvorte rozšírenie, prihláste sa a začnite automatické vypĺňanie." @@ -11721,11 +11759,11 @@ "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" }, "openExtensionFromToolbarPart1": { - "message": "If the extension didn't open, you may need to open Bitwarden from the icon ", + "message": "Ak sa rozšírenie neotvorilo, možno musíte otvoriť Bitwarden prostredníctvom ikony ", "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" }, "openExtensionFromToolbarPart2": { - "message": " on the toolbar.", + "message": " na paneli nástrojov.", "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" }, "gettingStartedWithBitwardenPart3": { @@ -11961,8 +11999,8 @@ "familiesMembership": { "message": "Členstvo pre rodiny" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Pokročilá bezpečnosť online" }, "planDescFamiliesV2": { "message": "Premium security for your family" @@ -12157,13 +12195,13 @@ "message": "Začať bezplatnú skúšku pre predplatné Rodiny" }, "blockClaimedDomainAccountCreation": { - "message": "Block account creation for claimed domains" + "message": "Zablokovať vytváranie účtov na privlastnenej doméne" }, "blockClaimedDomainAccountCreationDesc": { - "message": "Prevent users from creating accounts outside of your organization using email addresses from claimed domains." + "message": "Zabrániť používateľom mimo vašej organizácie vytvárať účet s emailom z privlastnených domén." }, "blockClaimedDomainAccountCreationPrerequisite": { - "message": "A domain must be claimed before activating this policy." + "message": "Pred zapnutím tohto pravidla musí byť doména privlastnená." }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Set up an unlock method to change your vault timeout action." @@ -12199,6 +12237,45 @@ } } }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "confirmNoSelectedCriticalApplicationsTitle": { "message": "Nie sú vybrané žiadne kritické aplikácie" }, @@ -12206,6 +12283,140 @@ "message": "Ste si istí, že chcete pokračovať?" }, "userVerificationFailed": { - "message": "User verification failed." + "message": "Zlyhalo overenie používateľa." + }, + "recoveryDeleteCiphersTitle": { + "message": "Delete unrecoverable vault items" + }, + "recoveryDeleteCiphersDesc": { + "message": "Some of your vault items could not be recovered. Do you want to delete these unrecoverable items from your vault?" + }, + "recoveryDeleteFoldersTitle": { + "message": "Delete unrecoverable folders" + }, + "recoveryDeleteFoldersDesc": { + "message": "Some of your folders could not be recovered. Do you want to delete these unrecoverable folders from your vault?" + }, + "recoveryReplacePrivateKeyTitle": { + "message": "Replace encryption key" + }, + "recoveryReplacePrivateKeyDesc": { + "message": "Your public-key encryption key pair could not be recovered. Do you want to replace your encryption key with a new key pair? This will require you to set up existing emergency-access and organization memberships again." + }, + "recoveryStepSyncTitle": { + "message": "Synchronizing data" + }, + "recoveryStepPrivateKeyTitle": { + "message": "Verifying encryption key integrity" + }, + "recoveryStepUserInfoTitle": { + "message": "Verifying user information" + }, + "recoveryStepCipherTitle": { + "message": "Verifying vault item integrity" + }, + "recoveryStepFoldersTitle": { + "message": "Verifying folder integrity" + }, + "dataRecoveryTitle": { + "message": "Data Recovery and Diagnostics" + }, + "dataRecoveryDescription": { + "message": "Use the data recovery tool to diagnose and repair issues with your account. After running diagnostics you have the option to save diagnostic logs for support and the option to repair any detected issues." + }, + "runDiagnostics": { + "message": "Run Diagnostics" + }, + "repairIssues": { + "message": "Repair Issues" + }, + "saveDiagnosticLogs": { + "message": "Save Diagnostic Logs" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "Toto nastavenie spravuje vaša organizácia." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Vaša organizácia nastavila maximálny časový limit relácie na $HOURS$ hod. a $MINUTES$ min.", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Vaša organizácia nastavila predvolený časový limit relácie na Pri reštarte prehliadača." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximálny časový limit nesmie prekročiť $HOURS$ hod. a $MINUTES$ min.", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "Pri reštarte prehliadača" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Nastavte metódu odomknutia, aby ste zmenili akciu pri vypršaní časového limitu" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/web/src/locales/sl/messages.json b/apps/web/src/locales/sl/messages.json index 10c734ec6b7..97a3f12c606 100644 --- a/apps/web/src/locales/sl/messages.json +++ b/apps/web/src/locales/sl/messages.json @@ -5169,9 +5169,21 @@ "message": "Fix", "description": "This is a verb. ex. 'Fix The Car'" }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "oldAttachmentsNeedFixDesc": { "message": "There are old file attachments in your vault that need to be fixed before you can rotate your account's encryption key." }, + "itemsTransferred": { + "message": "Items transferred" + }, "yourAccountsFingerprint": { "message": "Identifikacijsko geslo vašega računa", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -6453,6 +6465,32 @@ "bulkReinviteMessage": { "message": "Reinvited successfully" }, + "bulkReinviteSuccessToast": { + "message": "$COUNT$ users re-invited", + "placeholders": { + "count": { + "content": "$1", + "example": "12" + } + } + }, + "bulkReinviteLimitedSuccessToast": { + "message": "$LIMIT$ of $SELECTEDCOUNT$ users re-invited. $EXCLUDEDCOUNT$ were not invited due to the $LIMIT$ invite limit.", + "placeholders": { + "limit": { + "content": "$1", + "example": "4,000" + }, + "selectedCount": { + "content": "$2", + "example": "4,005" + }, + "excludedCount": { + "content": "$3", + "example": "5" + } + } + }, "bulkRemovedMessage": { "message": "Removed successfully" }, @@ -6773,8 +6811,8 @@ "vaultTimeoutRangeError": { "message": "Vault timeout is not within allowed range." }, - "disablePersonalVaultExport": { - "message": "Remove individual vault export" + "disableExport": { + "message": "Remove export" }, "disablePersonalVaultExportDescription": { "message": "Do not allow members to export data from their individual vault." @@ -7094,9 +7132,6 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "keyConnectorDomain": { "message": "Key Connector domain" }, @@ -7154,8 +7189,8 @@ "enabledSso": { "message": "SSO turned on" }, - "disabledSso": { - "message": "SSO turned on" + "ssoTurnedOff": { + "message": "SSO turned off" }, "emailMustLoginWithSso": { "message": "$EMAIL$ must login with Single Sign-on", @@ -9448,6 +9483,9 @@ "ssoLoginIsRequired": { "message": "SSO login is required" }, + "emailRequiredForSsoLogin": { + "message": "Email is required for SSO" + }, "selectedRegionFlag": { "message": "Selected region flag" }, @@ -11961,8 +11999,8 @@ "familiesMembership": { "message": "Families membership" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "planDescFamiliesV2": { "message": "Premium security for your family" @@ -12199,6 +12237,45 @@ } } }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "confirmNoSelectedCriticalApplicationsTitle": { "message": "No critical applications are selected" }, @@ -12207,5 +12284,139 @@ }, "userVerificationFailed": { "message": "User verification failed." + }, + "recoveryDeleteCiphersTitle": { + "message": "Delete unrecoverable vault items" + }, + "recoveryDeleteCiphersDesc": { + "message": "Some of your vault items could not be recovered. Do you want to delete these unrecoverable items from your vault?" + }, + "recoveryDeleteFoldersTitle": { + "message": "Delete unrecoverable folders" + }, + "recoveryDeleteFoldersDesc": { + "message": "Some of your folders could not be recovered. Do you want to delete these unrecoverable folders from your vault?" + }, + "recoveryReplacePrivateKeyTitle": { + "message": "Replace encryption key" + }, + "recoveryReplacePrivateKeyDesc": { + "message": "Your public-key encryption key pair could not be recovered. Do you want to replace your encryption key with a new key pair? This will require you to set up existing emergency-access and organization memberships again." + }, + "recoveryStepSyncTitle": { + "message": "Synchronizing data" + }, + "recoveryStepPrivateKeyTitle": { + "message": "Verifying encryption key integrity" + }, + "recoveryStepUserInfoTitle": { + "message": "Verifying user information" + }, + "recoveryStepCipherTitle": { + "message": "Verifying vault item integrity" + }, + "recoveryStepFoldersTitle": { + "message": "Verifying folder integrity" + }, + "dataRecoveryTitle": { + "message": "Data Recovery and Diagnostics" + }, + "dataRecoveryDescription": { + "message": "Use the data recovery tool to diagnose and repair issues with your account. After running diagnostics you have the option to save diagnostic logs for support and the option to repair any detected issues." + }, + "runDiagnostics": { + "message": "Run Diagnostics" + }, + "repairIssues": { + "message": "Repair Issues" + }, + "saveDiagnosticLogs": { + "message": "Save Diagnostic Logs" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On browser refresh." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On browser refresh" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/web/src/locales/sr_CS/messages.json b/apps/web/src/locales/sr_CS/messages.json index 0f8a2760370..5527124ca73 100644 --- a/apps/web/src/locales/sr_CS/messages.json +++ b/apps/web/src/locales/sr_CS/messages.json @@ -5169,9 +5169,21 @@ "message": "Fix", "description": "This is a verb. ex. 'Fix The Car'" }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "oldAttachmentsNeedFixDesc": { "message": "There are old file attachments in your vault that need to be fixed before you can rotate your account's encryption key." }, + "itemsTransferred": { + "message": "Items transferred" + }, "yourAccountsFingerprint": { "message": "Your account's fingerprint phrase", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -6453,6 +6465,32 @@ "bulkReinviteMessage": { "message": "Reinvited successfully" }, + "bulkReinviteSuccessToast": { + "message": "$COUNT$ users re-invited", + "placeholders": { + "count": { + "content": "$1", + "example": "12" + } + } + }, + "bulkReinviteLimitedSuccessToast": { + "message": "$LIMIT$ of $SELECTEDCOUNT$ users re-invited. $EXCLUDEDCOUNT$ were not invited due to the $LIMIT$ invite limit.", + "placeholders": { + "limit": { + "content": "$1", + "example": "4,000" + }, + "selectedCount": { + "content": "$2", + "example": "4,005" + }, + "excludedCount": { + "content": "$3", + "example": "5" + } + } + }, "bulkRemovedMessage": { "message": "Removed successfully" }, @@ -6773,8 +6811,8 @@ "vaultTimeoutRangeError": { "message": "Vault timeout is not within allowed range." }, - "disablePersonalVaultExport": { - "message": "Remove individual vault export" + "disableExport": { + "message": "Remove export" }, "disablePersonalVaultExportDescription": { "message": "Do not allow members to export data from their individual vault." @@ -7094,9 +7132,6 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "keyConnectorDomain": { "message": "Key Connector domain" }, @@ -7154,8 +7189,8 @@ "enabledSso": { "message": "SSO turned on" }, - "disabledSso": { - "message": "SSO turned on" + "ssoTurnedOff": { + "message": "SSO turned off" }, "emailMustLoginWithSso": { "message": "$EMAIL$ must login with Single Sign-on", @@ -9448,6 +9483,9 @@ "ssoLoginIsRequired": { "message": "SSO login is required" }, + "emailRequiredForSsoLogin": { + "message": "Email is required for SSO" + }, "selectedRegionFlag": { "message": "Selected region flag" }, @@ -11961,8 +11999,8 @@ "familiesMembership": { "message": "Families membership" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "planDescFamiliesV2": { "message": "Premium security for your family" @@ -12199,6 +12237,45 @@ } } }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "confirmNoSelectedCriticalApplicationsTitle": { "message": "No critical applications are selected" }, @@ -12207,5 +12284,139 @@ }, "userVerificationFailed": { "message": "User verification failed." + }, + "recoveryDeleteCiphersTitle": { + "message": "Delete unrecoverable vault items" + }, + "recoveryDeleteCiphersDesc": { + "message": "Some of your vault items could not be recovered. Do you want to delete these unrecoverable items from your vault?" + }, + "recoveryDeleteFoldersTitle": { + "message": "Delete unrecoverable folders" + }, + "recoveryDeleteFoldersDesc": { + "message": "Some of your folders could not be recovered. Do you want to delete these unrecoverable folders from your vault?" + }, + "recoveryReplacePrivateKeyTitle": { + "message": "Replace encryption key" + }, + "recoveryReplacePrivateKeyDesc": { + "message": "Your public-key encryption key pair could not be recovered. Do you want to replace your encryption key with a new key pair? This will require you to set up existing emergency-access and organization memberships again." + }, + "recoveryStepSyncTitle": { + "message": "Synchronizing data" + }, + "recoveryStepPrivateKeyTitle": { + "message": "Verifying encryption key integrity" + }, + "recoveryStepUserInfoTitle": { + "message": "Verifying user information" + }, + "recoveryStepCipherTitle": { + "message": "Verifying vault item integrity" + }, + "recoveryStepFoldersTitle": { + "message": "Verifying folder integrity" + }, + "dataRecoveryTitle": { + "message": "Data Recovery and Diagnostics" + }, + "dataRecoveryDescription": { + "message": "Use the data recovery tool to diagnose and repair issues with your account. After running diagnostics you have the option to save diagnostic logs for support and the option to repair any detected issues." + }, + "runDiagnostics": { + "message": "Run Diagnostics" + }, + "repairIssues": { + "message": "Repair Issues" + }, + "saveDiagnosticLogs": { + "message": "Save Diagnostic Logs" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On browser refresh." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On browser refresh" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/web/src/locales/sr_CY/messages.json b/apps/web/src/locales/sr_CY/messages.json index f1f7dd3ca73..835e4081be3 100644 --- a/apps/web/src/locales/sr_CY/messages.json +++ b/apps/web/src/locales/sr_CY/messages.json @@ -5169,9 +5169,21 @@ "message": "Фиксирај", "description": "This is a verb. ex. 'Fix The Car'" }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "oldAttachmentsNeedFixDesc": { "message": "У вашем сефу постоје стари прилози који треба поправити да бисте могли да промените кључ за шифровање свог налога." }, + "itemsTransferred": { + "message": "Items transferred" + }, "yourAccountsFingerprint": { "message": "Ваша Сигурносна Фраза Сефа", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -6453,6 +6465,32 @@ "bulkReinviteMessage": { "message": "Успешно поновно позивање." }, + "bulkReinviteSuccessToast": { + "message": "$COUNT$ users re-invited", + "placeholders": { + "count": { + "content": "$1", + "example": "12" + } + } + }, + "bulkReinviteLimitedSuccessToast": { + "message": "$LIMIT$ of $SELECTEDCOUNT$ users re-invited. $EXCLUDEDCOUNT$ were not invited due to the $LIMIT$ invite limit.", + "placeholders": { + "limit": { + "content": "$1", + "example": "4,000" + }, + "selectedCount": { + "content": "$2", + "example": "4,005" + }, + "excludedCount": { + "content": "$3", + "example": "5" + } + } + }, "bulkRemovedMessage": { "message": "Успешно уклоњено" }, @@ -6773,8 +6811,8 @@ "vaultTimeoutRangeError": { "message": "Временско ограничење сефа није у дозвољеном опсегу." }, - "disablePersonalVaultExport": { - "message": "Онемогућите извоз личног сефа" + "disableExport": { + "message": "Remove export" }, "disablePersonalVaultExportDescription": { "message": "Не дозволи члановима да извозе податке из свог индивидуалног сефа." @@ -7094,9 +7132,6 @@ "invalidVerificationCode": { "message": "Неисправан верификациони код" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "Главна лозинка више није потребна за чланове следеће организације. Молимо потврдите домен са администратором организације." - }, "keyConnectorDomain": { "message": "Домен конектора кључа" }, @@ -7154,8 +7189,8 @@ "enabledSso": { "message": "SSO омогућен" }, - "disabledSso": { - "message": "SSO онемогућен" + "ssoTurnedOff": { + "message": "SSO turned off" }, "emailMustLoginWithSso": { "message": "$EMAIL$ must login with Single Sign-on", @@ -9448,6 +9483,9 @@ "ssoLoginIsRequired": { "message": "SSO пријава је потребна" }, + "emailRequiredForSsoLogin": { + "message": "Email is required for SSO" + }, "selectedRegionFlag": { "message": "Одабрана застава" }, @@ -11961,8 +11999,8 @@ "familiesMembership": { "message": "Families membership" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "planDescFamiliesV2": { "message": "Premium security for your family" @@ -12199,6 +12237,45 @@ } } }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "confirmNoSelectedCriticalApplicationsTitle": { "message": "No critical applications are selected" }, @@ -12207,5 +12284,139 @@ }, "userVerificationFailed": { "message": "User verification failed." + }, + "recoveryDeleteCiphersTitle": { + "message": "Delete unrecoverable vault items" + }, + "recoveryDeleteCiphersDesc": { + "message": "Some of your vault items could not be recovered. Do you want to delete these unrecoverable items from your vault?" + }, + "recoveryDeleteFoldersTitle": { + "message": "Delete unrecoverable folders" + }, + "recoveryDeleteFoldersDesc": { + "message": "Some of your folders could not be recovered. Do you want to delete these unrecoverable folders from your vault?" + }, + "recoveryReplacePrivateKeyTitle": { + "message": "Replace encryption key" + }, + "recoveryReplacePrivateKeyDesc": { + "message": "Your public-key encryption key pair could not be recovered. Do you want to replace your encryption key with a new key pair? This will require you to set up existing emergency-access and organization memberships again." + }, + "recoveryStepSyncTitle": { + "message": "Synchronizing data" + }, + "recoveryStepPrivateKeyTitle": { + "message": "Verifying encryption key integrity" + }, + "recoveryStepUserInfoTitle": { + "message": "Verifying user information" + }, + "recoveryStepCipherTitle": { + "message": "Verifying vault item integrity" + }, + "recoveryStepFoldersTitle": { + "message": "Verifying folder integrity" + }, + "dataRecoveryTitle": { + "message": "Data Recovery and Diagnostics" + }, + "dataRecoveryDescription": { + "message": "Use the data recovery tool to diagnose and repair issues with your account. After running diagnostics you have the option to save diagnostic logs for support and the option to repair any detected issues." + }, + "runDiagnostics": { + "message": "Run Diagnostics" + }, + "repairIssues": { + "message": "Repair Issues" + }, + "saveDiagnosticLogs": { + "message": "Save Diagnostic Logs" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On browser refresh." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On browser refresh" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/web/src/locales/sv/messages.json b/apps/web/src/locales/sv/messages.json index 7f1275ee75c..57495a63fa8 100644 --- a/apps/web/src/locales/sv/messages.json +++ b/apps/web/src/locales/sv/messages.json @@ -94,7 +94,7 @@ "message": "Tilldela medlemmar uppgifter för att övervaka framsteg" }, "onceYouReviewApplications": { - "message": "Once you review applications and mark them as critical, assign tasks to your members to change their passwords." + "message": "När du granskar applikationer och markerar dem som kritiska, tilldela uppgifter till dina medlemmar att ändra sina lösenord." }, "sendReminders": { "message": "Skicka påminnelser" @@ -182,31 +182,31 @@ "message": "Ingen data hittades" }, "noDataInOrgDescription": { - "message": "Import your organization's login data to get started with Access Intelligence. Once you do that, you'll be able to:" + "message": "Importera din organisations inloggningsdata för att komma igång med Access Intelligence. När du gör det kommer du att:" }, "feature1Title": { "message": "Markera applikationer som kritiska" }, "feature1Description": { - "message": "This will help you remove risks to your most important applications first." + "message": "Detta kommer att hjälpa dig att minska risken för dina viktigaste applikationer först." }, "feature2Title": { "message": "Hjälp medlemmar att förbättra sin säkerhet" }, "feature2Description": { - "message": "Assign at-risk members guided security tasks to update credentials." + "message": "Tilldela medlemmar i riskzonen tydliga säkerhetsuppgifter för att uppdatera inloggningsuppgifter." }, "feature3Title": { "message": "Övervaka framsteg" }, "feature3Description": { - "message": "Track changes over time to show security improvements." + "message": "Spåra förändringar över tid för att visa säkerhetsförbättringar." }, "noReportsRunTitle": { "message": "Generera rapport" }, "noReportsRunDescription": { - "message": "You’re ready to start generating reports. Once you generate, you’ll be able to:" + "message": "Du är redo att börja generera rapporter. När du genererar dem kommer du att kunna:" }, "noCriticalApplicationsTitle": { "message": "Du har inte markerat några applikationer som kritiska" @@ -266,13 +266,13 @@ "message": "Riskutsatta medlemmar" }, "membersWithAccessToAtRiskItemsForCriticalApplications": { - "message": "These members have access to vulnerable items for critical applications." + "message": "Dessa medlemmar har tillgång till sårbara objekt för kritiska applikationer." }, "membersWithAtRiskPasswords": { "message": "Medlemmar med lösenord i riskzonen" }, "membersWillReceiveSecurityTask": { - "message": "Members of your organization will be assigned a task to change vulnerable passwords. They’ll receive a notification within their Bitwarden browser extension." + "message": "Medlemmar i din organisation kommer att tilldelas en uppgift att ändra sårbara lösenord. De kommer att få ett meddelande i deras Bitwarden-webbläsartillägg." }, "membersAtRiskCount": { "message": "$COUNT$ medlemmar i riskzonen", @@ -302,7 +302,7 @@ } }, "atRiskMemberDescription": { - "message": "These members are logging into critical applications with weak, exposed, or reused passwords." + "message": "Dessa medlemmar loggar in i kritiska applikationer med svaga, exponerade eller återanvända lösenord." }, "atRiskMembersDescriptionNone": { "message": "Det finns inga medlemmar som loggar in i applikationer med svaga, exponerade eller återanvända lösenord." @@ -362,13 +362,13 @@ "message": "Granska nu" }, "allCaughtUp": { - "message": "All caught up!" + "message": "Inget mer att göra!" }, "noNewApplicationsToReviewAtThisTime": { "message": "Inga nya applikationer att granska just nu" }, "organizationHasItemsSavedForApplications": { - "message": "Your organization has items saved for $COUNT$ applications", + "message": "Din organisation har sparade objekt för $COUNT$ applikationer", "placeholders": { "count": { "content": "$1", @@ -377,7 +377,7 @@ } }, "reviewApplicationsToSecureItems": { - "message": "Review applications to secure the items most critical to your organization's security" + "message": "Granska applikationer för att säkra de objekt som är mest kritiska för din organisations säkerhet" }, "reviewApplications": { "message": "Granska applikationer" @@ -386,22 +386,22 @@ "message": "Prioritera kritiska applikationer" }, "selectCriticalAppsDescription": { - "message": "Select which applications are most critical to your organization. Then, you’ll be able to assign security tasks to members to remove risks." + "message": "Välj vilka applikationer som är mest kritiska för din organisation. Då kommer du att kunna tilldela säkerhetsuppgifter till medlemmar för att ta bort risker." }, "reviewNewApplications": { "message": "Granska nya applikationer" }, "reviewNewAppsDescription": { - "message": "Review new applications with vulnerable items and mark those you’d like to monitor closely as critical. Then, you’ll be able to assign security tasks to members to remove risks." + "message": "Granska nya applikationer med sårbara objekt och markera dem som du vill övervaka noga som kritiska. Då kommer du att kunna tilldela säkerhetsuppgifter till medlemmar för att ta bort risker." }, "clickIconToMarkAppAsCritical": { - "message": "Click the star icon to mark an app as critical" + "message": "Klicka på stjärnikonen för att markera en app som kritisk" }, "markAsCriticalPlaceholder": { "message": "Markera som kritisk funktionalitet kommer att implementeras i en framtida uppdatering" }, "applicationReviewSaved": { - "message": "Application review saved" + "message": "Applikationsgranskning sparad" }, "newApplicationsReviewed": { "message": "Nya applikationer granskade" @@ -875,7 +875,7 @@ "message": "Favoriter" }, "taskSummary": { - "message": "Task summary" + "message": "Uppgiftssammandrag" }, "types": { "message": "Typer" @@ -1717,7 +1717,7 @@ "message": "Inga objekt i valvet" }, "emptyVaultDescription": { - "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + "message": "Valvet skyddar mer än bara dina lösenord. Förvara säkra inloggningar, ID-handlingar, kort och anteckningar säkert här." }, "emptyFavorites": { "message": "Du har inga favoritobjekt" @@ -3044,7 +3044,7 @@ "message": "Se till att ditt konto har tillräckligt mycket tillgänglig kredit för detta köp. Om ditt konto inte har tillräckligt med tillgänglig kredit, kommer din sparade standardbetalningsmetod användas för skillnaden. Du kan lägga till kredit till ditt konto från faktureringssidan." }, "notEnoughAccountCredit": { - "message": "You do not have enough account credit for this purchase. You can add credit to your account from the Billing page." + "message": "Du har inte tillräckligt med kontokredit för detta köp. Du kan lägga till kredit till ditt konto från faktureringssidan." }, "creditAppliedDesc": { "message": "Kontots kredit kan användas för att göra köp. Tillgänglig kredit kommer automatiskt tillämpas mot fakturor som genereras för detta konto." @@ -3063,7 +3063,7 @@ "message": "1 GB krypterad lagring." }, "premiumSignUpStorageV2": { - "message": "$SIZE$ encrypted storage for file attachments.", + "message": "$SIZE$ krypterad lagring för filbilagor.", "placeholders": { "size": { "content": "$1", @@ -3134,10 +3134,10 @@ } }, "premiumSubscriptionEnded": { - "message": "Your Premium subscription ended" + "message": "Din Premium-prenumeration avslutades" }, "premiumSubscriptionEndedDesc": { - "message": "To regain access to your archive, restart your Premium subscription. If you edit details for an archived item before restarting, it'll be moved back into your vault." + "message": "För att återfå åtkomst till ditt arkiv, starta om Premium-prenumerationen. Om du redigerar detaljer för ett arkiverat objekt innan du startar om kommer det att flyttas tillbaka till ditt valv." }, "restartPremium": { "message": "Starta om Premium" @@ -4479,7 +4479,7 @@ "message": "Uppdatera webbläsare" }, "generatingYourAccessIntelligence": { - "message": "Generating your Access Intelligence..." + "message": "Genererar din Access Intelligence..." }, "fetchingMemberData": { "message": "Hämtar medlemsdata..." @@ -5096,7 +5096,7 @@ "message": "Organisationen är avstängd" }, "organizationIsSuspendedDesc": { - "message": "Items in suspended organizations cannot be accessed. Contact your organization owner for assistance." + "message": "Objekt i inaktiverade organisationer är inte åtkomliga. Kontakta organisationens ägare för att få hjälp." }, "secretsAccessSuspended": { "message": "Avstängda organisationer kan inte nås. Kontakta din organisationsägare för hjälp." @@ -5169,9 +5169,21 @@ "message": "Åtgärda", "description": "This is a verb. ex. 'Fix The Car'" }, + "fixEncryption": { + "message": "Fixa kryptering" + }, + "fixEncryptionTooltip": { + "message": "Denna fil använder en föråldrad krypteringsmetod." + }, + "attachmentUpdated": { + "message": "Bilaga uppdaterad" + }, "oldAttachmentsNeedFixDesc": { "message": "Det finns gamla bilagor i ditt valv som behöver åtgärdas innan du kan rotera ditt kontos krypteringsnyckel." }, + "itemsTransferred": { + "message": "Objekt överförda" + }, "yourAccountsFingerprint": { "message": "Ditt kontos fingeravtrycksfras", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -5484,7 +5496,7 @@ "message": "SSO-identifierare" }, "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": "Ange detta id till dina medlemmar för att logga in med SSO. Medlemmar kan hoppa över att ange denna identifierare under SSO om en domän som gjorts anspråk på är inställd. ", "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": { @@ -5846,7 +5858,7 @@ "description": "This is the policy description shown in the policy list." }, "organizationDataOwnershipDescContent": { - "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the ", + "message": "Alla objekt kommer att ägas och sparas till organisationen, vilket möjliggör organisationsövergripande kontroller, synlighet och rapportering. När den är aktiverad kommer en standardsamling att finnas tillgänglig för varje medlem att lagra objekt. Läs mer om hur du hanterar ", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the credential lifecycle.'" }, "organizationDataOwnershipContentAnchor": { @@ -5897,7 +5909,7 @@ "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, "autoConfirmExtensionOpened": { - "message": "Successfully opened the Bitwarden browser extension. You can now activate the automatic user confirmation setting." + "message": "Lyckades öppna Bitwarden-webbläsartillägg. Du kan nu aktivera inställningen för automatisk bekräftelse av användaren." }, "autoConfirmPolicyEditDescription": { "message": "Nya användare som bjuds in till organisationen kommer automatiskt att bekräftas när en administratörs enhet är upplåst. Innan du aktiverar denna policy, granska och godkänn följande: ", @@ -5907,7 +5919,7 @@ "message": "Potentiell säkerhetsrisk. " }, "autoConfirmAcceptSecurityRiskDescription": { - "message": "Automatic user confirmation could pose a security risk to your organization’s data." + "message": "Automatisk användarbekräftelse kan innebära en säkerhetsrisk för din organisations data." }, "autoConfirmAcceptSecurityRiskLearnMore": { "message": "Läs mer om riskerna", @@ -5917,10 +5929,10 @@ "message": "En organisationspolicy krävs. " }, "autoConfirmSingleOrgRequiredDesc": { - "message": "All members must only belong to this organization to activate this automation." + "message": "Alla medlemmar får bara tillhöra denna organisation för att aktivera denna automatisering." }, "autoConfirmSingleOrgExemption": { - "message": "Single organization policy will extend to all roles. " + "message": "En gemensam organisation policy kommer att utvidgas till alla roller. " }, "autoConfirmNoEmergencyAccess": { "message": "Ingen nödåtkomst. " @@ -5993,7 +6005,7 @@ "message": "Standardmatchning för URI" }, "invalidUriMatchDefaultPolicySetting": { - "message": "Please select a valid URI match detection option.", + "message": "Välj ett giltigt upptäcktsalternativ för URI-matchning.", "description": "Error message displayed when a user attempts to save URI match detection policy settings with an invalid selection." }, "modifiedPolicyId": { @@ -6453,6 +6465,32 @@ "bulkReinviteMessage": { "message": "Bjöd in igen" }, + "bulkReinviteSuccessToast": { + "message": "$COUNT$ användare återinbjudna", + "placeholders": { + "count": { + "content": "$1", + "example": "12" + } + } + }, + "bulkReinviteLimitedSuccessToast": { + "message": "$LIMIT$ av $SELECTEDCOUNT$ användare återinbjudna. $EXCLUDEDCOUNT$ blev inte inbjudna på grund av gränsen på $LIMIT$ inbjudningar.", + "placeholders": { + "limit": { + "content": "$1", + "example": "4,000" + }, + "selectedCount": { + "content": "$2", + "example": "4,005" + }, + "excludedCount": { + "content": "$3", + "example": "5" + } + } + }, "bulkRemovedMessage": { "message": "Tog bort" }, @@ -6653,7 +6691,7 @@ "message": "Automatisk inloggning med SSO" }, "automaticAppLoginWithSSODesc": { - "message": "Extend SSO security and convenience to unmanaged apps. When users launch an app from your identity provider, their login details are automatically filled and submitted, creating a one-click, secure flow from the identity provider to the app." + "message": "Utöka SSO-säkerhet och bekvämlighet till ohanterade appar. När användare startar en app från din identitetsleverantör fylls deras inloggningsuppgifter automatiskt i och skickas in, skapa ett enda klick, säkert flöde från identitetsleverantör till appen." }, "automaticAppLoginIdpHostLabel": { "message": "Identitetsleverantörens värd" @@ -6773,8 +6811,8 @@ "vaultTimeoutRangeError": { "message": "Vault timeout ligger inte inom tillåtet intervall." }, - "disablePersonalVaultExport": { - "message": "Ta bort export av enskilda valv" + "disableExport": { + "message": "Ta bort export" }, "disablePersonalVaultExportDescription": { "message": "Tillåt inte medlemmar att exportera data från sina individuella valv." @@ -7094,9 +7132,6 @@ "invalidVerificationCode": { "message": "Ogiltig verifieringskod" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "Ett huvudlösenord krävs inte längre för medlemmar i följande organisation. Vänligen bekräfta domänen nedan med din organisationsadministratör." - }, "keyConnectorDomain": { "message": "Key Connector-domän" }, @@ -7154,8 +7189,8 @@ "enabledSso": { "message": "SSO aktiverad" }, - "disabledSso": { - "message": "SSO inaktiverad" + "ssoTurnedOff": { + "message": "SSO avstängd" }, "emailMustLoginWithSso": { "message": "$EMAIL$ måste logga in med Single Sign-on", @@ -7408,7 +7443,7 @@ } }, "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { - "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "message": "Endast organisationsvalvet som är associerat med $ORGANIZATION$ kommer att exporteras.", "placeholders": { "organization": { "content": "$1", @@ -7417,7 +7452,7 @@ } }, "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { - "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "message": "Endast organisationsvalvet som är associerat med $ORGANIZATION$ kommer att exporteras. Mina objektsamlingar kommer inte att inkluderas.", "placeholders": { "organization": { "content": "$1", @@ -8936,7 +8971,7 @@ } }, "accessedProjectWithIdentifier": { - "message": "Accessed a project with identifier: $PROJECT_ID$.", + "message": "Kom åt ett projekt med identifierare: $PROJECT_ID$.", "placeholders": { "project_id": { "content": "$1", @@ -8963,7 +8998,7 @@ } }, "nameUnavailableServiceAccountDeleted": { - "message": "Deleted machine account Id: $SERVICE_ACCOUNT_ID$", + "message": "Tog bort maskinkonto med id: $SERVICE_ACCOUNT_ID$", "placeholders": { "service_account_id": { "content": "$1", @@ -8981,7 +9016,7 @@ } }, "addedUserToServiceAccountWithId": { - "message": "Added user: $USER_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "message": "Lade till användare: $USER_ID$ till maskinkonto med identifierare: $SERVICE_ACCOUNT_ID$", "placeholders": { "user_id": { "content": "$1", @@ -8994,7 +9029,7 @@ } }, "removedUserToServiceAccountWithId": { - "message": "Removed user: $USER_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "message": "Tog bort användare: $USER_ID$ från maskinkonto med identifierare: $SERVICE_ACCOUNT_ID$", "placeholders": { "user_id": { "content": "$1", @@ -9448,6 +9483,9 @@ "ssoLoginIsRequired": { "message": "SSO-inloggning krävs" }, + "emailRequiredForSsoLogin": { + "message": "E-postadress krävs för SSO" + }, "selectedRegionFlag": { "message": "Flagga för vald region" }, @@ -11961,8 +11999,8 @@ "familiesMembership": { "message": "Familjemedlemskap" }, - "planDescPremium": { - "message": "Komplett säkerhet online" + "advancedOnlineSecurity": { + "message": "Avancerad säkerhet online" }, "planDescFamiliesV2": { "message": "Premiumsäkerhet för din familj" @@ -12157,13 +12195,13 @@ "message": "Starta gratis testperiod för Families" }, "blockClaimedDomainAccountCreation": { - "message": "Block account creation for claimed domains" + "message": "Blockera kontoskapande för domäner som gjorts anspråk på" }, "blockClaimedDomainAccountCreationDesc": { - "message": "Prevent users from creating accounts outside of your organization using email addresses from claimed domains." + "message": "Förhindra användare från att skapa konton utanför din organisation med hjälp av e-postadresser från domäner som gjorts anspråk på." }, "blockClaimedDomainAccountCreationPrerequisite": { - "message": "A domain must be claimed before activating this policy." + "message": "En domän måste göras anspråk på först innan denna policy aktiveras." }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Konfigurera en upplåsningsmetod för att ändra tidsgränsåtgärden för valvet." @@ -12199,13 +12237,186 @@ } } }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Din organisation använder inte längre huvudlösenord för att logga in på Bitwarden. För att fortsätta, verifiera organisationen och domänen." + }, + "continueWithLogIn": { + "message": "Fortsätt med inloggning" + }, + "doNotContinue": { + "message": "Fortsätt inte" + }, + "domain": { + "message": "Domän" + }, + "keyConnectorDomainTooltip": { + "message": "Denna domän kommer att lagra dina krypteringsnycklar, så se till att du litar på den. Om du inte är säker, kontrollera med din administratör." + }, + "verifyYourOrganization": { + "message": "Verifiera din organisation för att logga in" + }, + "organizationVerified": { + "message": "Organisation verifierad" + }, + "domainVerified": { + "message": "Domän verifierad" + }, + "leaveOrganizationContent": { + "message": "Om du inte verifierar din organisation kommer din åtkomst till organisationen att återkallas." + }, + "leaveNow": { + "message": "Lämna nu" + }, + "verifyYourDomainToLogin": { + "message": "Verifiera din domän för att logga in" + }, + "verifyYourDomainDescription": { + "message": "För att fortsätta med inloggning, verifiera denna domän." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "För att fortsätta logga in, verifiera organisationen och domänen." + }, "confirmNoSelectedCriticalApplicationsTitle": { - "message": "No critical applications are selected" + "message": "Inga kritiska applikationer är valda" }, "confirmNoSelectedCriticalApplicationsDesc": { "message": "Är du säker på att du vill fortsätta?" }, "userVerificationFailed": { "message": "Verifiering av användare misslyckades." + }, + "recoveryDeleteCiphersTitle": { + "message": "Ta bort oåterkalleliga valvobjekt" + }, + "recoveryDeleteCiphersDesc": { + "message": "Några av dina valvobjekt kunde inte återställas. Vill du ta bort dessa oåterkalleliga objekt från ditt valv?" + }, + "recoveryDeleteFoldersTitle": { + "message": "Ta bort oåterkalleliga mappar" + }, + "recoveryDeleteFoldersDesc": { + "message": "Några av dina mappar kunde inte återställas. Vill du ta bort dessa oåterkalleliga mappar från ditt valv?" + }, + "recoveryReplacePrivateKeyTitle": { + "message": "Ersätt krypteringsnyckel" + }, + "recoveryReplacePrivateKeyDesc": { + "message": "Ditt publika nyckelpar för kryptering kunde inte återställas. Vill du ersätta din krypteringsnyckel med ett nytt nyckelpar? Detta kommer att kräva att du konfigurerar befintlig nödåtkomst och organisationsmedlemskap igen." + }, + "recoveryStepSyncTitle": { + "message": "Synkroniserar data" + }, + "recoveryStepPrivateKeyTitle": { + "message": "Verifierar integritet för krypteringsnyckeln" + }, + "recoveryStepUserInfoTitle": { + "message": "Verifierar användarinformation" + }, + "recoveryStepCipherTitle": { + "message": "Verifierar integritet för valvobjekt" + }, + "recoveryStepFoldersTitle": { + "message": "Verifierar mappintegritet" + }, + "dataRecoveryTitle": { + "message": "Dataåterställning och diagnostik" + }, + "dataRecoveryDescription": { + "message": "Använd verktyget för dataåterställning för att diagnostisera och reparera problem med ditt konto. Efter att ha kört diagnostik har du möjlighet att spara diagnostikloggar för support och möjlighet att reparera eventuella upptäckta problem." + }, + "runDiagnostics": { + "message": "Kör diagnostik" + }, + "repairIssues": { + "message": "Reparera problem" + }, + "saveDiagnosticLogs": { + "message": "Spara diagnostikloggar" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "Den här inställningen hanteras av din organisation." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Din organisation har ställt in maximal sessionstidsgräns till $HOURS$ timmar och $MINUTES$ minut(er).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Din organisation har ställt in tidsgränsen för standardsession till Vid webbläsaruppdatering." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximal tidsgräns får inte överstiga $HOURS$ timmar och $MINUTES$ minut(er)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "Vid webbläsaruppdatering" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Ställ in en upplåsningsmetod för att ändra din tidsgränsåtgärd" + }, + "leaveConfirmationDialogTitle": { + "message": "Är du säker på att du vill lämna?" + }, + "leaveConfirmationDialogContentOne": { + "message": "Genom att avböja kommer dina personliga objekt att stanna på ditt konto, men du kommer att förlora åtkomst till delade objekt och organisationsfunktioner." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Kontakta administratören för att återfå åtkomst." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Lämna $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "Hur hanterar jag mitt valv?" + }, + "transferItemsToOrganizationTitle": { + "message": "Överför objekt till $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ kräver att alla objekt ägs av organisationen för säkerhet och efterlevnad. Klicka på godkänn för att överföra ägarskapet för dina objekt.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Godkänn överföring" + }, + "declineAndLeave": { + "message": "Avböj och lämna" + }, + "whyAmISeeingThis": { + "message": "Varför ser jag det här?" } } diff --git a/apps/web/src/locales/ta/messages.json b/apps/web/src/locales/ta/messages.json index 98970962d69..5f2a1afba42 100644 --- a/apps/web/src/locales/ta/messages.json +++ b/apps/web/src/locales/ta/messages.json @@ -5169,9 +5169,21 @@ "message": "சரிசெய்", "description": "This is a verb. ex. 'Fix The Car'" }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "oldAttachmentsNeedFixDesc": { "message": "உங்கள் வால்ட்டில் பழைய கோப்பு இணைப்புகள் உள்ளன, அவை உங்கள் கணக்கின் என்க்ரிப்ஷன் கீயைச் சுழற்றுவதற்கு முன் சரிசெய்யப்பட வேண்டும்." }, + "itemsTransferred": { + "message": "Items transferred" + }, "yourAccountsFingerprint": { "message": "உங்கள் கணக்கின் ஃபிங்கர்பிரிண்ட் ஃபிரேஸ்", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -6453,6 +6465,32 @@ "bulkReinviteMessage": { "message": "வெற்றிகரமாக மீண்டும் அழைக்கப்பட்டது" }, + "bulkReinviteSuccessToast": { + "message": "$COUNT$ users re-invited", + "placeholders": { + "count": { + "content": "$1", + "example": "12" + } + } + }, + "bulkReinviteLimitedSuccessToast": { + "message": "$LIMIT$ of $SELECTEDCOUNT$ users re-invited. $EXCLUDEDCOUNT$ were not invited due to the $LIMIT$ invite limit.", + "placeholders": { + "limit": { + "content": "$1", + "example": "4,000" + }, + "selectedCount": { + "content": "$2", + "example": "4,005" + }, + "excludedCount": { + "content": "$3", + "example": "5" + } + } + }, "bulkRemovedMessage": { "message": "வெற்றிகரமாக அகற்றப்பட்டது" }, @@ -6773,8 +6811,8 @@ "vaultTimeoutRangeError": { "message": "பெட்டக காலக்கெடு அனுமதிக்கப்பட்ட வரம்பிற்குள் இல்லை." }, - "disablePersonalVaultExport": { - "message": "தனிப்பட்ட பெட்டக ஏற்றுமதியை அகற்று" + "disableExport": { + "message": "Remove export" }, "disablePersonalVaultExportDescription": { "message": "உறுப்பினர்கள் தங்கள் தனிப்பட்ட பெட்டகத்திலிருந்து தரவை ஏற்றுமதி செய்ய அனுமதிக்க வேண்டாம்." @@ -7094,9 +7132,6 @@ "invalidVerificationCode": { "message": "தவறான சரிபார்ப்புக் குறியீடு" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "பின்வரும் நிறுவனத்தின் உறுப்பினர்களுக்கு மாஸ்டர் கடவுச்சொல் இனி தேவையில்லை. தயவுசெய்து கீழே உள்ள டொமைனை உங்கள் நிறுவன நிர்வாகியுடன் உறுதிப்படுத்தவும்." - }, "keyConnectorDomain": { "message": "கீ கனெக்டர் டொமைன்" }, @@ -7154,8 +7189,8 @@ "enabledSso": { "message": "SSO இயக்கப்பட்டது" }, - "disabledSso": { - "message": "SSO இயக்கப்பட்டது" + "ssoTurnedOff": { + "message": "SSO turned off" }, "emailMustLoginWithSso": { "message": "$EMAIL$ must login with Single Sign-on", @@ -9448,6 +9483,9 @@ "ssoLoginIsRequired": { "message": "SSO உள்நுழைவு தேவை" }, + "emailRequiredForSsoLogin": { + "message": "Email is required for SSO" + }, "selectedRegionFlag": { "message": "தேர்ந்தெடுக்கப்பட்ட பிராந்திய கொடி" }, @@ -11961,8 +11999,8 @@ "familiesMembership": { "message": "Families membership" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "planDescFamiliesV2": { "message": "Premium security for your family" @@ -12199,6 +12237,45 @@ } } }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "confirmNoSelectedCriticalApplicationsTitle": { "message": "No critical applications are selected" }, @@ -12207,5 +12284,139 @@ }, "userVerificationFailed": { "message": "User verification failed." + }, + "recoveryDeleteCiphersTitle": { + "message": "Delete unrecoverable vault items" + }, + "recoveryDeleteCiphersDesc": { + "message": "Some of your vault items could not be recovered. Do you want to delete these unrecoverable items from your vault?" + }, + "recoveryDeleteFoldersTitle": { + "message": "Delete unrecoverable folders" + }, + "recoveryDeleteFoldersDesc": { + "message": "Some of your folders could not be recovered. Do you want to delete these unrecoverable folders from your vault?" + }, + "recoveryReplacePrivateKeyTitle": { + "message": "Replace encryption key" + }, + "recoveryReplacePrivateKeyDesc": { + "message": "Your public-key encryption key pair could not be recovered. Do you want to replace your encryption key with a new key pair? This will require you to set up existing emergency-access and organization memberships again." + }, + "recoveryStepSyncTitle": { + "message": "Synchronizing data" + }, + "recoveryStepPrivateKeyTitle": { + "message": "Verifying encryption key integrity" + }, + "recoveryStepUserInfoTitle": { + "message": "Verifying user information" + }, + "recoveryStepCipherTitle": { + "message": "Verifying vault item integrity" + }, + "recoveryStepFoldersTitle": { + "message": "Verifying folder integrity" + }, + "dataRecoveryTitle": { + "message": "Data Recovery and Diagnostics" + }, + "dataRecoveryDescription": { + "message": "Use the data recovery tool to diagnose and repair issues with your account. After running diagnostics you have the option to save diagnostic logs for support and the option to repair any detected issues." + }, + "runDiagnostics": { + "message": "Run Diagnostics" + }, + "repairIssues": { + "message": "Repair Issues" + }, + "saveDiagnosticLogs": { + "message": "Save Diagnostic Logs" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On browser refresh." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On browser refresh" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/web/src/locales/te/messages.json b/apps/web/src/locales/te/messages.json index 608215f8155..fd419fe7397 100644 --- a/apps/web/src/locales/te/messages.json +++ b/apps/web/src/locales/te/messages.json @@ -5169,9 +5169,21 @@ "message": "Fix", "description": "This is a verb. ex. 'Fix The Car'" }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "oldAttachmentsNeedFixDesc": { "message": "There are old file attachments in your vault that need to be fixed before you can rotate your account's encryption key." }, + "itemsTransferred": { + "message": "Items transferred" + }, "yourAccountsFingerprint": { "message": "Your account's fingerprint phrase", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -6453,6 +6465,32 @@ "bulkReinviteMessage": { "message": "Reinvited successfully" }, + "bulkReinviteSuccessToast": { + "message": "$COUNT$ users re-invited", + "placeholders": { + "count": { + "content": "$1", + "example": "12" + } + } + }, + "bulkReinviteLimitedSuccessToast": { + "message": "$LIMIT$ of $SELECTEDCOUNT$ users re-invited. $EXCLUDEDCOUNT$ were not invited due to the $LIMIT$ invite limit.", + "placeholders": { + "limit": { + "content": "$1", + "example": "4,000" + }, + "selectedCount": { + "content": "$2", + "example": "4,005" + }, + "excludedCount": { + "content": "$3", + "example": "5" + } + } + }, "bulkRemovedMessage": { "message": "Removed successfully" }, @@ -6773,8 +6811,8 @@ "vaultTimeoutRangeError": { "message": "Vault timeout is not within allowed range." }, - "disablePersonalVaultExport": { - "message": "Remove individual vault export" + "disableExport": { + "message": "Remove export" }, "disablePersonalVaultExportDescription": { "message": "Do not allow members to export data from their individual vault." @@ -7094,9 +7132,6 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "keyConnectorDomain": { "message": "Key Connector domain" }, @@ -7154,8 +7189,8 @@ "enabledSso": { "message": "SSO turned on" }, - "disabledSso": { - "message": "SSO turned on" + "ssoTurnedOff": { + "message": "SSO turned off" }, "emailMustLoginWithSso": { "message": "$EMAIL$ must login with Single Sign-on", @@ -9448,6 +9483,9 @@ "ssoLoginIsRequired": { "message": "SSO login is required" }, + "emailRequiredForSsoLogin": { + "message": "Email is required for SSO" + }, "selectedRegionFlag": { "message": "Selected region flag" }, @@ -11961,8 +11999,8 @@ "familiesMembership": { "message": "Families membership" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "planDescFamiliesV2": { "message": "Premium security for your family" @@ -12199,6 +12237,45 @@ } } }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "confirmNoSelectedCriticalApplicationsTitle": { "message": "No critical applications are selected" }, @@ -12207,5 +12284,139 @@ }, "userVerificationFailed": { "message": "User verification failed." + }, + "recoveryDeleteCiphersTitle": { + "message": "Delete unrecoverable vault items" + }, + "recoveryDeleteCiphersDesc": { + "message": "Some of your vault items could not be recovered. Do you want to delete these unrecoverable items from your vault?" + }, + "recoveryDeleteFoldersTitle": { + "message": "Delete unrecoverable folders" + }, + "recoveryDeleteFoldersDesc": { + "message": "Some of your folders could not be recovered. Do you want to delete these unrecoverable folders from your vault?" + }, + "recoveryReplacePrivateKeyTitle": { + "message": "Replace encryption key" + }, + "recoveryReplacePrivateKeyDesc": { + "message": "Your public-key encryption key pair could not be recovered. Do you want to replace your encryption key with a new key pair? This will require you to set up existing emergency-access and organization memberships again." + }, + "recoveryStepSyncTitle": { + "message": "Synchronizing data" + }, + "recoveryStepPrivateKeyTitle": { + "message": "Verifying encryption key integrity" + }, + "recoveryStepUserInfoTitle": { + "message": "Verifying user information" + }, + "recoveryStepCipherTitle": { + "message": "Verifying vault item integrity" + }, + "recoveryStepFoldersTitle": { + "message": "Verifying folder integrity" + }, + "dataRecoveryTitle": { + "message": "Data Recovery and Diagnostics" + }, + "dataRecoveryDescription": { + "message": "Use the data recovery tool to diagnose and repair issues with your account. After running diagnostics you have the option to save diagnostic logs for support and the option to repair any detected issues." + }, + "runDiagnostics": { + "message": "Run Diagnostics" + }, + "repairIssues": { + "message": "Repair Issues" + }, + "saveDiagnosticLogs": { + "message": "Save Diagnostic Logs" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On browser refresh." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On browser refresh" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/web/src/locales/th/messages.json b/apps/web/src/locales/th/messages.json index c20cc2e5d8c..362db4faea2 100644 --- a/apps/web/src/locales/th/messages.json +++ b/apps/web/src/locales/th/messages.json @@ -5169,9 +5169,21 @@ "message": "Fix", "description": "This is a verb. ex. 'Fix The Car'" }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "oldAttachmentsNeedFixDesc": { "message": "There are old file attachments in your vault that need to be fixed before you can rotate your account's encryption key." }, + "itemsTransferred": { + "message": "Items transferred" + }, "yourAccountsFingerprint": { "message": "Your account's fingerprint phrase", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -6453,6 +6465,32 @@ "bulkReinviteMessage": { "message": "Reinvited successfully" }, + "bulkReinviteSuccessToast": { + "message": "$COUNT$ users re-invited", + "placeholders": { + "count": { + "content": "$1", + "example": "12" + } + } + }, + "bulkReinviteLimitedSuccessToast": { + "message": "$LIMIT$ of $SELECTEDCOUNT$ users re-invited. $EXCLUDEDCOUNT$ were not invited due to the $LIMIT$ invite limit.", + "placeholders": { + "limit": { + "content": "$1", + "example": "4,000" + }, + "selectedCount": { + "content": "$2", + "example": "4,005" + }, + "excludedCount": { + "content": "$3", + "example": "5" + } + } + }, "bulkRemovedMessage": { "message": "Removed successfully" }, @@ -6773,8 +6811,8 @@ "vaultTimeoutRangeError": { "message": "Vault timeout is not within allowed range." }, - "disablePersonalVaultExport": { - "message": "Remove individual vault export" + "disableExport": { + "message": "Remove export" }, "disablePersonalVaultExportDescription": { "message": "Do not allow members to export data from their individual vault." @@ -7094,9 +7132,6 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "keyConnectorDomain": { "message": "Key Connector domain" }, @@ -7154,8 +7189,8 @@ "enabledSso": { "message": "SSO turned on" }, - "disabledSso": { - "message": "SSO turned on" + "ssoTurnedOff": { + "message": "SSO turned off" }, "emailMustLoginWithSso": { "message": "$EMAIL$ must login with Single Sign-on", @@ -9448,6 +9483,9 @@ "ssoLoginIsRequired": { "message": "SSO login is required" }, + "emailRequiredForSsoLogin": { + "message": "Email is required for SSO" + }, "selectedRegionFlag": { "message": "Selected region flag" }, @@ -11961,8 +11999,8 @@ "familiesMembership": { "message": "Families membership" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "planDescFamiliesV2": { "message": "Premium security for your family" @@ -12199,6 +12237,45 @@ } } }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "confirmNoSelectedCriticalApplicationsTitle": { "message": "No critical applications are selected" }, @@ -12207,5 +12284,139 @@ }, "userVerificationFailed": { "message": "User verification failed." + }, + "recoveryDeleteCiphersTitle": { + "message": "Delete unrecoverable vault items" + }, + "recoveryDeleteCiphersDesc": { + "message": "Some of your vault items could not be recovered. Do you want to delete these unrecoverable items from your vault?" + }, + "recoveryDeleteFoldersTitle": { + "message": "Delete unrecoverable folders" + }, + "recoveryDeleteFoldersDesc": { + "message": "Some of your folders could not be recovered. Do you want to delete these unrecoverable folders from your vault?" + }, + "recoveryReplacePrivateKeyTitle": { + "message": "Replace encryption key" + }, + "recoveryReplacePrivateKeyDesc": { + "message": "Your public-key encryption key pair could not be recovered. Do you want to replace your encryption key with a new key pair? This will require you to set up existing emergency-access and organization memberships again." + }, + "recoveryStepSyncTitle": { + "message": "Synchronizing data" + }, + "recoveryStepPrivateKeyTitle": { + "message": "Verifying encryption key integrity" + }, + "recoveryStepUserInfoTitle": { + "message": "Verifying user information" + }, + "recoveryStepCipherTitle": { + "message": "Verifying vault item integrity" + }, + "recoveryStepFoldersTitle": { + "message": "Verifying folder integrity" + }, + "dataRecoveryTitle": { + "message": "Data Recovery and Diagnostics" + }, + "dataRecoveryDescription": { + "message": "Use the data recovery tool to diagnose and repair issues with your account. After running diagnostics you have the option to save diagnostic logs for support and the option to repair any detected issues." + }, + "runDiagnostics": { + "message": "Run Diagnostics" + }, + "repairIssues": { + "message": "Repair Issues" + }, + "saveDiagnosticLogs": { + "message": "Save Diagnostic Logs" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On browser refresh." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On browser refresh" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/web/src/locales/tr/messages.json b/apps/web/src/locales/tr/messages.json index 829c0ca17dc..dbb8eb2d299 100644 --- a/apps/web/src/locales/tr/messages.json +++ b/apps/web/src/locales/tr/messages.json @@ -39,7 +39,7 @@ } }, "noReportRan": { - "message": "You have not created a report yet" + "message": "Henüz bir rapor oluşturmadınız" }, "notifiedMembers": { "message": "Bildirilen üyeler" @@ -75,7 +75,7 @@ } }, "securityTasksCompleted": { - "message": "$COUNT$ out of $TOTAL$ security tasks completed", + "message": "$TOTAL$ güvenlik görevinden $COUNT$ tanesi tamamlandı", "placeholders": { "count": { "content": "$1", @@ -88,28 +88,28 @@ } }, "passwordChangeProgress": { - "message": "Password change progress" + "message": "Parola değiştirme ilerlemesi" }, "assignMembersTasksToMonitorProgress": { - "message": "Assign members tasks to monitor progress" + "message": "İlerlemeyi izlemek için üyelere görevler atayın" }, "onceYouReviewApplications": { - "message": "Once you review applications and mark them as critical, assign tasks to your members to change their passwords." + "message": "Uygulamaları gözden geçirip kritik olarak işaretledikten sonra, parolalarını değiştirmeleri için üyelerinize görevler atayın." }, "sendReminders": { - "message": "Send reminders" + "message": "Hatırlatıcı gönder" }, "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { - "message": "Once you mark applications critical, they will display here." + "message": "Uygulamaları kritik olarak işaretlediğinizde burada görüntülenecekler." }, "viewAtRiskMembers": { - "message": "View at-risk members" + "message": "Risk altındaki üyeleri görüntüle" }, "viewAtRiskApplications": { - "message": "View at-risk applications" + "message": "Risk altındaki uygulamaları görüntüle" }, "criticalApplicationsAreAtRisk": { - "message": "$COUNT$ out of $TOTAL$ critical applications are at-risk due to at-risk passwords", + "message": "Riskli parolalar nedeniyle $TOTAL$ kritik uygulamadan $COUNT$ tanesi risk altında", "placeholders": { "count": { "content": "$1", @@ -131,7 +131,7 @@ } }, "criticalApplicationsMarked": { - "message": "critical applications marked" + "message": "işaretlenmiş kritik uygulamalar" }, "countOfCriticalApplications": { "message": "$COUNT$ kritik uygulama", @@ -143,7 +143,7 @@ } }, "countOfApplicationsAtRisk": { - "message": "$COUNT$ applications at-risk", + "message": "$COUNT$ uygulama risk altında", "placeholders": { "count": { "content": "$1", @@ -152,7 +152,7 @@ } }, "countOfAtRiskPasswords": { - "message": "$COUNT$ passwords at-risk", + "message": "$COUNT$ parola risk altında", "placeholders": { "count": { "content": "$1", @@ -179,34 +179,34 @@ } }, "noDataInOrgTitle": { - "message": "No data found" + "message": "Veri bulunamadı" }, "noDataInOrgDescription": { - "message": "Import your organization's login data to get started with Access Intelligence. Once you do that, you'll be able to:" + "message": "Access Intelligence’i kullanmaya başlamak için kuruluşunuzun hesap verilerini içe aktarın. Bunu yaptıktan sonra şunları yapabileceksiniz:" }, "feature1Title": { - "message": "Mark applications as critical" + "message": "Uygulamaları kritik olarak işaretle" }, "feature1Description": { - "message": "This will help you remove risks to your most important applications first." + "message": "Bu, öncelikle en önemli uygulamalarınızdaki riskleri ortadan kaldırmanıza yardımcı olur." }, "feature2Title": { - "message": "Help members improve their security" + "message": "Üyelerin güvenliklerini iyileştirmelerine yardımcı olun" }, "feature2Description": { - "message": "Assign at-risk members guided security tasks to update credentials." + "message": "Kimlik bilgilerini güncellemeleri için risk altındaki üyelere rehberli güvenlik görevleri atayın." }, "feature3Title": { - "message": "Monitor progress" + "message": "İlerlemeyi izle" }, "feature3Description": { - "message": "Track changes over time to show security improvements." + "message": "Zaman içindeki değişiklikleri takip ederek güvenlikteki iyileşmeleri gösterin." }, "noReportsRunTitle": { - "message": "Generate report" + "message": "Rapor oluştur" }, "noReportsRunDescription": { - "message": "You’re ready to start generating reports. Once you generate, you’ll be able to:" + "message": "Artık rapor oluşturmaya hazırsınız. Rapor oluşturduğunuzda şunları yapabileceksiniz:" }, "noCriticalApplicationsTitle": { "message": "Hiçbir uygulamayı kritik olarak işaretlemediniz" @@ -224,19 +224,19 @@ "message": "Kritik olarak işaretle" }, "applicationsSelected": { - "message": "applications selected" + "message": "seçili uygulamalar" }, "selectApplication": { - "message": "Select application" + "message": "Uygulama seçin" }, "unselectApplication": { - "message": "Unselect application" + "message": "Uygulama seçimini kaldır" }, "applicationsMarkedAsCriticalSuccess": { "message": "Kritik olarak işaretlenmiş uygulamalar" }, "criticalApplicationsMarkedSuccess": { - "message": "$COUNT$ applications marked as critical", + "message": "$COUNT$ uygulama kritik olarak işaretlendi", "placeholders": { "count": { "content": "$1", @@ -245,7 +245,7 @@ } }, "applicationsMarkedAsCriticalFail": { - "message": "Failed to mark applications as critical" + "message": "Uygulamaların kritik olarak işaretlenmesi başarısız oldu" }, "application": { "message": "Uygulama" @@ -266,13 +266,13 @@ "message": "Riskli üyeler" }, "membersWithAccessToAtRiskItemsForCriticalApplications": { - "message": "These members have access to vulnerable items for critical applications." + "message": "Bu üyelerin kritik uygulamalara ait güvenlik zafiyeti olan kayıtlara erişimi var." }, "membersWithAtRiskPasswords": { - "message": "Members with at-risk passwords" + "message": "Riskli parolalara sahip üyeler" }, "membersWillReceiveSecurityTask": { - "message": "Members of your organization will be assigned a task to change vulnerable passwords. They’ll receive a notification within their Bitwarden browser extension." + "message": "Kuruluşunuzdaki üyelere, güvenlik zafiyeti olan parolaları değiştirmeleri için bir görev atanacaktır. Bildirimi Bitwarden tarayıcı uzantıları içinde alacaklar." }, "membersAtRiskCount": { "message": "$COUNT$ üye risk altında", @@ -302,7 +302,7 @@ } }, "atRiskMemberDescription": { - "message": "These members are logging into critical applications with weak, exposed, or reused passwords." + "message": "Bu üyeler kritik uygulamalara zayıf, ele geçirilmiş veya yeniden kullanılan parolalarla giriş yapıyor." }, "atRiskMembersDescriptionNone": { "message": "Zayıf, ele geçirilmiş veya tekrar kullanılan parolayla uygulamalara giriş yapan üye yok." @@ -341,13 +341,13 @@ "message": "Toplam uygulama" }, "applicationsNeedingReview": { - "message": "Applications needing review" + "message": "Gözden geçirilmesi gereken uygulamalar" }, "newApplicationsCardTitle": { - "message": "Review new applications" + "message": "Yeni uygulamaları gözden geçir" }, "newApplicationsWithCount": { - "message": "$COUNT$ new applications", + "message": "$COUNT$ yeni uygulama", "placeholders": { "count": { "content": "$1", @@ -356,19 +356,19 @@ } }, "newApplicationsDescription": { - "message": "Review new applications to mark as critical and keep your organization secure" + "message": "Kuruluşunuzun güvenliğini sağlamak için yeni uygulamaları gözden geçirip kritik olarak işaretleyin" }, "reviewNow": { - "message": "Review now" + "message": "Şimdi gözden geçir" }, "allCaughtUp": { "message": "Şimdilik bu kadar!" }, "noNewApplicationsToReviewAtThisTime": { - "message": "No new applications to review at this time" + "message": "Şu anda gözden geçirilecek yeni uygulama yok" }, "organizationHasItemsSavedForApplications": { - "message": "Your organization has items saved for $COUNT$ applications", + "message": "Kuruluşunuzun $COUNT$ uygulama için kaydedilmiş kayıtları var", "placeholders": { "count": { "content": "$1", @@ -377,37 +377,37 @@ } }, "reviewApplicationsToSecureItems": { - "message": "Review applications to secure the items most critical to your organization's security" + "message": "Kuruluşunuzun güvenliği açısından en kritik kayıtları güvence altına almak için uygulamaları gözden geçirin" }, "reviewApplications": { - "message": "Review applications" + "message": "Uygulamaları gözden geçir" }, "prioritizeCriticalApplications": { - "message": "Prioritize critical applications" + "message": "Kritik uygulamalara öncelik ver" }, "selectCriticalAppsDescription": { - "message": "Select which applications are most critical to your organization. Then, you’ll be able to assign security tasks to members to remove risks." + "message": "Kuruluşunuz için en kritik uygulamaları seçin. Ardından riskleri ortadan kaldırmak için üyelere güvenlik görevleri atayabilirsiniz." }, "reviewNewApplications": { - "message": "Review new applications" + "message": "Yeni uygulamaları gözden geçir" }, "reviewNewAppsDescription": { - "message": "Review new applications with vulnerable items and mark those you’d like to monitor closely as critical. Then, you’ll be able to assign security tasks to members to remove risks." + "message": "Güvenlik zafiyeti olan kayıtlara sahip yeni uygulamaları gözden geçirip yakından izlemek istediklerinizi kritik olarak işaretleyin. Ardından riskleri ortadan kaldırmak için üyelere güvenlik görevleri atayabilirsiniz." }, "clickIconToMarkAppAsCritical": { - "message": "Click the star icon to mark an app as critical" + "message": "Bir uygulamayı kritik olarak işaretlemek için yıldız simgesine tıklayın" }, "markAsCriticalPlaceholder": { - "message": "Mark as critical functionality will be implemented in a future update" + "message": "Kritik olarak işaretle işlevselliği gelecekteki bir güncellemede uygulanacaktır" }, "applicationReviewSaved": { - "message": "Application review saved" + "message": "Uygulama incelemesi kaydedildi" }, "newApplicationsReviewed": { - "message": "New applications reviewed" + "message": "Yeni uygulamalar incelendi" }, "errorSavingReviewStatus": { - "message": "Error saving review status" + "message": "İnceleme durumu kaydedilirken hata oluştu" }, "pleaseTryAgain": { "message": "Lütfen yeniden deneyin" @@ -1390,7 +1390,7 @@ "message": "Bu pencereyi açık tutun ve tarayıcınızdan gelen talimatları uygulayın." }, "passkeyAuthenticationFailed": { - "message": "Passkey authentication failed. Please try again." + "message": "Geçiş anahtarı ile kimlik doğrulama başarısız oldu. Lütfen tekrar deneyin." }, "useADifferentLogInMethod": { "message": "Başka bir giriş yöntemi kullan" @@ -1720,13 +1720,13 @@ "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" + "message": "Henüz hiçbir kaydı favorilere eklemediniz" }, "emptyFavoritesDesc": { - "message": "Add frequently used items to favorites for quick access." + "message": "Sık kullandığınız kayıtları hızlı erişim için favorilere ekleyin." }, "noSearchResults": { - "message": "No search results returned" + "message": "Arama sonucu bulunamadı" }, "clearFiltersOrTryAnother": { "message": "Filtreleri temizleyin veya başka bir arama yapmayı deneyin" @@ -3044,7 +3044,7 @@ "message": "Lütfen hesabınızda bu ödemeyi yapabilecek kadar kredi olduğundan emin olun. Eğer hesabınızda yeteri kadar kredi yoksa, aradaki fark varsayılan ödeme yöntemizinden karşılanacaktır. Faturalandırma sayfasından hesabınıza kredi ekleyebilirsiniz." }, "notEnoughAccountCredit": { - "message": "You do not have enough account credit for this purchase. You can add credit to your account from the Billing page." + "message": "Bu satın alma için yeterli hesap krediniz yok. Faturalandırma sayfasından hesabınıza kredi ekleyebilirsiniz." }, "creditAppliedDesc": { "message": "Hesabınızdaki krediyi satın alımlarda kullanabilirsiniz. Mevcut krediniz bu hesap için oluşturulan faturalardan otomatik olarak düşülecektir." @@ -3134,13 +3134,13 @@ } }, "premiumSubscriptionEnded": { - "message": "Your Premium subscription ended" + "message": "Premium aboneliğiniz sona erdi" }, "premiumSubscriptionEndedDesc": { - "message": "To regain access to your archive, restart your Premium subscription. If you edit details for an archived item before restarting, it'll be moved back into your vault." + "message": "Arşivinize yeniden erişim kazanmak için Premium aboneliğinizi yeniden başlatın. Yeniden başlatmadan önce arşivlenmiş bir kaydın ayrıntılarını düzenlerseniz, kayıt tekrar kasanıza taşınır." }, "restartPremium": { - "message": "Restart Premium" + "message": "Premium’u yeniden başlat" }, "additionalStorageGb": { "message": "Ek depolama alanı (GB)" @@ -4479,34 +4479,34 @@ "message": "Tarayıcıyı güncelle" }, "generatingYourAccessIntelligence": { - "message": "Generating your Access Intelligence..." + "message": "Access Intelligence’ınız oluşturuluyor..." }, "fetchingMemberData": { - "message": "Fetching member data..." + "message": "Üye verileri getiriliyor..." }, "analyzingPasswordHealth": { - "message": "Analyzing password health..." + "message": "Parola sağlığı analiz ediliyor..." }, "calculatingRiskScores": { - "message": "Calculating risk scores..." + "message": "Risk puanları hesaplanıyor..." }, "generatingReportData": { - "message": "Generating report data..." + "message": "Rapor verileri oluşturuluyor..." }, "savingReport": { - "message": "Saving report..." + "message": "Rapor kaydediliyor..." }, "compilingInsights": { - "message": "Compiling insights..." + "message": "İçgörüler derleniyor..." }, "loadingProgress": { - "message": "Loading progress" + "message": "Yükleme ilerlemesi" }, "thisMightTakeFewMinutes": { - "message": "This might take a few minutes." + "message": "Bu işlem birkaç dakika sürebilir." }, "riskInsightsRunReport": { - "message": "Run report" + "message": "Raporu çalıştır" }, "updateBrowserDesc": { "message": "Desteklenmeyen bir web tarayıcısı kullanıyorsunuz. Web kasası düzgün çalışmayabilir." @@ -4591,7 +4591,7 @@ "message": "Davet kabul edildi" }, "invitationAcceptedDesc": { - "message": "Successfully accepted your invitation." + "message": "Davetiniz başarıyla kabul edildi." }, "inviteInitAcceptedDesc": { "message": "Artık bu kuruluşa erişebilirsiniz." @@ -4627,19 +4627,19 @@ "message": "Daha fazla bilgi al" }, "migrationsFailed": { - "message": "An error occurred updating the encryption settings." + "message": "Şifreleme ayarları güncellenirken bir hata oluştu." }, "updateEncryptionSettingsTitle": { - "message": "Update your encryption settings" + "message": "Şifreleme ayarlarınızı güncelleyin" }, "updateEncryptionSettingsDesc": { - "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + "message": "Önerilen yeni şifreleme ayarları hesap güvenliğinizi artıracaktır. Şimdi güncellemek için ana parolanızı girin." }, "confirmIdentityToContinue": { - "message": "Confirm your identity to continue" + "message": "Devam etmek için kimliğinizi doğrulayın" }, "enterYourMasterPassword": { - "message": "Enter your master password" + "message": "Ana parolanızı girin" }, "updateSettings": { "message": "Ayarları güncelle" @@ -5169,9 +5169,21 @@ "message": "Düzelt", "description": "This is a verb. ex. 'Fix The Car'" }, + "fixEncryption": { + "message": "Şifrelemeyi düzelt" + }, + "fixEncryptionTooltip": { + "message": "Bu dosya eski bir şifreleme yöntemi kullanıyor." + }, + "attachmentUpdated": { + "message": "Ek güncellendi" + }, "oldAttachmentsNeedFixDesc": { "message": "Hesabınızın şifreleme anahtarını yenilemeden önce kasanızdaki eski dosya eklerini düzeltilmeniz gerekiyor." }, + "itemsTransferred": { + "message": "Kayıtlar aktarıldı" + }, "yourAccountsFingerprint": { "message": "Hesabınızın parmak izi ifadesi", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -5484,11 +5496,11 @@ "message": "SSO tanımlayıcı" }, "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": "Üyelerinizin SSO ile hesaplarına erişebilmeleri için bu kimliği onlara verin. Bir sahiplenilmiş etki alanı yapılandırılmışsa, SSO sırasında üyeler bu tanımlayıcıyı girmeyi atlayabilir.", "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": "Daha fazla bilgi alın", "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": { @@ -5846,7 +5858,7 @@ "description": "This is the policy description shown in the policy list." }, "organizationDataOwnershipDescContent": { - "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the ", + "message": "Tüm kayıtların mülkiyeti kuruluşa ait olacak ve kayıtlar kuruluşa kaydedilerek kuruluş genelinde denetim, görünürlük ve raporlama sağlanacaktır. Bu özellik açıldığında, her üyenin kayıtlarını kaydetmesi için varsayılan bir koleksiyon kullanıma sunulur. Yönetimi hakkında daha fazla bilgi edinin ", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the credential lifecycle.'" }, "organizationDataOwnershipContentAnchor": { @@ -5873,17 +5885,17 @@ "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Learn more about the credential lifecycle.'" }, "availableNow": { - "message": "Available now" + "message": "Şimdi kullanılabilir" }, "autoConfirm": { - "message": "Automatic confirmation of new users" + "message": "Yeni kullanıcıların otomatik onayı" }, "autoConfirmDescription": { - "message": "New users invited to the organization will be automatically confirmed when an admin’s device is unlocked.", + "message": "Kuruluşa davet edilen yeni kullanıcılar, bir yöneticinin cihazı kilitli değilken otomatik olarak onaylanacaktır.", "description": "This is the description of the policy as it appears in the 'Policies' page" }, "howToTurnOnAutoConfirm": { - "message": "How to turn on automatic user confirmation" + "message": "Otomatik kullanıcı onayı nasıl açılır" }, "autoConfirmExtension1": { "message": "Bitwarden uzantınızı açın" @@ -5897,39 +5909,39 @@ "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, "autoConfirmExtensionOpened": { - "message": "Successfully opened the Bitwarden browser extension. You can now activate the automatic user confirmation setting." + "message": "Bitwarden tarayıcı uzantısı başarıyla açıldı. Artık otomatik kullanıcı onayı ayarını etkinleştirebilirsiniz." }, "autoConfirmPolicyEditDescription": { - "message": "New users invited to the organization will be automatically confirmed when an admin’s device is unlocked. Before turning on this policy, please review and agree to the following: ", + "message": "Kuruluşa davet edilen yeni kullanıcılar, bir yöneticinin cihazı kilitli değilken otomatik olarak onaylanacaktır. Bu ilkeyi etkinleştirmeden önce lütfen aşağıdakileri inceleyip kabul edin: ", "description": "This is the description of the policy as it appears inside the policy edit dialog" }, "autoConfirmAcceptSecurityRiskTitle": { - "message": "Potential security risk. " + "message": "Olası güvenlik riski. " }, "autoConfirmAcceptSecurityRiskDescription": { - "message": "Automatic user confirmation could pose a security risk to your organization’s data." + "message": "Otomatik kullanıcı onayı, kuruluşunuzun verileri için bir güvenlik riski oluşturabilir." }, "autoConfirmAcceptSecurityRiskLearnMore": { - "message": "Learn about the risks", + "message": "Riskler hakkında bilgi edinin", "description": "The is the link copy for the first check box option in the edit policy dialog" }, "autoConfirmSingleOrgRequired": { - "message": "Single organization policy required. " + "message": "Tek kuruluş ilkesi gereklidir. " }, "autoConfirmSingleOrgRequiredDesc": { - "message": "All members must only belong to this organization to activate this automation." + "message": "Bu otomasyonu etkinleştirmek için tüm üyelerin yalnızca bu kuruluşa ait olması gerekir." }, "autoConfirmSingleOrgExemption": { - "message": "Single organization policy will extend to all roles. " + "message": "Tek kuruluş ilkesinin kapsamı tüm rollere genişletilecektir. " }, "autoConfirmNoEmergencyAccess": { - "message": "No emergency access. " + "message": "Acil durum erişimi yok. " }, "autoConfirmNoEmergencyAccessDescription": { - "message": "Emergency Access will be removed." + "message": "Acil Erişim kaldırılacak." }, "autoConfirmCheckBoxLabel": { - "message": "I accept these risks and policy updates" + "message": "Bu riskleri ve ilke güncellemelerini kabul ediyorum" }, "personalOwnership": { "message": "Kişisel kasayı kaldır" @@ -5944,10 +5956,10 @@ "message": "Bir kuruluş ilkesi nedeniyle kişisel kasanıza hesap kaydetmeniz kısıtlanmış. Sahip seçeneğini bir kuruluş olarak değiştirin ve mevcut koleksiyonlar arasından seçim yapın." }, "desktopAutotypePolicy": { - "message": "Desktop Autotype Default Setting" + "message": "Masaüstü Otomatik Yazma Varsayılan Ayarı" }, "desktopAutotypePolicyDesc": { - "message": "Turn Desktop Autotype ON by default for members. Members can turn Autotype off manually in the Desktop client.", + "message": "Üyeler için Masaüstü Otomatik Yazma özelliğini varsayılan olarak AÇIK duruma getirin. Üyeler, Masaüstü istemcisinde Otomatik Yazma’yı manuel olarak kapatabilir.", "description": "This policy will enable Desktop Autotype by default for members on Unlock." }, "disableSend": { @@ -5987,13 +5999,13 @@ "message": "Varsayılan URI eşleşme tespiti" }, "uriMatchDetectionPolicyDesc": { - "message": "Determine when logins are suggested for autofill. Admins and owners are exempt from this policy." + "message": "Hesapların otomatik doldurma için ne zaman önerileceğini belirleyin. Yöneticiler ve sahipler bu ilkeden muaftır." }, "uriMatchDetectionOptionsLabel": { "message": "Varsayılan URI eşleşme tespiti" }, "invalidUriMatchDefaultPolicySetting": { - "message": "Please select a valid URI match detection option.", + "message": "Lütfen geçerli bir URl eşleşme algılama seçeneği seçin.", "description": "Error message displayed when a user attempts to save URI match detection policy settings with an invalid selection." }, "modifiedPolicyId": { @@ -6453,6 +6465,32 @@ "bulkReinviteMessage": { "message": "Yeniden davet edildi" }, + "bulkReinviteSuccessToast": { + "message": "$COUNT$ kullanıcı yeniden davet edildi", + "placeholders": { + "count": { + "content": "$1", + "example": "12" + } + } + }, + "bulkReinviteLimitedSuccessToast": { + "message": "$SELECTEDCOUNT$ kullanıcıdan $LIMIT$ tanesi yeniden davet edildi. $EXCLUDEDCOUNT$ kullanıcı, $LIMIT$ davet sınırı nedeniyle davet edilmedi.", + "placeholders": { + "limit": { + "content": "$1", + "example": "4,000" + }, + "selectedCount": { + "content": "$2", + "example": "4,005" + }, + "excludedCount": { + "content": "$3", + "example": "5" + } + } + }, "bulkRemovedMessage": { "message": "Başarıyla kaldırıldı" }, @@ -6650,10 +6688,10 @@ "message": "Ana parolanız kuruluş ilkelerinizi karşılamıyor. Kasanıza erişmek için ana parolanızı güncellemelisiniz. Devam ettiğinizde oturumunuz kapanacak ve yeniden oturum açmanız gerekecektir. Diğer cihazlardaki aktif oturumlar bir saate kadar aktif kalabilir." }, "automaticAppLoginWithSSO": { - "message": "Automatic login with SSO" + "message": "SSO ile otomatik hesap girişi" }, "automaticAppLoginWithSSODesc": { - "message": "Extend SSO security and convenience to unmanaged apps. When users launch an app from your identity provider, their login details are automatically filled and submitted, creating a one-click, secure flow from the identity provider to the app." + "message": "SSO güvenliğini ve kullanım kolaylığını yönetilmeyen uygulamalara da genişletin. Kullanıcılar bir uygulamayı kimlik sağlayıcınızdan başlattığında, hesap bilgileri otomatik olarak doldurulup gönderilir ve böylece kimlik sağlayıcıdan uygulamaya tek tıklamalı, güvenli bir akış sağlanır." }, "automaticAppLoginIdpHostLabel": { "message": "Kimlik sağlayıcı ana bilgisayarı" @@ -6668,16 +6706,16 @@ "message": "Oturum zaman aşımı" }, "sessionTimeoutPolicyDescription": { - "message": "Set a maximum session timeout for all members except owners." + "message": "Sahipler dışındaki tüm üyeler için maksimum oturum zaman aşımı belirleyin." }, "maximumAllowedTimeout": { - "message": "Maximum allowed timeout" + "message": "İzin verilen maksimum zaman aşımı" }, "maximumAllowedTimeoutRequired": { - "message": "Maximum allowed timeout is required." + "message": "İzin verilen maksimum zaman aşımı zorunludur." }, "sessionTimeoutPolicyInvalidTime": { - "message": "Time is invalid. Change at least one value." + "message": "Süre geçersiz. En az bir değeri değiştirin." }, "sessionTimeoutAction": { "message": "Oturum zaman aşımı eylemi" @@ -6698,19 +6736,19 @@ "message": "Dakika" }, "sessionTimeoutConfirmationNeverTitle": { - "message": "Are you certain you want to allow a maximum timeout of \"Never\" for all members?" + "message": "Tüm üyeler için maksimum zaman aşımının \"Asla\" olmasına izin vermek istediğinizden emin misiniz?" }, "sessionTimeoutConfirmationNeverDescription": { - "message": "This option will save your members' encryption keys on their devices. If you choose this option, ensure that their devices are adequately protected." + "message": "Bu seçenek, üyelerinizin şifreleme anahtarlarını cihazlarında kaydedecektir. Bu seçeneği tercih ederseniz, üyelerinizin cihazlarının yeterince korunduğundan emin olun." }, "learnMoreAboutDeviceProtection": { - "message": "Learn more about device protection" + "message": "Cihaz koruması hakkında daha fazla bilgi edinin" }, "sessionTimeoutConfirmationOnSystemLockTitle": { - "message": "\"System lock\" will only apply to the browser and desktop app" + "message": "\"Sistem kilidi\" yalnızca tarayıcı ve masaüstü uygulaması için geçerli olacaktır" }, "sessionTimeoutConfirmationOnSystemLockDescription": { - "message": "The mobile and web app will use \"on app restart\" as their maximum allowed timeout, since the option is not supported." + "message": "Bu seçenek desteklenmediğinden, mobil ve web uygulamaları için izin verilen maksimum zaman aşımı olarak \"uygulama yeniden başlatıldığında\" kullanılacaktır." }, "vaultTimeoutPolicyInEffect": { "message": "Kuruluş ilkeleriniz izin verilen maksimum kasa zaman aşımını $HOURS$ saat $MINUTES$ dakika olarak belirlemiş.", @@ -6773,8 +6811,8 @@ "vaultTimeoutRangeError": { "message": "Kasa zaman aşımı izin verilen aralıkta değil." }, - "disablePersonalVaultExport": { - "message": "Kişisel kasayı dışa aktarmayı kaldır" + "disableExport": { + "message": "Remove export" }, "disablePersonalVaultExportDescription": { "message": "Üyelerin kişisel kasalarındaki verileri dışa aktarmasına izin verme." @@ -7094,9 +7132,6 @@ "invalidVerificationCode": { "message": "Geçersiz doğrulama kodu" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "Aşağıdaki organizasyonun üyeleri için artık ana parola gerekmemektedir. Lütfen alan adını organizasyon yöneticinizle doğrulayın." - }, "keyConnectorDomain": { "message": "Key Connector alan adı" }, @@ -7154,8 +7189,8 @@ "enabledSso": { "message": "SSO etkinleştirildi" }, - "disabledSso": { - "message": "SSO etkinleştirildi" + "ssoTurnedOff": { + "message": "SSO kapatıldı" }, "emailMustLoginWithSso": { "message": "$EMAIL$ çoklu oturum açma (SSO) ile giriş yapmalıdır", @@ -7408,7 +7443,7 @@ } }, "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { - "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "message": "Yalnızca $ORGANIZATION$ ile ilişkilendirilmiş kuruluş kasası dışa aktarılacaktır.", "placeholders": { "organization": { "content": "$1", @@ -7417,7 +7452,7 @@ } }, "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { - "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "message": "Yalnızca $ORGANIZATION$ ile ilişkilendirilmiş kuruluş kasası dışa aktarılacaktır. Kayıtlarım koleksiyonları dahil edilmeyecektir.", "placeholders": { "organization": { "content": "$1", @@ -7583,7 +7618,7 @@ "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." + "message": "Bilinmeyen makine hesabı, bu makine hesabına erişmek için izin talep etmeniz gerekebilir." }, "unknownProject": { "message": "Bilinmeyen proje, bu projeye erişmek için izin istemeniz gerekebilir." @@ -8936,7 +8971,7 @@ } }, "accessedProjectWithIdentifier": { - "message": "Accessed a project with identifier: $PROJECT_ID$.", + "message": "$PROJECT_ID$ tanımlayıcısına sahip bir projeye erişildi.", "placeholders": { "project_id": { "content": "$1", @@ -8963,7 +8998,7 @@ } }, "nameUnavailableServiceAccountDeleted": { - "message": "Deleted machine account Id: $SERVICE_ACCOUNT_ID$", + "message": "Silinen makine hesabı ID’si: $SERVICE_ACCOUNT_ID$", "placeholders": { "service_account_id": { "content": "$1", @@ -8981,7 +9016,7 @@ } }, "addedUserToServiceAccountWithId": { - "message": "Added user: $USER_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "message": "$SERVICE_ACCOUNT_ID$ kimlikli makine hesabına $USER_ID$ kullanıcısı eklendi", "placeholders": { "user_id": { "content": "$1", @@ -8994,7 +9029,7 @@ } }, "removedUserToServiceAccountWithId": { - "message": "Removed user: $USER_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "message": "$SERVICE_ACCOUNT_ID$ kimlikli makine hesabından $USER_ID$ kullanıcısı kaldırıldı", "placeholders": { "user_id": { "content": "$1", @@ -9007,7 +9042,7 @@ } }, "removedGroupFromServiceAccountWithId": { - "message": "Removed group: $GROUP_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "message": "$SERVICE_ACCOUNT_ID$ kimlikli makine hesabından $GROUP_ID$ grubu kaldırıldı", "placeholders": { "group_id": { "content": "$1", @@ -9020,7 +9055,7 @@ } }, "serviceAccountCreatedWithId": { - "message": "Created machine account with identifier: $SERVICE_ACCOUNT_ID$", + "message": "$SERVICE_ACCOUNT_ID$ kimlikli makine hesabı oluşturuldu", "placeholders": { "service_account_id": { "content": "$1", @@ -9029,7 +9064,7 @@ } }, "addedGroupToServiceAccountId": { - "message": "Added group: $GROUP_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "message": "$SERVICE_ACCOUNT_ID$ kimlikli makine hesabına $GROUP_ID$ grubu eklendi", "placeholders": { "group_id": { "content": "$1", @@ -9042,7 +9077,7 @@ } }, "serviceAccountDeletedWithId": { - "message": "Deleted machine account with identifier: $SERVICE_ACCOUNT_ID$", + "message": "$SERVICE_ACCOUNT_ID$ kimlikli makine hesabı silindi", "placeholders": { "service_account_id": { "content": "$1", @@ -9448,6 +9483,9 @@ "ssoLoginIsRequired": { "message": "SSO girişi gereklidir" }, + "emailRequiredForSsoLogin": { + "message": "SSO için e-posta gereklidir" + }, "selectedRegionFlag": { "message": "Seçilen bölgenin bayrağı" }, @@ -9857,11 +9895,11 @@ "description": "An option for the offboarding survey shown when a user cancels their subscription." }, "switchToFreePlan": { - "message": "Switching to free plan", + "message": "Ücretsiz plana geçiliyor", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, "switchToFreeOrg": { - "message": "Switching to free organization", + "message": "Ücretsiz kuruluşa geçiliyor", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, "freeForOneYear": { @@ -9892,10 +9930,10 @@ "message": "Ata" }, "assignTasks": { - "message": "Assign tasks" + "message": "Görev ata" }, "assignSecurityTasksToMembers": { - "message": "Send notifications to change passwords" + "message": "Parolaları değiştirmek için bildirim gönder" }, "assignToCollections": { "message": "Koleksiyonlara ata" @@ -10293,13 +10331,13 @@ "message": "Etkinlik verilerini Logscale'e gönderin" }, "datadogEventIntegrationDesc": { - "message": "Send vault event data to your Datadog instance" + "message": "Kasa olay verilerini Datadog örneğinize gönderin" }, "failedToSaveIntegration": { "message": "Entegrasyon kaydedilemedi. Lütfen daha sonra tekrar deneyin." }, "mustBeOrgOwnerToPerformAction": { - "message": "You must be the organization owner to perform this action." + "message": "Bu işlemi gerçekleştirmek için kuruluş sahibi olmalısınız." }, "failedToDeleteIntegration": { "message": "Entegrasyon silinemedi. Lütfen daha sonra tekrar deneyin." @@ -11565,36 +11603,36 @@ "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ünür ve genel arama sonuçları ile otomatik doldurma önerilerinden hariç tutulur." }, "itemWasSentToArchive": { - "message": "Item was sent to archive" + "message": "Kayıt arşive gönderildi" }, "itemsWereSentToArchive": { - "message": "Items were sent to archive" + "message": "Kayıtlar arşive gönderildi" }, "itemUnarchived": { - "message": "Item was unarchived" + "message": "Kayıt arşivden çıkarıldı" }, "bulkArchiveItems": { - "message": "Items archived" + "message": "Kayıtlar arşivlendi" }, "bulkUnarchiveItems": { - "message": "Items unarchived" + "message": "Kayıtlar arşivden çıkarıldı" }, "archiveItem": { "message": "Kaydı arşivle", "description": "Verb" }, "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ğinizden emin misiniz?" }, "archiveBulkItems": { - "message": "Archive items", + "message": "Kayıtları arşivle", "description": "Verb" }, "archiveBulkItemsConfirmDesc": { - "message": "Archived items are excluded from general search results and autofill suggestions. Are you sure you want to archive these items?" + "message": "Arşivlenmiş kayıtlar genel arama sonuçları ve otomatik doldurma önerilerinden hariç tutulur. Bu kayıtları arşivlemek istediğinizden emin misiniz?" }, "businessUnit": { "message": "İş Birimi" @@ -11961,8 +11999,8 @@ "familiesMembership": { "message": "Aile üyeliği" }, - "planDescPremium": { - "message": "Tam çevrimiçi güvenlik" + "advancedOnlineSecurity": { + "message": "Gelişmiş çevrimiçi güvenlik" }, "planDescFamiliesV2": { "message": "Aileniz için Premium güvenlik" @@ -12073,34 +12111,34 @@ "message": "Sorunsuz entegrasyon" }, "families": { - "message": "Families" + "message": "Aileler" }, "upgradeToFamilies": { - "message": "Upgrade to Families" + "message": "Aileler planına yükselt" }, "upgradeToPremium": { - "message": "Upgrade to Premium" + "message": "Premium’a yükselt" }, "familiesUpdated": { - "message": "You've upgraded to Families!" + "message": "Aileler planına yükseltildiniz!" }, "taxCalculationError": { - "message": "There was an error calculating tax for your location. Please try again." + "message": "Konumunuz için vergi hesaplanırken bir hata oluştu. Lütfen tekrar deneyin." }, "individualUpgradeWelcomeMessage": { - "message": "Welcome to Bitwarden" + "message": "Bitwarden’a Hoş Geldiniz" }, "individualUpgradeDescriptionMessage": { - "message": "Unlock more security features with Premium, or start sharing items with Families" + "message": "Premium ile daha fazla güvenlik özelliğinin kilidini açın veya Aileler ile kayıtlarınızı paylaşmaya başlayın" }, "individualUpgradeTaxInformationMessage": { - "message": "Prices exclude tax and are billed annually." + "message": "Fiyatlara vergi dahil değildir ve yıllık olarak faturalandırılır." }, "organizationNameDescription": { - "message": "Your organization name will appear in invitations you send to members." + "message": "Kuruluş adınız, üyelere gönderdiğiniz davetlerde görünecektir." }, "continueWithoutUpgrading": { - "message": "Continue without upgrading" + "message": "Yükseltmeden devam et" }, "upgradeYourPlan": { "message": "Planınızı yükseltin" @@ -12109,19 +12147,19 @@ "message": "Şimdi yükselt" }, "formWillCreateNewFamiliesOrgMessage": { - "message": "Completing this form will create a new Families organization. You can upgrade your Free organization from the Admin Console." + "message": "Bu formu doldurmanız yeni bir Aileler kuruluşu oluşturacaktır. Ücretsiz kuruluşunuzu Yönetici Konsolu’ndan yükseltebilirsiniz." }, "upgradeErrorMessage": { - "message": "We encountered an error while processing your upgrade. Please try again." + "message": "Yükseltmenizi işlerken bir hata ile karşılaştık. Lütfen tekrar deneyin." }, "bitwardenFreeplanMessage": { - "message": "You have the Bitwarden Free plan" + "message": "Bitwarden Ücretsiz planını kullanıyorsunuz" }, "upgradeCompleteSecurity": { - "message": "Upgrade for complete security" + "message": "Tam güvenlik için yükselt" }, "viewbusinessplans": { - "message": "View business plans" + "message": "Kurumsal planları görüntüle" }, "updateEncryptionSettings": { "message": "Şifreleme ayarlarını güncelle" @@ -12133,37 +12171,37 @@ "message": "Algoritma" }, "encryptionKeySettingsHowShouldWeEncryptYourData": { - "message": "Choose how Bitwarden should encrypt your vault data. All options are secure, but stronger methods offer better protection - especially against brute-force attacks. Bitwarden recommends the default setting for most users." + "message": "Bitwarden’ın kasanızdaki verileri nasıl şifrelemesi gerektiğini seçin. Tüm seçenekler güvenlidir, ancak daha güçlü yöntemler özellikle kaba kuvvet saldırılarına karşı daha iyi koruma sağlar. Bitwarden, çoğu kullanıcı için varsayılan ayarı önerir." }, "encryptionKeySettingsIncreaseImproveSecurity": { - "message": "Increasing the values above the default will improve security, but your vault may take longer to unlock as a result." + "message": "Değerleri varsayılanın üzerine çıkarmak güvenliği artıracaktır, ancak bunun sonucunda kasanızın kilidinin açılması daha uzun sürebilir." }, "encryptionKeySettingsAlgorithmPopoverTitle": { - "message": "About encryption algorithms" + "message": "Şifreleme algoritmaları hakkında" }, "encryptionKeySettingsAlgorithmPopoverPBKDF2": { - "message": "PBKDF2-SHA256 is a well-tested encryption method that balances security and performance. Good for all users." + "message": "PBKDF2-SHA256, güvenlik ve performans arasında denge sağlayan, kapsamlı şekilde test edilmiş bir şifreleme yöntemidir. Tüm kullanıcılar için uygundur." }, "encryptionKeySettingsAlgorithmPopoverArgon2Id": { - "message": "Argon2id offers stronger protection against modern attacks. Best for advanced users with powerful devices." + "message": "Argon2id, modern saldırılara karşı daha güçlü koruma sağlar. Güçlü cihazlara sahip ileri düzey kullanıcılar için en iyisidir." }, "zipPostalCodeLabel": { - "message": "ZIP / Postal code" + "message": "Posta kodu" }, "cardNumberLabel": { - "message": "Card number" + "message": "Kart numarası" }, "startFreeFamiliesTrial": { - "message": "Start free Families trial" + "message": "Ücretsiz Aileler denemesini başlat" }, "blockClaimedDomainAccountCreation": { - "message": "Block account creation for claimed domains" + "message": "Sahiplenilen etki alanları için hesap oluşturulmasını engelle" }, "blockClaimedDomainAccountCreationDesc": { - "message": "Prevent users from creating accounts outside of your organization using email addresses from claimed domains." + "message": "Kullanıcıların, sahiplenilen etki alanlarına ait e-posta adreslerini kullanarak kuruluşunuzun dışında hesap oluşturmalarını engelleyin." }, "blockClaimedDomainAccountCreationPrerequisite": { - "message": "A domain must be claimed before activating this policy." + "message": "Bu ilkeyi etkinleştirmeden önce bir etki alanının sahiplenilmesi gerekir." }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Kasa zaman aşımı eyleminizi değiştirmek için kilit açma yönteminizi ayarlayın." @@ -12199,13 +12237,186 @@ } } }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "confirmNoSelectedCriticalApplicationsTitle": { - "message": "No critical applications are selected" + "message": "Hiçbir kritik uygulama seçili değil" }, "confirmNoSelectedCriticalApplicationsDesc": { "message": "Devam etmek istediğinizden emin misiniz?" }, "userVerificationFailed": { "message": "Kullanıcı doğrulaması başarısız oldu." + }, + "recoveryDeleteCiphersTitle": { + "message": "Delete unrecoverable vault items" + }, + "recoveryDeleteCiphersDesc": { + "message": "Some of your vault items could not be recovered. Do you want to delete these unrecoverable items from your vault?" + }, + "recoveryDeleteFoldersTitle": { + "message": "Delete unrecoverable folders" + }, + "recoveryDeleteFoldersDesc": { + "message": "Some of your folders could not be recovered. Do you want to delete these unrecoverable folders from your vault?" + }, + "recoveryReplacePrivateKeyTitle": { + "message": "Replace encryption key" + }, + "recoveryReplacePrivateKeyDesc": { + "message": "Your public-key encryption key pair could not be recovered. Do you want to replace your encryption key with a new key pair? This will require you to set up existing emergency-access and organization memberships again." + }, + "recoveryStepSyncTitle": { + "message": "Synchronizing data" + }, + "recoveryStepPrivateKeyTitle": { + "message": "Verifying encryption key integrity" + }, + "recoveryStepUserInfoTitle": { + "message": "Verifying user information" + }, + "recoveryStepCipherTitle": { + "message": "Verifying vault item integrity" + }, + "recoveryStepFoldersTitle": { + "message": "Verifying folder integrity" + }, + "dataRecoveryTitle": { + "message": "Data Recovery and Diagnostics" + }, + "dataRecoveryDescription": { + "message": "Use the data recovery tool to diagnose and repair issues with your account. After running diagnostics you have the option to save diagnostic logs for support and the option to repair any detected issues." + }, + "runDiagnostics": { + "message": "Run Diagnostics" + }, + "repairIssues": { + "message": "Repair Issues" + }, + "saveDiagnosticLogs": { + "message": "Save Diagnostic Logs" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "Bu ayar kuruluşunuz tarafından yönetiliyor." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Kuruluşunuz maksimum kasa zaman aşımını $HOURS$ saat $MINUTES$ dakika olarak belirlemiş.", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Kuruluşunuz varsayılan oturum zaman aşımını \"Tarayıcı yenilendiğinde\" olarak ayarladı." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maksimum zaman aşımı en fazla $HOURS$ saat $MINUTES$ dakika olabilir", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "Tarayıcı yenilendiğinde" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Zaman aşımı eyleminizi değiştirmek için kilit açma yönteminizi ayarlayın" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/web/src/locales/uk/messages.json b/apps/web/src/locales/uk/messages.json index be571b0c146..0cc96b69904 100644 --- a/apps/web/src/locales/uk/messages.json +++ b/apps/web/src/locales/uk/messages.json @@ -5169,9 +5169,21 @@ "message": "Виправити", "description": "This is a verb. ex. 'Fix The Car'" }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "oldAttachmentsNeedFixDesc": { "message": "У вашому сховищі є старі вкладені файли, які необхідно виправити перед тим, як оновлювати ключ шифрування облікового запису." }, + "itemsTransferred": { + "message": "Items transferred" + }, "yourAccountsFingerprint": { "message": "Фраза відбитка вашого облікового запису", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -6453,6 +6465,32 @@ "bulkReinviteMessage": { "message": "Повторне запрошення успішне" }, + "bulkReinviteSuccessToast": { + "message": "$COUNT$ users re-invited", + "placeholders": { + "count": { + "content": "$1", + "example": "12" + } + } + }, + "bulkReinviteLimitedSuccessToast": { + "message": "$LIMIT$ of $SELECTEDCOUNT$ users re-invited. $EXCLUDEDCOUNT$ were not invited due to the $LIMIT$ invite limit.", + "placeholders": { + "limit": { + "content": "$1", + "example": "4,000" + }, + "selectedCount": { + "content": "$2", + "example": "4,005" + }, + "excludedCount": { + "content": "$3", + "example": "5" + } + } + }, "bulkRemovedMessage": { "message": "Успішно вилучено" }, @@ -6773,8 +6811,8 @@ "vaultTimeoutRangeError": { "message": "Час очікування сховища поза межами дозволеного діапазону." }, - "disablePersonalVaultExport": { - "message": "Вилучити експорт особистого сховища" + "disableExport": { + "message": "Remove export" }, "disablePersonalVaultExportDescription": { "message": "Не дозволяти учасникам експортувати дані з їхнього особистого сховища." @@ -7094,9 +7132,6 @@ "invalidVerificationCode": { "message": "Недійсний код підтвердження" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "Головний пароль більше не є обов'язковим для учасників зазначеної організації. Підтвердьте вказаний нижче домен з адміністратором вашої організації." - }, "keyConnectorDomain": { "message": "Домен Key Connector" }, @@ -7154,8 +7189,8 @@ "enabledSso": { "message": "SSO увімкнено" }, - "disabledSso": { - "message": "SSO вимкнено" + "ssoTurnedOff": { + "message": "SSO turned off" }, "emailMustLoginWithSso": { "message": "$EMAIL$ must login with Single Sign-on", @@ -9448,6 +9483,9 @@ "ssoLoginIsRequired": { "message": "Потрібно увійти через SSO" }, + "emailRequiredForSsoLogin": { + "message": "Email is required for SSO" + }, "selectedRegionFlag": { "message": "Прапор вибраного регіону" }, @@ -11961,8 +11999,8 @@ "familiesMembership": { "message": "Families membership" }, - "planDescPremium": { - "message": "Повна онлайн-безпека" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "planDescFamiliesV2": { "message": "Безпека Premium для вашої сім'ї" @@ -12199,6 +12237,45 @@ } } }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "confirmNoSelectedCriticalApplicationsTitle": { "message": "No critical applications are selected" }, @@ -12207,5 +12284,139 @@ }, "userVerificationFailed": { "message": "User verification failed." + }, + "recoveryDeleteCiphersTitle": { + "message": "Delete unrecoverable vault items" + }, + "recoveryDeleteCiphersDesc": { + "message": "Some of your vault items could not be recovered. Do you want to delete these unrecoverable items from your vault?" + }, + "recoveryDeleteFoldersTitle": { + "message": "Delete unrecoverable folders" + }, + "recoveryDeleteFoldersDesc": { + "message": "Some of your folders could not be recovered. Do you want to delete these unrecoverable folders from your vault?" + }, + "recoveryReplacePrivateKeyTitle": { + "message": "Replace encryption key" + }, + "recoveryReplacePrivateKeyDesc": { + "message": "Your public-key encryption key pair could not be recovered. Do you want to replace your encryption key with a new key pair? This will require you to set up existing emergency-access and organization memberships again." + }, + "recoveryStepSyncTitle": { + "message": "Synchronizing data" + }, + "recoveryStepPrivateKeyTitle": { + "message": "Verifying encryption key integrity" + }, + "recoveryStepUserInfoTitle": { + "message": "Verifying user information" + }, + "recoveryStepCipherTitle": { + "message": "Verifying vault item integrity" + }, + "recoveryStepFoldersTitle": { + "message": "Verifying folder integrity" + }, + "dataRecoveryTitle": { + "message": "Data Recovery and Diagnostics" + }, + "dataRecoveryDescription": { + "message": "Use the data recovery tool to diagnose and repair issues with your account. After running diagnostics you have the option to save diagnostic logs for support and the option to repair any detected issues." + }, + "runDiagnostics": { + "message": "Run Diagnostics" + }, + "repairIssues": { + "message": "Repair Issues" + }, + "saveDiagnosticLogs": { + "message": "Save Diagnostic Logs" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On browser refresh." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On browser refresh" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/web/src/locales/vi/messages.json b/apps/web/src/locales/vi/messages.json index 35e7db50023..aa32aa4254c 100644 --- a/apps/web/src/locales/vi/messages.json +++ b/apps/web/src/locales/vi/messages.json @@ -21,7 +21,7 @@ "message": "Mật khẩu rủi ro" }, "noEditPermissions": { - "message": "You don't have permission to edit this item" + "message": "Bạn không có quyền chỉnh sửa mục này" }, "reviewAtRiskPasswords": { "message": "Kiểm tra các mật khẩu có rủi ro (yếu, bị lộ hoặc được sử dụng lại) trên các ứng dụng. Chọn các ứng dụng quan trọng nhất của bạn để ưu tiên các biện pháp bảo mật cho người dùng nhằm giải quyết các mật khẩu có rủi ro." @@ -94,7 +94,7 @@ "message": "Giao tác vụ cho thành viên để theo dõi tiến trình" }, "onceYouReviewApplications": { - "message": "Once you review applications and mark them as critical, assign tasks to your members to change their passwords." + "message": "Khi bạn xem xét các ứng dụng và đánh dấu chúng là quan trọng, hãy giao nhiệm vụ cho các thành viên của bạn thay đổi mật khẩu." }, "sendReminders": { "message": "Gửi lời nhắc" @@ -179,34 +179,34 @@ } }, "noDataInOrgTitle": { - "message": "No data found" + "message": "Không tìm thấy dữ liệu" }, "noDataInOrgDescription": { - "message": "Import your organization's login data to get started with Access Intelligence. Once you do that, you'll be able to:" + "message": "Nhập dữ liệu đăng nhập của tổ chức bạn để bắt đầu với Access Intelligence. Khi thực hiện xong, bạn sẽ có thể:" }, "feature1Title": { - "message": "Mark applications as critical" + "message": "Đánh dấu ứng dụng là quan trọng" }, "feature1Description": { - "message": "This will help you remove risks to your most important applications first." + "message": "Điều này sẽ giúp bạn loại bỏ rủi ro đối với các ứng dụng quan trọng nhất trước tiên." }, "feature2Title": { - "message": "Help members improve their security" + "message": "Giúp thành viên cải thiện bảo mật" }, "feature2Description": { - "message": "Assign at-risk members guided security tasks to update credentials." + "message": "Giao các nhiệm vụ bảo mật có hướng dẫn cho các thành viên có rủi ro để cập nhật thông tin xác thực." }, "feature3Title": { - "message": "Monitor progress" + "message": "Theo dõi tiến độ" }, "feature3Description": { - "message": "Track changes over time to show security improvements." + "message": "Theo dõi các thay đổi theo thời gian để hiển thị những cải tiến bảo mật." }, "noReportsRunTitle": { - "message": "Generate report" + "message": "Tạo báo cáo" }, "noReportsRunDescription": { - "message": "You’re ready to start generating reports. Once you generate, you’ll be able to:" + "message": "Bạn đã sẵn sàng để bắt đầu tạo báo cáo. Một khi bạn tạo, bạn sẽ có thể:" }, "noCriticalApplicationsTitle": { "message": "Bạn chưa đánh dấu ứng dụng nào là quan trọng" @@ -266,13 +266,13 @@ "message": "Các thành viên có rủi ro" }, "membersWithAccessToAtRiskItemsForCriticalApplications": { - "message": "These members have access to vulnerable items for critical applications." + "message": "Các thành viên này có quyền truy cập vào các mục dễ bị tổn thương cho các ứng dụng quan trọng." }, "membersWithAtRiskPasswords": { "message": "Thành viên có mật khẩu rủi ro" }, "membersWillReceiveSecurityTask": { - "message": "Members of your organization will be assigned a task to change vulnerable passwords. They’ll receive a notification within their Bitwarden browser extension." + "message": "Các thành viên trong tổ chức của bạn sẽ được giao nhiệm vụ thay đổi mật khẩu dễ bị tổn thương. Họ sẽ nhận được thông báo trong tiện ích mở rộng trình duyệt Bitwarden của họ." }, "membersAtRiskCount": { "message": "$COUNT$ thành viên gặp rủi ro", @@ -302,7 +302,7 @@ } }, "atRiskMemberDescription": { - "message": "These members are logging into critical applications with weak, exposed, or reused passwords." + "message": "Các thành viên này đang đăng nhập vào các ứng dụng quan trọng bằng mật khẩu yếu, bị lộ hoặc được sử dụng lại." }, "atRiskMembersDescriptionNone": { "message": "Không có thành viên nào đăng nhập vào ứng dụng bằng mật khẩu yếu, dễ bị lộ hoặc được sử dụng lại." @@ -344,7 +344,7 @@ "message": "Ứng dụng cần xem lại" }, "newApplicationsCardTitle": { - "message": "Review new applications" + "message": "Xem xét các ứng dụng mới" }, "newApplicationsWithCount": { "message": "$COUNT$ ứng dụng mới", @@ -368,7 +368,7 @@ "message": "Hiện tại không có ứng dụng mới nào để đánh giá" }, "organizationHasItemsSavedForApplications": { - "message": "Your organization has items saved for $COUNT$ applications", + "message": "Tổ chức của bạn có các mục được lưu cho $COUNT$ ứng dụng", "placeholders": { "count": { "content": "$1", @@ -377,22 +377,22 @@ } }, "reviewApplicationsToSecureItems": { - "message": "Review applications to secure the items most critical to your organization's security" + "message": "Xem xét các ứng dụng để bảo mật các mục quan trọng nhất đối với an ninh của tổ chức bạn" }, "reviewApplications": { - "message": "Review applications" + "message": "Xem xét ứng dụng" }, "prioritizeCriticalApplications": { "message": "Ưu tiên các ứng dụng quan trọng" }, "selectCriticalAppsDescription": { - "message": "Select which applications are most critical to your organization. Then, you’ll be able to assign security tasks to members to remove risks." + "message": "Chọn những ứng dụng nào là quan trọng nhất đối với tổ chức của bạn. Sau đó, bạn sẽ có thể giao các nhiệm vụ bảo mật cho các thành viên để loại bỏ rủi ro." }, "reviewNewApplications": { - "message": "Review new applications" + "message": "Xem xét các ứng dụng mới" }, "reviewNewAppsDescription": { - "message": "Review new applications with vulnerable items and mark those you’d like to monitor closely as critical. Then, you’ll be able to assign security tasks to members to remove risks." + "message": "Xem xét các ứng dụng mới có các mục dễ bị tổn thương và đánh dấu những ứng dụng bạn muốn giám sát chặt chẽ là quan trọng. Sau đó, bạn sẽ có thể giao các nhiệm vụ bảo mật cho các thành viên để loại bỏ rủi ro." }, "clickIconToMarkAppAsCritical": { "message": "Nhấp vào biểu tượng ngôi sao để đánh dấu một ứng dụng là quan trọng" @@ -3063,7 +3063,7 @@ "message": "1GB bộ nhớ lưu trữ được mã hóa cho các tệp đính kèm." }, "premiumSignUpStorageV2": { - "message": "$SIZE$ encrypted storage for file attachments.", + "message": "$SIZE$ bộ nhớ lưu trữ được mã hóa cho các tệp đính kèm.", "placeholders": { "size": { "content": "$1", @@ -3134,13 +3134,13 @@ } }, "premiumSubscriptionEnded": { - "message": "Your Premium subscription ended" + "message": "Gói đăng ký Cao cấp của bạn đã kết thúc" }, "premiumSubscriptionEndedDesc": { - "message": "To regain access to your archive, restart your Premium subscription. If you edit details for an archived item before restarting, it'll be moved back into your vault." + "message": "Để lấy lại quyền truy cập vào lưu trữ của bạn, hãy khởi động lại gói đăng ký Cao cấp. Nếu bạn chỉnh sửa chi tiết cho một mục đã lưu trữ trước khi khởi động lại, mục đó sẽ được chuyển trở lại kho của bạn." }, "restartPremium": { - "message": "Restart Premium" + "message": "Khởi động lại gói Cao cấp" }, "additionalStorageGb": { "message": "Dung lượng lưu trữ bổ sung (GB)" @@ -3263,16 +3263,16 @@ "message": "Lần thanh toán tiếp theo" }, "nextChargeHeader": { - "message": "Next Charge" + "message": "Lần thanh toán tiếp theo" }, "plan": { - "message": "Plan" + "message": "Gói" }, "details": { "message": "Chi tiết" }, "discount": { - "message": "discount" + "message": "giảm giá" }, "downloadLicense": { "message": "Tải về tệp giấy phép" @@ -4479,31 +4479,31 @@ "message": "Cập nhật trình duyệt" }, "generatingYourAccessIntelligence": { - "message": "Generating your Access Intelligence..." + "message": "Đang tạo Access Intelligence của bạn..." }, "fetchingMemberData": { - "message": "Fetching member data..." + "message": "Đang lấy dữ liệu thành viên..." }, "analyzingPasswordHealth": { - "message": "Analyzing password health..." + "message": "Đang phân tích độ mạnh mật khẩu..." }, "calculatingRiskScores": { - "message": "Calculating risk scores..." + "message": "Đang tính điểm rủi ro..." }, "generatingReportData": { - "message": "Generating report data..." + "message": "Đang tạo dữ liệu báo cáo..." }, "savingReport": { - "message": "Saving report..." + "message": "Đang lưu báo cáo..." }, "compilingInsights": { - "message": "Compiling insights..." + "message": "Đang biên soạn thông tin chi tiết..." }, "loadingProgress": { - "message": "Loading progress" + "message": "Đang tải tiến trình" }, "thisMightTakeFewMinutes": { - "message": "This might take a few minutes." + "message": "Quá trình này có thể mất vài phút." }, "riskInsightsRunReport": { "message": "Chạy báo cáo" @@ -4627,19 +4627,19 @@ "message": "Tìm hiểu thêm" }, "migrationsFailed": { - "message": "An error occurred updating the encryption settings." + "message": "Đã xảy ra lỗi khi cập nhật cài đặt mã hóa." }, "updateEncryptionSettingsTitle": { - "message": "Update your encryption settings" + "message": "Cập nhật cài đặt mã hóa của bạn" }, "updateEncryptionSettingsDesc": { - "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + "message": "Cài đặt mã hóa được khuyến nghị sẽ cải thiện bảo mật cho tài khoản của bạn. Nhập mật khẩu chính để cập nhật ngay." }, "confirmIdentityToContinue": { - "message": "Confirm your identity to continue" + "message": "Xác minh danh tính để tiếp tục" }, "enterYourMasterPassword": { - "message": "Enter your master password" + "message": "Nhập mật khẩu chính của bạn" }, "updateSettings": { "message": "Cập nhật cài đặt" @@ -5169,9 +5169,21 @@ "message": "Sửa", "description": "This is a verb. ex. 'Fix The Car'" }, + "fixEncryption": { + "message": "Sửa mã hóa" + }, + "fixEncryptionTooltip": { + "message": "Tệp này đang sử dụng phương pháp mã hóa lỗi thời." + }, + "attachmentUpdated": { + "message": "Tệp đính kèm đã được cập nhật" + }, "oldAttachmentsNeedFixDesc": { "message": "Có một số tệp đính kèm cũ trong kho lưu trữ của bạn cần được sửa chữa trước khi bạn có thể thay đổi khóa mã hóa của tài khoản." }, + "itemsTransferred": { + "message": "Các mục đã chuyển" + }, "yourAccountsFingerprint": { "message": "Cụm từ xác thực tài khoản của bạn", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -5846,7 +5858,7 @@ "description": "This is the policy description shown in the policy list." }, "organizationDataOwnershipDescContent": { - "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the ", + "message": "Tất cả các mục sẽ thuộc sở hữu và được lưu vào tổ chức, cho phép kiểm soát, hiển thị và báo cáo trên toàn tổ chức. Khi được bật, một bộ sưu tập mặc định sẽ có sẵn cho mỗi thành viên để lưu trữ các mục. Tìm hiểu thêm về việc quản lý ", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the credential lifecycle.'" }, "organizationDataOwnershipContentAnchor": { @@ -5886,14 +5898,14 @@ "message": "Cách bật xác nhận người dùng tự động" }, "autoConfirmExtension1": { - "message": "Open your Bitwarden extension" + "message": "Mở tiện ích mở rộng Bitwarden của bạn" }, "autoConfirmExtension2": { - "message": "Select", + "message": "Chọn", "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, "autoConfirmExtension3": { - "message": " Turn on", + "message": " Bật", "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, "autoConfirmExtensionOpened": { @@ -6453,6 +6465,32 @@ "bulkReinviteMessage": { "message": "Đã mời lại thành công" }, + "bulkReinviteSuccessToast": { + "message": "Đã mời lại $COUNT$ người dùng", + "placeholders": { + "count": { + "content": "$1", + "example": "12" + } + } + }, + "bulkReinviteLimitedSuccessToast": { + "message": "Đã mời lại $LIMIT$ trong số $SELECTEDCOUNT$ người dùng. $EXCLUDEDCOUNT$ người đã không được mời do giới hạn mời là $LIMIT$.", + "placeholders": { + "limit": { + "content": "$1", + "example": "4,000" + }, + "selectedCount": { + "content": "$2", + "example": "4,005" + }, + "excludedCount": { + "content": "$3", + "example": "5" + } + } + }, "bulkRemovedMessage": { "message": "Đã xóa thành công" }, @@ -6773,8 +6811,8 @@ "vaultTimeoutRangeError": { "message": "Thời gian chờ của kho lưu trữ không nằm trong phạm vi cho phép." }, - "disablePersonalVaultExport": { - "message": "Xóa xuất dữ liệu kho riêng lẻ" + "disableExport": { + "message": "Xóa xuất" }, "disablePersonalVaultExportDescription": { "message": "Không cho phép thành viên xuất dữ liệu từ kho lưu trữ cá nhân của họ." @@ -7094,9 +7132,6 @@ "invalidVerificationCode": { "message": "Mã xác minh không đúng" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "Không còn yêu cầu mật khẩu chính đối với các thành viên của tổ chức sau. Vui lòng xác nhận tên miền bên dưới với quản trị viên của tổ chức bạn." - }, "keyConnectorDomain": { "message": "Tên miền Key Connector" }, @@ -7154,8 +7189,8 @@ "enabledSso": { "message": "SSO đã bật" }, - "disabledSso": { - "message": "SSO đã được bật" + "ssoTurnedOff": { + "message": "Đã tắt SSO" }, "emailMustLoginWithSso": { "message": "$EMAIL$ phải đăng nhập bằng Đăng nhập một lần", @@ -9448,6 +9483,9 @@ "ssoLoginIsRequired": { "message": "Yêu cầu đăng nhập bằng SSO" }, + "emailRequiredForSsoLogin": { + "message": "Email is required for SSO" + }, "selectedRegionFlag": { "message": "Cờ vùng đã chọn" }, @@ -9857,11 +9895,11 @@ "description": "An option for the offboarding survey shown when a user cancels their subscription." }, "switchToFreePlan": { - "message": "Switching to free plan", + "message": "Chuyển sang gói miễn phí", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, "switchToFreeOrg": { - "message": "Switching to free organization", + "message": "Chuyển sang tổ chức miễn phí", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, "freeForOneYear": { @@ -9895,7 +9933,7 @@ "message": "Giao tác vụ" }, "assignSecurityTasksToMembers": { - "message": "Send notifications to change passwords" + "message": "Gửi thông báo thay đổi mật khẩu" }, "assignToCollections": { "message": "Gán vào bộ sưu tập" @@ -11704,7 +11742,7 @@ "message": "Đã cài đặt tiện ích mở rộng Bitwarden!" }, "bitwardenExtensionIsInstalled": { - "message": "Bitwarden extension is installed!" + "message": "Tiện ích mở rộng Bitwarden đã được cài đặt!" }, "openExtensionToAutofill": { "message": "Mở tiện ích mở rộng để đăng nhập và bắt đầu tự động điền." @@ -11721,11 +11759,11 @@ "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" }, "openExtensionFromToolbarPart1": { - "message": "If the extension didn't open, you may need to open Bitwarden from the icon ", + "message": "Nếu tiện ích mở rộng không mở, bạn có thể cần mở Bitwarden từ biểu tượng ", "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" }, "openExtensionFromToolbarPart2": { - "message": " on the toolbar.", + "message": " trên thanh công cụ.", "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" }, "gettingStartedWithBitwardenPart3": { @@ -11961,8 +11999,8 @@ "familiesMembership": { "message": "Gói Bitwarden Gia đình" }, - "planDescPremium": { - "message": "Bảo mật trực tuyến toàn diện" + "advancedOnlineSecurity": { + "message": "Bảo mật trực tuyến nâng cao" }, "planDescFamiliesV2": { "message": "Bảo mật cao cấp cho gia đình bạn" @@ -12157,37 +12195,37 @@ "message": "Bắt đầu dùng thử Gói Gia đình miễn phí" }, "blockClaimedDomainAccountCreation": { - "message": "Block account creation for claimed domains" + "message": "Chặn tạo tài khoản cho các tên miền đã xác nhận quyền sở hữu" }, "blockClaimedDomainAccountCreationDesc": { - "message": "Prevent users from creating accounts outside of your organization using email addresses from claimed domains." + "message": "Ngăn người dùng tạo tài khoản bên ngoài tổ chức của bạn bằng địa chỉ email từ các tên miền đã xác nhận." }, "blockClaimedDomainAccountCreationPrerequisite": { - "message": "A domain must be claimed before activating this policy." + "message": "Cần phải xác nhận tên miền trước khi kích hoạt chính sách này." }, "unlockMethodNeededToChangeTimeoutActionDesc": { - "message": "Set up an unlock method to change your vault timeout action." + "message": "Thiết lập phương thức mở khóa để thay đổi hành động sau khi đóng kho." }, "vaultTimeoutPolicyAffectingOptions": { - "message": "Enterprise policy requirements have been applied to your timeout options" + "message": "Các yêu cầu chính sách của doanh nghiệp đã được áp dụng cho các tùy chọn thời gian mở kho của bạn" }, "vaultTimeoutTooLarge": { - "message": "Your vault timeout exceeds the restrictions set by your organization." + "message": "Thời gian mở kho vượt quá giới hạn do tổ chức của bạn đặt ra." }, "neverLockWarning": { - "message": "Are you sure you want to use the \"Never\" option? Setting your lock options to \"Never\" stores your vault's encryption key on your device. If you use this option you should ensure that you keep your device properly protected." + "message": "Bạn có chắc chắn muốn chọn \"Không bao giờ\" không? Lựa chọn này sẽ lưu khóa mã hóa kho của bạn trực tiếp trên thiết bị. Hãy nhớ bảo vệ thiết bị của bạn thật cẩn thận nếu bạn chọn tùy chọn này." }, "sessionTimeoutSettingsAction": { - "message": "Timeout action" + "message": "Hành động sau khi đóng kho" }, "sessionTimeoutHeader": { - "message": "Session timeout" + "message": "Thời gian hết phiên" }, "appearance": { - "message": "Appearance" + "message": "Giao diện" }, "vaultTimeoutPolicyMaximumError": { - "message": "Timeout exceeds the restriction set by your organization: $HOURS$ hour(s) and $MINUTES$ minute(s) maximum", + "message": "Thời gian mở kho đã vượt quá giới hạn do tổ chức của bạn đặt ra: Tối đa $HOURS$ giờ và $MINUTES$ phút", "placeholders": { "hours": { "content": "$1", @@ -12199,13 +12237,186 @@ } } }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "confirmNoSelectedCriticalApplicationsTitle": { - "message": "No critical applications are selected" + "message": "Không có ứng dụng quan trọng nào được chọn" }, "confirmNoSelectedCriticalApplicationsDesc": { - "message": "Are you sure you want to continue?" + "message": "Bạn có chắc chắn muốn tiếp tục không?" }, "userVerificationFailed": { - "message": "User verification failed." + "message": "Xác minh người dùng thất bại." + }, + "recoveryDeleteCiphersTitle": { + "message": "Xóa các mục kho không thể khôi phục" + }, + "recoveryDeleteCiphersDesc": { + "message": "Một số mục trong kho của bạn không thể khôi phục được. Bạn có muốn xóa các mục không thể khôi phục này khỏi kho của bạn không?" + }, + "recoveryDeleteFoldersTitle": { + "message": "Xóa các thư mục không thể khôi phục" + }, + "recoveryDeleteFoldersDesc": { + "message": "Một số thư mục của bạn không thể khôi phục được. Bạn có muốn xóa các thư mục không thể khôi phục này khỏi kho của bạn không?" + }, + "recoveryReplacePrivateKeyTitle": { + "message": "Thay thế khóa mã hóa" + }, + "recoveryReplacePrivateKeyDesc": { + "message": "Cặp khóa mã hóa công khai của bạn không thể khôi phục được. Bạn có muốn thay thế khóa mã hóa của mình bằng một cặp khóa mới không? Việc này sẽ yêu cầu bạn thiết lập lại quyền truy cập khẩn cấp và tư cách thành viên tổ chức hiện có." + }, + "recoveryStepSyncTitle": { + "message": "Đang đồng bộ hóa dữ liệu" + }, + "recoveryStepPrivateKeyTitle": { + "message": "Đang xác minh tính toàn vẹn của khóa mã hóa" + }, + "recoveryStepUserInfoTitle": { + "message": "Đang xác minh thông tin người dùng" + }, + "recoveryStepCipherTitle": { + "message": "Đang xác minh tính toàn vẹn của mục kho" + }, + "recoveryStepFoldersTitle": { + "message": "Đang xác minh tính toàn vẹn của thư mục" + }, + "dataRecoveryTitle": { + "message": "Khôi phục Dữ liệu và Chẩn đoán" + }, + "dataRecoveryDescription": { + "message": "Sử dụng công cụ khôi phục dữ liệu để chẩn đoán và sửa chữa các sự cố với tài khoản của bạn. Sau khi chạy chẩn đoán, bạn có tùy chọn lưu nhật ký chẩn đoán để được hỗ trợ và tùy chọn sửa chữa bất kỳ sự cố nào được phát hiện." + }, + "runDiagnostics": { + "message": "Chạy Chẩn đoán" + }, + "repairIssues": { + "message": "Sửa chữa Sự cố" + }, + "saveDiagnosticLogs": { + "message": "Lưu Nhật ký Chẩn đoán" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "Cài đặt này do tổ chức của bạn quản lý." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Tổ chức của bạn đã đặt thời gian chờ phiên tối đa là $HOURS$ giờ và $MINUTES$ phút.", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Tổ chức của bạn đã đặt thời gian chờ phiên mặc định là Khi làm mới trình duyệt." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Thời gian chờ tối đa không thể vượt quá $HOURS$ giờ và $MINUTES$ phút", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "Khi làm mới trình duyệt" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Đặt phương thức mở khóa để thay đổi hành động khi hết thời gian chờ" + }, + "leaveConfirmationDialogTitle": { + "message": "Bạn có chắc chắn muốn rời đi không?" + }, + "leaveConfirmationDialogContentOne": { + "message": "Bằng việc từ chối, các mục cá nhân sẽ vẫn nằm trong tài khoản của bạn, nhưng bạn sẽ mất quyền truy cập vào các mục được chia sẻ và tính năng tổ chức." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Liên hệ quản trị viên của bạn để lấy lại quyền truy cập." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Rời khỏi $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "Tôi quản lý kho của mình như thế nào?" + }, + "transferItemsToOrganizationTitle": { + "message": "Chuyển các mục đến $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ yêu cầu tất cả các mục phải thuộc sở hữu của tổ chức để đảm bảo an ninh và tuân thủ. Nhấp chấp nhận để chuyển quyền sở hữu các mục của bạn.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Chấp nhận chuyển" + }, + "declineAndLeave": { + "message": "Từ chối và rời đi" + }, + "whyAmISeeingThis": { + "message": "Tại sao tôi thấy điều này?" } } diff --git a/apps/web/src/locales/zh_CN/messages.json b/apps/web/src/locales/zh_CN/messages.json index 7d898c79952..e6da8945be8 100644 --- a/apps/web/src/locales/zh_CN/messages.json +++ b/apps/web/src/locales/zh_CN/messages.json @@ -3010,7 +3010,7 @@ "message": "泄漏报告于" }, "reportError": { - "message": "加载报告时发生错误。请重试" + "message": "尝试加载报告时发生错误。请重试" }, "billing": { "message": "计费" @@ -3134,13 +3134,13 @@ } }, "premiumSubscriptionEnded": { - "message": "Your Premium subscription ended" + "message": "您的高级版订阅已结束" }, "premiumSubscriptionEndedDesc": { - "message": "To regain access to your archive, restart your Premium subscription. If you edit details for an archived item before restarting, it'll be moved back into your vault." + "message": "要重新获取存档的访问权限,请重启您的高级版订阅。如果您在重启前编辑了存档项目的详细信息,它将被移回您的密码库中。" }, "restartPremium": { - "message": "Restart Premium" + "message": "重启高级版" }, "additionalStorageGb": { "message": "附加存储 (GB)" @@ -4627,19 +4627,19 @@ "message": "进一步了解" }, "migrationsFailed": { - "message": "An error occurred updating the encryption settings." + "message": "更新加密设置时发生错误。" }, "updateEncryptionSettingsTitle": { - "message": "Update your encryption settings" + "message": "更新您的加密设置" }, "updateEncryptionSettingsDesc": { - "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + "message": "新推荐的加密设置将提高您的账户安全性。输入您的主密码以立即更新。" }, "confirmIdentityToContinue": { - "message": "Confirm your identity to continue" + "message": "确认您的身份后继续" }, "enterYourMasterPassword": { - "message": "Enter your master password" + "message": "输入您的主密码" }, "updateSettings": { "message": "更新设置" @@ -5163,15 +5163,27 @@ "message": "此项目有需要修复的旧文件附件。" }, "attachmentFixDescription": { - "message": "此附件使用了过时的加密方式。请选择「修复」以下载、重新加密并重新上传附件。" + "message": "此附件使用了过时的加密方式。选择「修复」以下载、重新加密然后重新上传此附件。" }, "fix": { "message": "修复", "description": "This is a verb. ex. 'Fix The Car'" }, + "fixEncryption": { + "message": "修复加密" + }, + "fixEncryptionTooltip": { + "message": "此文件正在使用过时的加密方式。" + }, + "attachmentUpdated": { + "message": "附件已更新" + }, "oldAttachmentsNeedFixDesc": { "message": "需要先修复您的密码库中的旧文件附件,然后才能轮换您账户的加密密钥。" }, + "itemsTransferred": { + "message": "项目已传输" + }, "yourAccountsFingerprint": { "message": "您的账户指纹短语", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -6453,6 +6465,32 @@ "bulkReinviteMessage": { "message": "重新邀请成功" }, + "bulkReinviteSuccessToast": { + "message": "已重新邀请 $COUNT$ 位用户", + "placeholders": { + "count": { + "content": "$1", + "example": "12" + } + } + }, + "bulkReinviteLimitedSuccessToast": { + "message": "选中了 $SELECTEDCOUNT$ 位用户,已经重新邀请了 $LIMIT$ 位。由于邀请限制 $LIMIT$ 人,$EXCLUDEDCOUNT$ 位用户没有被邀请。", + "placeholders": { + "limit": { + "content": "$1", + "example": "4,000" + }, + "selectedCount": { + "content": "$2", + "example": "4,005" + }, + "excludedCount": { + "content": "$3", + "example": "5" + } + } + }, "bulkRemovedMessage": { "message": "移除成功" }, @@ -6773,8 +6811,8 @@ "vaultTimeoutRangeError": { "message": "密码库超时不在允许的范围内。" }, - "disablePersonalVaultExport": { - "message": "禁用个人密码库导出" + "disableExport": { + "message": "移除导出" }, "disablePersonalVaultExportDescription": { "message": "不允许成员从个人密码库导出数据。" @@ -7094,9 +7132,6 @@ "invalidVerificationCode": { "message": "无效的验证码" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "以下组织的成员不再需要主密码。请与您的组织管理员确认下面的域名。" - }, "keyConnectorDomain": { "message": "Key Connector 域名" }, @@ -7154,8 +7189,8 @@ "enabledSso": { "message": "SSO 已启用" }, - "disabledSso": { - "message": "SSO 已关闭" + "ssoTurnedOff": { + "message": "SSO 已停用" }, "emailMustLoginWithSso": { "message": "$EMAIL$ 必须使用单点登录", @@ -8870,7 +8905,7 @@ "message": "描述" }, "errorReadingImportFile": { - "message": "尝试读取导入的文件时出错" + "message": "尝试读取导入的文件时发生错误" }, "accessedSecretWithId": { "message": "访问了标识符为 $SECRET_ID$ 的机密", @@ -9448,6 +9483,9 @@ "ssoLoginIsRequired": { "message": "要求 SSO 登录" }, + "emailRequiredForSsoLogin": { + "message": "Email is required for SSO" + }, "selectedRegionFlag": { "message": "选择的区域旗帜" }, @@ -10579,7 +10617,7 @@ "message": "无效的税务 ID,如有疑问,请联系支持。" }, "billingPreviewInvoiceError": { - "message": "预览账单时出错。请稍后再试。" + "message": "预览账单时发生错误。请稍后再试。" }, "unverified": { "message": "未验证" @@ -11961,8 +11999,8 @@ "familiesMembership": { "message": "家庭成员" }, - "planDescPremium": { - "message": "全面的在线安全防护" + "advancedOnlineSecurity": { + "message": "高级在线安全防护" }, "planDescFamiliesV2": { "message": "为您的家庭提供高级安全防护" @@ -12199,6 +12237,45 @@ } } }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "confirmNoSelectedCriticalApplicationsTitle": { "message": "未选择任何关键应用程序" }, @@ -12207,5 +12284,139 @@ }, "userVerificationFailed": { "message": "用户验证失败。" + }, + "recoveryDeleteCiphersTitle": { + "message": "删除无法恢复的密码库项目" + }, + "recoveryDeleteCiphersDesc": { + "message": "您的部分密码库项目无法恢复。要从您的密码库中删除这些无法恢复的项目吗?" + }, + "recoveryDeleteFoldersTitle": { + "message": "删除无法恢复的文件夹" + }, + "recoveryDeleteFoldersDesc": { + "message": "您的部分文件夹无法恢复。要从您的密码库中删除这些无法恢复的文件夹吗?" + }, + "recoveryReplacePrivateKeyTitle": { + "message": "替换加密密钥" + }, + "recoveryReplacePrivateKeyDesc": { + "message": "您的公钥加密密钥对无法恢复。要使用新的密钥对替换您的加密密钥吗?这将要求您重新设置现有的紧急访问和组织成员资格。" + }, + "recoveryStepSyncTitle": { + "message": "正在同步数据" + }, + "recoveryStepPrivateKeyTitle": { + "message": "正在验证加密密钥的完整性" + }, + "recoveryStepUserInfoTitle": { + "message": "正在验证用户信息" + }, + "recoveryStepCipherTitle": { + "message": "正在验证密码库项目的完整性" + }, + "recoveryStepFoldersTitle": { + "message": "正在验证文件夹的完整性" + }, + "dataRecoveryTitle": { + "message": "数据恢复和诊断" + }, + "dataRecoveryDescription": { + "message": "使用数据恢复工具诊断并修复您的账户问题。运行诊断程序后,您可以选择保存诊断日志以供支持使用,也可以选择修复任何检测到的问题。" + }, + "runDiagnostics": { + "message": "运行诊断程序" + }, + "repairIssues": { + "message": "修复问题" + }, + "saveDiagnosticLogs": { + "message": "保存诊断日志" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "此设置由您的组织管理。" + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "您的组织已将最大会话超时设置为 $HOURS$ 小时 $MINUTES$ 分钟。", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "您的组织已将默认会话超时设置为「浏览器刷新时」。" + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "最大超时时间不能超过 $HOURS$ 小时 $MINUTES$ 分钟", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "浏览器刷新时" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "设置一个解锁方式以更改您的超时动作" + }, + "leaveConfirmationDialogTitle": { + "message": "确定要离开吗?" + }, + "leaveConfirmationDialogContentOne": { + "message": "拒绝后,您的个人项目将保留在您的账户中,但您将失去对共享项目和组织功能的访问权限。" + }, + "leaveConfirmationDialogContentTwo": { + "message": "联系您的管理员以重新获取访问权限。" + }, + "leaveConfirmationDialogConfirmButton": { + "message": "离开 $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "我如何管理我的密码库?" + }, + "transferItemsToOrganizationTitle": { + "message": "传输项目到 $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "出于安全和合规考虑,$ORGANIZATION$ 要求所有项目的所有权均归组织所有。点击「接受」以传输您的项目的所有权。", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "接受传输" + }, + "declineAndLeave": { + "message": "拒绝并离开" + }, + "whyAmISeeingThis": { + "message": "为什么我会看到这个?" } } diff --git a/apps/web/src/locales/zh_TW/messages.json b/apps/web/src/locales/zh_TW/messages.json index 95c345f74d7..3479ca48f27 100644 --- a/apps/web/src/locales/zh_TW/messages.json +++ b/apps/web/src/locales/zh_TW/messages.json @@ -3063,7 +3063,7 @@ "message": "用於檔案附件的 1 GB 的加密檔案儲存空間。" }, "premiumSignUpStorageV2": { - "message": "$SIZE$ encrypted storage for file attachments.", + "message": "用於檔案附件的 $SIZE$ 加密儲存空間。", "placeholders": { "size": { "content": "$1", @@ -3134,13 +3134,13 @@ } }, "premiumSubscriptionEnded": { - "message": "Your Premium subscription ended" + "message": "您的進階版訂閱已到期" }, "premiumSubscriptionEndedDesc": { - "message": "To regain access to your archive, restart your Premium subscription. If you edit details for an archived item before restarting, it'll be moved back into your vault." + "message": "若要重新存取您的封存項目,請重新啟用進階版訂閱。若您在重新啟用前編輯封存項目的詳細資料,它將會被移回您的密碼庫。" }, "restartPremium": { - "message": "Restart Premium" + "message": "重新啟用進階版" }, "additionalStorageGb": { "message": "額外儲存空間 (GB)" @@ -4627,19 +4627,19 @@ "message": "深入了解" }, "migrationsFailed": { - "message": "An error occurred updating the encryption settings." + "message": "更新加密設定時發生錯誤。" }, "updateEncryptionSettingsTitle": { - "message": "Update your encryption settings" + "message": "更新您的加密設定" }, "updateEncryptionSettingsDesc": { - "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + "message": "新的建議加密設定將提升您的帳戶安全性。請輸入主密碼以立即更新。" }, "confirmIdentityToContinue": { - "message": "Confirm your identity to continue" + "message": "請先確認身分後再繼續" }, "enterYourMasterPassword": { - "message": "Enter your master password" + "message": "輸入您的主密碼" }, "updateSettings": { "message": "更新設定" @@ -5169,9 +5169,21 @@ "message": "修正", "description": "This is a verb. ex. 'Fix The Car'" }, + "fixEncryption": { + "message": "修正加密" + }, + "fixEncryptionTooltip": { + "message": "此檔案使用了過時的加密方式。" + }, + "attachmentUpdated": { + "message": "附件已更新" + }, "oldAttachmentsNeedFixDesc": { "message": "需要先修正密碼庫中舊的檔案附件,然後才能輪換帳戶的加密金鑰。" }, + "itemsTransferred": { + "message": "Items transferred" + }, "yourAccountsFingerprint": { "message": "您帳戶的指紋短語", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -6453,6 +6465,32 @@ "bulkReinviteMessage": { "message": "已成功重新邀請。" }, + "bulkReinviteSuccessToast": { + "message": "已重新邀請 $COUNT$ 位使用者", + "placeholders": { + "count": { + "content": "$1", + "example": "12" + } + } + }, + "bulkReinviteLimitedSuccessToast": { + "message": "已重新邀請 $SELECTEDCOUNT$ 位使用者中的 $LIMIT$ 位。有 $EXCLUDEDCOUNT$ 位因達到 $LIMIT$ 的邀請上限而未被邀請。", + "placeholders": { + "limit": { + "content": "$1", + "example": "4,000" + }, + "selectedCount": { + "content": "$2", + "example": "4,005" + }, + "excludedCount": { + "content": "$3", + "example": "5" + } + } + }, "bulkRemovedMessage": { "message": "已成功移除。" }, @@ -6773,8 +6811,8 @@ "vaultTimeoutRangeError": { "message": "密碼庫逾時時間不在允許的範圍內。" }, - "disablePersonalVaultExport": { - "message": "停用個人密碼庫匯出" + "disableExport": { + "message": "Remove export" }, "disablePersonalVaultExportDescription": { "message": "不允許成員從其個人密碼庫匯出資料。" @@ -7094,9 +7132,6 @@ "invalidVerificationCode": { "message": "無效的驗證碼" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "以下組織的成員已不再需要主密碼。請與你的組織管理員確認下方的網域。" - }, "keyConnectorDomain": { "message": "Key Connector 網域" }, @@ -7154,8 +7189,8 @@ "enabledSso": { "message": "已啟用 SSO" }, - "disabledSso": { - "message": "已停用 SSO" + "ssoTurnedOff": { + "message": "SSO 已關閉" }, "emailMustLoginWithSso": { "message": "$EMAIL$ 必須使用單一登入 (SSO) 登入", @@ -9448,6 +9483,9 @@ "ssoLoginIsRequired": { "message": "需要登入 SSO" }, + "emailRequiredForSsoLogin": { + "message": "Email is required for SSO" + }, "selectedRegionFlag": { "message": "選定的區域標記" }, @@ -11961,8 +11999,8 @@ "familiesMembership": { "message": "家庭會員" }, - "planDescPremium": { - "message": "完整的線上安全" + "advancedOnlineSecurity": { + "message": "進階線上安全防護" }, "planDescFamiliesV2": { "message": "為你的家人提供進階安全" @@ -12199,6 +12237,45 @@ } } }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "confirmNoSelectedCriticalApplicationsTitle": { "message": "未選取任何關鍵應用程式" }, @@ -12207,5 +12284,139 @@ }, "userVerificationFailed": { "message": "使用者驗證失敗。" + }, + "recoveryDeleteCiphersTitle": { + "message": "Delete unrecoverable vault items" + }, + "recoveryDeleteCiphersDesc": { + "message": "Some of your vault items could not be recovered. Do you want to delete these unrecoverable items from your vault?" + }, + "recoveryDeleteFoldersTitle": { + "message": "Delete unrecoverable folders" + }, + "recoveryDeleteFoldersDesc": { + "message": "Some of your folders could not be recovered. Do you want to delete these unrecoverable folders from your vault?" + }, + "recoveryReplacePrivateKeyTitle": { + "message": "Replace encryption key" + }, + "recoveryReplacePrivateKeyDesc": { + "message": "Your public-key encryption key pair could not be recovered. Do you want to replace your encryption key with a new key pair? This will require you to set up existing emergency-access and organization memberships again." + }, + "recoveryStepSyncTitle": { + "message": "Synchronizing data" + }, + "recoveryStepPrivateKeyTitle": { + "message": "Verifying encryption key integrity" + }, + "recoveryStepUserInfoTitle": { + "message": "Verifying user information" + }, + "recoveryStepCipherTitle": { + "message": "Verifying vault item integrity" + }, + "recoveryStepFoldersTitle": { + "message": "Verifying folder integrity" + }, + "dataRecoveryTitle": { + "message": "Data Recovery and Diagnostics" + }, + "dataRecoveryDescription": { + "message": "Use the data recovery tool to diagnose and repair issues with your account. After running diagnostics you have the option to save diagnostic logs for support and the option to repair any detected issues." + }, + "runDiagnostics": { + "message": "Run Diagnostics" + }, + "repairIssues": { + "message": "Repair Issues" + }, + "saveDiagnosticLogs": { + "message": "Save Diagnostic Logs" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "此設定由您的組織管理。" + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "您的組織已將最長工作階段逾時設為 $HOURS$ 小時與 $MINUTES$ 分鐘。", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "您的組織已將預設工作階段逾時設定為「在瀏覽器重新整理時」。" + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "最長逾時時間不可超過 $HOURS$ 小時 $MINUTES$ 分鐘", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "於瀏覽器重新整理時" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "設定一個解鎖方式來變更您的密碼庫逾時動作。" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } From 4b93df98c89548d4c4ac6f9d63242c6491c669e9 Mon Sep 17 00:00:00 2001 From: Todd Martin <106564991+trmartin4@users.noreply.github.com> Date: Mon, 15 Dec 2025 16:35:44 -0500 Subject: [PATCH 075/188] chore(README): Update READMEs missing H1 headers * Update READMEs missing H1 headers. * Changed casing. --- apps/web/README.md | 2 ++ libs/common/src/tools/integration/README.md | 2 ++ libs/dirt/card/README.md | 2 +- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/web/README.md b/apps/web/README.md index c5e03eebb59..0640d7199af 100644 --- a/apps/web/README.md +++ b/apps/web/README.md @@ -1,3 +1,5 @@ +# Bitwarden Web App + <p align="center"> <img src="https://raw.githubusercontent.com/bitwarden/brand/main/screenshots/web-vault.png" alt="" width="600" height="358" /> </p> diff --git a/libs/common/src/tools/integration/README.md b/libs/common/src/tools/integration/README.md index 9d7edb2d360..1dc74f729e6 100644 --- a/libs/common/src/tools/integration/README.md +++ b/libs/common/src/tools/integration/README.md @@ -1,3 +1,5 @@ +# Tools Vendor Integration + This module defines interfaces and helpers for creating vendor integration sites. ## RPC diff --git a/libs/dirt/card/README.md b/libs/dirt/card/README.md index b97bfd56590..5d21a045658 100644 --- a/libs/dirt/card/README.md +++ b/libs/dirt/card/README.md @@ -1,4 +1,4 @@ -## DIRT Card +# DIRT Card Package name: `@bitwarden/dirt-card` From d130c443b8effd189cd79b0b0267b7973a3ac573 Mon Sep 17 00:00:00 2001 From: Jason Ng <jng@bitwarden.com> Date: Mon, 15 Dec 2025 18:16:04 -0500 Subject: [PATCH 076/188] [PM-26514] Archive With Non Premium User (#17820) * Add callout for archive non premium, add premium check, add archive badge to view/edit modal, update btn text --- .../vault-item-dialog.component.html | 126 ++++++++++-------- .../vault-item-dialog.component.spec.ts | 36 +++++ .../vault-item-dialog.component.ts | 24 +++- .../vault-cipher-row.component.html | 2 +- .../vault-items/vault-cipher-row.component.ts | 18 +-- .../individual-vault/vault.component.html | 30 +++-- .../vault/individual-vault/vault.component.ts | 4 + apps/web/src/locales/en/messages.json | 3 + .../src/dialog/dialog/dialog.component.html | 25 ++-- .../src/dialog/dialog/dialog.stories.ts | 40 ++++++ 10 files changed, 219 insertions(+), 89 deletions(-) diff --git a/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.html b/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.html index 6eb9b89b05b..3aa2f4b3bc1 100644 --- a/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.html +++ b/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.html @@ -2,43 +2,53 @@ <span bitDialogTitle aria-live="polite"> {{ title }} </span> + @if (cipherIsArchived) { + <span bitBadge bitDialogHeaderEnd> {{ "archiveNoun" | i18n }} </span> + } + <div bitDialogContent #dialogContent> - <app-cipher-view - *ngIf="showCipherView" - [cipher]="cipher" - [collections]="collections" - [isAdminConsole]="formConfig.isAdminConsole" - ></app-cipher-view> - <vault-cipher-form - *ngIf="loadForm" - formId="cipherForm" - [config]="formConfig" - [submitBtn]="submitBtn" - (formReady)="onFormReady()" - (cipherSaved)="onCipherSaved($event)" - (formStatusChange$)="formStatusChanged($event)" - > - <bit-item slot="attachment-button"> - <button - [disabled]="attachmentsButtonDisabled" - bit-item-content - type="button" - (click)="openAttachmentsDialog()" - > - <div class="tw-flex tw-items-center tw-gap-2"> - {{ "attachments" | i18n }} - <app-premium-badge></app-premium-badge> - </div> - <i slot="end" class="bwi bwi-angle-right" aria-hidden="true"></i> - </button> - </bit-item> - </vault-cipher-form> + @if (showCipherView) { + <app-cipher-view + [cipher]="cipher" + [collections]="collections" + [isAdminConsole]="formConfig.isAdminConsole" + ></app-cipher-view> + } + + @if (loadForm) { + <vault-cipher-form + formId="cipherForm" + [config]="formConfig" + [submitBtn]="submitBtn" + (formReady)="onFormReady()" + (cipherSaved)="onCipherSaved($event)" + (formStatusChange$)="formStatusChanged($event)" + > + <bit-item slot="attachment-button"> + <button + [disabled]="attachmentsButtonDisabled" + bit-item-content + type="button" + (click)="openAttachmentsDialog()" + > + <div class="tw-flex tw-items-center tw-gap-2"> + {{ "attachments" | i18n }} + <app-premium-badge></app-premium-badge> + </div> + <i slot="end" class="bwi bwi-angle-right" aria-hidden="true"></i> + </button> + </bit-item> + </vault-cipher-form> + } </div> <ng-container bitDialogFooter> - <button *ngIf="showRestore" [bitAction]="restore" bitButton buttonType="primary" type="button"> - {{ "restore" | i18n }} - </button> - <ng-container *ngIf="showEdit"> + @if (showRestore) { + <button [bitAction]="restore" bitButton buttonType="primary" type="button"> + {{ "restore" | i18n }} + </button> + } + + @if (showEdit) { <button bitButton [bitAction]="switchToEdit" @@ -48,7 +58,8 @@ > {{ "edit" | i18n }} </button> - </ng-container> + } + <button bitButton type="submit" @@ -57,24 +68,33 @@ #submitBtn [hidden]="showCipherView || showRestore" > - {{ "save" | i18n }} + {{ submitButtonText$ | async }} </button> - <button bitButton type="button" buttonType="secondary" (click)="cancel()" *ngIf="showCancel"> - {{ "cancel" | i18n }} - </button> - <button bitButton type="button" buttonType="secondary" (click)="cancel()" *ngIf="showClose"> - {{ "close" | i18n }} - </button> - <div class="tw-ml-auto" *ngIf="showDelete"> - <button - bitIconButton="bwi-trash" - type="button" - buttonType="danger" - [label]="'delete' | i18n" - [bitAction]="delete" - [disabled]="!canDelete" - data-testid="delete-cipher-btn" - ></button> - </div> + + @if (showCancel) { + <button bitButton type="button" buttonType="secondary" (click)="cancel()"> + {{ "cancel" | i18n }} + </button> + } + + @if (showClose) { + <button bitButton type="button" buttonType="secondary" (click)="cancel()"> + {{ "close" | i18n }} + </button> + } + + @if (showDelete) { + <div class="tw-ml-auto"> + <button + bitIconButton="bwi-trash" + type="button" + buttonType="danger" + [label]="'delete' | i18n" + [bitAction]="delete" + [disabled]="!canDelete" + data-testid="delete-cipher-btn" + ></button> + </div> + } </ng-container> </bit-dialog> diff --git a/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.spec.ts b/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.spec.ts index 6716cde629a..11862b569fc 100644 --- a/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.spec.ts +++ b/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.spec.ts @@ -1,6 +1,7 @@ import { ComponentFixture, TestBed } from "@angular/core/testing"; import { provideNoopAnimations } from "@angular/platform-browser/animations"; import { ActivatedRoute, Router } from "@angular/router"; +import { of } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; @@ -95,6 +96,10 @@ describe("VaultItemDialogComponent", () => { fixture = TestBed.createComponent(TestVaultItemDialogComponent); component = fixture.componentInstance; + Object.defineProperty(component, "userHasPremium$", { + get: () => of(false), + configurable: true, + }); fixture.detectChanges(); }); @@ -135,4 +140,35 @@ describe("VaultItemDialogComponent", () => { expect(component.getTestTitle()).toBe("newItemHeaderCard"); }); }); + describe("submitButtonText$", () => { + it("should return 'unArchiveAndSave' when premium is false and cipher is archived", (done) => { + jest.spyOn(component as any, "userHasPremium$", "get").mockReturnValue(of(false)); + component["cipherIsArchived"] = true; + + component["submitButtonText$"].subscribe((text) => { + expect(text).toBe("unArchiveAndSave"); + done(); + }); + }); + + it("should return 'save' when cipher is archived and user has premium", (done) => { + jest.spyOn(component as any, "userHasPremium$", "get").mockReturnValue(of(true)); + component["cipherIsArchived"] = true; + + component["submitButtonText$"].subscribe((text) => { + expect(text).toBe("save"); + done(); + }); + }); + + it("should return 'save' when cipher is not archived", (done) => { + jest.spyOn(component as any, "userHasPremium$", "get").mockReturnValue(of(false)); + component["cipherIsArchived"] = false; + + component["submitButtonText$"].subscribe((text) => { + expect(text).toBe("save"); + done(); + }); + }); + }); }); diff --git a/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts b/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts index 8508596a67b..d7b9ee97123 100644 --- a/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts +++ b/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts @@ -12,7 +12,7 @@ import { } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { Router } from "@angular/router"; -import { firstValueFrom, Subject, switchMap } from "rxjs"; +import { firstValueFrom, Observable, Subject, switchMap } from "rxjs"; import { map } from "rxjs/operators"; import { CollectionView } from "@bitwarden/admin-console/common"; @@ -222,10 +222,10 @@ export class VaultItemDialogComponent implements OnInit, OnDestroy { protected collections?: CollectionView[]; /** - * Flag to indicate if the user has access to attachments via a premium subscription. + * Flag to indicate if the user has a premium subscription. Using for access to attachments, and archives * @protected */ - protected canAccessAttachments$ = this.accountService.activeAccount$.pipe( + protected userHasPremium$ = this.accountService.activeAccount$.pipe( switchMap((account) => this.billingAccountProfileStateService.hasPremiumFromAnySource$(account.id), ), @@ -253,6 +253,8 @@ export class VaultItemDialogComponent implements OnInit, OnDestroy { protected showRestore: boolean; + protected cipherIsArchived: boolean; + protected get loadingForm() { return this.loadForm && !this.formReady; } @@ -278,6 +280,16 @@ export class VaultItemDialogComponent implements OnInit, OnDestroy { return this.cipher != undefined && (this.params.mode === "view" || this.loadingForm); } + protected get submitButtonText$(): Observable<string> { + return this.userHasPremium$.pipe( + map((hasPremium) => + this.cipherIsArchived && !hasPremium + ? this.i18nService.t("unArchiveAndSave") + : this.i18nService.t("save"), + ), + ); + } + /** * Flag to initialize/attach the form component. */ @@ -363,6 +375,7 @@ export class VaultItemDialogComponent implements OnInit, OnDestroy { this.filter = await firstValueFrom(this.routedVaultFilterService.filter$); this.showRestore = await this.canUserRestore(); + this.cipherIsArchived = this.cipher.isArchived; this.performingInitialLoad = false; } @@ -392,6 +405,9 @@ export class VaultItemDialogComponent implements OnInit, OnDestroy { cipherView.collectionIds?.includes(c.id), ); + // Track cipher archive state for btn text and badge updates + this.cipherIsArchived = this.cipher.isArchived; + // If the cipher was newly created (via add/clone), switch the form to edit for subsequent edits. if (this._originalFormMode === "add" || this._originalFormMode === "clone") { this.formConfig.mode = "edit"; @@ -468,7 +484,7 @@ export class VaultItemDialogComponent implements OnInit, OnDestroy { }; openAttachmentsDialog = async () => { - const canAccessAttachments = await firstValueFrom(this.canAccessAttachments$); + const canAccessAttachments = await firstValueFrom(this.userHasPremium$); if (!canAccessAttachments) { await this.premiumUpgradeService.promptForPremium(this.cipher?.organizationId); diff --git a/apps/web/src/app/vault/components/vault-items/vault-cipher-row.component.html b/apps/web/src/app/vault/components/vault-items/vault-cipher-row.component.html index d56c9d15cff..5b9f9db3e62 100644 --- a/apps/web/src/app/vault/components/vault-items/vault-cipher-row.component.html +++ b/apps/web/src/app/vault/components/vault-items/vault-cipher-row.component.html @@ -172,7 +172,7 @@ <bit-menu-divider *ngIf="showMenuDivider"></bit-menu-divider> @if (!viewingOrgVault) { - <button bitMenuItem type="button" (click)="toggleFavorite()"> + <button bitMenuItem type="button" *ngIf="showFavorite" (click)="toggleFavorite()"> <i class="bwi bwi-fw bwi-star" aria-hidden="true"></i> {{ (cipher.favorite ? "unfavorite" : "favorite") | i18n }} </button> diff --git a/apps/web/src/app/vault/components/vault-items/vault-cipher-row.component.ts b/apps/web/src/app/vault/components/vault-items/vault-cipher-row.component.ts index 92c49ac218a..a723f1e942b 100644 --- a/apps/web/src/app/vault/components/vault-items/vault-cipher-row.component.ts +++ b/apps/web/src/app/vault/components/vault-items/vault-cipher-row.component.ts @@ -253,14 +253,6 @@ export class VaultCipherRowComponent<C extends CipherViewLike> implements OnInit ); } - protected get hasPasswordToCopy() { - return CipherViewLikeUtils.hasCopyableValue(this.cipher, "password"); - } - - protected get hasUsernameToCopy() { - return CipherViewLikeUtils.hasCopyableValue(this.cipher, "username"); - } - protected get permissionText() { if (!this.cipher.organizationId || this.cipher.collectionIds.length === 0) { return this.i18nService.t("manageCollection"); @@ -319,6 +311,9 @@ export class VaultCipherRowComponent<C extends CipherViewLike> implements OnInit } protected get isIdentityCipher() { + if (CipherViewLikeUtils.isArchived(this.cipher) && !this.userCanArchive) { + return false; + } return CipherViewLikeUtils.getType(this.cipher) === this.CipherType.Identity && !this.isDeleted; } @@ -394,6 +389,13 @@ export class VaultCipherRowComponent<C extends CipherViewLike> implements OnInit return this.organization.canEditAllCiphers || (this.cipher.edit && this.cipher.viewPassword); } + protected get showFavorite() { + if (CipherViewLikeUtils.isArchived(this.cipher) && !this.userCanArchive) { + return false; + } + return true; + } + protected toggleFavorite() { this.onEvent.emit({ type: "toggleFavorite", diff --git a/apps/web/src/app/vault/individual-vault/vault.component.html b/apps/web/src/app/vault/individual-vault/vault.component.html index 522b63c21fd..df1b727154f 100644 --- a/apps/web/src/app/vault/individual-vault/vault.component.html +++ b/apps/web/src/app/vault/individual-vault/vault.component.html @@ -31,19 +31,23 @@ ></app-vault-filter> </div> <div class="tw-basis-3/4 tw-max-w-3/4 tw-px-2.5"> - <bit-callout type="warning" *ngIf="activeFilter.isDeleted"> - {{ trashCleanupWarning }} - </bit-callout> - <bit-callout - type="info" - [title]="'premiumSubscriptionEnded' | i18n" - *ngIf="showSubscriptionEndedMessaging$ | async" - > - <p>{{ "premiumSubscriptionEndedDesc" | i18n }}</p> - <a routerLink="/settings/subscription/premium" bitButton buttonType="primary">{{ - "restartPremium" | i18n - }}</a> - </bit-callout> + @if (activeFilter.isDeleted) { + <bit-callout type="warning"> + {{ trashCleanupWarning }} + </bit-callout> + } + + @if (showSubscriptionEndedMessaging$ | async) { + <bit-callout type="default" [title]="'premiumSubscriptionEnded' | i18n"> + <ng-container> + <div> + {{ "premiumSubscriptionEndedDesc" | i18n }} + </div> + <a bitLink (click)="navigateToGetPremium()"> {{ "restartPremium" | i18n }} </a> + </ng-container> + </bit-callout> + } + <app-vault-items #vaultItems [ciphers]="ciphers" 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 78a8889bc8f..e791ca7a90b 100644 --- a/apps/web/src/app/vault/individual-vault/vault.component.ts +++ b/apps/web/src/app/vault/individual-vault/vault.component.ts @@ -658,6 +658,10 @@ export class VaultComponent<C extends CipherViewLike> implements OnInit, OnDestr this.vaultFilterService.clearOrganizationFilter(); } + async navigateToGetPremium() { + await this.router.navigate(["/settings/subscription/premium"]); + } + async onVaultItemsEvent(event: VaultItemEvent<C>) { this.processingEvent = true; try { diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 0f8b0c1b466..680c28f0747 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -11591,6 +11591,9 @@ "unArchive": { "message": "Unarchive" }, + "unArchiveAndSave": { + "message": "Unarchive and save" + }, "itemsInArchive": { "message": "Items in archive" }, diff --git a/libs/components/src/dialog/dialog/dialog.component.html b/libs/components/src/dialog/dialog/dialog.component.html index be946c76a57..22aa99c44cb 100644 --- a/libs/components/src/dialog/dialog/dialog.component.html +++ b/libs/components/src/dialog/dialog/dialog.component.html @@ -34,16 +34,21 @@ } <ng-content select="[bitDialogTitle]"></ng-content> </h2> - @if (!this.dialogRef?.disableClose) { - <button - type="button" - bitIconButton="bwi-close" - buttonType="main" - size="default" - bitDialogClose - [label]="'close' | i18n" - ></button> - } + + <div class="tw-flex tw-items-center tw-justify-center"> + <ng-content select="[bitDialogHeaderEnd]"></ng-content> + + @if (!this.dialogRef?.disableClose) { + <button + type="button" + bitIconButton="bwi-close" + buttonType="main" + size="default" + bitDialogClose + [label]="'close' | i18n" + ></button> + } + </div> </header> <div diff --git a/libs/components/src/dialog/dialog/dialog.stories.ts b/libs/components/src/dialog/dialog/dialog.stories.ts index 1f33ab7e877..012bb77f2ac 100644 --- a/libs/components/src/dialog/dialog/dialog.stories.ts +++ b/libs/components/src/dialog/dialog/dialog.stories.ts @@ -94,6 +94,7 @@ export const Default: Story = { <ng-container bitDialogTitle> <span bitBadge variant="success">Foobar</span> </ng-container> + <ng-container bitDialogContent>Dialog body text goes here.</ng-container> <ng-container bitDialogFooter> <button type="button" bitButton buttonType="primary" [disabled]="loading">Save</button> @@ -292,3 +293,42 @@ export const WithCards: Story = { disableAnimations: true, }, }; + +export const HeaderEnd: Story = { + render: (args) => ({ + props: args, + template: /*html*/ ` + <bit-dialog + [dialogSize]="dialogSize" + [title]="title" + [subtitle]="subtitle" + [loading]="loading" + [disablePadding]="disablePadding" + [disableAnimations]="disableAnimations"> + + <ng-container bitDialogHeaderEnd> + <span bitBadge>Archived</span> + </ng-container> + + <ng-container bitDialogContent>Dialog body text goes here.</ng-container> + <ng-container bitDialogFooter> + <button type="button" bitButton buttonType="primary" [disabled]="loading">Save</button> + <button type="button" bitButton buttonType="secondary" [disabled]="loading">Cancel</button> + <button + type="button" + [disabled]="loading" + class="tw-ms-auto" + bitIconButton="bwi-trash" + buttonType="danger" + size="default" + label="Delete"></button> + </ng-container> + </bit-dialog> + `, + }), + args: { + dialogSize: "small", + title: "Very Long Title That Should Be Truncated After Two Lines To Test Header End Slot", + subtitle: "Subtitle", + }, +}; From a7d3056f502ce9d27f84f2793bfd26bbe90a2d6f Mon Sep 17 00:00:00 2001 From: "bw-ghapp[bot]" <178206702+bw-ghapp[bot]@users.noreply.github.com> Date: Tue, 16 Dec 2025 09:20:53 +0100 Subject: [PATCH 077/188] Autosync the updated translations (#17972) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/web/src/locales/af/messages.json | 15 +-- apps/web/src/locales/ar/messages.json | 15 +-- apps/web/src/locales/az/messages.json | 41 ++++---- apps/web/src/locales/be/messages.json | 15 +-- apps/web/src/locales/bg/messages.json | 15 +-- apps/web/src/locales/bn/messages.json | 15 +-- apps/web/src/locales/bs/messages.json | 15 +-- apps/web/src/locales/ca/messages.json | 15 +-- apps/web/src/locales/cs/messages.json | 15 +-- apps/web/src/locales/cy/messages.json | 15 +-- apps/web/src/locales/da/messages.json | 15 +-- apps/web/src/locales/de/messages.json | 77 +++++++-------- apps/web/src/locales/el/messages.json | 15 +-- apps/web/src/locales/en_GB/messages.json | 15 +-- apps/web/src/locales/en_IN/messages.json | 15 +-- apps/web/src/locales/eo/messages.json | 15 +-- apps/web/src/locales/es/messages.json | 15 +-- apps/web/src/locales/et/messages.json | 15 +-- apps/web/src/locales/eu/messages.json | 15 +-- apps/web/src/locales/fa/messages.json | 15 +-- apps/web/src/locales/fi/messages.json | 15 +-- apps/web/src/locales/fil/messages.json | 15 +-- apps/web/src/locales/fr/messages.json | 15 +-- apps/web/src/locales/gl/messages.json | 15 +-- apps/web/src/locales/he/messages.json | 15 +-- apps/web/src/locales/hi/messages.json | 15 +-- apps/web/src/locales/hr/messages.json | 15 +-- apps/web/src/locales/hu/messages.json | 15 +-- apps/web/src/locales/id/messages.json | 15 +-- apps/web/src/locales/it/messages.json | 15 +-- apps/web/src/locales/ja/messages.json | 15 +-- apps/web/src/locales/ka/messages.json | 15 +-- apps/web/src/locales/km/messages.json | 15 +-- apps/web/src/locales/kn/messages.json | 15 +-- apps/web/src/locales/ko/messages.json | 15 +-- apps/web/src/locales/lv/messages.json | 15 +-- apps/web/src/locales/ml/messages.json | 15 +-- apps/web/src/locales/mr/messages.json | 15 +-- apps/web/src/locales/my/messages.json | 15 +-- apps/web/src/locales/nb/messages.json | 15 +-- apps/web/src/locales/ne/messages.json | 15 +-- apps/web/src/locales/nl/messages.json | 15 +-- apps/web/src/locales/nn/messages.json | 15 +-- apps/web/src/locales/or/messages.json | 15 +-- apps/web/src/locales/pl/messages.json | 15 +-- apps/web/src/locales/pt_BR/messages.json | 77 +++++++-------- apps/web/src/locales/pt_PT/messages.json | 41 ++++---- apps/web/src/locales/ro/messages.json | 15 +-- apps/web/src/locales/ru/messages.json | 59 +++++------- apps/web/src/locales/si/messages.json | 15 +-- apps/web/src/locales/sk/messages.json | 15 +-- apps/web/src/locales/sl/messages.json | 15 +-- apps/web/src/locales/sr_CS/messages.json | 15 +-- apps/web/src/locales/sr_CY/messages.json | 115 +++++++++++------------ apps/web/src/locales/sv/messages.json | 15 +-- apps/web/src/locales/ta/messages.json | 15 +-- apps/web/src/locales/te/messages.json | 15 +-- apps/web/src/locales/th/messages.json | 15 +-- apps/web/src/locales/tr/messages.json | 15 +-- apps/web/src/locales/uk/messages.json | 15 +-- apps/web/src/locales/vi/messages.json | 15 +-- apps/web/src/locales/zh_CN/messages.json | 69 ++++++-------- apps/web/src/locales/zh_TW/messages.json | 15 +-- 63 files changed, 376 insertions(+), 943 deletions(-) diff --git a/apps/web/src/locales/af/messages.json b/apps/web/src/locales/af/messages.json index b5701c01e86..d5314274775 100644 --- a/apps/web/src/locales/af/messages.json +++ b/apps/web/src/locales/af/messages.json @@ -1975,12 +1975,6 @@ "exportFrom": { "message": "Export from" }, - "exportVault": { - "message": "Stuur kluis uit" - }, - "exportSecrets": { - "message": "Export secrets" - }, "fileFormat": { "message": "Lêerformaat" }, @@ -1993,9 +1987,6 @@ "confirmMasterPassword": { "message": "Bevestig hoofwagwoord" }, - "confirmFormat": { - "message": "Bevestig formaat" - }, "filePassword": { "message": "Lêerwagwoord" }, @@ -2306,6 +2297,9 @@ "tools": { "message": "Nutsmiddels" }, + "import": { + "message": "Import" + }, "importData": { "message": "Voer data in" }, @@ -8753,9 +8747,6 @@ "server": { "message": "Bediener" }, - "exportData": { - "message": "Stuur data uit" - }, "exportingOrganizationSecretDataTitle": { "message": "Stuur tans organisasiegeheimdata uit" }, diff --git a/apps/web/src/locales/ar/messages.json b/apps/web/src/locales/ar/messages.json index 807689cebae..c85ad682d0a 100644 --- a/apps/web/src/locales/ar/messages.json +++ b/apps/web/src/locales/ar/messages.json @@ -1975,12 +1975,6 @@ "exportFrom": { "message": "التصدير من" }, - "exportVault": { - "message": "تصدير الخزانة" - }, - "exportSecrets": { - "message": "Export secrets" - }, "fileFormat": { "message": "صيغة الملف" }, @@ -1993,9 +1987,6 @@ "confirmMasterPassword": { "message": "تأكيد كلمة المرور الرئيسية" }, - "confirmFormat": { - "message": "تأكيد التنسيق" - }, "filePassword": { "message": "كلمة مرور الملف" }, @@ -2306,6 +2297,9 @@ "tools": { "message": "الأدوات" }, + "import": { + "message": "Import" + }, "importData": { "message": "استيراد البيانات" }, @@ -8753,9 +8747,6 @@ "server": { "message": "Server" }, - "exportData": { - "message": "Export data" - }, "exportingOrganizationSecretDataTitle": { "message": "Exporting Organization Secret Data" }, diff --git a/apps/web/src/locales/az/messages.json b/apps/web/src/locales/az/messages.json index 77df36f5c45..9068514ce55 100644 --- a/apps/web/src/locales/az/messages.json +++ b/apps/web/src/locales/az/messages.json @@ -1975,12 +1975,6 @@ "exportFrom": { "message": "Buradan xaricə köçür" }, - "exportVault": { - "message": "Seyfi xaricə köçür" - }, - "exportSecrets": { - "message": "Sirləri xaricə köçür" - }, "fileFormat": { "message": "Fayl formatı" }, @@ -1993,9 +1987,6 @@ "confirmMasterPassword": { "message": "Ana parolu təsdiqlə" }, - "confirmFormat": { - "message": "Formatı təsdiqlə" - }, "filePassword": { "message": "Fayl parolu" }, @@ -2306,6 +2297,9 @@ "tools": { "message": "Alətlər" }, + "import": { + "message": "Daxilə köçür" + }, "importData": { "message": "Veriləri daxilə köçür" }, @@ -8753,9 +8747,6 @@ "server": { "message": "Server" }, - "exportData": { - "message": "Veriləri xaricə köçür" - }, "exportingOrganizationSecretDataTitle": { "message": "Təşkilatın Sirr Verilərini xaricə köçürmə" }, @@ -12238,43 +12229,43 @@ } }, "removeMasterPasswordForOrgUserKeyConnector": { - "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + "message": "Təşkilatınız, artıq Bitwarden-ə giriş etmək üçün ana parol istifadə etmir. Davam etmək üçün təşkilatı və domeni doğrulayın." }, "continueWithLogIn": { - "message": "Continue with log in" + "message": "Giriş etməyə davam" }, "doNotContinue": { - "message": "Do not continue" + "message": "Davam etmə" }, "domain": { - "message": "Domain" + "message": "Domen" }, "keyConnectorDomainTooltip": { - "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + "message": "Bu domen, hesabınızın şifrələmə açarlarını saxlayacaq, ona görə də, bu domenə güvəndiyinizə əmin olun. Əmin deyilsinizsə, adminizlə əlaqə saxlayın." }, "verifyYourOrganization": { - "message": "Verify your organization to log in" + "message": "Giriş etmək üçün təşkilatınızı doğrulayın" }, "organizationVerified": { - "message": "Organization verified" + "message": "Təşkilat doğrulandı" }, "domainVerified": { - "message": "Domain verified" + "message": "Domen doğrulandı" }, "leaveOrganizationContent": { - "message": "If you don't verify your organization, your access to the organization will be revoked." + "message": "Təşkilatınızı doğrulamasanız, təşkilata erişiminiz ləğv ediləcək." }, "leaveNow": { - "message": "Leave now" + "message": "İndi tərk et" }, "verifyYourDomainToLogin": { - "message": "Verify your domain to log in" + "message": "Giriş etmək üçün domeninizi doğrulayın" }, "verifyYourDomainDescription": { - "message": "To continue with log in, verify this domain." + "message": "Giriş prosesini davam etdirmək üçün bu domeni doğrulayın." }, "confirmKeyConnectorOrganizationUserDescription": { - "message": "To continue with log in, verify the organization and domain." + "message": "Giriş prosesini davam etdirmək üçün bu təşkilatı və domeni doğrulayın." }, "confirmNoSelectedCriticalApplicationsTitle": { "message": "Heç bir kritik tətbiq seçilməyib" diff --git a/apps/web/src/locales/be/messages.json b/apps/web/src/locales/be/messages.json index c670f82a00d..b5f7a4dfa18 100644 --- a/apps/web/src/locales/be/messages.json +++ b/apps/web/src/locales/be/messages.json @@ -1975,12 +1975,6 @@ "exportFrom": { "message": "Export from" }, - "exportVault": { - "message": "Экспартаваць сховішча" - }, - "exportSecrets": { - "message": "Экспартаванне сакрэтаў" - }, "fileFormat": { "message": "Фармат файла" }, @@ -1993,9 +1987,6 @@ "confirmMasterPassword": { "message": "Пацвердзіць асноўны пароль" }, - "confirmFormat": { - "message": "Пацвердзіць фармат" - }, "filePassword": { "message": "Пароль файла" }, @@ -2306,6 +2297,9 @@ "tools": { "message": "Інструменты" }, + "import": { + "message": "Import" + }, "importData": { "message": "Імпартаванне даных" }, @@ -8753,9 +8747,6 @@ "server": { "message": "Сервер" }, - "exportData": { - "message": "Экспартаванне даных" - }, "exportingOrganizationSecretDataTitle": { "message": "Экспартаванне сакрэтных даных арганізацыі" }, diff --git a/apps/web/src/locales/bg/messages.json b/apps/web/src/locales/bg/messages.json index 3e3451084fc..a57f9bcebb2 100644 --- a/apps/web/src/locales/bg/messages.json +++ b/apps/web/src/locales/bg/messages.json @@ -1975,12 +1975,6 @@ "exportFrom": { "message": "Изнасяне от" }, - "exportVault": { - "message": "Изнасяне на трезора" - }, - "exportSecrets": { - "message": "Изнасяне на тайните" - }, "fileFormat": { "message": "Формат на файла" }, @@ -1993,9 +1987,6 @@ "confirmMasterPassword": { "message": "Потвърждаване на главната парола" }, - "confirmFormat": { - "message": "Потвърждаване на формата" - }, "filePassword": { "message": "Парола на файла" }, @@ -2306,6 +2297,9 @@ "tools": { "message": "Инструменти" }, + "import": { + "message": "Внасяне" + }, "importData": { "message": "Внасяне на данни" }, @@ -8753,9 +8747,6 @@ "server": { "message": "Сървър" }, - "exportData": { - "message": "Изнасяне на данни" - }, "exportingOrganizationSecretDataTitle": { "message": "Изнасяне на тайните данни на организацията" }, diff --git a/apps/web/src/locales/bn/messages.json b/apps/web/src/locales/bn/messages.json index 4e8c53b1598..f2ff447005c 100644 --- a/apps/web/src/locales/bn/messages.json +++ b/apps/web/src/locales/bn/messages.json @@ -1975,12 +1975,6 @@ "exportFrom": { "message": "Export from" }, - "exportVault": { - "message": "Export vault" - }, - "exportSecrets": { - "message": "Export secrets" - }, "fileFormat": { "message": "File format" }, @@ -1993,9 +1987,6 @@ "confirmMasterPassword": { "message": "Confirm master password" }, - "confirmFormat": { - "message": "Confirm format" - }, "filePassword": { "message": "File password" }, @@ -2306,6 +2297,9 @@ "tools": { "message": "Tools" }, + "import": { + "message": "Import" + }, "importData": { "message": "Import data" }, @@ -8753,9 +8747,6 @@ "server": { "message": "Server" }, - "exportData": { - "message": "Export data" - }, "exportingOrganizationSecretDataTitle": { "message": "Exporting Organization Secret Data" }, diff --git a/apps/web/src/locales/bs/messages.json b/apps/web/src/locales/bs/messages.json index defb970c237..63c22b86251 100644 --- a/apps/web/src/locales/bs/messages.json +++ b/apps/web/src/locales/bs/messages.json @@ -1975,12 +1975,6 @@ "exportFrom": { "message": "Export from" }, - "exportVault": { - "message": "Export vault" - }, - "exportSecrets": { - "message": "Export secrets" - }, "fileFormat": { "message": "File format" }, @@ -1993,9 +1987,6 @@ "confirmMasterPassword": { "message": "Confirm master password" }, - "confirmFormat": { - "message": "Confirm format" - }, "filePassword": { "message": "File password" }, @@ -2306,6 +2297,9 @@ "tools": { "message": "Tools" }, + "import": { + "message": "Import" + }, "importData": { "message": "Import data" }, @@ -8753,9 +8747,6 @@ "server": { "message": "Server" }, - "exportData": { - "message": "Export data" - }, "exportingOrganizationSecretDataTitle": { "message": "Exporting Organization Secret Data" }, diff --git a/apps/web/src/locales/ca/messages.json b/apps/web/src/locales/ca/messages.json index 9b8bb1fce7d..7e6c8e20085 100644 --- a/apps/web/src/locales/ca/messages.json +++ b/apps/web/src/locales/ca/messages.json @@ -1975,12 +1975,6 @@ "exportFrom": { "message": "Exporta des de" }, - "exportVault": { - "message": "Exporta la caixa forta" - }, - "exportSecrets": { - "message": "Exporta secrets" - }, "fileFormat": { "message": "Format de fitxer" }, @@ -1993,9 +1987,6 @@ "confirmMasterPassword": { "message": "Confirma la contrasenya mestra" }, - "confirmFormat": { - "message": "Confirma el format" - }, "filePassword": { "message": "Contrasenya del fitxer" }, @@ -2306,6 +2297,9 @@ "tools": { "message": "Eines" }, + "import": { + "message": "Import" + }, "importData": { "message": "Importa dades" }, @@ -8753,9 +8747,6 @@ "server": { "message": "Servidor" }, - "exportData": { - "message": "Exportar dades" - }, "exportingOrganizationSecretDataTitle": { "message": "Exportació de dades secretes de l’organització" }, diff --git a/apps/web/src/locales/cs/messages.json b/apps/web/src/locales/cs/messages.json index 4eacb212138..923cfb66a14 100644 --- a/apps/web/src/locales/cs/messages.json +++ b/apps/web/src/locales/cs/messages.json @@ -1975,12 +1975,6 @@ "exportFrom": { "message": "Exportovat z" }, - "exportVault": { - "message": "Exportovat trezor" - }, - "exportSecrets": { - "message": "Exportovat tajné klíče" - }, "fileFormat": { "message": "Formát souboru" }, @@ -1993,9 +1987,6 @@ "confirmMasterPassword": { "message": "Potvrzení hlavního hesla" }, - "confirmFormat": { - "message": "Potvrdit formát" - }, "filePassword": { "message": "Heslo souboru" }, @@ -2306,6 +2297,9 @@ "tools": { "message": "Nástroje" }, + "import": { + "message": "Importovat" + }, "importData": { "message": "Importovat data" }, @@ -8753,9 +8747,6 @@ "server": { "message": "Server" }, - "exportData": { - "message": "Exportovat data" - }, "exportingOrganizationSecretDataTitle": { "message": "Exportování tajných dat organizace" }, diff --git a/apps/web/src/locales/cy/messages.json b/apps/web/src/locales/cy/messages.json index f8839744e98..5a8845a48cc 100644 --- a/apps/web/src/locales/cy/messages.json +++ b/apps/web/src/locales/cy/messages.json @@ -1975,12 +1975,6 @@ "exportFrom": { "message": "Export from" }, - "exportVault": { - "message": "Export vault" - }, - "exportSecrets": { - "message": "Export secrets" - }, "fileFormat": { "message": "File format" }, @@ -1993,9 +1987,6 @@ "confirmMasterPassword": { "message": "Confirm master password" }, - "confirmFormat": { - "message": "Confirm format" - }, "filePassword": { "message": "File password" }, @@ -2306,6 +2297,9 @@ "tools": { "message": "Tools" }, + "import": { + "message": "Import" + }, "importData": { "message": "Import data" }, @@ -8753,9 +8747,6 @@ "server": { "message": "Server" }, - "exportData": { - "message": "Export data" - }, "exportingOrganizationSecretDataTitle": { "message": "Exporting Organization Secret Data" }, diff --git a/apps/web/src/locales/da/messages.json b/apps/web/src/locales/da/messages.json index 3b583416b25..56ccbf6abdf 100644 --- a/apps/web/src/locales/da/messages.json +++ b/apps/web/src/locales/da/messages.json @@ -1975,12 +1975,6 @@ "exportFrom": { "message": "Eksportér fra" }, - "exportVault": { - "message": "Eksportér boks" - }, - "exportSecrets": { - "message": "Eksportér hemmeligheder" - }, "fileFormat": { "message": "Filformat" }, @@ -1993,9 +1987,6 @@ "confirmMasterPassword": { "message": "Bekræft hovedadgangskode" }, - "confirmFormat": { - "message": "Bekræft format" - }, "filePassword": { "message": "Filadgangskode" }, @@ -2306,6 +2297,9 @@ "tools": { "message": "Værktøjer" }, + "import": { + "message": "Import" + }, "importData": { "message": "Importér data" }, @@ -8753,9 +8747,6 @@ "server": { "message": "Server" }, - "exportData": { - "message": "Eksportér data" - }, "exportingOrganizationSecretDataTitle": { "message": "Eksporterer organisations hemmelige data" }, diff --git a/apps/web/src/locales/de/messages.json b/apps/web/src/locales/de/messages.json index 6eb0b1d5dc7..d4ccac0d49f 100644 --- a/apps/web/src/locales/de/messages.json +++ b/apps/web/src/locales/de/messages.json @@ -1975,12 +1975,6 @@ "exportFrom": { "message": "Exportieren ab" }, - "exportVault": { - "message": "Tresor exportieren" - }, - "exportSecrets": { - "message": "Geheimnisse exportieren" - }, "fileFormat": { "message": "Dateiformat" }, @@ -1993,9 +1987,6 @@ "confirmMasterPassword": { "message": "Master-Passwort bestätigen" }, - "confirmFormat": { - "message": "Format bestätigen" - }, "filePassword": { "message": "Dateipasswort" }, @@ -2306,6 +2297,9 @@ "tools": { "message": "Werkzeuge" }, + "import": { + "message": "Import" + }, "importData": { "message": "Daten importieren" }, @@ -6466,7 +6460,7 @@ "message": "Erfolgreich erneut eingeladen" }, "bulkReinviteSuccessToast": { - "message": "$COUNT$ users re-invited", + "message": "$COUNT$ Benutzer erneut eingeladen", "placeholders": { "count": { "content": "$1", @@ -6475,7 +6469,7 @@ } }, "bulkReinviteLimitedSuccessToast": { - "message": "$LIMIT$ of $SELECTEDCOUNT$ users re-invited. $EXCLUDEDCOUNT$ were not invited due to the $LIMIT$ invite limit.", + "message": "$LIMIT$ von $SELECTEDCOUNT$ Benutzern erneut eingeladen. $EXCLUDEDCOUNT$ wurden wegen des Einladungslimits von $LIMIT$ nicht eingeladen.", "placeholders": { "limit": { "content": "$1", @@ -8753,9 +8747,6 @@ "server": { "message": "Server" }, - "exportData": { - "message": "Daten exportieren" - }, "exportingOrganizationSecretDataTitle": { "message": "Geheimnisdaten der Organisation exportieren" }, @@ -12241,19 +12232,19 @@ "message": "Deine Organisation verwendet keine Master-Passwörter mehr, um sich bei Bitwarden anzumelden. Verifiziere die Organisation und Domain, um fortzufahren." }, "continueWithLogIn": { - "message": "Continue with log in" + "message": "Mit der Anmeldung fortfahren" }, "doNotContinue": { - "message": "Do not continue" + "message": "Nicht fortfahren" }, "domain": { "message": "Domain" }, "keyConnectorDomainTooltip": { - "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + "message": "Diese Domain speichert die Verschlüsselungsschlüssel deines Kontos. Stelle daher sicher, dass du ihr vertraust. Wenn du dir nicht sicher bist, wende dich an deinen Administrator." }, "verifyYourOrganization": { - "message": "Verify your organization to log in" + "message": "Verifiziere deine Organisation, um dich anzumelden" }, "organizationVerified": { "message": "Organisation verifiziert" @@ -12265,16 +12256,16 @@ "message": "Wenn du deine Organisation nicht verifizierst, wird dein Zugriff auf die Organisation widerrufen." }, "leaveNow": { - "message": "Leave now" + "message": "Jetzt verlassen" }, "verifyYourDomainToLogin": { - "message": "Verify your domain to log in" + "message": "Verifiziere deine Domain, um dich anzumelden" }, "verifyYourDomainDescription": { - "message": "To continue with log in, verify this domain." + "message": "Verifiziere diese Domain, um mit der Anmeldung fortzufahren." }, "confirmKeyConnectorOrganizationUserDescription": { - "message": "To continue with log in, verify the organization and domain." + "message": "Um mit der Anmeldung fortzufahren, verifiziere die Organisation und Domain." }, "confirmNoSelectedCriticalApplicationsTitle": { "message": "Es sind keine kritischen Anwendungen ausgewählt" @@ -12286,52 +12277,52 @@ "message": "Benutzerverifizierung fehlgeschlagen." }, "recoveryDeleteCiphersTitle": { - "message": "Delete unrecoverable vault items" + "message": "Nicht-wiederherstellbare Tresor-Einträge löschen" }, "recoveryDeleteCiphersDesc": { - "message": "Some of your vault items could not be recovered. Do you want to delete these unrecoverable items from your vault?" + "message": "Einige deiner Tresor-Einträge konnten nicht wiederhergestellt werden. Möchtest du diese nicht-wiederherstellbaren Einträge in deinem Tresor löschen?" }, "recoveryDeleteFoldersTitle": { - "message": "Delete unrecoverable folders" + "message": "Nicht-wiederherstellbare Ordner löschen" }, "recoveryDeleteFoldersDesc": { - "message": "Some of your folders could not be recovered. Do you want to delete these unrecoverable folders from your vault?" + "message": "Einige deiner Ordner konnten nicht wiederhergestellt werden. Möchtest du diese nicht-wiederherstellbaren Ordner in deinem Tresor löschen?" }, "recoveryReplacePrivateKeyTitle": { "message": "Verschlüsselungsschlüssel ersetzen" }, "recoveryReplacePrivateKeyDesc": { - "message": "Your public-key encryption key pair could not be recovered. Do you want to replace your encryption key with a new key pair? This will require you to set up existing emergency-access and organization memberships again." + "message": "Dein öffentliches Verschlüsselungsschlüsselpaar konnte nicht wiederhergestellt werden. Möchtest du deinen Verschlüsselungsschlüssel durch ein neues Schlüsselpaar ersetzen? Dazu musst du bestehenden Notfallzugänge und Organisationsmitgliedschaften erneut einrichten." }, "recoveryStepSyncTitle": { - "message": "Synchronizing data" + "message": "Daten werden synchronisiert" }, "recoveryStepPrivateKeyTitle": { - "message": "Verifying encryption key integrity" + "message": "Integrität des Verschlüsselungsschlüssels wird verifiziert" }, "recoveryStepUserInfoTitle": { - "message": "Verifying user information" + "message": "Benutzerinformationen werden verifiziert" }, "recoveryStepCipherTitle": { - "message": "Verifying vault item integrity" + "message": "Integrität des Tresoreintrags wird verifiziert" }, "recoveryStepFoldersTitle": { - "message": "Verifying folder integrity" + "message": "Ordnerintegrität wird verifiziert" }, "dataRecoveryTitle": { - "message": "Data Recovery and Diagnostics" + "message": "Datenwiederherstellung und Diagnose" }, "dataRecoveryDescription": { - "message": "Use the data recovery tool to diagnose and repair issues with your account. After running diagnostics you have the option to save diagnostic logs for support and the option to repair any detected issues." + "message": "Verwende das Werkzeug zur Datenwiederherstellung, um Probleme mit deinem Konto zu diagnostizieren und zu beheben. Nach dem Ausführen der Diagnose hast du die Möglichkeit, die Diagnoseprotokolle für den Support zu speichern und alle erkannten Probleme zu beheben." }, "runDiagnostics": { - "message": "Run Diagnostics" + "message": "Diagnose ausführen" }, "repairIssues": { - "message": "Repair Issues" + "message": "Probleme beheben" }, "saveDiagnosticLogs": { - "message": "Save Diagnostic Logs" + "message": "Diagnoseprotokolle speichern" }, "sessionTimeoutSettingsManagedByOrganization": { "message": "Diese Einstellung wird von deiner Organisation verwaltet." @@ -12372,16 +12363,16 @@ "message": "Stell eine Entsperrmethode ein, um deine Timeout-Aktion zu ändern" }, "leaveConfirmationDialogTitle": { - "message": "Are you sure you want to leave?" + "message": "Bist du sicher, dass du gehen willst?" }, "leaveConfirmationDialogContentOne": { "message": "Wenn du ablehnst, bleiben deine persönlichen Einträge in deinem Konto erhalten, aber du wirst den Zugriff auf geteilte Einträge und Organisationsfunktionen verlieren." }, "leaveConfirmationDialogContentTwo": { - "message": "Contact your admin to regain access." + "message": "Kontaktiere deinen Administrator, um wieder Zugriff zu erhalten." }, "leaveConfirmationDialogConfirmButton": { - "message": "Leave $ORGANIZATION$", + "message": "$ORGANIZATION$ verlassen", "placeholders": { "organization": { "content": "$1", @@ -12390,10 +12381,10 @@ } }, "howToManageMyVault": { - "message": "How do I manage my vault?" + "message": "Wie kann ich meinen Tresor verwalten?" }, "transferItemsToOrganizationTitle": { - "message": "Transfer items to $ORGANIZATION$", + "message": "Einträge zu $ORGANIZATION$ übertragen", "placeholders": { "organization": { "content": "$1", @@ -12402,7 +12393,7 @@ } }, "transferItemsToOrganizationContent": { - "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "message": "$ORGANIZATION$ erfordert zur Sicherheit und Compliance, dass alle Einträge der Organisation gehören. Klicke auf Akzeptieren, um den Besitz deiner Einträge zu übertragen.", "placeholders": { "organization": { "content": "$1", diff --git a/apps/web/src/locales/el/messages.json b/apps/web/src/locales/el/messages.json index f8efb8f78f8..92dd55f4c8b 100644 --- a/apps/web/src/locales/el/messages.json +++ b/apps/web/src/locales/el/messages.json @@ -1975,12 +1975,6 @@ "exportFrom": { "message": "Εξαγωγή από" }, - "exportVault": { - "message": "Εξαγωγή Vault" - }, - "exportSecrets": { - "message": "Εξαγωγή μυστικών" - }, "fileFormat": { "message": "Μορφή Αρχείου" }, @@ -1993,9 +1987,6 @@ "confirmMasterPassword": { "message": "Επιβεβαίωση κύριου κωδικού πρόσβασης" }, - "confirmFormat": { - "message": "Επιβεβαίωση μορφής" - }, "filePassword": { "message": "Κωδικός πρόσβασης αρχείου" }, @@ -2306,6 +2297,9 @@ "tools": { "message": "Εργαλεία" }, + "import": { + "message": "Import" + }, "importData": { "message": "Εισαγωγή Δεδομένων" }, @@ -8753,9 +8747,6 @@ "server": { "message": "Διακομιστής" }, - "exportData": { - "message": "Εξαγωγή δεδομένων" - }, "exportingOrganizationSecretDataTitle": { "message": "Exporting Organization Secret Data" }, diff --git a/apps/web/src/locales/en_GB/messages.json b/apps/web/src/locales/en_GB/messages.json index cdcf727df78..e49ac4ac0f6 100644 --- a/apps/web/src/locales/en_GB/messages.json +++ b/apps/web/src/locales/en_GB/messages.json @@ -1975,12 +1975,6 @@ "exportFrom": { "message": "Export from" }, - "exportVault": { - "message": "Export vault" - }, - "exportSecrets": { - "message": "Export secrets" - }, "fileFormat": { "message": "File format" }, @@ -1993,9 +1987,6 @@ "confirmMasterPassword": { "message": "Confirm master password" }, - "confirmFormat": { - "message": "Confirm format" - }, "filePassword": { "message": "File password" }, @@ -2306,6 +2297,9 @@ "tools": { "message": "Tools" }, + "import": { + "message": "Import" + }, "importData": { "message": "Import data" }, @@ -8753,9 +8747,6 @@ "server": { "message": "Server" }, - "exportData": { - "message": "Export data" - }, "exportingOrganizationSecretDataTitle": { "message": "Exporting Organisation Secret Data" }, diff --git a/apps/web/src/locales/en_IN/messages.json b/apps/web/src/locales/en_IN/messages.json index ba0927f73fc..cfcfa1681c8 100644 --- a/apps/web/src/locales/en_IN/messages.json +++ b/apps/web/src/locales/en_IN/messages.json @@ -1975,12 +1975,6 @@ "exportFrom": { "message": "Export from" }, - "exportVault": { - "message": "Export vault" - }, - "exportSecrets": { - "message": "Export secrets" - }, "fileFormat": { "message": "File format" }, @@ -1993,9 +1987,6 @@ "confirmMasterPassword": { "message": "Confirm master password" }, - "confirmFormat": { - "message": "Confirm format" - }, "filePassword": { "message": "File password" }, @@ -2306,6 +2297,9 @@ "tools": { "message": "Tools" }, + "import": { + "message": "Import" + }, "importData": { "message": "Import data" }, @@ -8753,9 +8747,6 @@ "server": { "message": "Server" }, - "exportData": { - "message": "Export data" - }, "exportingOrganizationSecretDataTitle": { "message": "Exporting Organization Secret Data" }, diff --git a/apps/web/src/locales/eo/messages.json b/apps/web/src/locales/eo/messages.json index dbccabfe6d7..8dd7923b58a 100644 --- a/apps/web/src/locales/eo/messages.json +++ b/apps/web/src/locales/eo/messages.json @@ -1975,12 +1975,6 @@ "exportFrom": { "message": "Elporti el" }, - "exportVault": { - "message": "Elporti la trezorejon" - }, - "exportSecrets": { - "message": "Elporti la sekretojn" - }, "fileFormat": { "message": "Dosierformato" }, @@ -1993,9 +1987,6 @@ "confirmMasterPassword": { "message": "Konfirmi la ĉefpasvorton" }, - "confirmFormat": { - "message": "Konfirmu formaton" - }, "filePassword": { "message": "Pasvorto de la dosiero" }, @@ -2306,6 +2297,9 @@ "tools": { "message": "Iloj" }, + "import": { + "message": "Import" + }, "importData": { "message": "Importi Datumojn" }, @@ -8753,9 +8747,6 @@ "server": { "message": "Servilo" }, - "exportData": { - "message": "Elporti datumon" - }, "exportingOrganizationSecretDataTitle": { "message": "En elportado de Sekretaj Datumoj de Organizo" }, diff --git a/apps/web/src/locales/es/messages.json b/apps/web/src/locales/es/messages.json index 218cfea5617..b0778ef02cc 100644 --- a/apps/web/src/locales/es/messages.json +++ b/apps/web/src/locales/es/messages.json @@ -1975,12 +1975,6 @@ "exportFrom": { "message": "Exportar desde" }, - "exportVault": { - "message": "Exportar caja fuerte" - }, - "exportSecrets": { - "message": "Exportar secretos" - }, "fileFormat": { "message": "Formato de archivo" }, @@ -1993,9 +1987,6 @@ "confirmMasterPassword": { "message": "Confirmar contraseña maestra" }, - "confirmFormat": { - "message": "Confirmar formato" - }, "filePassword": { "message": "Contraseña del archivo" }, @@ -2306,6 +2297,9 @@ "tools": { "message": "Herramientas" }, + "import": { + "message": "Import" + }, "importData": { "message": "Importar datos" }, @@ -8753,9 +8747,6 @@ "server": { "message": "Servidor" }, - "exportData": { - "message": "Exportar datos" - }, "exportingOrganizationSecretDataTitle": { "message": "Exportando caja fuerte de la organización" }, diff --git a/apps/web/src/locales/et/messages.json b/apps/web/src/locales/et/messages.json index df1baf98bc2..03e56857413 100644 --- a/apps/web/src/locales/et/messages.json +++ b/apps/web/src/locales/et/messages.json @@ -1975,12 +1975,6 @@ "exportFrom": { "message": "Ekspordi asukohast" }, - "exportVault": { - "message": "Hoidla sisu eksportimine" - }, - "exportSecrets": { - "message": "Ekspordi saladused" - }, "fileFormat": { "message": "Failivorming" }, @@ -1993,9 +1987,6 @@ "confirmMasterPassword": { "message": "Kinnita ülemparool" }, - "confirmFormat": { - "message": "Kinnita formaat" - }, "filePassword": { "message": "Faili parool" }, @@ -2306,6 +2297,9 @@ "tools": { "message": "Tööriistad" }, + "import": { + "message": "Import" + }, "importData": { "message": "Andmete importimine" }, @@ -8753,9 +8747,6 @@ "server": { "message": "Server" }, - "exportData": { - "message": "Export data" - }, "exportingOrganizationSecretDataTitle": { "message": "Exporting Organization Secret Data" }, diff --git a/apps/web/src/locales/eu/messages.json b/apps/web/src/locales/eu/messages.json index e633cefdbf0..f28b334e9ff 100644 --- a/apps/web/src/locales/eu/messages.json +++ b/apps/web/src/locales/eu/messages.json @@ -1975,12 +1975,6 @@ "exportFrom": { "message": "Export from" }, - "exportVault": { - "message": "Esportatu kutxa gotorra" - }, - "exportSecrets": { - "message": "Export secrets" - }, "fileFormat": { "message": "Fitxategiaren formatua" }, @@ -1993,9 +1987,6 @@ "confirmMasterPassword": { "message": "Pasahitz nagusia baieztatu" }, - "confirmFormat": { - "message": "Formatua baieztatu" - }, "filePassword": { "message": "Fitxategi pasahitza" }, @@ -2306,6 +2297,9 @@ "tools": { "message": "Tresnak" }, + "import": { + "message": "Import" + }, "importData": { "message": "Inportatu datuak" }, @@ -8753,9 +8747,6 @@ "server": { "message": "Server" }, - "exportData": { - "message": "Export data" - }, "exportingOrganizationSecretDataTitle": { "message": "Exporting Organization Secret Data" }, diff --git a/apps/web/src/locales/fa/messages.json b/apps/web/src/locales/fa/messages.json index 5039584050d..75235dca1b3 100644 --- a/apps/web/src/locales/fa/messages.json +++ b/apps/web/src/locales/fa/messages.json @@ -1975,12 +1975,6 @@ "exportFrom": { "message": "برون ریزی از" }, - "exportVault": { - "message": "برون ریزی گاوصندوق" - }, - "exportSecrets": { - "message": "برون ریزی رازها" - }, "fileFormat": { "message": "فرمت پرونده" }, @@ -1993,9 +1987,6 @@ "confirmMasterPassword": { "message": "تأیید کلمه عبور اصلی" }, - "confirmFormat": { - "message": "فرمت را تأیید کنید" - }, "filePassword": { "message": "کلمه عبور پرونده" }, @@ -2306,6 +2297,9 @@ "tools": { "message": "ابزار" }, + "import": { + "message": "Import" + }, "importData": { "message": "درون ریزی داده" }, @@ -8753,9 +8747,6 @@ "server": { "message": "سرور" }, - "exportData": { - "message": "برون ریزی داده" - }, "exportingOrganizationSecretDataTitle": { "message": "برون ریزی داده‌های مخفی سازمان" }, diff --git a/apps/web/src/locales/fi/messages.json b/apps/web/src/locales/fi/messages.json index 8a70ab58fde..adda39d725b 100644 --- a/apps/web/src/locales/fi/messages.json +++ b/apps/web/src/locales/fi/messages.json @@ -1975,12 +1975,6 @@ "exportFrom": { "message": "Vie lähteestä" }, - "exportVault": { - "message": "Vie holvi" - }, - "exportSecrets": { - "message": "Vie salaisuudet" - }, "fileFormat": { "message": "Tiedostomuoto" }, @@ -1993,9 +1987,6 @@ "confirmMasterPassword": { "message": "Vahvista pääsalasana" }, - "confirmFormat": { - "message": "Vahvista muoto" - }, "filePassword": { "message": "Tiedoston salasana" }, @@ -2306,6 +2297,9 @@ "tools": { "message": "Työkalut" }, + "import": { + "message": "Import" + }, "importData": { "message": "Tuo tietoja" }, @@ -8753,9 +8747,6 @@ "server": { "message": "Palvelin" }, - "exportData": { - "message": "Vie tietoja" - }, "exportingOrganizationSecretDataTitle": { "message": "Organisaation salaisten tietojen vienti" }, diff --git a/apps/web/src/locales/fil/messages.json b/apps/web/src/locales/fil/messages.json index cd201f852ef..1a4720094b9 100644 --- a/apps/web/src/locales/fil/messages.json +++ b/apps/web/src/locales/fil/messages.json @@ -1975,12 +1975,6 @@ "exportFrom": { "message": "Export from" }, - "exportVault": { - "message": "I-export ang vault" - }, - "exportSecrets": { - "message": "Export secrets" - }, "fileFormat": { "message": "Format ng file" }, @@ -1993,9 +1987,6 @@ "confirmMasterPassword": { "message": "Kumpirmahin ang master password" }, - "confirmFormat": { - "message": "Kumpirmahin ang pag-format" - }, "filePassword": { "message": "Password ng file" }, @@ -2306,6 +2297,9 @@ "tools": { "message": "Mga Kagamitan" }, + "import": { + "message": "Import" + }, "importData": { "message": "Mag-import ng data" }, @@ -8753,9 +8747,6 @@ "server": { "message": "Server" }, - "exportData": { - "message": "I-export ang mga datos" - }, "exportingOrganizationSecretDataTitle": { "message": "Pag-eexport ng Sekretong Data ng Organisasyon" }, diff --git a/apps/web/src/locales/fr/messages.json b/apps/web/src/locales/fr/messages.json index 311f8912137..c2f107b9437 100644 --- a/apps/web/src/locales/fr/messages.json +++ b/apps/web/src/locales/fr/messages.json @@ -1975,12 +1975,6 @@ "exportFrom": { "message": "Exporter à partir de" }, - "exportVault": { - "message": "Exporter le coffre" - }, - "exportSecrets": { - "message": "Exporter les secrets" - }, "fileFormat": { "message": "Format de fichier" }, @@ -1993,9 +1987,6 @@ "confirmMasterPassword": { "message": "Confirmer le mot de passe principal" }, - "confirmFormat": { - "message": "Confirmer le format" - }, "filePassword": { "message": "Mot de Passe du Fichier" }, @@ -2306,6 +2297,9 @@ "tools": { "message": "Outils" }, + "import": { + "message": "Importer" + }, "importData": { "message": "Importer des données" }, @@ -8753,9 +8747,6 @@ "server": { "message": "Serveur" }, - "exportData": { - "message": "Exporter les données" - }, "exportingOrganizationSecretDataTitle": { "message": "Exportation des Données Secrètes de l'Organisation" }, diff --git a/apps/web/src/locales/gl/messages.json b/apps/web/src/locales/gl/messages.json index f882247b331..6e33295162c 100644 --- a/apps/web/src/locales/gl/messages.json +++ b/apps/web/src/locales/gl/messages.json @@ -1975,12 +1975,6 @@ "exportFrom": { "message": "Export from" }, - "exportVault": { - "message": "Export vault" - }, - "exportSecrets": { - "message": "Export secrets" - }, "fileFormat": { "message": "File format" }, @@ -1993,9 +1987,6 @@ "confirmMasterPassword": { "message": "Confirm master password" }, - "confirmFormat": { - "message": "Confirm format" - }, "filePassword": { "message": "File password" }, @@ -2306,6 +2297,9 @@ "tools": { "message": "Tools" }, + "import": { + "message": "Import" + }, "importData": { "message": "Import data" }, @@ -8753,9 +8747,6 @@ "server": { "message": "Server" }, - "exportData": { - "message": "Export data" - }, "exportingOrganizationSecretDataTitle": { "message": "Exporting Organization Secret Data" }, diff --git a/apps/web/src/locales/he/messages.json b/apps/web/src/locales/he/messages.json index 3a56db568a7..baf7bafeb7a 100644 --- a/apps/web/src/locales/he/messages.json +++ b/apps/web/src/locales/he/messages.json @@ -1975,12 +1975,6 @@ "exportFrom": { "message": "ייצא מ־" }, - "exportVault": { - "message": "ייצא כספת" - }, - "exportSecrets": { - "message": "ייצא סודות" - }, "fileFormat": { "message": "פורמט קובץ" }, @@ -1993,9 +1987,6 @@ "confirmMasterPassword": { "message": "אמת סיסמה ראשית" }, - "confirmFormat": { - "message": "אשר פורמט" - }, "filePassword": { "message": "סיסמת קובץ" }, @@ -2306,6 +2297,9 @@ "tools": { "message": "כלים" }, + "import": { + "message": "Import" + }, "importData": { "message": "ייבא נתונים" }, @@ -8753,9 +8747,6 @@ "server": { "message": "שרת" }, - "exportData": { - "message": "ייצא נתונים" - }, "exportingOrganizationSecretDataTitle": { "message": "ייצוא נתונים ארגון סודיים" }, diff --git a/apps/web/src/locales/hi/messages.json b/apps/web/src/locales/hi/messages.json index 5909b46ff33..bd4089e240f 100644 --- a/apps/web/src/locales/hi/messages.json +++ b/apps/web/src/locales/hi/messages.json @@ -1975,12 +1975,6 @@ "exportFrom": { "message": "Export from" }, - "exportVault": { - "message": "Export vault" - }, - "exportSecrets": { - "message": "Export secrets" - }, "fileFormat": { "message": "File format" }, @@ -1993,9 +1987,6 @@ "confirmMasterPassword": { "message": "Confirm master password" }, - "confirmFormat": { - "message": "Confirm format" - }, "filePassword": { "message": "File password" }, @@ -2306,6 +2297,9 @@ "tools": { "message": "Tools" }, + "import": { + "message": "Import" + }, "importData": { "message": "Import data" }, @@ -8753,9 +8747,6 @@ "server": { "message": "Server" }, - "exportData": { - "message": "Export data" - }, "exportingOrganizationSecretDataTitle": { "message": "Exporting Organization Secret Data" }, diff --git a/apps/web/src/locales/hr/messages.json b/apps/web/src/locales/hr/messages.json index ff7cf0be88e..1716bdd25e8 100644 --- a/apps/web/src/locales/hr/messages.json +++ b/apps/web/src/locales/hr/messages.json @@ -1975,12 +1975,6 @@ "exportFrom": { "message": "Izvezi iz" }, - "exportVault": { - "message": "Izvezi trezor" - }, - "exportSecrets": { - "message": "Izvezi tajne" - }, "fileFormat": { "message": "Format datoteke" }, @@ -1993,9 +1987,6 @@ "confirmMasterPassword": { "message": "Potvrdi glavnu lozinku" }, - "confirmFormat": { - "message": "Potvrdi oblik" - }, "filePassword": { "message": "Lozinka datoteke" }, @@ -2306,6 +2297,9 @@ "tools": { "message": "Alati" }, + "import": { + "message": "Import" + }, "importData": { "message": "Uvezi podatke" }, @@ -8753,9 +8747,6 @@ "server": { "message": "Poslužitelj" }, - "exportData": { - "message": "Izvezi podatke" - }, "exportingOrganizationSecretDataTitle": { "message": "Izvoz tajnih podataka organizacije" }, diff --git a/apps/web/src/locales/hu/messages.json b/apps/web/src/locales/hu/messages.json index eb437dcc678..38720b614fc 100644 --- a/apps/web/src/locales/hu/messages.json +++ b/apps/web/src/locales/hu/messages.json @@ -1975,12 +1975,6 @@ "exportFrom": { "message": "Exportálás innen:" }, - "exportVault": { - "message": "Széf exportálása" - }, - "exportSecrets": { - "message": "Titkos adatok exportálása" - }, "fileFormat": { "message": "Fájlformátum" }, @@ -1993,9 +1987,6 @@ "confirmMasterPassword": { "message": "Mesterjelszó megerősítése" }, - "confirmFormat": { - "message": "Formátum megerősítése" - }, "filePassword": { "message": "Fájl jelszó" }, @@ -2306,6 +2297,9 @@ "tools": { "message": "Eszközök" }, + "import": { + "message": "Importálás" + }, "importData": { "message": "Adatok importálása" }, @@ -8753,9 +8747,6 @@ "server": { "message": "Szerver" }, - "exportData": { - "message": "Adatok exportálása" - }, "exportingOrganizationSecretDataTitle": { "message": "Szervezeti titkos adatok exportálása" }, diff --git a/apps/web/src/locales/id/messages.json b/apps/web/src/locales/id/messages.json index 74804d5db8e..165deb7b917 100644 --- a/apps/web/src/locales/id/messages.json +++ b/apps/web/src/locales/id/messages.json @@ -1975,12 +1975,6 @@ "exportFrom": { "message": "Ekspor dari" }, - "exportVault": { - "message": "Ekspor Brankas" - }, - "exportSecrets": { - "message": "Ekspor rahasia" - }, "fileFormat": { "message": "Format Berkas" }, @@ -1993,9 +1987,6 @@ "confirmMasterPassword": { "message": "Konfirmasi sandi utama" }, - "confirmFormat": { - "message": "Konfirmasi format" - }, "filePassword": { "message": "Sandi berkas" }, @@ -2306,6 +2297,9 @@ "tools": { "message": "Alat" }, + "import": { + "message": "Import" + }, "importData": { "message": "Impor Data" }, @@ -8753,9 +8747,6 @@ "server": { "message": "Server" }, - "exportData": { - "message": "Export data" - }, "exportingOrganizationSecretDataTitle": { "message": "Exporting Organization Secret Data" }, diff --git a/apps/web/src/locales/it/messages.json b/apps/web/src/locales/it/messages.json index 3814df96b74..87015d32666 100644 --- a/apps/web/src/locales/it/messages.json +++ b/apps/web/src/locales/it/messages.json @@ -1975,12 +1975,6 @@ "exportFrom": { "message": "Esporta da" }, - "exportVault": { - "message": "Esporta cassaforte" - }, - "exportSecrets": { - "message": "Esporta segreti" - }, "fileFormat": { "message": "Formato file" }, @@ -1993,9 +1987,6 @@ "confirmMasterPassword": { "message": "Conferma password principale" }, - "confirmFormat": { - "message": "Conferma formato" - }, "filePassword": { "message": "Password del file" }, @@ -2306,6 +2297,9 @@ "tools": { "message": "Strumenti" }, + "import": { + "message": "Import" + }, "importData": { "message": "Importa dati" }, @@ -8753,9 +8747,6 @@ "server": { "message": "Server" }, - "exportData": { - "message": "Esporta dati" - }, "exportingOrganizationSecretDataTitle": { "message": "Esportando dati segreti dell'organizzazione" }, diff --git a/apps/web/src/locales/ja/messages.json b/apps/web/src/locales/ja/messages.json index 930b2ddd81d..b7f3426fa6e 100644 --- a/apps/web/src/locales/ja/messages.json +++ b/apps/web/src/locales/ja/messages.json @@ -1975,12 +1975,6 @@ "exportFrom": { "message": "エクスポート元" }, - "exportVault": { - "message": "保管庫のエクスポート" - }, - "exportSecrets": { - "message": "シークレットのエクスポート" - }, "fileFormat": { "message": "ファイル形式" }, @@ -1993,9 +1987,6 @@ "confirmMasterPassword": { "message": "マスターパスワードの確認" }, - "confirmFormat": { - "message": "フォーマットの確認" - }, "filePassword": { "message": "ファイルパスワード" }, @@ -2306,6 +2297,9 @@ "tools": { "message": "ツール" }, + "import": { + "message": "Import" + }, "importData": { "message": "データをインポート" }, @@ -8753,9 +8747,6 @@ "server": { "message": "サーバー" }, - "exportData": { - "message": "データのエクスポート" - }, "exportingOrganizationSecretDataTitle": { "message": "組織のシークレットデータのエクスポート" }, diff --git a/apps/web/src/locales/ka/messages.json b/apps/web/src/locales/ka/messages.json index d1f614c61d7..1018f2255fc 100644 --- a/apps/web/src/locales/ka/messages.json +++ b/apps/web/src/locales/ka/messages.json @@ -1975,12 +1975,6 @@ "exportFrom": { "message": "Export from" }, - "exportVault": { - "message": "ექსპორტი საცავის" - }, - "exportSecrets": { - "message": "Export secrets" - }, "fileFormat": { "message": "ფაილის ფორმატი" }, @@ -1993,9 +1987,6 @@ "confirmMasterPassword": { "message": "Confirm master password" }, - "confirmFormat": { - "message": "Confirm format" - }, "filePassword": { "message": "File password" }, @@ -2306,6 +2297,9 @@ "tools": { "message": "Tools" }, + "import": { + "message": "Import" + }, "importData": { "message": "Import data" }, @@ -8753,9 +8747,6 @@ "server": { "message": "Server" }, - "exportData": { - "message": "Export data" - }, "exportingOrganizationSecretDataTitle": { "message": "Exporting Organization Secret Data" }, diff --git a/apps/web/src/locales/km/messages.json b/apps/web/src/locales/km/messages.json index fd419fe7397..26f146809b5 100644 --- a/apps/web/src/locales/km/messages.json +++ b/apps/web/src/locales/km/messages.json @@ -1975,12 +1975,6 @@ "exportFrom": { "message": "Export from" }, - "exportVault": { - "message": "Export vault" - }, - "exportSecrets": { - "message": "Export secrets" - }, "fileFormat": { "message": "File format" }, @@ -1993,9 +1987,6 @@ "confirmMasterPassword": { "message": "Confirm master password" }, - "confirmFormat": { - "message": "Confirm format" - }, "filePassword": { "message": "File password" }, @@ -2306,6 +2297,9 @@ "tools": { "message": "Tools" }, + "import": { + "message": "Import" + }, "importData": { "message": "Import data" }, @@ -8753,9 +8747,6 @@ "server": { "message": "Server" }, - "exportData": { - "message": "Export data" - }, "exportingOrganizationSecretDataTitle": { "message": "Exporting Organization Secret Data" }, diff --git a/apps/web/src/locales/kn/messages.json b/apps/web/src/locales/kn/messages.json index 5d5c07f2baa..9b4407e3ec6 100644 --- a/apps/web/src/locales/kn/messages.json +++ b/apps/web/src/locales/kn/messages.json @@ -1975,12 +1975,6 @@ "exportFrom": { "message": "Export from" }, - "exportVault": { - "message": "ರಫ್ತು ವಾಲ್ಟ್" - }, - "exportSecrets": { - "message": "Export secrets" - }, "fileFormat": { "message": "ಫೈಲ್ ಮಾದರಿ" }, @@ -1993,9 +1987,6 @@ "confirmMasterPassword": { "message": "Confirm master password" }, - "confirmFormat": { - "message": "Confirm format" - }, "filePassword": { "message": "File password" }, @@ -2306,6 +2297,9 @@ "tools": { "message": "ಉಪಕರಣ" }, + "import": { + "message": "Import" + }, "importData": { "message": "ಡೇಟಾವನ್ನು ಆಮದು ಮಾಡಿ" }, @@ -8753,9 +8747,6 @@ "server": { "message": "Server" }, - "exportData": { - "message": "Export data" - }, "exportingOrganizationSecretDataTitle": { "message": "Exporting Organization Secret Data" }, diff --git a/apps/web/src/locales/ko/messages.json b/apps/web/src/locales/ko/messages.json index a3ea2982a4a..db88018ce26 100644 --- a/apps/web/src/locales/ko/messages.json +++ b/apps/web/src/locales/ko/messages.json @@ -1975,12 +1975,6 @@ "exportFrom": { "message": "Export from" }, - "exportVault": { - "message": "보관함 내보내기" - }, - "exportSecrets": { - "message": "비밀 데이터 내보내기" - }, "fileFormat": { "message": "파일 형식" }, @@ -1993,9 +1987,6 @@ "confirmMasterPassword": { "message": "마스터 비밀번호 확인" }, - "confirmFormat": { - "message": "형식 확인" - }, "filePassword": { "message": "파일 암호" }, @@ -2306,6 +2297,9 @@ "tools": { "message": "도구" }, + "import": { + "message": "Import" + }, "importData": { "message": "데이터 가져오기" }, @@ -8753,9 +8747,6 @@ "server": { "message": "Server" }, - "exportData": { - "message": "Export data" - }, "exportingOrganizationSecretDataTitle": { "message": "Exporting Organization Secret Data" }, diff --git a/apps/web/src/locales/lv/messages.json b/apps/web/src/locales/lv/messages.json index 42d8dc15f3c..4b1a5307179 100644 --- a/apps/web/src/locales/lv/messages.json +++ b/apps/web/src/locales/lv/messages.json @@ -1975,12 +1975,6 @@ "exportFrom": { "message": "Izgūt no" }, - "exportVault": { - "message": "Izgūt glabātavas saturu" - }, - "exportSecrets": { - "message": "Izgūt noslēpumus" - }, "fileFormat": { "message": "Datnes veids" }, @@ -1993,9 +1987,6 @@ "confirmMasterPassword": { "message": "Apstiprināt galveno paroli" }, - "confirmFormat": { - "message": "Apstiprināt veidolu" - }, "filePassword": { "message": "Datnes parole" }, @@ -2306,6 +2297,9 @@ "tools": { "message": "Rīki" }, + "import": { + "message": "Ievietot" + }, "importData": { "message": "Ievietot datus" }, @@ -8753,9 +8747,6 @@ "server": { "message": "Serveris" }, - "exportData": { - "message": "Izgūt datus" - }, "exportingOrganizationSecretDataTitle": { "message": "Apvienības slepeno datu izgūšana" }, diff --git a/apps/web/src/locales/ml/messages.json b/apps/web/src/locales/ml/messages.json index 907dd90fa2e..5943f3630a3 100644 --- a/apps/web/src/locales/ml/messages.json +++ b/apps/web/src/locales/ml/messages.json @@ -1975,12 +1975,6 @@ "exportFrom": { "message": "Export from" }, - "exportVault": { - "message": "വാൾട് എക്സ്പോർട്" - }, - "exportSecrets": { - "message": "Export secrets" - }, "fileFormat": { "message": "ഫയൽ ഫോർമാറ്റ്" }, @@ -1993,9 +1987,6 @@ "confirmMasterPassword": { "message": "Confirm master password" }, - "confirmFormat": { - "message": "Confirm format" - }, "filePassword": { "message": "File password" }, @@ -2306,6 +2297,9 @@ "tools": { "message": "ഉപകരണങ്ങൾ" }, + "import": { + "message": "Import" + }, "importData": { "message": "Import Data" }, @@ -8753,9 +8747,6 @@ "server": { "message": "Server" }, - "exportData": { - "message": "Export data" - }, "exportingOrganizationSecretDataTitle": { "message": "Exporting Organization Secret Data" }, diff --git a/apps/web/src/locales/mr/messages.json b/apps/web/src/locales/mr/messages.json index d8d7999dbe2..5d938996627 100644 --- a/apps/web/src/locales/mr/messages.json +++ b/apps/web/src/locales/mr/messages.json @@ -1975,12 +1975,6 @@ "exportFrom": { "message": "Export from" }, - "exportVault": { - "message": "Export vault" - }, - "exportSecrets": { - "message": "Export secrets" - }, "fileFormat": { "message": "File format" }, @@ -1993,9 +1987,6 @@ "confirmMasterPassword": { "message": "Confirm master password" }, - "confirmFormat": { - "message": "Confirm format" - }, "filePassword": { "message": "File password" }, @@ -2306,6 +2297,9 @@ "tools": { "message": "Tools" }, + "import": { + "message": "Import" + }, "importData": { "message": "Import data" }, @@ -8753,9 +8747,6 @@ "server": { "message": "Server" }, - "exportData": { - "message": "Export data" - }, "exportingOrganizationSecretDataTitle": { "message": "Exporting Organization Secret Data" }, diff --git a/apps/web/src/locales/my/messages.json b/apps/web/src/locales/my/messages.json index fd419fe7397..26f146809b5 100644 --- a/apps/web/src/locales/my/messages.json +++ b/apps/web/src/locales/my/messages.json @@ -1975,12 +1975,6 @@ "exportFrom": { "message": "Export from" }, - "exportVault": { - "message": "Export vault" - }, - "exportSecrets": { - "message": "Export secrets" - }, "fileFormat": { "message": "File format" }, @@ -1993,9 +1987,6 @@ "confirmMasterPassword": { "message": "Confirm master password" }, - "confirmFormat": { - "message": "Confirm format" - }, "filePassword": { "message": "File password" }, @@ -2306,6 +2297,9 @@ "tools": { "message": "Tools" }, + "import": { + "message": "Import" + }, "importData": { "message": "Import data" }, @@ -8753,9 +8747,6 @@ "server": { "message": "Server" }, - "exportData": { - "message": "Export data" - }, "exportingOrganizationSecretDataTitle": { "message": "Exporting Organization Secret Data" }, diff --git a/apps/web/src/locales/nb/messages.json b/apps/web/src/locales/nb/messages.json index 444a13a68e1..9e5c0a6cbf4 100644 --- a/apps/web/src/locales/nb/messages.json +++ b/apps/web/src/locales/nb/messages.json @@ -1975,12 +1975,6 @@ "exportFrom": { "message": "Eksport fra" }, - "exportVault": { - "message": "Eksporter hvelvet" - }, - "exportSecrets": { - "message": "Eksporter hemmeligheter" - }, "fileFormat": { "message": "Filformat" }, @@ -1993,9 +1987,6 @@ "confirmMasterPassword": { "message": "Bekreft hovedpassord" }, - "confirmFormat": { - "message": "Bekreft format" - }, "filePassword": { "message": "Filpassord" }, @@ -2306,6 +2297,9 @@ "tools": { "message": "Verktøy" }, + "import": { + "message": "Import" + }, "importData": { "message": "Importer data" }, @@ -8753,9 +8747,6 @@ "server": { "message": "Server" }, - "exportData": { - "message": "Eksporter data" - }, "exportingOrganizationSecretDataTitle": { "message": "Eksporterer organisasjonens hemmelige data" }, diff --git a/apps/web/src/locales/ne/messages.json b/apps/web/src/locales/ne/messages.json index 99f87c4fb75..9de1b17c391 100644 --- a/apps/web/src/locales/ne/messages.json +++ b/apps/web/src/locales/ne/messages.json @@ -1975,12 +1975,6 @@ "exportFrom": { "message": "Export from" }, - "exportVault": { - "message": "Export vault" - }, - "exportSecrets": { - "message": "Export secrets" - }, "fileFormat": { "message": "File format" }, @@ -1993,9 +1987,6 @@ "confirmMasterPassword": { "message": "Confirm master password" }, - "confirmFormat": { - "message": "Confirm format" - }, "filePassword": { "message": "File password" }, @@ -2306,6 +2297,9 @@ "tools": { "message": "Tools" }, + "import": { + "message": "Import" + }, "importData": { "message": "Import data" }, @@ -8753,9 +8747,6 @@ "server": { "message": "Server" }, - "exportData": { - "message": "Export data" - }, "exportingOrganizationSecretDataTitle": { "message": "Exporting Organization Secret Data" }, diff --git a/apps/web/src/locales/nl/messages.json b/apps/web/src/locales/nl/messages.json index 0b648eaf204..51ac08fd814 100644 --- a/apps/web/src/locales/nl/messages.json +++ b/apps/web/src/locales/nl/messages.json @@ -1975,12 +1975,6 @@ "exportFrom": { "message": "Exporteren vanuit" }, - "exportVault": { - "message": "Kluis exporteren" - }, - "exportSecrets": { - "message": "Geheimen exporteren" - }, "fileFormat": { "message": "Bestandsindeling" }, @@ -1993,9 +1987,6 @@ "confirmMasterPassword": { "message": "Nieuw hoofdwachtwoord bevestigen" }, - "confirmFormat": { - "message": "Formaat bevestigen" - }, "filePassword": { "message": "Bestandswachtwoord" }, @@ -2306,6 +2297,9 @@ "tools": { "message": "Hulpmiddelen" }, + "import": { + "message": "Importeren" + }, "importData": { "message": "Gegevens importeren" }, @@ -8753,9 +8747,6 @@ "server": { "message": "Server" }, - "exportData": { - "message": "Gegevens exporteren" - }, "exportingOrganizationSecretDataTitle": { "message": "Geheime gegevens van de organisatie exporteren" }, diff --git a/apps/web/src/locales/nn/messages.json b/apps/web/src/locales/nn/messages.json index 8d1c2d1b394..389bf4516e3 100644 --- a/apps/web/src/locales/nn/messages.json +++ b/apps/web/src/locales/nn/messages.json @@ -1975,12 +1975,6 @@ "exportFrom": { "message": "Export from" }, - "exportVault": { - "message": "Export vault" - }, - "exportSecrets": { - "message": "Export secrets" - }, "fileFormat": { "message": "File format" }, @@ -1993,9 +1987,6 @@ "confirmMasterPassword": { "message": "Confirm master password" }, - "confirmFormat": { - "message": "Confirm format" - }, "filePassword": { "message": "File password" }, @@ -2306,6 +2297,9 @@ "tools": { "message": "Tools" }, + "import": { + "message": "Import" + }, "importData": { "message": "Import data" }, @@ -8753,9 +8747,6 @@ "server": { "message": "Server" }, - "exportData": { - "message": "Export data" - }, "exportingOrganizationSecretDataTitle": { "message": "Exporting Organization Secret Data" }, diff --git a/apps/web/src/locales/or/messages.json b/apps/web/src/locales/or/messages.json index fd419fe7397..26f146809b5 100644 --- a/apps/web/src/locales/or/messages.json +++ b/apps/web/src/locales/or/messages.json @@ -1975,12 +1975,6 @@ "exportFrom": { "message": "Export from" }, - "exportVault": { - "message": "Export vault" - }, - "exportSecrets": { - "message": "Export secrets" - }, "fileFormat": { "message": "File format" }, @@ -1993,9 +1987,6 @@ "confirmMasterPassword": { "message": "Confirm master password" }, - "confirmFormat": { - "message": "Confirm format" - }, "filePassword": { "message": "File password" }, @@ -2306,6 +2297,9 @@ "tools": { "message": "Tools" }, + "import": { + "message": "Import" + }, "importData": { "message": "Import data" }, @@ -8753,9 +8747,6 @@ "server": { "message": "Server" }, - "exportData": { - "message": "Export data" - }, "exportingOrganizationSecretDataTitle": { "message": "Exporting Organization Secret Data" }, diff --git a/apps/web/src/locales/pl/messages.json b/apps/web/src/locales/pl/messages.json index e90d3e1ab47..18207f16ca6 100644 --- a/apps/web/src/locales/pl/messages.json +++ b/apps/web/src/locales/pl/messages.json @@ -1975,12 +1975,6 @@ "exportFrom": { "message": "Eksportuj z" }, - "exportVault": { - "message": "Eksportuj sejf" - }, - "exportSecrets": { - "message": "Eksportuj sekrety" - }, "fileFormat": { "message": "Format pliku" }, @@ -1993,9 +1987,6 @@ "confirmMasterPassword": { "message": "Potwierdź hasło główne" }, - "confirmFormat": { - "message": "Potwierdź format" - }, "filePassword": { "message": "Hasło pliku" }, @@ -2306,6 +2297,9 @@ "tools": { "message": "Narzędzia" }, + "import": { + "message": "Import" + }, "importData": { "message": "Importuj dane" }, @@ -8753,9 +8747,6 @@ "server": { "message": "Serwer" }, - "exportData": { - "message": "Eksportuj dane" - }, "exportingOrganizationSecretDataTitle": { "message": "Eksportowanie sekretnych danych organizacji" }, diff --git a/apps/web/src/locales/pt_BR/messages.json b/apps/web/src/locales/pt_BR/messages.json index 4c79ec5ecc4..297afd6f0bb 100644 --- a/apps/web/src/locales/pt_BR/messages.json +++ b/apps/web/src/locales/pt_BR/messages.json @@ -1519,7 +1519,7 @@ "message": "Pressione sua YubiKey para autenticar-se" }, "authenticationTimeout": { - "message": "Tempo limite da autenticação atingido" + "message": "Limite de tempo da autenticação atingido" }, "authenticationSessionTimedOut": { "message": "A sessão de autenticação expirou. Reinicie o processo de autenticação." @@ -1975,12 +1975,6 @@ "exportFrom": { "message": "Exportar de" }, - "exportVault": { - "message": "Exportar cofre" - }, - "exportSecrets": { - "message": "Exportar segredos" - }, "fileFormat": { "message": "Formato do arquivo" }, @@ -1993,9 +1987,6 @@ "confirmMasterPassword": { "message": "Confirmar senha principal" }, - "confirmFormat": { - "message": "Confirmar formato" - }, "filePassword": { "message": "Senha do arquivo" }, @@ -2306,6 +2297,9 @@ "tools": { "message": "Ferramentas" }, + "import": { + "message": "Importar" + }, "importData": { "message": "Importar dados" }, @@ -2871,7 +2865,7 @@ } }, "noExposedPasswords": { - "message": "Nenhum item em seu cofre tem senhas expostas em vazamentos de dados conhecidos." + "message": "Nenhum item no seu cofre tem senhas expostas em vazamentos de dados conhecidos." }, "checkExposedPasswords": { "message": "Conferir senhas expostas" @@ -3078,7 +3072,7 @@ "message": "Acesso de emergência" }, "premiumSignUpReports": { - "message": "Higiene de senhas, saúde da conta, e relatórios sobre vazamentos de dados para manter o seu cofre seguro." + "message": "Relatórios de higiene de senha, saúde da conta, e vazamentos de dados para manter o seu cofre seguro." }, "premiumSignUpTotp": { "message": "Gerador de código de verificação TOTP (2FA) para credenciais no seu cofre." @@ -5045,13 +5039,13 @@ "message": "Filtros" }, "vaultTimeout": { - "message": "Tempo limite do cofre" + "message": "Limite de tempo do cofre" }, "vaultTimeout1": { - "message": "Tempo limite" + "message": "Limite de tempo" }, "vaultTimeoutDesc": { - "message": "Escolha quando o seu cofre executará a ação do tempo limite do cofre." + "message": "Escolha quando o seu cofre executará a ação do limite de tempo do cofre." }, "vaultTimeoutLogoutDesc": { "message": "Escolha quando seu cofre será desconectado." @@ -5332,7 +5326,7 @@ "message": "Preferência do usuário" }, "vaultTimeoutAction": { - "message": "Ação do tempo limite do cofre" + "message": "Ação do limite de tempo do cofre" }, "vaultTimeoutActionLockDesc": { "message": "A senha principal ou outro método de desbloqueio é necessário para acessar seu cofre novamente." @@ -5409,10 +5403,10 @@ } }, "vaultTimeoutLogOutConfirmation": { - "message": "Desconectar-se irá remover todo o acesso ao seu cofre e requirirá autenticação on-line após o período de tempo limite. Tem certeza de que deseja usar esta configuração?" + "message": "Desconectar-se irá remover todo o acesso ao seu cofre e requirirá autenticação on-line após o período de limite de tempo. Tem certeza de que deseja usar esta configuração?" }, "vaultTimeoutLogOutConfirmationTitle": { - "message": "Confirmação de ação do tempo limite" + "message": "Confirmação de ação do limite de tempo" }, "hidePasswords": { "message": "Ocultar senhas" @@ -6703,22 +6697,22 @@ "message": "Sua organização atualizou suas opções de descriptografia. Configure uma senha principal para acessar seu cofre." }, "sessionTimeoutPolicyTitle": { - "message": "Tempo limite da sessão" + "message": "Limite de tempo da sessão" }, "sessionTimeoutPolicyDescription": { - "message": "Configure um tempo limite máximo para a sessão de todos os membros, exceto os proprietários." + "message": "Configure um limite de tempo máximo para a sessão de todos os membros, exceto os proprietários." }, "maximumAllowedTimeout": { - "message": "Tempo limite máximo permitido" + "message": "Limite de tempo máximo permitido" }, "maximumAllowedTimeoutRequired": { - "message": "Tempo limite máximo permitido é necessário." + "message": "Limite de tempo máximo permitido é necessário." }, "sessionTimeoutPolicyInvalidTime": { "message": "Tempo é inválido. Altere pelo menos um valor." }, "sessionTimeoutAction": { - "message": "Ação do tempo limite da sessão" + "message": "Ação do limite de tempo da sessão" }, "immediately": { "message": "Imediatamente" @@ -6736,7 +6730,7 @@ "message": "Minutos" }, "sessionTimeoutConfirmationNeverTitle": { - "message": "Você tem certeza de que deseja permitir um tempo limite máximo de \"Nunca\" para todos os membros?" + "message": "Você tem certeza de que deseja permitir um limite de tempo máximo de \"Nunca\" para todos os membros?" }, "sessionTimeoutConfirmationNeverDescription": { "message": "Esta opção salvará as chaves criptográficas dos seus membros em seus dispositivos. Se escolher esta opção, certifique-se de que seus dispositivos estão adequadamente protegidos." @@ -6751,7 +6745,7 @@ "message": "O aplicativo móvel e web usarão \"no reinício do aplicativo\" como tempo máximo permitido, já que a opção não é suportada." }, "vaultTimeoutPolicyInEffect": { - "message": "As políticas da sua organização configuraram o seu máximo permitido do tempo limite do cofre para $HOURS$ hora(s) e $MINUTES$ minuto(s).", + "message": "As políticas da sua organização configuraram o seu máximo permitido do limite de tempo do cofre para $HOURS$ hora(s) e $MINUTES$ minuto(s).", "placeholders": { "hours": { "content": "$1", @@ -6777,7 +6771,7 @@ } }, "vaultTimeoutPolicyWithActionInEffect": { - "message": "As políticas da sua organização estão afetando o tempo limite do seu cofre. \nO tempo limite máximo permitido para o cofre é $HOURS$ hora(s) e $MINUTES$ minuto(s). A ação de tempo limite do seu cofre está configurada como $ACTION$.", + "message": "As políticas da sua organização estão afetando o limite de tempo do seu cofre. \nO limite de tempo máximo permitido para o cofre é $HOURS$ hora(s) e $MINUTES$ minuto(s). A ação de limite de tempo do seu cofre está configurada como $ACTION$.", "placeholders": { "hours": { "content": "$1", @@ -6794,7 +6788,7 @@ } }, "vaultTimeoutActionPolicyInEffect": { - "message": "As políticas da sua organização configuraram a ação do tempo limite do seu cofre para $ACTION$.", + "message": "As políticas da sua organização configuraram a ação do limite de tempo do seu cofre para $ACTION$.", "placeholders": { "action": { "content": "$1", @@ -6803,13 +6797,13 @@ } }, "vaultTimeoutToLarge": { - "message": "O tempo limite do seu cofre excede as restrições estabelecidas pela sua organização." + "message": "O limite de tempo do seu cofre excede as restrições estabelecidas pela sua organização." }, "vaultCustomTimeoutMinimum": { - "message": "O mínimo do tempo limite personalizado é de 1 minuto." + "message": "O mínimo do limite de tempo personalizado é de 1 minuto." }, "vaultTimeoutRangeError": { - "message": "Tempo limite do cofre não está dentro do intervalo permitido." + "message": "Limite de tempo do cofre não está dentro do intervalo permitido." }, "disableExport": { "message": "Remover exportação" @@ -8753,9 +8747,6 @@ "server": { "message": "Servidor" }, - "exportData": { - "message": "Exportar dados" - }, "exportingOrganizationSecretDataTitle": { "message": "Exportando dados de segredos da organização" }, @@ -9188,7 +9179,7 @@ "message": "Remover acesso" }, "checkForBreaches": { - "message": "Conferir vazamentos de dados conhecidos por esta senha" + "message": "Conferir se esta senha vazou ao público" }, "exposedMasterPassword": { "message": "Senha principal exposta" @@ -12204,28 +12195,28 @@ "message": "Um domínio deve ser reivindicado antes de ativar esta política." }, "unlockMethodNeededToChangeTimeoutActionDesc": { - "message": "Configure um método de desbloqueio para alterar a ação do tempo limite do cofre." + "message": "Configure um método de desbloqueio para alterar a ação do limite de tempo do cofre." }, "vaultTimeoutPolicyAffectingOptions": { - "message": "Os requisitos de política empresarial foram aplicados às suas opções de tempo limite" + "message": "Os requisitos de política empresarial foram aplicados às suas opções de limite de tempo" }, "vaultTimeoutTooLarge": { - "message": "O tempo limite do seu cofre excede as restrições estabelecidas pela sua organização." + "message": "O limite de tempo do seu cofre excede as restrições estabelecidas pela sua organização." }, "neverLockWarning": { "message": "Você tem certeza que deseja usar a opção \"Nunca\"? Ao usar o \"Nunca\", a chave de criptografia do seu cofre é armazenada no seu dispositivo. Se você usar esta opção, deve garantir que mantém seu dispositivo devidamente protegido." }, "sessionTimeoutSettingsAction": { - "message": "Ação do tempo limite" + "message": "Ação do limite de tempo" }, "sessionTimeoutHeader": { - "message": "Tempo limite da sessão" + "message": "Limite de tempo da sessão" }, "appearance": { "message": "Aparência" }, "vaultTimeoutPolicyMaximumError": { - "message": "O tempo limite excede a restrição configurada pela sua organização: máximo de $HOURS$ hora(s) e $MINUTES$ minuto(s)", + "message": "O limite de tempo excede a restrição configurada pela sua organização: máximo de $HOURS$ hora(s) e $MINUTES$ minuto(s)", "placeholders": { "hours": { "content": "$1", @@ -12350,10 +12341,10 @@ } }, "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { - "message": "A sua organização configurou o tempo de limite máximo padrão da sessão para ser no recarregar do navegador." + "message": "A sua organização configurou o limite de tempo padrão da sessão para ser no recarregar do navegador." }, "sessionTimeoutSettingsPolicyMaximumError": { - "message": "O tempo limite máximo não pode exceder $HOURS$ hora(s) e $MINUTES$ minuto(s)", + "message": "O limite de tempo máximo não pode exceder $HOURS$ hora(s) e $MINUTES$ minuto(s)", "placeholders": { "hours": { "content": "$1", @@ -12369,7 +12360,7 @@ "message": "No recarregar do navegador" }, "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { - "message": "Configure um método de desbloqueio para alterar a ação do tempo limite" + "message": "Configure um método de desbloqueio para alterar a ação do limite de tempo" }, "leaveConfirmationDialogTitle": { "message": "Tem certeza de que quer sair?" diff --git a/apps/web/src/locales/pt_PT/messages.json b/apps/web/src/locales/pt_PT/messages.json index 7b0e9e8a197..a3a5a627215 100644 --- a/apps/web/src/locales/pt_PT/messages.json +++ b/apps/web/src/locales/pt_PT/messages.json @@ -1975,12 +1975,6 @@ "exportFrom": { "message": "Exportar de" }, - "exportVault": { - "message": "Exportar cofre" - }, - "exportSecrets": { - "message": "Exportar segredos" - }, "fileFormat": { "message": "Formato do ficheiro" }, @@ -1993,9 +1987,6 @@ "confirmMasterPassword": { "message": "Confirmar a palavra-passe mestra" }, - "confirmFormat": { - "message": "Confirmar formato" - }, "filePassword": { "message": "Palavra-passe do ficheiro" }, @@ -2306,6 +2297,9 @@ "tools": { "message": "Ferramentas" }, + "import": { + "message": "Importar" + }, "importData": { "message": "Importar dados" }, @@ -8753,9 +8747,6 @@ "server": { "message": "Servidor" }, - "exportData": { - "message": "Exportar dados" - }, "exportingOrganizationSecretDataTitle": { "message": "A exportar dados secretos da organização" }, @@ -12238,43 +12229,43 @@ } }, "removeMasterPasswordForOrgUserKeyConnector": { - "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + "message": "A sua organização já não utiliza palavras-passe mestras para iniciar sessão no Bitwarden. Para continuar, verifique a organização e o domínio." }, "continueWithLogIn": { - "message": "Continue with log in" + "message": "Continuar com o início de sessão" }, "doNotContinue": { - "message": "Do not continue" + "message": "Não continuar" }, "domain": { - "message": "Domain" + "message": "Domínio" }, "keyConnectorDomainTooltip": { - "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + "message": "Este domínio armazenará as chaves de encriptação da sua conta, portanto certifique-se de que confia nele. Se não tiver a certeza, verifique com o seu administrador." }, "verifyYourOrganization": { - "message": "Verify your organization to log in" + "message": "Verifique a sua organização para iniciar sessão" }, "organizationVerified": { - "message": "Organization verified" + "message": "Organização verificada" }, "domainVerified": { - "message": "Domain verified" + "message": "Domínio verificado" }, "leaveOrganizationContent": { - "message": "If you don't verify your organization, your access to the organization will be revoked." + "message": "Se não verificar a sua organização, o seu acesso à organização será revogado." }, "leaveNow": { - "message": "Leave now" + "message": "Sair agora" }, "verifyYourDomainToLogin": { - "message": "Verify your domain to log in" + "message": "Verifique o seu domínio para iniciar sessão" }, "verifyYourDomainDescription": { - "message": "To continue with log in, verify this domain." + "message": "Para continuar com o início de sessão, verifique este domínio." }, "confirmKeyConnectorOrganizationUserDescription": { - "message": "To continue with log in, verify the organization and domain." + "message": "Para continuar com o início de sessão, verifique a organização e o domínio." }, "confirmNoSelectedCriticalApplicationsTitle": { "message": "Não foram selecionadas aplicações críticas" diff --git a/apps/web/src/locales/ro/messages.json b/apps/web/src/locales/ro/messages.json index 9ae83220ebd..1a573ef82fe 100644 --- a/apps/web/src/locales/ro/messages.json +++ b/apps/web/src/locales/ro/messages.json @@ -1975,12 +1975,6 @@ "exportFrom": { "message": "Export from" }, - "exportVault": { - "message": "Export seif" - }, - "exportSecrets": { - "message": "Export secrets" - }, "fileFormat": { "message": "Format fișier" }, @@ -1993,9 +1987,6 @@ "confirmMasterPassword": { "message": "Confirmare parolă principală" }, - "confirmFormat": { - "message": "Confirmare format" - }, "filePassword": { "message": "Parola fișierului" }, @@ -2306,6 +2297,9 @@ "tools": { "message": "Unelte" }, + "import": { + "message": "Import" + }, "importData": { "message": "Importare date" }, @@ -8753,9 +8747,6 @@ "server": { "message": "Server" }, - "exportData": { - "message": "Export data" - }, "exportingOrganizationSecretDataTitle": { "message": "Exporting Organization Secret Data" }, diff --git a/apps/web/src/locales/ru/messages.json b/apps/web/src/locales/ru/messages.json index 7637c2baab5..0d7ccc71c00 100644 --- a/apps/web/src/locales/ru/messages.json +++ b/apps/web/src/locales/ru/messages.json @@ -1522,7 +1522,7 @@ "message": "Таймаут аутентификации" }, "authenticationSessionTimedOut": { - "message": "Сеанс аутентификации завершился по времени. Пожалуйста, попробуйте войти еще раз." + "message": "Сессия аутентификации завершилась по времени. Пожалуйста, перезапустите процесс авторизации." }, "verifyYourIdentity": { "message": "Подтвердите вашу личность" @@ -1975,12 +1975,6 @@ "exportFrom": { "message": "Экспорт из" }, - "exportVault": { - "message": "Экспорт хранилища" - }, - "exportSecrets": { - "message": "Экспорт секретов" - }, "fileFormat": { "message": "Формат файла" }, @@ -1993,9 +1987,6 @@ "confirmMasterPassword": { "message": "Подтвердите мастер-пароль" }, - "confirmFormat": { - "message": "Подтвердить формат" - }, "filePassword": { "message": "Пароль к файлу" }, @@ -2306,6 +2297,9 @@ "tools": { "message": "Инструменты" }, + "import": { + "message": "Импорт" + }, "importData": { "message": "Импорт данных" }, @@ -4479,7 +4473,7 @@ "message": "Обновить браузер" }, "generatingYourAccessIntelligence": { - "message": "Generating your Access Intelligence..." + "message": "Ваша информация о доступе генерируется..." }, "fetchingMemberData": { "message": "Получение данных о пользователях..." @@ -5054,7 +5048,7 @@ "message": "Выберите тайм-аут для хранилища и действие, которое необходимо предпринять." }, "vaultTimeoutLogoutDesc": { - "message": "Выберите, когда сеанс хранилища будет завершен." + "message": "Выберите время выхода из хранилища." }, "oneMinute": { "message": "1 минута" @@ -6466,7 +6460,7 @@ "message": "Повторно приглашен успешно" }, "bulkReinviteSuccessToast": { - "message": "$COUNT$ users re-invited", + "message": "$COUNT$ пользователей снова приглашены", "placeholders": { "count": { "content": "$1", @@ -6475,7 +6469,7 @@ } }, "bulkReinviteLimitedSuccessToast": { - "message": "$LIMIT$ of $SELECTEDCOUNT$ users re-invited. $EXCLUDEDCOUNT$ were not invited due to the $LIMIT$ invite limit.", + "message": "$LIMIT$ из $SELECTEDCOUNT$ пользователей повторно приглашены. $EXCLUDEDCOUNT$ не был приглашен из-за лимита приглашений $LIMIT$.", "placeholders": { "limit": { "content": "$1", @@ -6685,7 +6679,7 @@ "message": "Мастер-пароль не соответствует требованиям политики этой организации. Чтобы присоединиться к организации, нужно обновить мастер-пароль. Текущая сессия будет завершена и потребуется повторная авторизация. Сессии на других устройствах могут оставаться активными в течение часа." }, "updateWeakMasterPasswordWarning": { - "message": "Ваш мастер-пароль не соответствует требованиям политики вашей организации. Для доступа к хранилищу вы должны обновить свой мастер-пароль прямо сейчас. При этом текущий сеанс будет завершен и потребуется повторная авторизация. Сеансы на других устройствах могут оставаться активными в течение часа." + "message": "Ваш мастер-пароль не соответствует требованиям политики вашей организации. Для доступа к хранилищу вы должны обновить свой мастер-пароль прямо сейчас. При этом текущая сессия будет завершена и потребуется повторная авторизация. Сессии на других устройствах могут оставаться активными в течение часа." }, "automaticAppLoginWithSSO": { "message": "Автовход с помощью SSO" @@ -6703,10 +6697,10 @@ "message": "Ваша организация обновила параметры расшифровки. Пожалуйста, установите мастер-пароль для доступа к вашему хранилищу." }, "sessionTimeoutPolicyTitle": { - "message": "Тайм-аут сеанса" + "message": "Тайм-аут сессии" }, "sessionTimeoutPolicyDescription": { - "message": "Установите максимальный тайм-аут сеанса для всех участников, кроме владельцев." + "message": "Установите максимальный тайм-аут сессии для всех участников, кроме владельцев." }, "maximumAllowedTimeout": { "message": "Максимально допустимый тайм-аут" @@ -6718,7 +6712,7 @@ "message": "Время указано неверно. Измените хотя бы одно значение." }, "sessionTimeoutAction": { - "message": "Действие при тайм-ауте сеанса" + "message": "Действие при тайм-ауте сессии" }, "immediately": { "message": "Немедленно" @@ -8753,9 +8747,6 @@ "server": { "message": "Сервер" }, - "exportData": { - "message": "Экспорт данных" - }, "exportingOrganizationSecretDataTitle": { "message": "Экспорт секретных данных организации" }, @@ -12000,7 +11991,7 @@ "message": "Членство Families" }, "advancedOnlineSecurity": { - "message": "Advanced online security" + "message": "Расширенная онлайн-безопасность" }, "planDescFamiliesV2": { "message": "Премиальная защищенность \n для вашей семьи" @@ -12210,22 +12201,22 @@ "message": "К настройкам тайм-аута были применены требования корпоративной политики" }, "vaultTimeoutTooLarge": { - "message": "Your vault timeout exceeds the restrictions set by your organization." + "message": "Тайм-аут вашего хранилища превышает ограничения, установленные вашей организацией." }, "neverLockWarning": { - "message": "Are you sure you want to use the \"Never\" option? Setting your lock options to \"Never\" stores your vault's encryption key on your device. If you use this option you should ensure that you keep your device properly protected." + "message": "Вы действительно хотите отключить блокировку хранилища? В этом случае ключ шифрования вашего хранилища будет сохранен на вашем устройстве. Отключая блокировку, вы должны убедиться, что ваше устройство надежно защищено." }, "sessionTimeoutSettingsAction": { - "message": "Timeout action" + "message": "Тайм-аут действия" }, "sessionTimeoutHeader": { - "message": "Session timeout" + "message": "Тайм-аут сессии" }, "appearance": { - "message": "Appearance" + "message": "Внешний вид" }, "vaultTimeoutPolicyMaximumError": { - "message": "Timeout exceeds the restriction set by your organization: $HOURS$ hour(s) and $MINUTES$ minute(s) maximum", + "message": "Время ожидания превышает установленное организацией максимальное ограничение: $HOURS$ час. $MINUTES$ мин.", "placeholders": { "hours": { "content": "$1", @@ -12334,10 +12325,10 @@ "message": "Сохранить журналы диагностики" }, "sessionTimeoutSettingsManagedByOrganization": { - "message": "This setting is managed by your organization." + "message": "Эта настройка управляется вашей организацией." }, "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { - "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "message": "В вашей организации максимальный тайм-аут сессии установлен равным $HOURS$ час. и $MINUTES$ мин.", "placeholders": { "hours": { "content": "$1", @@ -12350,10 +12341,10 @@ } }, "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { - "message": "Your organization has set the default session timeout to On browser refresh." + "message": "Ваша организация установила тайм-аут сессии по умолчанию на При обновлении браузера." }, "sessionTimeoutSettingsPolicyMaximumError": { - "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "message": "Максимальный тайм-аут не может превышать $HOURS$ час. и $MINUTES$ мин.", "placeholders": { "hours": { "content": "$1", @@ -12366,10 +12357,10 @@ } }, "sessionTimeoutOnRestart": { - "message": "On browser refresh" + "message": "При обновлении браузера" }, "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { - "message": "Set an unlock method to change your timeout action" + "message": "Установите способ разблокировки для изменения действия при истечении тайм-аута" }, "leaveConfirmationDialogTitle": { "message": "Вы уверены, что хотите покинуть?" diff --git a/apps/web/src/locales/si/messages.json b/apps/web/src/locales/si/messages.json index 432a61608b5..f896cc8c79a 100644 --- a/apps/web/src/locales/si/messages.json +++ b/apps/web/src/locales/si/messages.json @@ -1975,12 +1975,6 @@ "exportFrom": { "message": "Export from" }, - "exportVault": { - "message": "Export vault" - }, - "exportSecrets": { - "message": "Export secrets" - }, "fileFormat": { "message": "File format" }, @@ -1993,9 +1987,6 @@ "confirmMasterPassword": { "message": "Confirm master password" }, - "confirmFormat": { - "message": "Confirm format" - }, "filePassword": { "message": "File password" }, @@ -2306,6 +2297,9 @@ "tools": { "message": "Tools" }, + "import": { + "message": "Import" + }, "importData": { "message": "Import data" }, @@ -8753,9 +8747,6 @@ "server": { "message": "Server" }, - "exportData": { - "message": "Export data" - }, "exportingOrganizationSecretDataTitle": { "message": "Exporting Organization Secret Data" }, diff --git a/apps/web/src/locales/sk/messages.json b/apps/web/src/locales/sk/messages.json index 698c71f458f..cb0adb25c0a 100644 --- a/apps/web/src/locales/sk/messages.json +++ b/apps/web/src/locales/sk/messages.json @@ -1975,12 +1975,6 @@ "exportFrom": { "message": "Exportovať z" }, - "exportVault": { - "message": "Export trezoru" - }, - "exportSecrets": { - "message": "Exportovať položky" - }, "fileFormat": { "message": "Formát Súboru" }, @@ -1993,9 +1987,6 @@ "confirmMasterPassword": { "message": "Potvrdiť hlavné heslo" }, - "confirmFormat": { - "message": "Potvrdiť formát" - }, "filePassword": { "message": "Heslo súboru" }, @@ -2306,6 +2297,9 @@ "tools": { "message": "Nástroje" }, + "import": { + "message": "Import" + }, "importData": { "message": "Import dát" }, @@ -8753,9 +8747,6 @@ "server": { "message": "Server" }, - "exportData": { - "message": "Exportovať dáta" - }, "exportingOrganizationSecretDataTitle": { "message": "Exportovať tajné dáta Organizácie" }, diff --git a/apps/web/src/locales/sl/messages.json b/apps/web/src/locales/sl/messages.json index 97a3f12c606..8b86e7b6060 100644 --- a/apps/web/src/locales/sl/messages.json +++ b/apps/web/src/locales/sl/messages.json @@ -1975,12 +1975,6 @@ "exportFrom": { "message": "Export from" }, - "exportVault": { - "message": "Izvoz trezorja" - }, - "exportSecrets": { - "message": "Export secrets" - }, "fileFormat": { "message": "Format datoteke" }, @@ -1993,9 +1987,6 @@ "confirmMasterPassword": { "message": "Potrdite glavno geslo" }, - "confirmFormat": { - "message": "Potrdi format" - }, "filePassword": { "message": "Geslo datoteke" }, @@ -2306,6 +2297,9 @@ "tools": { "message": "Orodja" }, + "import": { + "message": "Import" + }, "importData": { "message": "Uvozi podatke" }, @@ -8753,9 +8747,6 @@ "server": { "message": "Server" }, - "exportData": { - "message": "Export data" - }, "exportingOrganizationSecretDataTitle": { "message": "Exporting Organization Secret Data" }, diff --git a/apps/web/src/locales/sr_CS/messages.json b/apps/web/src/locales/sr_CS/messages.json index 5527124ca73..1ff92f67064 100644 --- a/apps/web/src/locales/sr_CS/messages.json +++ b/apps/web/src/locales/sr_CS/messages.json @@ -1975,12 +1975,6 @@ "exportFrom": { "message": "Export from" }, - "exportVault": { - "message": "Izvezi trezor" - }, - "exportSecrets": { - "message": "Export secrets" - }, "fileFormat": { "message": "File format" }, @@ -1993,9 +1987,6 @@ "confirmMasterPassword": { "message": "Confirm master password" }, - "confirmFormat": { - "message": "Confirm format" - }, "filePassword": { "message": "File password" }, @@ -2306,6 +2297,9 @@ "tools": { "message": "Alati" }, + "import": { + "message": "Import" + }, "importData": { "message": "Uvezi podatke" }, @@ -8753,9 +8747,6 @@ "server": { "message": "Server" }, - "exportData": { - "message": "Export data" - }, "exportingOrganizationSecretDataTitle": { "message": "Exporting Organization Secret Data" }, diff --git a/apps/web/src/locales/sr_CY/messages.json b/apps/web/src/locales/sr_CY/messages.json index 835e4081be3..e1d19054ac3 100644 --- a/apps/web/src/locales/sr_CY/messages.json +++ b/apps/web/src/locales/sr_CY/messages.json @@ -197,13 +197,13 @@ "message": "Assign at-risk members guided security tasks to update credentials." }, "feature3Title": { - "message": "Monitor progress" + "message": "Праћење напретка" }, "feature3Description": { - "message": "Track changes over time to show security improvements." + "message": "Пратите промене током времена да бисте показали безбедносна побољшања." }, "noReportsRunTitle": { - "message": "Generate report" + "message": "Генеришите извештај" }, "noReportsRunDescription": { "message": "You’re ready to start generating reports. Once you generate, you’ll be able to:" @@ -344,10 +344,10 @@ "message": "Апликације које треба прегледати" }, "newApplicationsCardTitle": { - "message": "Review new applications" + "message": "Прегледајте нове апликације" }, "newApplicationsWithCount": { - "message": "$COUNT$ new applications", + "message": "Нове апликације: $COUNT$", "placeholders": { "count": { "content": "$1", @@ -380,7 +380,7 @@ "message": "Review applications to secure the items most critical to your organization's security" }, "reviewApplications": { - "message": "Review applications" + "message": "Прегледајте апликације" }, "prioritizeCriticalApplications": { "message": "Дајте приоритет критичним апликацијама" @@ -1975,12 +1975,6 @@ "exportFrom": { "message": "Извоз од" }, - "exportVault": { - "message": "Извоз сефа" - }, - "exportSecrets": { - "message": "Извоз тајне" - }, "fileFormat": { "message": "Формат датотеке" }, @@ -1993,9 +1987,6 @@ "confirmMasterPassword": { "message": "Потрдити Главну Лозинку" }, - "confirmFormat": { - "message": "Потврдити формат" - }, "filePassword": { "message": "Лозинка датотеке" }, @@ -2306,6 +2297,9 @@ "tools": { "message": "Алатке" }, + "import": { + "message": "Увоз" + }, "importData": { "message": "Увези податке" }, @@ -3063,7 +3057,7 @@ "message": "1ГБ шифровано складиште за прилоге." }, "premiumSignUpStorageV2": { - "message": "$SIZE$ encrypted storage for file attachments.", + "message": "$SIZE$ шифровано складиште за прилоге.", "placeholders": { "size": { "content": "$1", @@ -3134,7 +3128,7 @@ } }, "premiumSubscriptionEnded": { - "message": "Your Premium subscription ended" + "message": "Ваша Премијум претплата је завршена" }, "premiumSubscriptionEndedDesc": { "message": "To regain access to your archive, restart your Premium subscription. If you edit details for an archived item before restarting, it'll be moved back into your vault." @@ -3263,10 +3257,10 @@ "message": "Следеће пуњење" }, "nextChargeHeader": { - "message": "Next Charge" + "message": "Следеће пуњење" }, "plan": { - "message": "Plan" + "message": "План" }, "details": { "message": "Детаљи" @@ -4636,10 +4630,10 @@ "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." }, "confirmIdentityToContinue": { - "message": "Confirm your identity to continue" + "message": "Да бисте наставили потврдите ваш идентитет" }, "enterYourMasterPassword": { - "message": "Enter your master password" + "message": "Унети вашу главну лозинку" }, "updateSettings": { "message": "Ажурирај подешавања" @@ -5170,19 +5164,19 @@ "description": "This is a verb. ex. 'Fix The Car'" }, "fixEncryption": { - "message": "Fix encryption" + "message": "Поправи шифровање" }, "fixEncryptionTooltip": { - "message": "This file is using an outdated encryption method." + "message": "Ова датотека користи застарели метод шифровања." }, "attachmentUpdated": { - "message": "Attachment updated" + "message": "Прилог је ажуриран" }, "oldAttachmentsNeedFixDesc": { "message": "У вашем сефу постоје стари прилози који треба поправити да бисте могли да промените кључ за шифровање свог налога." }, "itemsTransferred": { - "message": "Items transferred" + "message": "Пренете ставке" }, "yourAccountsFingerprint": { "message": "Ваша Сигурносна Фраза Сефа", @@ -8753,9 +8747,6 @@ "server": { "message": "Сервер" }, - "exportData": { - "message": "Увези податке" - }, "exportingOrganizationSecretDataTitle": { "message": "Извоз тајних података организације" }, @@ -11594,31 +11585,31 @@ "description": "Verb" }, "unArchive": { - "message": "Unarchive" + "message": "Врати из архиве" }, "itemsInArchive": { - "message": "Items in archive" + "message": "Ставке у архиви" }, "noItemsInArchive": { "message": "Нема ставка у архиви" }, "noItemsInArchiveDesc": { - "message": "Archived items will appear here and will be excluded from general search results and autofill suggestions." + "message": "Архивиране ставке ће се овде појавити и бити искључени из општих резултата претраге и сугестија о ауто-пуњењу." }, "itemWasSentToArchive": { - "message": "Item was sent to archive" + "message": "Ставка је послата у архиву" }, "itemsWereSentToArchive": { - "message": "Items were sent to archive" + "message": "Ставке су послате у архиву" }, "itemUnarchived": { - "message": "Item was unarchived" + "message": "Ставка враћена из архиве" }, "bulkArchiveItems": { - "message": "Items archived" + "message": "Ставке у архиви" }, "bulkUnarchiveItems": { - "message": "Items unarchived" + "message": "Ставке враћене из архиве" }, "archiveItem": { "message": "Archive item", @@ -12126,7 +12117,7 @@ "message": "There was an error calculating tax for your location. Please try again." }, "individualUpgradeWelcomeMessage": { - "message": "Welcome to Bitwarden" + "message": "Добродошли у Bitwarden" }, "individualUpgradeDescriptionMessage": { "message": "Unlock more security features with Premium, or start sharing items with Families" @@ -12144,7 +12135,7 @@ "message": "Upgrade your plan" }, "upgradeNow": { - "message": "Upgrade now" + "message": "Надогради сада" }, "formWillCreateNewFamiliesOrgMessage": { "message": "Completing this form will create a new Families organization. You can upgrade your Free organization from the Admin Console." @@ -12204,28 +12195,28 @@ "message": "A domain must be claimed before activating this policy." }, "unlockMethodNeededToChangeTimeoutActionDesc": { - "message": "Set up an unlock method to change your vault timeout action." + "message": "Подесите метод откључавања да бисте променили радњу временског ограничења сефа." }, "vaultTimeoutPolicyAffectingOptions": { - "message": "Enterprise policy requirements have been applied to your timeout options" + "message": "Захтеви политике предузећа су примењени на опције тајмаута" }, "vaultTimeoutTooLarge": { - "message": "Your vault timeout exceeds the restrictions set by your organization." + "message": "Време истека вашег сефа је премашило дозвољена ограничења од стране ваше организације." }, "neverLockWarning": { - "message": "Are you sure you want to use the \"Never\" option? Setting your lock options to \"Never\" stores your vault's encryption key on your device. If you use this option you should ensure that you keep your device properly protected." + "message": "Да ли сте сигурни да желите да користите опцију „Никад“? Ако поставите опције закључавања на „Никада“, на вашем уређају се чува кључ за шифровање сефа. Ако користите ову опцију, осигурајте да је уређај правилно заштићен." }, "sessionTimeoutSettingsAction": { - "message": "Timeout action" + "message": "Акција тајмаута" }, "sessionTimeoutHeader": { - "message": "Session timeout" + "message": "Истек сесије" }, "appearance": { - "message": "Appearance" + "message": "Изглед" }, "vaultTimeoutPolicyMaximumError": { - "message": "Timeout exceeds the restriction set by your organization: $HOURS$ hour(s) and $MINUTES$ minute(s) maximum", + "message": "Тајмаут је већи него што је решила организација: макимум $HOURS$ сат(а) и $MINUTES$ минут(а)", "placeholders": { "hours": { "content": "$1", @@ -12238,49 +12229,49 @@ } }, "removeMasterPasswordForOrgUserKeyConnector": { - "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + "message": "Ваша организација више не користи главне лозинке за пријаву на Bitwarden. Да бисте наставили, верификујте организацију и домен." }, "continueWithLogIn": { - "message": "Continue with log in" + "message": "Наставити са пријавом" }, "doNotContinue": { - "message": "Do not continue" + "message": "Не настави" }, "domain": { - "message": "Domain" + "message": "Домен" }, "keyConnectorDomainTooltip": { - "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + "message": "Овај домен ће чувати кључеве за шифровање вашег налога, па се уверите да му верујете. Ако нисте сигурни, проверите код свог администратора." }, "verifyYourOrganization": { - "message": "Verify your organization to log in" + "message": "Верификујте своју организацију да бисте се пријавили" }, "organizationVerified": { - "message": "Organization verified" + "message": "Организација верификована" }, "domainVerified": { - "message": "Domain verified" + "message": "Домен верификован" }, "leaveOrganizationContent": { - "message": "If you don't verify your organization, your access to the organization will be revoked." + "message": "Ако не верификујете своју организацију, ваш приступ организацији ће бити опозван." }, "leaveNow": { - "message": "Leave now" + "message": "Напусти сада" }, "verifyYourDomainToLogin": { - "message": "Verify your domain to log in" + "message": "Верификујте домен да бисте се пријавили" }, "verifyYourDomainDescription": { - "message": "To continue with log in, verify this domain." + "message": "Да бисте наставили са пријављивањем, верификујте овај домен." }, "confirmKeyConnectorOrganizationUserDescription": { - "message": "To continue with log in, verify the organization and domain." + "message": "Да бисте наставили са пријављивањем, верификујте организацију и домен." }, "confirmNoSelectedCriticalApplicationsTitle": { "message": "No critical applications are selected" }, "confirmNoSelectedCriticalApplicationsDesc": { - "message": "Are you sure you want to continue?" + "message": "Желите ли заиста да наставите?" }, "userVerificationFailed": { "message": "User verification failed." @@ -12304,7 +12295,7 @@ "message": "Your public-key encryption key pair could not be recovered. Do you want to replace your encryption key with a new key pair? This will require you to set up existing emergency-access and organization memberships again." }, "recoveryStepSyncTitle": { - "message": "Synchronizing data" + "message": "Усклађивање података" }, "recoveryStepPrivateKeyTitle": { "message": "Verifying encryption key integrity" @@ -12381,7 +12372,7 @@ "message": "Contact your admin to regain access." }, "leaveConfirmationDialogConfirmButton": { - "message": "Leave $ORGANIZATION$", + "message": "Напустити $ORGANIZATION$", "placeholders": { "organization": { "content": "$1", diff --git a/apps/web/src/locales/sv/messages.json b/apps/web/src/locales/sv/messages.json index 57495a63fa8..7cca1d484b7 100644 --- a/apps/web/src/locales/sv/messages.json +++ b/apps/web/src/locales/sv/messages.json @@ -1975,12 +1975,6 @@ "exportFrom": { "message": "Exportera från" }, - "exportVault": { - "message": "Exportera valv" - }, - "exportSecrets": { - "message": "Exportera hemligheter" - }, "fileFormat": { "message": "Filformat" }, @@ -1993,9 +1987,6 @@ "confirmMasterPassword": { "message": "Bekräfta huvudlösenord" }, - "confirmFormat": { - "message": "Bekräfta format" - }, "filePassword": { "message": "Fillösenord" }, @@ -2306,6 +2297,9 @@ "tools": { "message": "Verktyg" }, + "import": { + "message": "Importera" + }, "importData": { "message": "Importera data" }, @@ -8753,9 +8747,6 @@ "server": { "message": "Server" }, - "exportData": { - "message": "Exportera data" - }, "exportingOrganizationSecretDataTitle": { "message": "Export av hemliga organisationsdata" }, diff --git a/apps/web/src/locales/ta/messages.json b/apps/web/src/locales/ta/messages.json index 5f2a1afba42..62e28d08097 100644 --- a/apps/web/src/locales/ta/messages.json +++ b/apps/web/src/locales/ta/messages.json @@ -1975,12 +1975,6 @@ "exportFrom": { "message": "இதிலிருந்து ஏற்றுமதி" }, - "exportVault": { - "message": "பெட்டகத்தை ஏற்றுமதி செய்" - }, - "exportSecrets": { - "message": "ரகசியங்களை ஏற்றுமதி செய்" - }, "fileFormat": { "message": "கோப்பு வடிவம்" }, @@ -1993,9 +1987,6 @@ "confirmMasterPassword": { "message": "முதன்மை கடவுச்சொல்லை உறுதிப்படுத்தவும்" }, - "confirmFormat": { - "message": "வடிவத்தை உறுதிப்படுத்தவும்" - }, "filePassword": { "message": "கோப்பு கடவுச்சொல்" }, @@ -2306,6 +2297,9 @@ "tools": { "message": "கருவிகள்" }, + "import": { + "message": "Import" + }, "importData": { "message": "தரவை இம்போர்ட் செய்யவும்" }, @@ -8753,9 +8747,6 @@ "server": { "message": "சேவையகம்" }, - "exportData": { - "message": "தரவை ஏற்றுமதி செய்" - }, "exportingOrganizationSecretDataTitle": { "message": "நிறுவன இரகசிய தரவை ஏற்றுமதி செய்தல்" }, diff --git a/apps/web/src/locales/te/messages.json b/apps/web/src/locales/te/messages.json index fd419fe7397..26f146809b5 100644 --- a/apps/web/src/locales/te/messages.json +++ b/apps/web/src/locales/te/messages.json @@ -1975,12 +1975,6 @@ "exportFrom": { "message": "Export from" }, - "exportVault": { - "message": "Export vault" - }, - "exportSecrets": { - "message": "Export secrets" - }, "fileFormat": { "message": "File format" }, @@ -1993,9 +1987,6 @@ "confirmMasterPassword": { "message": "Confirm master password" }, - "confirmFormat": { - "message": "Confirm format" - }, "filePassword": { "message": "File password" }, @@ -2306,6 +2297,9 @@ "tools": { "message": "Tools" }, + "import": { + "message": "Import" + }, "importData": { "message": "Import data" }, @@ -8753,9 +8747,6 @@ "server": { "message": "Server" }, - "exportData": { - "message": "Export data" - }, "exportingOrganizationSecretDataTitle": { "message": "Exporting Organization Secret Data" }, diff --git a/apps/web/src/locales/th/messages.json b/apps/web/src/locales/th/messages.json index 362db4faea2..431c7fc014c 100644 --- a/apps/web/src/locales/th/messages.json +++ b/apps/web/src/locales/th/messages.json @@ -1975,12 +1975,6 @@ "exportFrom": { "message": "Export from" }, - "exportVault": { - "message": "Export vault" - }, - "exportSecrets": { - "message": "Export secrets" - }, "fileFormat": { "message": "รูปแบบไฟล์" }, @@ -1993,9 +1987,6 @@ "confirmMasterPassword": { "message": "Confirm master password" }, - "confirmFormat": { - "message": "Confirm format" - }, "filePassword": { "message": "File password" }, @@ -2306,6 +2297,9 @@ "tools": { "message": "Tools" }, + "import": { + "message": "Import" + }, "importData": { "message": "Import data" }, @@ -8753,9 +8747,6 @@ "server": { "message": "Server" }, - "exportData": { - "message": "Export data" - }, "exportingOrganizationSecretDataTitle": { "message": "Exporting Organization Secret Data" }, diff --git a/apps/web/src/locales/tr/messages.json b/apps/web/src/locales/tr/messages.json index dbb8eb2d299..99e1225731f 100644 --- a/apps/web/src/locales/tr/messages.json +++ b/apps/web/src/locales/tr/messages.json @@ -1975,12 +1975,6 @@ "exportFrom": { "message": "Dışa aktarılacak konum" }, - "exportVault": { - "message": "Kasayı dışa aktar" - }, - "exportSecrets": { - "message": "Sırları dışa aktar" - }, "fileFormat": { "message": "Dosya biçimi" }, @@ -1993,9 +1987,6 @@ "confirmMasterPassword": { "message": "Ana parolayı onaylayın" }, - "confirmFormat": { - "message": "Biçimi onayla" - }, "filePassword": { "message": "Dosya parolası" }, @@ -2306,6 +2297,9 @@ "tools": { "message": "Araçlar" }, + "import": { + "message": "İçe aktar" + }, "importData": { "message": "Verileri içe aktar" }, @@ -8753,9 +8747,6 @@ "server": { "message": "Sunucu" }, - "exportData": { - "message": "Verileri dışarı aktar" - }, "exportingOrganizationSecretDataTitle": { "message": "Kuruluş Gizli Verilerini Dışa Aktarma" }, diff --git a/apps/web/src/locales/uk/messages.json b/apps/web/src/locales/uk/messages.json index 0cc96b69904..b269058799d 100644 --- a/apps/web/src/locales/uk/messages.json +++ b/apps/web/src/locales/uk/messages.json @@ -1975,12 +1975,6 @@ "exportFrom": { "message": "Експортувати з" }, - "exportVault": { - "message": "Експортувати сховище" - }, - "exportSecrets": { - "message": "Експортувати секрети" - }, "fileFormat": { "message": "Формат файлу" }, @@ -1993,9 +1987,6 @@ "confirmMasterPassword": { "message": "Підтвердьте головний пароль" }, - "confirmFormat": { - "message": "Підтвердити формат" - }, "filePassword": { "message": "Пароль файлу" }, @@ -2306,6 +2297,9 @@ "tools": { "message": "Інструменти" }, + "import": { + "message": "Import" + }, "importData": { "message": "Імпортувати дані" }, @@ -8753,9 +8747,6 @@ "server": { "message": "Сервер" }, - "exportData": { - "message": "Експортувати дані" - }, "exportingOrganizationSecretDataTitle": { "message": "Експорт секретних даних організації" }, diff --git a/apps/web/src/locales/vi/messages.json b/apps/web/src/locales/vi/messages.json index aa32aa4254c..7397aed6f94 100644 --- a/apps/web/src/locales/vi/messages.json +++ b/apps/web/src/locales/vi/messages.json @@ -1975,12 +1975,6 @@ "exportFrom": { "message": "Xuất từ" }, - "exportVault": { - "message": "Xuất kho" - }, - "exportSecrets": { - "message": "Xuất bí mật" - }, "fileFormat": { "message": "Định dạng tập tin" }, @@ -1993,9 +1987,6 @@ "confirmMasterPassword": { "message": "Nhập lại mật khẩu chính" }, - "confirmFormat": { - "message": "Xác nhận định dạng" - }, "filePassword": { "message": "Mật khẩu tập tin" }, @@ -2306,6 +2297,9 @@ "tools": { "message": "Công cụ" }, + "import": { + "message": "Import" + }, "importData": { "message": "Nhập dữ liệu" }, @@ -8753,9 +8747,6 @@ "server": { "message": "Máy chủ" }, - "exportData": { - "message": "Xuất dữ liệu" - }, "exportingOrganizationSecretDataTitle": { "message": "Xuất dữ liệu bí mật của tổ chức" }, diff --git a/apps/web/src/locales/zh_CN/messages.json b/apps/web/src/locales/zh_CN/messages.json index e6da8945be8..073afec7fe6 100644 --- a/apps/web/src/locales/zh_CN/messages.json +++ b/apps/web/src/locales/zh_CN/messages.json @@ -1375,7 +1375,7 @@ "message": "使用设备登录" }, "loginWithDeviceEnabledNote": { - "message": "必须在 Bitwarden App 的设置中启用设备登录。需要其他登录选项吗?" + "message": "必须在 Bitwarden App 的设置中启用设备登录。需要其他选项吗?" }, "needAnotherOptionV1": { "message": "需要其他选项吗?" @@ -1411,7 +1411,7 @@ "message": "通行密钥无效。请重试。" }, "twoFactorForPasskeysNotSupportedOnClientUpdateToLogIn": { - "message": "不支持通行密钥 2FA。请更新 App 再登录。" + "message": "不支持通行密钥 2FA。请更新 App 以登录。" }, "loginWithPasskeyInfo": { "message": "使用已生成的通行密钥,无需密码即可自动登录。生物识别(例如面部识别或指纹)或其他 FIDO2 安全方法将用于验证您的身份。" @@ -1975,12 +1975,6 @@ "exportFrom": { "message": "导出自" }, - "exportVault": { - "message": "导出密码库" - }, - "exportSecrets": { - "message": "导出机密" - }, "fileFormat": { "message": "文件格式" }, @@ -1993,9 +1987,6 @@ "confirmMasterPassword": { "message": "确认主密码" }, - "confirmFormat": { - "message": "确认格式" - }, "filePassword": { "message": "文件密码" }, @@ -2306,6 +2297,9 @@ "tools": { "message": "工具" }, + "import": { + "message": "导入" + }, "importData": { "message": "导入数据" }, @@ -3655,7 +3649,7 @@ "message": "确定要退出该组织吗?" }, "leftOrganization": { - "message": "您已经退出该组织。" + "message": "您已退出该组织" }, "defaultCollection": { "message": "默认集合" @@ -3718,7 +3712,7 @@ "message": "撤销成员后,他们将不再具有对组织数据的访问权限。要快速恢复此成员的访问权限,请转到「已撤销」标签页。" }, "removeUserConfirmationKeyConnector": { - "message": "警告!此用户需要 Key Connector 来管理他们的加密。从您的组织中移除此用户将永久停用他们的账户。此操作无法撤消。您要继续吗?" + "message": "警告!此用户需要 Key Connector 来管理他们的加密。从您的组织中移除此用户将永久停用他们的账户。此操作无法撤消。要继续吗?" }, "externalId": { "message": "外部 ID" @@ -4636,7 +4630,7 @@ "message": "新推荐的加密设置将提高您的账户安全性。输入您的主密码以立即更新。" }, "confirmIdentityToContinue": { - "message": "确认您的身份后继续" + "message": "确认您的身份以继续" }, "enterYourMasterPassword": { "message": "输入您的主密码" @@ -6475,7 +6469,7 @@ } }, "bulkReinviteLimitedSuccessToast": { - "message": "选中了 $SELECTEDCOUNT$ 位用户,已经重新邀请了 $LIMIT$ 位。由于邀请限制 $LIMIT$ 人,$EXCLUDEDCOUNT$ 位用户没有被邀请。", + "message": "已重新邀请 $SELECTEDCOUNT$ 位用户中的 $LIMIT$ 位。由于邀请限制为 $LIMIT$ 人,$EXCLUDEDCOUNT$ 位用户没有被邀请。", "placeholders": { "limit": { "content": "$1", @@ -8580,7 +8574,7 @@ "message": "您不能两次声明同一个域名。" }, "domainNotAvailable": { - "message": "别人正在使用 $DOMAIN$。请使用其他域名。", + "message": "别人正在使用 $DOMAIN$。请使用其他域名以继续。", "placeholders": { "DOMAIN": { "content": "$1", @@ -8753,9 +8747,6 @@ "server": { "message": "服务器" }, - "exportData": { - "message": "导出数据" - }, "exportingOrganizationSecretDataTitle": { "message": "正在导出组织机密数据" }, @@ -9484,7 +9475,7 @@ "message": "要求 SSO 登录" }, "emailRequiredForSsoLogin": { - "message": "Email is required for SSO" + "message": "SSO 要求使用电子邮箱" }, "selectedRegionFlag": { "message": "选择的区域旗帜" @@ -12238,43 +12229,43 @@ } }, "removeMasterPasswordForOrgUserKeyConnector": { - "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + "message": "您的组织已不再使用主密码登录 Bitwarden。要继续,请验证组织和域名。" }, "continueWithLogIn": { - "message": "Continue with log in" + "message": "继续登录" }, "doNotContinue": { - "message": "Do not continue" + "message": "不要继续" }, "domain": { - "message": "Domain" + "message": "域名" }, "keyConnectorDomainTooltip": { - "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + "message": "此域名将存储您的账户加密密钥,所以请确保您信任它。如果您不确定,请与您的管理员联系。" }, "verifyYourOrganization": { - "message": "Verify your organization to log in" + "message": "验证您的组织以登录" }, "organizationVerified": { - "message": "Organization verified" + "message": "组织已验证" }, "domainVerified": { - "message": "Domain verified" + "message": "域名已验证" }, "leaveOrganizationContent": { - "message": "If you don't verify your organization, your access to the organization will be revoked." + "message": "如果不验证您的组织,您对组织的访问权限将被撤销。" }, "leaveNow": { - "message": "Leave now" + "message": "立即退出" }, "verifyYourDomainToLogin": { - "message": "Verify your domain to log in" + "message": "验证您的域名以登录" }, "verifyYourDomainDescription": { - "message": "To continue with log in, verify this domain." + "message": "要继续登录,请验证此域名。" }, "confirmKeyConnectorOrganizationUserDescription": { - "message": "To continue with log in, verify the organization and domain." + "message": "要继续登录,请验证组织和域名。" }, "confirmNoSelectedCriticalApplicationsTitle": { "message": "未选择任何关键应用程序" @@ -12353,7 +12344,7 @@ "message": "您的组织已将默认会话超时设置为「浏览器刷新时」。" }, "sessionTimeoutSettingsPolicyMaximumError": { - "message": "最大超时时间不能超过 $HOURS$ 小时 $MINUTES$ 分钟", + "message": "最大超时不能超过 $HOURS$ 小时 $MINUTES$ 分钟", "placeholders": { "hours": { "content": "$1", @@ -12372,7 +12363,7 @@ "message": "设置一个解锁方式以更改您的超时动作" }, "leaveConfirmationDialogTitle": { - "message": "确定要离开吗?" + "message": "确定要退出吗?" }, "leaveConfirmationDialogContentOne": { "message": "拒绝后,您的个人项目将保留在您的账户中,但您将失去对共享项目和组织功能的访问权限。" @@ -12381,7 +12372,7 @@ "message": "联系您的管理员以重新获取访问权限。" }, "leaveConfirmationDialogConfirmButton": { - "message": "离开 $ORGANIZATION$", + "message": "退出 $ORGANIZATION$", "placeholders": { "organization": { "content": "$1", @@ -12390,7 +12381,7 @@ } }, "howToManageMyVault": { - "message": "我如何管理我的密码库?" + "message": "我该如何管理我的密码库?" }, "transferItemsToOrganizationTitle": { "message": "传输项目到 $ORGANIZATION$", @@ -12402,7 +12393,7 @@ } }, "transferItemsToOrganizationContent": { - "message": "出于安全和合规考虑,$ORGANIZATION$ 要求所有项目的所有权均归组织所有。点击「接受」以传输您的项目的所有权。", + "message": "出于安全和合规考虑,$ORGANIZATION$ 要求所有项目归组织所有。点击「接受」以传输您的项目的所有权。", "placeholders": { "organization": { "content": "$1", @@ -12414,7 +12405,7 @@ "message": "接受传输" }, "declineAndLeave": { - "message": "拒绝并离开" + "message": "拒绝并退出" }, "whyAmISeeingThis": { "message": "为什么我会看到这个?" diff --git a/apps/web/src/locales/zh_TW/messages.json b/apps/web/src/locales/zh_TW/messages.json index 3479ca48f27..364389a1535 100644 --- a/apps/web/src/locales/zh_TW/messages.json +++ b/apps/web/src/locales/zh_TW/messages.json @@ -1975,12 +1975,6 @@ "exportFrom": { "message": "匯出自" }, - "exportVault": { - "message": "匯出密碼庫" - }, - "exportSecrets": { - "message": "匯出機密" - }, "fileFormat": { "message": "檔案格式" }, @@ -1993,9 +1987,6 @@ "confirmMasterPassword": { "message": "確認主密碼" }, - "confirmFormat": { - "message": "確認格式" - }, "filePassword": { "message": "檔案密碼" }, @@ -2306,6 +2297,9 @@ "tools": { "message": "工具" }, + "import": { + "message": "Import" + }, "importData": { "message": "匯入資料" }, @@ -8753,9 +8747,6 @@ "server": { "message": "伺服器" }, - "exportData": { - "message": "匯出資料" - }, "exportingOrganizationSecretDataTitle": { "message": "匯出組織機密資料" }, From 0c8c755404620ec3566d2572ed30dbae51d0f15a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 16 Dec 2025 07:52:32 -0600 Subject: [PATCH 078/188] [deps]: Update codecov/codecov-action action to v5.5.2 (#17992) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index faee7220e7b..e3ba6112b7d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -190,7 +190,7 @@ jobs: path: ./apps/desktop/desktop_native - name: Upload coverage to codecov.io - uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5.5.1 + uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2 with: files: | ./lcov.info From d7a7f19abdaa9aa95f0919df2e8aa8f249d6c813 Mon Sep 17 00:00:00 2001 From: cyprain-okeke <108260115+cyprain-okeke@users.noreply.github.com> Date: Tue, 16 Dec 2025 14:55:40 +0100 Subject: [PATCH 079/188] [PM-29633]Discount badge misaligned on Subscription page for Organizations and MSP/BUP (#17924) * Fix the discount * Fix the spacing and alignment issue * reverted unneeded change * Changes for provider discount badge --- .../organization-subscription-cloud.component.html | 14 ++++++++------ .../provider-subscription.component.html | 9 ++++++--- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.html b/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.html index 8b9b98dc390..860f80eb346 100644 --- a/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.html +++ b/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.html @@ -14,16 +14,18 @@ ></app-subscription-status> <ng-container *ngIf="userOrg.canEditSubscription"> <div class="tw-flex-col"> - <strong class="tw-block tw-border-0 tw-border-b tw-border-solid tw-border-secondary-300" - >{{ "details" | i18n - }}<span - class="tw-ml-3" + <strong + class="tw-flex tw-items-center tw-gap-3 tw-border-0 tw-border-b tw-border-solid tw-border-secondary-300 tw-pb-2" + > + {{ "details" | i18n }} + <span *ngIf="customerDiscount?.percentOff > 0 && !isSecretsManagerTrial()" bitBadge variant="success" + class="tw-inline-flex tw-items-center" >{{ "providerDiscount" | i18n: customerDiscount?.percentOff }}</span - ></strong - > + > + </strong> <bit-table> <ng-template body> <ng-container *ngIf="subscription && !userOrg.isFreeOrg"> 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 d71dcf77170..783bd155e61 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 @@ -11,11 +11,14 @@ <ng-container> <div class="tw-flex-col"> <strong - class="tw-block tw-border-0 tw-border-b tw-border-solid tw-border-secondary-300 tw-pb-2" - >{{ "details" | i18n }} &#160;<span + class="tw-flex tw-items-center tw-gap-3 tw-border-0 tw-border-b tw-border-solid tw-border-secondary-300 tw-pb-2" + > + {{ "details" | i18n }} + <span + *ngIf="subscription.discountPercentage" bitBadge variant="success" - *ngIf="subscription.discountPercentage" + class="tw-inline-flex tw-items-center" >{{ "providerDiscount" | i18n: subscription.discountPercentage }}</span > </strong> From ede027a43e7d3ba0d7c16d089042a85abb06abef Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 16 Dec 2025 10:10:20 -0500 Subject: [PATCH 080/188] [deps]: Update actions/cache action to v5 (#17993) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/build-desktop.yml | 26 +++++++++++++------------- .github/workflows/chromatic.yml | 2 +- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/.github/workflows/build-desktop.yml b/.github/workflows/build-desktop.yml index efb94e44c7a..5a7703adb78 100644 --- a/.github/workflows/build-desktop.yml +++ b/.github/workflows/build-desktop.yml @@ -246,7 +246,7 @@ jobs: npm link ../sdk-internal - name: Cache Native Module - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 id: cache with: path: | @@ -409,7 +409,7 @@ jobs: npm link ../sdk-internal - name: Cache Native Module - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 id: cache with: path: | @@ -572,7 +572,7 @@ jobs: npm link ../sdk-internal - name: Cache Native Module - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 id: cache with: path: | @@ -837,7 +837,7 @@ jobs: npm link ../sdk-internal - name: Cache Native Module - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 id: cache with: path: | @@ -1042,14 +1042,14 @@ jobs: - name: Cache Build id: build-cache - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 with: path: apps/desktop/build key: ${{ runner.os }}-${{ github.run_id }}-build - name: Cache Safari id: safari-cache - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 with: path: apps/browser/dist/Safari key: ${{ runner.os }}-${{ github.run_id }}-safari-extension @@ -1195,7 +1195,7 @@ jobs: npm link ../sdk-internal - name: Cache Native Module - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 id: cache with: path: | @@ -1282,14 +1282,14 @@ jobs: - name: Get Build Cache id: build-cache - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 with: path: apps/desktop/build key: ${{ runner.os }}-${{ github.run_id }}-build - name: Setup Safari Cache id: safari-cache - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 with: path: apps/browser/dist/Safari key: ${{ runner.os }}-${{ github.run_id }}-safari-extension @@ -1419,7 +1419,7 @@ jobs: npm link ../sdk-internal - name: Cache Native Module - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 id: cache with: path: | @@ -1557,14 +1557,14 @@ jobs: - name: Get Build Cache id: build-cache - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 with: path: apps/desktop/build key: ${{ runner.os }}-${{ github.run_id }}-build - name: Setup Safari Cache id: safari-cache - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 with: path: apps/browser/dist/Safari key: ${{ runner.os }}-${{ github.run_id }}-safari-extension @@ -1702,7 +1702,7 @@ jobs: npm link ../sdk-internal - name: Cache Native Module - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 id: cache with: path: | diff --git a/.github/workflows/chromatic.yml b/.github/workflows/chromatic.yml index 677d3dfc1df..44ea21276e2 100644 --- a/.github/workflows/chromatic.yml +++ b/.github/workflows/chromatic.yml @@ -65,7 +65,7 @@ jobs: - name: Cache NPM id: npm-cache - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 with: path: "~/.npm" key: ${{ runner.os }}-npm-chromatic-${{ hashFiles('**/package-lock.json') }} From ac23878847c85c0ef3d1b0cb2753577bf84469e3 Mon Sep 17 00:00:00 2001 From: Oscar Hinton <Hinton@users.noreply.github.com> Date: Tue, 16 Dec 2025 16:15:34 +0100 Subject: [PATCH 081/188] [CL-934] Migrate breadcrumbs to OnPush (#17390) Migrates BreadcrumbComponent and BreadcrumbsComponent to OnPush and changes any property to computed signals. --- .../src/breadcrumbs/breadcrumb.component.html | 2 +- .../src/breadcrumbs/breadcrumb.component.ts | 42 +++++++++++--- .../breadcrumbs/breadcrumbs.component.html | 10 ++-- .../src/breadcrumbs/breadcrumbs.component.ts | 55 ++++++++++--------- .../src/breadcrumbs/breadcrumbs.stories.ts | 5 +- 5 files changed, 71 insertions(+), 43 deletions(-) diff --git a/libs/components/src/breadcrumbs/breadcrumb.component.html b/libs/components/src/breadcrumbs/breadcrumb.component.html index b0334f1ac09..bca34bb0e8b 100644 --- a/libs/components/src/breadcrumbs/breadcrumb.component.html +++ b/libs/components/src/breadcrumbs/breadcrumb.component.html @@ -1,6 +1,6 @@ <ng-template> @if (icon(); as icon) { - <i class="bwi {{ icon }} !tw-me-2" aria-hidden="true"></i> + <i class="bwi !tw-me-2" [class]="icon" aria-hidden="true"></i> } <ng-content></ng-content> </ng-template> diff --git a/libs/components/src/breadcrumbs/breadcrumb.component.ts b/libs/components/src/breadcrumbs/breadcrumb.component.ts index 6c79b449a28..18024c0ef20 100644 --- a/libs/components/src/breadcrumbs/breadcrumb.component.ts +++ b/libs/components/src/breadcrumbs/breadcrumb.component.ts @@ -1,29 +1,55 @@ -import { Component, EventEmitter, Output, TemplateRef, input, viewChild } from "@angular/core"; +import { + ChangeDetectionStrategy, + Component, + TemplateRef, + input, + output, + viewChild, +} from "@angular/core"; import { QueryParamsHandling } from "@angular/router"; -// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush -// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection +/** + * Individual breadcrumb item used within the `bit-breadcrumbs` component. + * Represents a single navigation step in the breadcrumb trail. + * + * This component should be used as a child of `bit-breadcrumbs` and supports both + * router navigation and custom click handlers. + */ @Component({ selector: "bit-breadcrumb", templateUrl: "./breadcrumb.component.html", + changeDetection: ChangeDetectionStrategy.OnPush, }) export class BreadcrumbComponent { + /** + * Optional icon to display before the breadcrumb text. + */ readonly icon = input<string>(); + /** + * Router link for the breadcrumb. Can be a string or an array of route segments. + */ readonly route = input<string | any[]>(); + /** + * Query parameters to include in the router link. + */ readonly queryParams = input<Record<string, string>>({}); + /** + * How to handle query parameters when navigating. Options include 'merge' or 'preserve'. + */ readonly queryParamsHandling = input<QueryParamsHandling>(); - // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals - // eslint-disable-next-line @angular-eslint/prefer-output-emitter-ref - @Output() - click = new EventEmitter(); + /** + * Emitted when the breadcrumb is clicked. + */ + readonly click = output<unknown>(); + /** Used by the BreadcrumbsComponent to access the breadcrumb content */ readonly content = viewChild(TemplateRef); onClick(args: unknown) { - this.click.next(args); + this.click.emit(args); } } diff --git a/libs/components/src/breadcrumbs/breadcrumbs.component.html b/libs/components/src/breadcrumbs/breadcrumbs.component.html index b63b21de76b..ee5ad79c739 100644 --- a/libs/components/src/breadcrumbs/breadcrumbs.component.html +++ b/libs/components/src/breadcrumbs/breadcrumbs.component.html @@ -1,4 +1,4 @@ -@for (breadcrumb of beforeOverflow; track breadcrumb; let last = $last) { +@for (breadcrumb of beforeOverflow(); track breadcrumb; let last = $last) { @if (breadcrumb.route(); as route) { <a bitLink @@ -26,8 +26,8 @@ } } -@if (hasOverflow) { - @if (beforeOverflow.length > 0) { +@if (hasOverflow()) { + @if (beforeOverflow().length > 0) { <i class="bwi bwi-angle-right tw-mx-1.5 tw-text-main"></i> } <button @@ -38,7 +38,7 @@ [label]="'moreBreadcrumbs' | i18n" ></button> <bit-menu #overflowMenu> - @for (breadcrumb of overflow; track breadcrumb) { + @for (breadcrumb of overflow(); track breadcrumb) { @if (breadcrumb.route(); as route) { <a bitMenuItem @@ -57,7 +57,7 @@ } </bit-menu> <i class="bwi bwi-angle-right tw-mx-1.5 tw-text-main"></i> - @for (breadcrumb of afterOverflow; track breadcrumb; let last = $last) { + @for (breadcrumb of afterOverflow(); track breadcrumb; let last = $last) { @if (breadcrumb.route(); as route) { <a bitLink diff --git a/libs/components/src/breadcrumbs/breadcrumbs.component.ts b/libs/components/src/breadcrumbs/breadcrumbs.component.ts index f0e4c4557c3..0ef5e60ee70 100644 --- a/libs/components/src/breadcrumbs/breadcrumbs.component.ts +++ b/libs/components/src/breadcrumbs/breadcrumbs.component.ts @@ -1,5 +1,11 @@ import { CommonModule } from "@angular/common"; -import { Component, ContentChildren, QueryList, input } from "@angular/core"; +import { + ChangeDetectionStrategy, + Component, + computed, + contentChildren, + input, +} from "@angular/core"; import { RouterModule } from "@angular/router"; import { I18nPipe } from "@bitwarden/ui-common"; @@ -15,42 +21,39 @@ import { BreadcrumbComponent } from "./breadcrumb.component"; * Bitwarden uses this component to indicate the user's current location in a set of data organized in * containers (Collections, Folders, or Projects). */ -// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush -// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "bit-breadcrumbs", templateUrl: "./breadcrumbs.component.html", imports: [I18nPipe, CommonModule, LinkModule, RouterModule, IconButtonModule, MenuModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class BreadcrumbsComponent { + /** + * The maximum number of breadcrumbs to show before overflow. + */ readonly show = input(3); - private breadcrumbs: BreadcrumbComponent[] = []; + protected readonly breadcrumbs = contentChildren(BreadcrumbComponent); - // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals - // eslint-disable-next-line @angular-eslint/prefer-signals - @ContentChildren(BreadcrumbComponent) - protected set breadcrumbList(value: QueryList<BreadcrumbComponent>) { - this.breadcrumbs = value.toArray(); - } + /** Whether the breadcrumbs exceed the show limit and require an overflow menu */ + protected readonly hasOverflow = computed(() => this.breadcrumbs().length > this.show()); - protected get beforeOverflow() { - if (this.hasOverflow) { - return this.breadcrumbs.slice(0, this.show() - 1); + /** Breadcrumbs shown before the overflow menu */ + protected readonly beforeOverflow = computed(() => { + const items = this.breadcrumbs(); + const showCount = this.show(); + + if (items.length > showCount) { + return items.slice(0, showCount - 1); } + return items; + }); - return this.breadcrumbs; - } + /** Breadcrumbs hidden in the overflow menu */ + protected readonly overflow = computed(() => { + return this.breadcrumbs().slice(this.show() - 1, -1); + }); - protected get overflow() { - return this.breadcrumbs.slice(this.show() - 1, -1); - } - - protected get afterOverflow() { - return this.breadcrumbs.slice(-1); - } - - protected get hasOverflow() { - return this.breadcrumbs.length > this.show(); - } + /** The last breadcrumb, shown after the overflow menu */ + protected readonly afterOverflow = computed(() => this.breadcrumbs().slice(-1)); } diff --git a/libs/components/src/breadcrumbs/breadcrumbs.stories.ts b/libs/components/src/breadcrumbs/breadcrumbs.stories.ts index 3064ffe3cb4..d0ce221a780 100644 --- a/libs/components/src/breadcrumbs/breadcrumbs.stories.ts +++ b/libs/components/src/breadcrumbs/breadcrumbs.stories.ts @@ -1,4 +1,4 @@ -import { Component, importProvidersFrom } from "@angular/core"; +import { ChangeDetectionStrategy, Component, importProvidersFrom } from "@angular/core"; import { RouterModule } from "@angular/router"; import { Meta, StoryObj, applicationConfig, moduleMetadata } from "@storybook/angular"; @@ -18,10 +18,9 @@ interface Breadcrumb { route: string; } -// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush -// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ template: "", + changeDetection: ChangeDetectionStrategy.OnPush, }) class EmptyComponent {} From d2b48df9c08c4c74c0031154439ed445fb549b84 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 16 Dec 2025 09:18:02 -0600 Subject: [PATCH 082/188] [deps] AC: Update core-js to v3.47.0 (#17032) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- apps/cli/package.json | 2 +- package-lock.json | 10 +++++----- package.json | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/cli/package.json b/apps/cli/package.json index 0ae4bf9ce30..ff74664ac76 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -69,7 +69,7 @@ "browser-hrtime": "1.1.8", "chalk": "4.1.2", "commander": "14.0.0", - "core-js": "3.45.0", + "core-js": "3.47.0", "form-data": "4.0.4", "https-proxy-agent": "7.0.6", "inquirer": "8.2.6", diff --git a/package-lock.json b/package-lock.json index a1e2f1121a7..879cda31ec7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -43,7 +43,7 @@ "bufferutil": "4.0.9", "chalk": "4.1.2", "commander": "14.0.0", - "core-js": "3.45.0", + "core-js": "3.47.0", "form-data": "4.0.4", "https-proxy-agent": "7.0.6", "inquirer": "8.2.6", @@ -205,7 +205,7 @@ "browser-hrtime": "1.1.8", "chalk": "4.1.2", "commander": "14.0.0", - "core-js": "3.45.0", + "core-js": "3.47.0", "form-data": "4.0.4", "https-proxy-agent": "7.0.6", "inquirer": "8.2.6", @@ -20457,9 +20457,9 @@ } }, "node_modules/core-js": { - "version": "3.45.0", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.45.0.tgz", - "integrity": "sha512-c2KZL9lP4DjkN3hk/an4pWn5b5ZefhRJnAc42n6LJ19kSnbeRbdQZE5dSeE2LBol1OwJD3X1BQvFTAsa8ReeDA==", + "version": "3.47.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.47.0.tgz", + "integrity": "sha512-c3Q2VVkGAUyupsjRnaNX6u8Dq2vAdzm9iuPj5FW0fRxzlxgq9Q39MDq10IvmQSpLgHQNyQzQmOo6bgGHmH3NNg==", "hasInstallScript": true, "license": "MIT", "funding": { diff --git a/package.json b/package.json index cd1262ac373..1bb7c5dbe40 100644 --- a/package.json +++ b/package.json @@ -177,7 +177,7 @@ "bufferutil": "4.0.9", "chalk": "4.1.2", "commander": "14.0.0", - "core-js": "3.45.0", + "core-js": "3.47.0", "form-data": "4.0.4", "https-proxy-agent": "7.0.6", "inquirer": "8.2.6", From b1591da1b2d9aa84a122c139462a8ebe7c3fbc93 Mon Sep 17 00:00:00 2001 From: "bw-ghapp[bot]" <178206702+bw-ghapp[bot]@users.noreply.github.com> Date: Tue, 16 Dec 2025 16:46:31 +0100 Subject: [PATCH 083/188] Autosync the updated translations (#17936) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/browser/src/_locales/ar/messages.json | 180 ++++++++- apps/browser/src/_locales/az/messages.json | 196 +++++++-- apps/browser/src/_locales/be/messages.json | 180 ++++++++- apps/browser/src/_locales/bg/messages.json | 178 ++++++++- apps/browser/src/_locales/bn/messages.json | 180 ++++++++- apps/browser/src/_locales/bs/messages.json | 180 ++++++++- apps/browser/src/_locales/ca/messages.json | 180 ++++++++- apps/browser/src/_locales/cs/messages.json | 180 ++++++++- apps/browser/src/_locales/cy/messages.json | 180 ++++++++- apps/browser/src/_locales/da/messages.json | 180 ++++++++- apps/browser/src/_locales/de/messages.json | 206 ++++++++-- apps/browser/src/_locales/el/messages.json | 180 ++++++++- apps/browser/src/_locales/en_GB/messages.json | 180 ++++++++- apps/browser/src/_locales/en_IN/messages.json | 180 ++++++++- apps/browser/src/_locales/es/messages.json | 180 ++++++++- apps/browser/src/_locales/et/messages.json | 180 ++++++++- apps/browser/src/_locales/eu/messages.json | 180 ++++++++- apps/browser/src/_locales/fa/messages.json | 180 ++++++++- apps/browser/src/_locales/fi/messages.json | 180 ++++++++- apps/browser/src/_locales/fil/messages.json | 180 ++++++++- apps/browser/src/_locales/fr/messages.json | 206 ++++++++-- apps/browser/src/_locales/gl/messages.json | 180 ++++++++- apps/browser/src/_locales/he/messages.json | 180 ++++++++- apps/browser/src/_locales/hi/messages.json | 180 ++++++++- apps/browser/src/_locales/hr/messages.json | 180 ++++++++- apps/browser/src/_locales/hu/messages.json | 180 ++++++++- apps/browser/src/_locales/id/messages.json | 180 ++++++++- apps/browser/src/_locales/it/messages.json | 180 ++++++++- apps/browser/src/_locales/ja/messages.json | 294 ++++++++++---- apps/browser/src/_locales/ka/messages.json | 180 ++++++++- apps/browser/src/_locales/km/messages.json | 180 ++++++++- apps/browser/src/_locales/kn/messages.json | 180 ++++++++- apps/browser/src/_locales/ko/messages.json | 180 ++++++++- apps/browser/src/_locales/lt/messages.json | 180 ++++++++- apps/browser/src/_locales/lv/messages.json | 180 ++++++++- apps/browser/src/_locales/ml/messages.json | 180 ++++++++- apps/browser/src/_locales/mr/messages.json | 180 ++++++++- apps/browser/src/_locales/my/messages.json | 180 ++++++++- apps/browser/src/_locales/nb/messages.json | 180 ++++++++- apps/browser/src/_locales/ne/messages.json | 180 ++++++++- apps/browser/src/_locales/nl/messages.json | 180 ++++++++- apps/browser/src/_locales/nn/messages.json | 180 ++++++++- apps/browser/src/_locales/or/messages.json | 180 ++++++++- apps/browser/src/_locales/pl/messages.json | 190 ++++++++- apps/browser/src/_locales/pt_BR/messages.json | 240 ++++++++--- apps/browser/src/_locales/pt_PT/messages.json | 182 ++++++++- apps/browser/src/_locales/ro/messages.json | 180 ++++++++- apps/browser/src/_locales/ru/messages.json | 188 ++++++++- apps/browser/src/_locales/si/messages.json | 180 ++++++++- apps/browser/src/_locales/sk/messages.json | 184 ++++++++- apps/browser/src/_locales/sl/messages.json | 180 ++++++++- apps/browser/src/_locales/sr/messages.json | 222 +++++++++-- apps/browser/src/_locales/sv/messages.json | 180 ++++++++- apps/browser/src/_locales/ta/messages.json | 372 ++++++++++++------ apps/browser/src/_locales/te/messages.json | 180 ++++++++- apps/browser/src/_locales/th/messages.json | 182 ++++++++- apps/browser/src/_locales/tr/messages.json | 212 ++++++++-- apps/browser/src/_locales/uk/messages.json | 180 ++++++++- apps/browser/src/_locales/vi/messages.json | 206 ++++++++-- apps/browser/src/_locales/zh_CN/messages.json | 210 ++++++++-- apps/browser/src/_locales/zh_TW/messages.json | 208 ++++++++-- 61 files changed, 10312 insertions(+), 1284 deletions(-) diff --git a/apps/browser/src/_locales/ar/messages.json b/apps/browser/src/_locales/ar/messages.json index 452cbafe6d5..f1e2da53768 100644 --- a/apps/browser/src/_locales/ar/messages.json +++ b/apps/browser/src/_locales/ar/messages.json @@ -436,8 +436,8 @@ "sync": { "message": "المزامنة" }, - "syncVaultNow": { - "message": "مزامنة الخزانة الآن" + "syncNow": { + "message": "Sync now" }, "lastSync": { "message": "آخر مزامنة:" @@ -455,9 +455,6 @@ "bitWebVaultApp": { "message": "تطبيق ويب Bitwarden" }, - "importItems": { - "message": "استيراد العناصر" - }, "select": { "message": "تحديد" }, @@ -1325,8 +1322,11 @@ "exportFrom": { "message": "التصدير من" }, - "exportVault": { - "message": "تصدير الخزانة" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "صيغة الملف" @@ -1457,6 +1457,15 @@ "attachmentSaved": { "message": "تم حفظ المرفقات" }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "file": { "message": "الملف" }, @@ -1466,6 +1475,9 @@ "selectFile": { "message": "حدد ملفًا" }, + "itemsTransferred": { + "message": "Items transferred" + }, "maxFileSize": { "message": "الحجم الأقصى للملف هو 500 ميجابايت." }, @@ -3240,9 +3252,6 @@ "copyCustomFieldNameNotUnique": { "message": "لم يتم العثور على معرف فريد." }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "organizationName": { "message": "Organization name" }, @@ -4206,10 +4215,6 @@ "ignore": { "message": "تجاهل" }, - "importData": { - "message": "استيراد البيانات", - "description": "Used for the header of the import dialog, the import button and within the file-password-prompt" - }, "importError": { "message": "خطأ في الاستيراد" }, @@ -4796,6 +4801,15 @@ "accountSecurity": { "message": "Account security" }, + "phishingBlocker": { + "message": "Phishing Blocker" + }, + "enablePhishingDetection": { + "message": "Phishing detection" + }, + "enablePhishingDetectionDesc": { + "message": "Display warning before accessing suspected phishing sites" + }, "notifications": { "message": "Notifications" }, @@ -5848,8 +5862,8 @@ "andMoreFeatures": { "message": "And more!" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "upgradeToPremium": { "message": "Upgrade to Premium" @@ -5879,7 +5893,141 @@ "cardNumberLabel": { "message": "Card number" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { "message": "Timeout action" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToImmediately": { + "message": "Your organization has set the default session timeout to Immediately." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Your organization has set the default session timeout to On system lock." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On browser restart." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On browser restart" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/browser/src/_locales/az/messages.json b/apps/browser/src/_locales/az/messages.json index c4197f831d3..81c9fb6a131 100644 --- a/apps/browser/src/_locales/az/messages.json +++ b/apps/browser/src/_locales/az/messages.json @@ -436,8 +436,8 @@ "sync": { "message": "Sinxr" }, - "syncVaultNow": { - "message": "Seyfi indi sinxronlaşdır" + "syncNow": { + "message": "İndi sinxr." }, "lastSync": { "message": "Son sinxr:" @@ -455,9 +455,6 @@ "bitWebVaultApp": { "message": "Bitwarden veb tətbiqi" }, - "importItems": { - "message": "Elementləri daxilə köçür" - }, "select": { "message": "Seçin" }, @@ -1325,8 +1322,11 @@ "exportFrom": { "message": "Buradan xaricə köçür" }, - "exportVault": { - "message": "Seyfi xaricə köçür" + "export": { + "message": "Xaricə köçür" + }, + "import": { + "message": "Daxilə köçür" }, "fileFormat": { "message": "Fayl formatı" @@ -1407,25 +1407,25 @@ "message": "Daha ətraflı" }, "migrationsFailed": { - "message": "An error occurred updating the encryption settings." + "message": "Şifrələmə ayarlarını güncəlləyərkən bir xəta baş verdi." }, "updateEncryptionSettingsTitle": { - "message": "Update your encryption settings" + "message": "Şifrələmə ayarlarınızı güncəlləyin" }, "updateEncryptionSettingsDesc": { - "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + "message": "Tövsiyə edilən yeni şifrələmə ayarları, hesabınızın təhlükəsizliyini artıracaq. İndi güncəlləmək üçün ana parolunuzu daxil edin." }, "confirmIdentityToContinue": { - "message": "Confirm your identity to continue" + "message": "Davam etmək üçün kimliyinizi təsdiqləyin" }, "enterYourMasterPassword": { - "message": "Enter your master password" + "message": "Ana parolunuzu daxil edin" }, "updateSettings": { - "message": "Update settings" + "message": "Ayarları güncəllə" }, "later": { - "message": "Later" + "message": "Sonra" }, "authenticatorKeyTotp": { "message": "Kimlik doğrulayıcı açarı (TOTP)" @@ -1457,6 +1457,15 @@ "attachmentSaved": { "message": "Qoşma saxlanıldı." }, + "fixEncryption": { + "message": "Şifrələməni düzəlt" + }, + "fixEncryptionTooltip": { + "message": "Bu fayl, köhnə bir şifrələmə üsulunu istifadə edir." + }, + "attachmentUpdated": { + "message": "Qoşma güncəllənib" + }, "file": { "message": "Fayl" }, @@ -1466,6 +1475,9 @@ "selectFile": { "message": "Bir fayl seçin" }, + "itemsTransferred": { + "message": "Elementlər köçürüldü" + }, "maxFileSize": { "message": "Maksimal fayl həcmi 500 MB-dır" }, @@ -1497,7 +1509,7 @@ "message": "Fayl qoşmaları üçün 1 GB şifrələnmiş anbar sahəsi" }, "premiumSignUpStorageV2": { - "message": "$SIZE$ encrypted storage for file attachments.", + "message": "Fayl qoşmaları üçün $SIZE$ şifrələnmiş anbar sahəsi.", "placeholders": { "size": { "content": "$1", @@ -3240,9 +3252,6 @@ "copyCustomFieldNameNotUnique": { "message": "Unikal identifikator tapılmadı." }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "Aşağıdakı təşkilatların üzvləri üçün artıq ana parol tələb olunmur. Lütfən aşağıdakı domeni təşkilatınızın inzibatçısı ilə təsdiqləyin." - }, "organizationName": { "message": "Təşkilat adı" }, @@ -4206,10 +4215,6 @@ "ignore": { "message": "Yox say" }, - "importData": { - "message": "Veriləri daxilə köçür", - "description": "Used for the header of the import dialog, the import button and within the file-password-prompt" - }, "importError": { "message": "Daxilə köçürmə xətası" }, @@ -4796,6 +4801,15 @@ "accountSecurity": { "message": "Hesab güvənliyi" }, + "phishingBlocker": { + "message": "Phishing Blocker" + }, + "enablePhishingDetection": { + "message": "Phishing detection" + }, + "enablePhishingDetectionDesc": { + "message": "Display warning before accessing suspected phishing sites" + }, "notifications": { "message": "Bildirişlər" }, @@ -5848,8 +5862,8 @@ "andMoreFeatures": { "message": "Və daha çoxu!" }, - "planDescPremium": { - "message": "Tam onlayn təhlükəsizlik" + "advancedOnlineSecurity": { + "message": "Qabaqcıl onlayn təhlükəsizlik" }, "upgradeToPremium": { "message": "\"Premium\"a yüksəlt" @@ -5879,7 +5893,141 @@ "cardNumberLabel": { "message": "Kart nömrəsi" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Təşkilatınız, artıq Bitwarden-ə giriş etmək üçün ana parol istifadə etmir. Davam etmək üçün təşkilatı və domeni doğrulayın." + }, + "continueWithLogIn": { + "message": "Giriş etməyə davam" + }, + "doNotContinue": { + "message": "Davam etmə" + }, + "domain": { + "message": "Domen" + }, + "keyConnectorDomainTooltip": { + "message": "Bu domen, hesabınızın şifrələmə açarlarını saxlayacaq, ona görə də, bu domenə güvəndiyinizə əmin olun. Əmin deyilsinizsə, adminizlə əlaqə saxlayın." + }, + "verifyYourOrganization": { + "message": "Giriş etmək üçün təşkilatınızı doğrulayın" + }, + "organizationVerified": { + "message": "Təşkilat doğrulandı" + }, + "domainVerified": { + "message": "Domen doğrulandı" + }, + "leaveOrganizationContent": { + "message": "Təşkilatınızı doğrulamasanız, təşkilata erişiminiz ləğv ediləcək." + }, + "leaveNow": { + "message": "İndi tərk et" + }, + "verifyYourDomainToLogin": { + "message": "Giriş etmək üçün domeninizi doğrulayın" + }, + "verifyYourDomainDescription": { + "message": "Giriş prosesini davam etdirmək üçün bu domeni doğrulayın." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "Giriş prosesini davam etdirmək üçün bu təşkilatı və domeni doğrulayın." + }, "sessionTimeoutSettingsAction": { "message": "Vaxt bitmə əməliyyatı" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "Bu ayar, təşkilatınız tərəfindən idarə olunur." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Təşkilatınız, maksimum seyf bitmə vaxtını $HOURS$ saat $MINUTES$ dəqiqə olaraq ayarladı.", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToImmediately": { + "message": "Təşkilatınız, seansın ilkin bitmə vaxtını dərhal olaraq təyin etdi." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Təşkilatınız, seansın ilkin bitmə vaxtını Sistem kilidi açılanda olaraq təyin etdi." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Təşkilatınız, seansın ilkin bitmə vaxtını Brauzer yenidən başladılanda olaraq təyin etdi." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maksimum bitmə vaxtı $HOURS$ saat $MINUTES$ dəqiqə dəyərini aşa bilməz", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "Brauzer yenidən başladılanda" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Vaxt bitmə əməliyyatınızı dəyişdirmək üçün bir kilid açma üsulu qurun." + }, + "upgrade": { + "message": "Yüksəlt" + }, + "leaveConfirmationDialogTitle": { + "message": "Tərk etmək istədiyinizə əminsiniz?" + }, + "leaveConfirmationDialogContentOne": { + "message": "Rədd cavabı versəniz, fərdi elementləriniz hesabınızda qalacaq, paylaşılan elementlərə və təşkilat özəlliklərinə erişimi itirəcəksiniz." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Erişimi təkrar qazanmaq üçün admininizlə əlaqə saxlayın." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Tərk et: $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "Seyfimi necə idarə edim?" + }, + "transferItemsToOrganizationTitle": { + "message": "Elementləri bura köçür: $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$, təhlükəsizlik və riayətlilik üçün bütün elementlərin təşkilata aid olmasını tələb edir. Elementlərinizin sahibliyini transfer etmək üçün qəbul edin.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Transferi qəbul et" + }, + "declineAndLeave": { + "message": "Rədd et və tərk et" + }, + "whyAmISeeingThis": { + "message": "Bunu niyə görürəm?" } } diff --git a/apps/browser/src/_locales/be/messages.json b/apps/browser/src/_locales/be/messages.json index 806aedfab27..afe26551760 100644 --- a/apps/browser/src/_locales/be/messages.json +++ b/apps/browser/src/_locales/be/messages.json @@ -436,8 +436,8 @@ "sync": { "message": "Сінхранізаваць" }, - "syncVaultNow": { - "message": "Сінхранізаваць сховішча зараз" + "syncNow": { + "message": "Sync now" }, "lastSync": { "message": "Апошняя сінхранізацыя:" @@ -455,9 +455,6 @@ "bitWebVaultApp": { "message": "Вэб-праграма Bitwarden" }, - "importItems": { - "message": "Імпартаванне элементаў" - }, "select": { "message": "Выбраць" }, @@ -1325,8 +1322,11 @@ "exportFrom": { "message": "Экспартаванне з" }, - "exportVault": { - "message": "Экспартаваць сховішча" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "Фармат файла" @@ -1457,6 +1457,15 @@ "attachmentSaved": { "message": "Далучэнне захавана." }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "file": { "message": "Файл" }, @@ -1466,6 +1475,9 @@ "selectFile": { "message": "Выберыце файл." }, + "itemsTransferred": { + "message": "Items transferred" + }, "maxFileSize": { "message": "Максімальны памер файла 500 МБ." }, @@ -3240,9 +3252,6 @@ "copyCustomFieldNameNotUnique": { "message": "Не знойдзены ўнікальны ідэнтыфікатар." }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "organizationName": { "message": "Organization name" }, @@ -4206,10 +4215,6 @@ "ignore": { "message": "Iгнараваць" }, - "importData": { - "message": "Імпартаванне даных", - "description": "Used for the header of the import dialog, the import button and within the file-password-prompt" - }, "importError": { "message": "Памылка імпартавання" }, @@ -4796,6 +4801,15 @@ "accountSecurity": { "message": "Бяспеке акаўнта" }, + "phishingBlocker": { + "message": "Phishing Blocker" + }, + "enablePhishingDetection": { + "message": "Phishing detection" + }, + "enablePhishingDetectionDesc": { + "message": "Display warning before accessing suspected phishing sites" + }, "notifications": { "message": "Апавяшчэнні" }, @@ -5848,8 +5862,8 @@ "andMoreFeatures": { "message": "And more!" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "upgradeToPremium": { "message": "Upgrade to Premium" @@ -5879,7 +5893,141 @@ "cardNumberLabel": { "message": "Card number" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { "message": "Timeout action" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToImmediately": { + "message": "Your organization has set the default session timeout to Immediately." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Your organization has set the default session timeout to On system lock." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On browser restart." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On browser restart" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/browser/src/_locales/bg/messages.json b/apps/browser/src/_locales/bg/messages.json index 76b20f8bd2e..adaa86369c6 100644 --- a/apps/browser/src/_locales/bg/messages.json +++ b/apps/browser/src/_locales/bg/messages.json @@ -436,7 +436,7 @@ "sync": { "message": "Синхронизиране" }, - "syncVaultNow": { + "syncNow": { "message": "Синхронизиране сега" }, "lastSync": { @@ -455,9 +455,6 @@ "bitWebVaultApp": { "message": "Уеб приложение" }, - "importItems": { - "message": "Внасяне на елементи" - }, "select": { "message": "Избор" }, @@ -1325,8 +1322,11 @@ "exportFrom": { "message": "Изнасяне от" }, - "exportVault": { - "message": "Изнасяне на трезора" + "export": { + "message": "Изнасяне" + }, + "import": { + "message": "Внасяне" }, "fileFormat": { "message": "Формат на файла" @@ -1457,6 +1457,15 @@ "attachmentSaved": { "message": "Прикаченият файл е запазен." }, + "fixEncryption": { + "message": "Поправяне на шифроването" + }, + "fixEncryptionTooltip": { + "message": "Този файл използва остарял метод на шифроване." + }, + "attachmentUpdated": { + "message": "Прикаченият файл е актуализиран" + }, "file": { "message": "Файл" }, @@ -1466,6 +1475,9 @@ "selectFile": { "message": "Изберете файл." }, + "itemsTransferred": { + "message": "Елементите са прехвърлени" + }, "maxFileSize": { "message": "Големината на файла е най-много 500 MB." }, @@ -3240,9 +3252,6 @@ "copyCustomFieldNameNotUnique": { "message": "Няма намерен уникален идентификатор." }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "За членовете на следната организация вече не се изисква главна парола. Потвърдете домейна по-долу с администратора на организацията си." - }, "organizationName": { "message": "Име на организацията" }, @@ -4206,10 +4215,6 @@ "ignore": { "message": "Пренебрегване" }, - "importData": { - "message": "Внасяне на данни", - "description": "Used for the header of the import dialog, the import button and within the file-password-prompt" - }, "importError": { "message": "Грешка при внасянето" }, @@ -4796,6 +4801,15 @@ "accountSecurity": { "message": "Защита на регистрацията" }, + "phishingBlocker": { + "message": "Phishing Blocker" + }, + "enablePhishingDetection": { + "message": "Phishing detection" + }, + "enablePhishingDetectionDesc": { + "message": "Display warning before accessing suspected phishing sites" + }, "notifications": { "message": "Известия" }, @@ -5848,8 +5862,8 @@ "andMoreFeatures": { "message": "И още!" }, - "planDescPremium": { - "message": "Пълна сигурност в Интернет" + "advancedOnlineSecurity": { + "message": "Разширена сигурност в Интернет" }, "upgradeToPremium": { "message": "Надградете до Платения план" @@ -5879,7 +5893,141 @@ "cardNumberLabel": { "message": "Номер на картата" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Вашата организация вече не използва главни пароли за вписване в Битуорден. За да продължите, потвърдете организацията и домейна." + }, + "continueWithLogIn": { + "message": "Продължаване с вписването" + }, + "doNotContinue": { + "message": "Не продължавам" + }, + "domain": { + "message": "Домейн" + }, + "keyConnectorDomainTooltip": { + "message": "Този домейн ще съхранява ключовете за шифроване на акаунта Ви, така че се уверете, че му имате доверие. Ако имате съмнения, свържете се с администратора си." + }, + "verifyYourOrganization": { + "message": "Потвърдете организацията си, за да се впишете" + }, + "organizationVerified": { + "message": "Организацията е потвърдена" + }, + "domainVerified": { + "message": "Домейнът е потвърден" + }, + "leaveOrganizationContent": { + "message": "Ако не потвърдите организацията, достъпът Ви до нея ще бъде преустановен." + }, + "leaveNow": { + "message": "Напускане сега" + }, + "verifyYourDomainToLogin": { + "message": "Потвърдете домейна си, за да се впишете" + }, + "verifyYourDomainDescription": { + "message": "За да продължите с вписването, потвърдете този домейн." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "За да продължите с вписването, потвърдете организацията и домейна." + }, "sessionTimeoutSettingsAction": { "message": "Действие при изтичането на времето за достъп" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "Тази настройка се управлява от организацията Ви." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Организацията Ви е настроила максималното разрешено време за достъп на [%1$i] час(а) и [%2$i] минути.", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToImmediately": { + "message": "Организацията Ви е настроила стандартното разрешено време за достъп да бъде нула." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Организацията Ви е настроила стандартното разрешено време за достъп да бъде до заключване на системата." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Организацията Ви е настроила стандартното разрешено време за достъп да бъде до рестартиране на браузъра." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Максималното време на достъп не може да превишава $HOURS$ час(а) и $MINUTES$ минути", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "При рестартиране на браузъра" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Задайте метод за отключване, за да може да промените действието при изтичане на времето за достъп" + }, + "upgrade": { + "message": "Надграждане" + }, + "leaveConfirmationDialogTitle": { + "message": "Наистина ли искате да напуснете?" + }, + "leaveConfirmationDialogContentOne": { + "message": "Ако откажете, Вашите собствени елементи ще останат в акаунта Ви, но ще загубите достъп до споделените елементи и функционалностите на организацията." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Свържете се с администратор, за да получите достъп отново." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Напускане на $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "Как да управлявам трезора си?" + }, + "transferItemsToOrganizationTitle": { + "message": "Прехвърляне на елементи към $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ изисква всички елементи да станат притежание на организацията, за по-добра сигурност и съвместимост. Изберете, че приемате, за да прехвърлите собствеността на елементите си към организацията.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Приемане на прехвърлянето" + }, + "declineAndLeave": { + "message": "Отказване и напускане" + }, + "whyAmISeeingThis": { + "message": "Защо виждам това?" } } diff --git a/apps/browser/src/_locales/bn/messages.json b/apps/browser/src/_locales/bn/messages.json index 4845282e825..9b2248da59b 100644 --- a/apps/browser/src/_locales/bn/messages.json +++ b/apps/browser/src/_locales/bn/messages.json @@ -436,8 +436,8 @@ "sync": { "message": "সিঙ্ক" }, - "syncVaultNow": { - "message": "এখনই ভল্ট সিঙ্ক করুন" + "syncNow": { + "message": "Sync now" }, "lastSync": { "message": "শেষ সিঙ্ক:" @@ -455,9 +455,6 @@ "bitWebVaultApp": { "message": "Bitwarden web app" }, - "importItems": { - "message": "বস্তু আমদানি" - }, "select": { "message": "নির্বাচন করুন" }, @@ -1325,8 +1322,11 @@ "exportFrom": { "message": "Export from" }, - "exportVault": { - "message": "ভল্ট রফতানি" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "ফাইলের ধরণ" @@ -1457,6 +1457,15 @@ "attachmentSaved": { "message": "সংযুক্তিটি সংরক্ষণ করা হয়েছে।" }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "file": { "message": "ফাইল" }, @@ -1466,6 +1475,9 @@ "selectFile": { "message": "একটি ফাইল নির্বাচন করুন।" }, + "itemsTransferred": { + "message": "Items transferred" + }, "maxFileSize": { "message": "সর্বোচ্চ ফাইলের আকার ১০০ এমবি।" }, @@ -3240,9 +3252,6 @@ "copyCustomFieldNameNotUnique": { "message": "No unique identifier found." }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "organizationName": { "message": "Organization name" }, @@ -4206,10 +4215,6 @@ "ignore": { "message": "Ignore" }, - "importData": { - "message": "Import data", - "description": "Used for the header of the import dialog, the import button and within the file-password-prompt" - }, "importError": { "message": "Import error" }, @@ -4796,6 +4801,15 @@ "accountSecurity": { "message": "Account security" }, + "phishingBlocker": { + "message": "Phishing Blocker" + }, + "enablePhishingDetection": { + "message": "Phishing detection" + }, + "enablePhishingDetectionDesc": { + "message": "Display warning before accessing suspected phishing sites" + }, "notifications": { "message": "Notifications" }, @@ -5848,8 +5862,8 @@ "andMoreFeatures": { "message": "And more!" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "upgradeToPremium": { "message": "Upgrade to Premium" @@ -5879,7 +5893,141 @@ "cardNumberLabel": { "message": "Card number" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { "message": "Timeout action" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToImmediately": { + "message": "Your organization has set the default session timeout to Immediately." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Your organization has set the default session timeout to On system lock." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On browser restart." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On browser restart" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/browser/src/_locales/bs/messages.json b/apps/browser/src/_locales/bs/messages.json index cff1dd150cb..5c3c01a0ab5 100644 --- a/apps/browser/src/_locales/bs/messages.json +++ b/apps/browser/src/_locales/bs/messages.json @@ -436,8 +436,8 @@ "sync": { "message": "Sync" }, - "syncVaultNow": { - "message": "Sync vault now" + "syncNow": { + "message": "Sync now" }, "lastSync": { "message": "Last sync:" @@ -455,9 +455,6 @@ "bitWebVaultApp": { "message": "Bitwarden web app" }, - "importItems": { - "message": "Import items" - }, "select": { "message": "Select" }, @@ -1325,8 +1322,11 @@ "exportFrom": { "message": "Export from" }, - "exportVault": { - "message": "Export vault" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "File format" @@ -1457,6 +1457,15 @@ "attachmentSaved": { "message": "Attachment saved" }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "file": { "message": "File" }, @@ -1466,6 +1475,9 @@ "selectFile": { "message": "Select a file" }, + "itemsTransferred": { + "message": "Items transferred" + }, "maxFileSize": { "message": "Maximum file size is 500 MB." }, @@ -3240,9 +3252,6 @@ "copyCustomFieldNameNotUnique": { "message": "No unique identifier found." }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "organizationName": { "message": "Organization name" }, @@ -4206,10 +4215,6 @@ "ignore": { "message": "Ignore" }, - "importData": { - "message": "Import data", - "description": "Used for the header of the import dialog, the import button and within the file-password-prompt" - }, "importError": { "message": "Import error" }, @@ -4796,6 +4801,15 @@ "accountSecurity": { "message": "Account security" }, + "phishingBlocker": { + "message": "Phishing Blocker" + }, + "enablePhishingDetection": { + "message": "Phishing detection" + }, + "enablePhishingDetectionDesc": { + "message": "Display warning before accessing suspected phishing sites" + }, "notifications": { "message": "Notifications" }, @@ -5848,8 +5862,8 @@ "andMoreFeatures": { "message": "And more!" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "upgradeToPremium": { "message": "Upgrade to Premium" @@ -5879,7 +5893,141 @@ "cardNumberLabel": { "message": "Card number" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { "message": "Timeout action" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToImmediately": { + "message": "Your organization has set the default session timeout to Immediately." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Your organization has set the default session timeout to On system lock." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On browser restart." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On browser restart" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/browser/src/_locales/ca/messages.json b/apps/browser/src/_locales/ca/messages.json index ff0e2de51af..61a12bfb50c 100644 --- a/apps/browser/src/_locales/ca/messages.json +++ b/apps/browser/src/_locales/ca/messages.json @@ -436,8 +436,8 @@ "sync": { "message": "Sincronització" }, - "syncVaultNow": { - "message": "Sincronitza la caixa forta ara" + "syncNow": { + "message": "Sync now" }, "lastSync": { "message": "Última sincronització:" @@ -455,9 +455,6 @@ "bitWebVaultApp": { "message": "Aplicació web Bitwarden" }, - "importItems": { - "message": "Importa elements" - }, "select": { "message": "Selecciona" }, @@ -1325,8 +1322,11 @@ "exportFrom": { "message": "Exporta des de" }, - "exportVault": { - "message": "Exporta caixa forta" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "Format de fitxer" @@ -1457,6 +1457,15 @@ "attachmentSaved": { "message": "S'ha guardat el fitxer adjunt" }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "file": { "message": "Fitxer" }, @@ -1466,6 +1475,9 @@ "selectFile": { "message": "Seleccioneu un fitxer" }, + "itemsTransferred": { + "message": "Items transferred" + }, "maxFileSize": { "message": "La mida màxima del fitxer és de 500 MB." }, @@ -3240,9 +3252,6 @@ "copyCustomFieldNameNotUnique": { "message": "No s'ha trobat cap identificador únic." }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "organizationName": { "message": "Organization name" }, @@ -4206,10 +4215,6 @@ "ignore": { "message": "Ignora" }, - "importData": { - "message": "Importa dades", - "description": "Used for the header of the import dialog, the import button and within the file-password-prompt" - }, "importError": { "message": "Error d'importació" }, @@ -4796,6 +4801,15 @@ "accountSecurity": { "message": "Seguretat del compte" }, + "phishingBlocker": { + "message": "Phishing Blocker" + }, + "enablePhishingDetection": { + "message": "Phishing detection" + }, + "enablePhishingDetectionDesc": { + "message": "Display warning before accessing suspected phishing sites" + }, "notifications": { "message": "Notificacions" }, @@ -5848,8 +5862,8 @@ "andMoreFeatures": { "message": "And more!" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "upgradeToPremium": { "message": "Upgrade to Premium" @@ -5879,7 +5893,141 @@ "cardNumberLabel": { "message": "Card number" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { "message": "Timeout action" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToImmediately": { + "message": "Your organization has set the default session timeout to Immediately." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Your organization has set the default session timeout to On system lock." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On browser restart." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On browser restart" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/browser/src/_locales/cs/messages.json b/apps/browser/src/_locales/cs/messages.json index fa8dc426c05..309c7361de2 100644 --- a/apps/browser/src/_locales/cs/messages.json +++ b/apps/browser/src/_locales/cs/messages.json @@ -436,8 +436,8 @@ "sync": { "message": "Synchronizace" }, - "syncVaultNow": { - "message": "Synchronizovat trezor nyní" + "syncNow": { + "message": "Synchronizovat nyní" }, "lastSync": { "message": "Poslední synchronizace:" @@ -455,9 +455,6 @@ "bitWebVaultApp": { "message": "Webová aplikace Bitwardenu" }, - "importItems": { - "message": "Importovat položky" - }, "select": { "message": "Vybrat" }, @@ -1325,8 +1322,11 @@ "exportFrom": { "message": "Exportovat z" }, - "exportVault": { - "message": "Exportovat trezor" + "export": { + "message": "Exportovat" + }, + "import": { + "message": "Importovat" }, "fileFormat": { "message": "Formát souboru" @@ -1457,6 +1457,15 @@ "attachmentSaved": { "message": "Příloha byla uložena" }, + "fixEncryption": { + "message": "Opravit šifrování" + }, + "fixEncryptionTooltip": { + "message": "Tento soubor používá zastaralou šifrovací metodu." + }, + "attachmentUpdated": { + "message": "Příloha byla aktualizována" + }, "file": { "message": "Soubor" }, @@ -1466,6 +1475,9 @@ "selectFile": { "message": "Zvolte soubor" }, + "itemsTransferred": { + "message": "Převedené položky" + }, "maxFileSize": { "message": "Maximální velikost souboru je 500 MB." }, @@ -3240,9 +3252,6 @@ "copyCustomFieldNameNotUnique": { "message": "Nenalezen žádný jedinečný identifikátor." }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "Hlavní heslo již není vyžadováno pro členy následující organizace. Potvrďte níže uvedenou doménu u správce Vaší organizace." - }, "organizationName": { "message": "Název organizace" }, @@ -4206,10 +4215,6 @@ "ignore": { "message": "Ignorovat" }, - "importData": { - "message": "Importovat data", - "description": "Used for the header of the import dialog, the import button and within the file-password-prompt" - }, "importError": { "message": "Chyba importu" }, @@ -4796,6 +4801,15 @@ "accountSecurity": { "message": "Zabezpečení účtu" }, + "phishingBlocker": { + "message": "Blokování phishingu" + }, + "enablePhishingDetection": { + "message": "Detekce phishingu" + }, + "enablePhishingDetectionDesc": { + "message": "Zobrazí varování před přístupem k podezřelým phishingovým stránkám." + }, "notifications": { "message": "Oznámení" }, @@ -5848,8 +5862,8 @@ "andMoreFeatures": { "message": "A ještě více!" }, - "planDescPremium": { - "message": "Dokončit online zabezpečení" + "advancedOnlineSecurity": { + "message": "Pokročilé zabezpečení online" }, "upgradeToPremium": { "message": "Aktualizovat na Premium" @@ -5879,7 +5893,141 @@ "cardNumberLabel": { "message": "Číslo karty" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Vaše organizace již k přihlášení do Bitwardenu nepoužívá hlavní hesla. Chcete-li pokračovat, ověřte organizaci a doménu." + }, + "continueWithLogIn": { + "message": "Pokračovat s přihlášením" + }, + "doNotContinue": { + "message": "Nepokračovat" + }, + "domain": { + "message": "Doména" + }, + "keyConnectorDomainTooltip": { + "message": "Tato doména uloží šifrovací klíče Vašeho účtu, takže se ujistěte, že jí věříte. Pokud si nejste jisti, kontaktujte Vašeho správce." + }, + "verifyYourOrganization": { + "message": "Ověřte svou organizaci pro přihlášení" + }, + "organizationVerified": { + "message": "Organizace byla ověřena" + }, + "domainVerified": { + "message": "Doména byla ověřena" + }, + "leaveOrganizationContent": { + "message": "Pokud neověříte svou organizaci, Váš přístup k organizaci bude zrušen." + }, + "leaveNow": { + "message": "Opustit hned" + }, + "verifyYourDomainToLogin": { + "message": "Ověřte svou doménu pro přihlášení" + }, + "verifyYourDomainDescription": { + "message": "Chcete-li pokračovat v přihlášení, ověřte tuto doménu." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "Chcete-li pokračovat v přihlášení, ověřte organizaci a doménu." + }, "sessionTimeoutSettingsAction": { "message": "Akce vypršení časového limitu" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "Tato nastavení je spravováno Vaší organizací." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Vaše organizace nastavila maximální časový limit relace na $HOURS$ hodin a $MINUTES$ minut.", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToImmediately": { + "message": "Vaše organizace nastavila výchozí časový limit relace na Okamžitě." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Vaše organizace nastavila výchozí časový limit relace na Při uzamknutí systému." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Vaše organizace nastavila výchozí časový limit relace na Při restartu prohlížeče." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximální časový limit nesmí překročit $HOURS$ hodin a $MINUTES$ minut", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "Při restartu prohlížeče" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Nastavte metodu odemknutí, abyste změnili akci při vypršení časového limitu" + }, + "upgrade": { + "message": "Aktualizovat" + }, + "leaveConfirmationDialogTitle": { + "message": "Opravdu chcete odejít?" + }, + "leaveConfirmationDialogContentOne": { + "message": "Odmítnutím zůstanou Vaše osobní položky ve Vašem účtu, ale ztratíte přístup ke sdíleným položkám a funkcím organizace." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Obraťte se na svého správce, abyste znovu získali přístup." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Opustit $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "Jak mohu spravovat svůj trezor?" + }, + "transferItemsToOrganizationTitle": { + "message": "Přenést položky do $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ vyžaduje, aby byly všechny položky vlastněny organizací z důvodu bezpečnosti a shody. Klepnutím na tlačítko pro převod vlastnictví Vašich položek.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Přijmout převod" + }, + "declineAndLeave": { + "message": "Odmítnout a opustit" + }, + "whyAmISeeingThis": { + "message": "Proč se mi toto zobrazuje?" } } diff --git a/apps/browser/src/_locales/cy/messages.json b/apps/browser/src/_locales/cy/messages.json index 45f91f5ccc7..b5d090cc505 100644 --- a/apps/browser/src/_locales/cy/messages.json +++ b/apps/browser/src/_locales/cy/messages.json @@ -436,8 +436,8 @@ "sync": { "message": "Cysoni" }, - "syncVaultNow": { - "message": "Cysoni'r gell nawr" + "syncNow": { + "message": "Sync now" }, "lastSync": { "message": "Wedi'i chysoni ddiwethaf:" @@ -455,9 +455,6 @@ "bitWebVaultApp": { "message": "Bitwarden web app" }, - "importItems": { - "message": "Mewnforio eitemau" - }, "select": { "message": "Dewis" }, @@ -1325,8 +1322,11 @@ "exportFrom": { "message": "Export from" }, - "exportVault": { - "message": "Allforio'r gell" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "Fformat y ffeil" @@ -1457,6 +1457,15 @@ "attachmentSaved": { "message": "Attachment saved" }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "file": { "message": "Ffeil" }, @@ -1466,6 +1475,9 @@ "selectFile": { "message": "Dewis ffeil" }, + "itemsTransferred": { + "message": "Items transferred" + }, "maxFileSize": { "message": "Maximum file size is 500 MB." }, @@ -3240,9 +3252,6 @@ "copyCustomFieldNameNotUnique": { "message": "No unique identifier found." }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "organizationName": { "message": "Organization name" }, @@ -4206,10 +4215,6 @@ "ignore": { "message": "Anwybyddu" }, - "importData": { - "message": "Import data", - "description": "Used for the header of the import dialog, the import button and within the file-password-prompt" - }, "importError": { "message": "Import error" }, @@ -4796,6 +4801,15 @@ "accountSecurity": { "message": "Diogelwch eich cyfrif" }, + "phishingBlocker": { + "message": "Phishing Blocker" + }, + "enablePhishingDetection": { + "message": "Phishing detection" + }, + "enablePhishingDetectionDesc": { + "message": "Display warning before accessing suspected phishing sites" + }, "notifications": { "message": "Hysbysiadau" }, @@ -5848,8 +5862,8 @@ "andMoreFeatures": { "message": "And more!" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "upgradeToPremium": { "message": "Upgrade to Premium" @@ -5879,7 +5893,141 @@ "cardNumberLabel": { "message": "Card number" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { "message": "Timeout action" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToImmediately": { + "message": "Your organization has set the default session timeout to Immediately." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Your organization has set the default session timeout to On system lock." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On browser restart." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On browser restart" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/browser/src/_locales/da/messages.json b/apps/browser/src/_locales/da/messages.json index 50d79ff1f9d..0627c97766f 100644 --- a/apps/browser/src/_locales/da/messages.json +++ b/apps/browser/src/_locales/da/messages.json @@ -436,8 +436,8 @@ "sync": { "message": "Synkronisér" }, - "syncVaultNow": { - "message": "Synkronisér boks nu" + "syncNow": { + "message": "Sync now" }, "lastSync": { "message": "Seneste synkronisering:" @@ -455,9 +455,6 @@ "bitWebVaultApp": { "message": "Bitwarden web-app" }, - "importItems": { - "message": "Importér elementer" - }, "select": { "message": "Vælg" }, @@ -1325,8 +1322,11 @@ "exportFrom": { "message": "Eksportér fra" }, - "exportVault": { - "message": "Eksportér boks" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "Filformat" @@ -1457,6 +1457,15 @@ "attachmentSaved": { "message": "Vedhæftning gemt" }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "file": { "message": "Fil" }, @@ -1466,6 +1475,9 @@ "selectFile": { "message": "Vælg en fil" }, + "itemsTransferred": { + "message": "Items transferred" + }, "maxFileSize": { "message": "Maksimum filstørrelse er 500 MB." }, @@ -3240,9 +3252,6 @@ "copyCustomFieldNameNotUnique": { "message": "Ingen entydig identifikator fundet." }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "organizationName": { "message": "Organization name" }, @@ -4206,10 +4215,6 @@ "ignore": { "message": "Ignorér" }, - "importData": { - "message": "Importér data", - "description": "Used for the header of the import dialog, the import button and within the file-password-prompt" - }, "importError": { "message": "Importfejl" }, @@ -4796,6 +4801,15 @@ "accountSecurity": { "message": "Kontosikkerhed" }, + "phishingBlocker": { + "message": "Phishing Blocker" + }, + "enablePhishingDetection": { + "message": "Phishing detection" + }, + "enablePhishingDetectionDesc": { + "message": "Display warning before accessing suspected phishing sites" + }, "notifications": { "message": "Notifikationer" }, @@ -5848,8 +5862,8 @@ "andMoreFeatures": { "message": "And more!" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "upgradeToPremium": { "message": "Upgrade to Premium" @@ -5879,7 +5893,141 @@ "cardNumberLabel": { "message": "Card number" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { "message": "Timeout action" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToImmediately": { + "message": "Your organization has set the default session timeout to Immediately." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Your organization has set the default session timeout to On system lock." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On browser restart." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On browser restart" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/browser/src/_locales/de/messages.json b/apps/browser/src/_locales/de/messages.json index 969f8c63de3..dc972697dc6 100644 --- a/apps/browser/src/_locales/de/messages.json +++ b/apps/browser/src/_locales/de/messages.json @@ -436,8 +436,8 @@ "sync": { "message": "Synchronisierung" }, - "syncVaultNow": { - "message": "Tresor jetzt synchronisieren" + "syncNow": { + "message": "Jetzt synchronisieren" }, "lastSync": { "message": "Zuletzt synchronisiert:" @@ -455,9 +455,6 @@ "bitWebVaultApp": { "message": "Bitwarden Web-App" }, - "importItems": { - "message": "Einträge importieren" - }, "select": { "message": "Auswählen" }, @@ -562,7 +559,7 @@ "description": "Verb" }, "unArchive": { - "message": "Wiederherstellen" + "message": "Nicht mehr archivieren" }, "itemsInArchive": { "message": "Einträge im Archiv" @@ -577,7 +574,7 @@ "message": "Eintrag wurde archiviert" }, "itemUnarchived": { - "message": "Eintrag wurde wiederhergestellt" + "message": "Eintrag wird nicht mehr archiviert" }, "archiveItem": { "message": "Eintrag archivieren" @@ -1325,8 +1322,11 @@ "exportFrom": { "message": "Export aus" }, - "exportVault": { - "message": "Tresor exportieren" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "Dateiformat" @@ -1407,25 +1407,25 @@ "message": "Erfahre mehr" }, "migrationsFailed": { - "message": "An error occurred updating the encryption settings." + "message": "Beim Aktualisieren der Verschlüsselungseinstellungen ist ein Fehler aufgetreten." }, "updateEncryptionSettingsTitle": { - "message": "Update your encryption settings" + "message": "Aktualisiere deine Verschlüsselungseinstellungen" }, "updateEncryptionSettingsDesc": { - "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + "message": "Die neuen empfohlenen Verschlüsselungseinstellungen verbessern deine Kontosicherheit. Gib dein Master-Passwort ein, um sie zu aktualisieren." }, "confirmIdentityToContinue": { - "message": "Confirm your identity to continue" + "message": "Bestätige deine Identität, um fortzufahren" }, "enterYourMasterPassword": { - "message": "Enter your master password" + "message": "Gib dein Master-Passwort ein" }, "updateSettings": { - "message": "Update settings" + "message": "Einstellungen aktualisieren" }, "later": { - "message": "Later" + "message": "Später" }, "authenticatorKeyTotp": { "message": "Authentifizierungsschlüssel (TOTP)" @@ -1457,6 +1457,15 @@ "attachmentSaved": { "message": "Anhang gespeichert" }, + "fixEncryption": { + "message": "Verschlüsselung reparieren" + }, + "fixEncryptionTooltip": { + "message": "Diese Datei verwendet eine veraltete Verschlüsselungsmethode." + }, + "attachmentUpdated": { + "message": "Anhang aktualisiert" + }, "file": { "message": "Datei" }, @@ -1466,6 +1475,9 @@ "selectFile": { "message": "Wähle eine Datei" }, + "itemsTransferred": { + "message": "Einträge wurden übertragen" + }, "maxFileSize": { "message": "Die maximale Dateigröße beträgt 500 MB." }, @@ -1904,7 +1916,7 @@ "message": "Ablaufjahr" }, "monthly": { - "message": "Monatlich" + "message": "Monat" }, "expiration": { "message": "Gültig bis" @@ -2678,7 +2690,7 @@ "description": "A category title describing the concept of web domains" }, "blockedDomains": { - "message": "Gesperrte Domains" + "message": "Blockierte Domains" }, "learnMoreAboutBlockedDomains": { "message": "Erfahre mehr über blockierte Domains" @@ -2696,7 +2708,7 @@ "message": "Automatisches Ausfüllen und andere zugehörige Funktionen werden für diese Webseiten nicht angeboten. Du musst die Seite neu laden, damit die Änderungen wirksam werden." }, "autofillBlockedNoticeV2": { - "message": "Automatisches Ausfüllen ist für diese Website gesperrt." + "message": "Automatisches Ausfüllen ist für diese Website blockiert." }, "autofillBlockedNoticeGuidance": { "message": "Dies in den Einstellungen ändern" @@ -2850,7 +2862,7 @@ } }, "blockedDomainsSavedSuccess": { - "message": "Änderungen gesperrter Domains gespeichert" + "message": "Änderungen blockierter Domains gespeichert" }, "excludedDomainsSavedSuccess": { "message": "Änderungen der ausgeschlossenen Domain gespeichert" @@ -3240,9 +3252,6 @@ "copyCustomFieldNameNotUnique": { "message": "Keine eindeutige Kennung gefunden." }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "Für Mitglieder der folgenden Organisation ist kein Master-Passwort mehr erforderlich. Bitte bestätige die folgende Domain bei deinem Organisations-Administrator." - }, "organizationName": { "message": "Name der Organisation" }, @@ -4206,10 +4215,6 @@ "ignore": { "message": "Ignorieren" }, - "importData": { - "message": "Daten importieren", - "description": "Used for the header of the import dialog, the import button and within the file-password-prompt" - }, "importError": { "message": "Importfehler" }, @@ -4796,6 +4801,15 @@ "accountSecurity": { "message": "Kontosicherheit" }, + "phishingBlocker": { + "message": "Phishing Blocker" + }, + "enablePhishingDetection": { + "message": "Phishing detection" + }, + "enablePhishingDetectionDesc": { + "message": "Display warning before accessing suspected phishing sites" + }, "notifications": { "message": "Benachrichtigungen" }, @@ -5848,8 +5862,8 @@ "andMoreFeatures": { "message": "Und mehr!" }, - "planDescPremium": { - "message": "Umfassende Online-Sicherheit" + "advancedOnlineSecurity": { + "message": "Erweiterte Online-Sicherheit" }, "upgradeToPremium": { "message": "Upgrade auf Premium" @@ -5879,7 +5893,141 @@ "cardNumberLabel": { "message": "Kartennummer" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Deine Organisation verwendet keine Master-Passwörter mehr, um sich bei Bitwarden anzumelden. Verifiziere die Organisation und Domain, um fortzufahren." + }, + "continueWithLogIn": { + "message": "Mit der Anmeldung fortfahren" + }, + "doNotContinue": { + "message": "Nicht fortfahren" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "Diese Domain speichert die Verschlüsselungsschlüssel deines Kontos. Stelle daher sicher, dass du ihr vertraust. Wenn du dir nicht sicher bist, wende dich an deinen Administrator." + }, + "verifyYourOrganization": { + "message": "Verifiziere deine Organisation, um dich anzumelden" + }, + "organizationVerified": { + "message": "Organisation verifiziert" + }, + "domainVerified": { + "message": "Domain verifiziert" + }, + "leaveOrganizationContent": { + "message": "Wenn du deine Organisation nicht verifizierst, wird dein Zugriff auf die Organisation widerrufen." + }, + "leaveNow": { + "message": "Jetzt verlassen" + }, + "verifyYourDomainToLogin": { + "message": "Verifiziere deine Domain, um dich anzumelden" + }, + "verifyYourDomainDescription": { + "message": "Verifiziere diese Domain, um mit der Anmeldung fortzufahren." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "Um mit der Anmeldung fortzufahren, verifiziere die Organisation und Domain." + }, "sessionTimeoutSettingsAction": { "message": "Timeout-Aktion" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "Diese Einstellung wird von deiner Organisation verwaltet." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Deine Organisation hat das maximale Sitzungs-Timeout auf $HOURS$ Stunde(n) und $MINUTES$ Minute(n) festgelegt.", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToImmediately": { + "message": "Deine Organisation hat das Standard-Sitzungs-Timeout auf \"Sofort\" gesetzt." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Deine Organisation hat das Standard-Sitzungs-Timeout auf \"Wenn System gesperrt\" gesetzt." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Deine Organisation hat das Standard-Sitzungs-Timeout auf \"Bei Neustart des Browsers\" gesetzt." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Das maximale Timeout darf $HOURS$ Stunde(n) und $MINUTES$ Minute(n) nicht überschreiten", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "Bei Neustart des Browsers" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Stell eine Entsperrmethode ein, um deine Timeout-Aktion zu ändern" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Bist du sicher, dass du gehen willst?" + }, + "leaveConfirmationDialogContentOne": { + "message": "Wenn du ablehnst, bleiben deine persönlichen Einträge in deinem Konto erhalten, aber du wirst den Zugriff auf geteilte Einträge und Organisationsfunktionen verlieren." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Kontaktiere deinen Administrator, um wieder Zugriff zu erhalten." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "$ORGANIZATION$ verlassen", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "Wie kann ich meinen Tresor verwalten?" + }, + "transferItemsToOrganizationTitle": { + "message": "Einträge zu $ORGANIZATION$ übertragen", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ erfordert zur Sicherheit und Compliance, dass alle Einträge der Organisation gehören. Klicke auf Akzeptieren, um den Besitz deiner Einträge zu übertragen.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Übertragung annehmen" + }, + "declineAndLeave": { + "message": "Ablehnen und verlassen" + }, + "whyAmISeeingThis": { + "message": "Warum wird mir das angezeigt?" } } diff --git a/apps/browser/src/_locales/el/messages.json b/apps/browser/src/_locales/el/messages.json index 2be53da65ec..e4b7b28a512 100644 --- a/apps/browser/src/_locales/el/messages.json +++ b/apps/browser/src/_locales/el/messages.json @@ -436,8 +436,8 @@ "sync": { "message": "Συγχρονισμός" }, - "syncVaultNow": { - "message": "Συγχρονισμός θησαυ/κιου τώρα" + "syncNow": { + "message": "Sync now" }, "lastSync": { "message": "Τελευταίος συγχρονισμός:" @@ -455,9 +455,6 @@ "bitWebVaultApp": { "message": "Διαδικτυακή εφαρμογή Bitwarden" }, - "importItems": { - "message": "Εισαγωγή στοιχείων" - }, "select": { "message": "Επιλογή" }, @@ -1325,8 +1322,11 @@ "exportFrom": { "message": "Εξαγωγή από" }, - "exportVault": { - "message": "Εξαγωγή Vault" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "Τύπος αρχείου" @@ -1457,6 +1457,15 @@ "attachmentSaved": { "message": "Το συνημμένο αποθηκεύτηκε" }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "file": { "message": "Αρχείο" }, @@ -1466,6 +1475,9 @@ "selectFile": { "message": "Επιλέξτε αρχείο" }, + "itemsTransferred": { + "message": "Items transferred" + }, "maxFileSize": { "message": "Το μέγιστο μέγεθος αρχείου είναι 500 MB." }, @@ -3240,9 +3252,6 @@ "copyCustomFieldNameNotUnique": { "message": "Δε βρέθηκε μοναδικό αναγνωριστικό." }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "organizationName": { "message": "Όνομα οργανισμού" }, @@ -4206,10 +4215,6 @@ "ignore": { "message": "Παράβλεψη" }, - "importData": { - "message": "Εισαγωγή δεδομένων", - "description": "Used for the header of the import dialog, the import button and within the file-password-prompt" - }, "importError": { "message": "Σφάλμα εισαγωγής" }, @@ -4796,6 +4801,15 @@ "accountSecurity": { "message": "Ασφάλεια λογαριασμού" }, + "phishingBlocker": { + "message": "Phishing Blocker" + }, + "enablePhishingDetection": { + "message": "Phishing detection" + }, + "enablePhishingDetectionDesc": { + "message": "Display warning before accessing suspected phishing sites" + }, "notifications": { "message": "Ειδοποιήσεις" }, @@ -5848,8 +5862,8 @@ "andMoreFeatures": { "message": "And more!" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "upgradeToPremium": { "message": "Upgrade to Premium" @@ -5879,7 +5893,141 @@ "cardNumberLabel": { "message": "Card number" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { "message": "Timeout action" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToImmediately": { + "message": "Your organization has set the default session timeout to Immediately." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Your organization has set the default session timeout to On system lock." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On browser restart." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On browser restart" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/browser/src/_locales/en_GB/messages.json b/apps/browser/src/_locales/en_GB/messages.json index 63d9214632a..1abb01fcb14 100644 --- a/apps/browser/src/_locales/en_GB/messages.json +++ b/apps/browser/src/_locales/en_GB/messages.json @@ -436,8 +436,8 @@ "sync": { "message": "Sync" }, - "syncVaultNow": { - "message": "Sync vault now" + "syncNow": { + "message": "Sync now" }, "lastSync": { "message": "Last sync:" @@ -455,9 +455,6 @@ "bitWebVaultApp": { "message": "Bitwarden web app" }, - "importItems": { - "message": "Import items" - }, "select": { "message": "Select" }, @@ -1325,8 +1322,11 @@ "exportFrom": { "message": "Export from" }, - "exportVault": { - "message": "Export vault" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "File format" @@ -1457,6 +1457,15 @@ "attachmentSaved": { "message": "Attachment saved" }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "file": { "message": "File" }, @@ -1466,6 +1475,9 @@ "selectFile": { "message": "Select a file" }, + "itemsTransferred": { + "message": "Items transferred" + }, "maxFileSize": { "message": "Maximum file size is 500 MB." }, @@ -3240,9 +3252,6 @@ "copyCustomFieldNameNotUnique": { "message": "No unique identifier found." }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organisation. Please confirm the domain below with your organisation administrator." - }, "organizationName": { "message": "Organisation name" }, @@ -4206,10 +4215,6 @@ "ignore": { "message": "Ignore" }, - "importData": { - "message": "Import data", - "description": "Used for the header of the import dialog, the import button and within the file-password-prompt" - }, "importError": { "message": "Import error" }, @@ -4796,6 +4801,15 @@ "accountSecurity": { "message": "Account security" }, + "phishingBlocker": { + "message": "Phishing Blocker" + }, + "enablePhishingDetection": { + "message": "Phishing detection" + }, + "enablePhishingDetectionDesc": { + "message": "Display warning before accessing suspected phishing sites" + }, "notifications": { "message": "Notifications" }, @@ -5848,8 +5862,8 @@ "andMoreFeatures": { "message": "And more!" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "upgradeToPremium": { "message": "Upgrade to Premium" @@ -5879,7 +5893,141 @@ "cardNumberLabel": { "message": "Card number" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organisation is no longer using master passwords to log into Bitwarden. To continue, verify the organisation and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organisation to log in" + }, + "organizationVerified": { + "message": "Organisation verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organisation, your access to the organisation will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organisation and domain." + }, "sessionTimeoutSettingsAction": { "message": "Timeout action" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organisation." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organisation has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToImmediately": { + "message": "Your organisation has set the default session timeout to Immediately." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Your organisation has set the default session timeout to On system lock." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organisation has set the default session timeout to On browser restart." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On browser restart" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organisation features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organisation for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/browser/src/_locales/en_IN/messages.json b/apps/browser/src/_locales/en_IN/messages.json index 9ef5cb2a061..8b5d976a5e9 100644 --- a/apps/browser/src/_locales/en_IN/messages.json +++ b/apps/browser/src/_locales/en_IN/messages.json @@ -436,8 +436,8 @@ "sync": { "message": "Sync" }, - "syncVaultNow": { - "message": "Sync vault now" + "syncNow": { + "message": "Sync now" }, "lastSync": { "message": "Last sync:" @@ -455,9 +455,6 @@ "bitWebVaultApp": { "message": "Bitwarden web app" }, - "importItems": { - "message": "Import items" - }, "select": { "message": "Select" }, @@ -1325,8 +1322,11 @@ "exportFrom": { "message": "Export from" }, - "exportVault": { - "message": "Export vault" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "File format" @@ -1457,6 +1457,15 @@ "attachmentSaved": { "message": "The attachment has been saved." }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "file": { "message": "File" }, @@ -1466,6 +1475,9 @@ "selectFile": { "message": "Select a file." }, + "itemsTransferred": { + "message": "Items transferred" + }, "maxFileSize": { "message": "Maximum file size is 500 MB." }, @@ -3240,9 +3252,6 @@ "copyCustomFieldNameNotUnique": { "message": "No unique identifier found." }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organisation. Please confirm the domain below with your organisation administrator." - }, "organizationName": { "message": "Organisation name" }, @@ -4206,10 +4215,6 @@ "ignore": { "message": "Ignore" }, - "importData": { - "message": "Import data", - "description": "Used for the header of the import dialog, the import button and within the file-password-prompt" - }, "importError": { "message": "Import error" }, @@ -4796,6 +4801,15 @@ "accountSecurity": { "message": "Account security" }, + "phishingBlocker": { + "message": "Phishing Blocker" + }, + "enablePhishingDetection": { + "message": "Phishing detection" + }, + "enablePhishingDetectionDesc": { + "message": "Display warning before accessing suspected phishing sites" + }, "notifications": { "message": "Notifications" }, @@ -5848,8 +5862,8 @@ "andMoreFeatures": { "message": "And more!" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "upgradeToPremium": { "message": "Upgrade to Premium" @@ -5879,7 +5893,141 @@ "cardNumberLabel": { "message": "Card number" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organisation is no longer using master passwords to log into Bitwarden. To continue, verify the organisation and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organisation to log in" + }, + "organizationVerified": { + "message": "Organisation verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organisation, your access to the organisation will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organisation and domain." + }, "sessionTimeoutSettingsAction": { "message": "Timeout action" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organisation." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organisation has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToImmediately": { + "message": "Your organisation has set the default session timeout to Immediately." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Your organisation has set the default session timeout to On system lock." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organisation has set the default session timeout to On browser restart." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On browser restart" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organisation features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organisation for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/browser/src/_locales/es/messages.json b/apps/browser/src/_locales/es/messages.json index 92f6226a3de..80139915ff0 100644 --- a/apps/browser/src/_locales/es/messages.json +++ b/apps/browser/src/_locales/es/messages.json @@ -436,8 +436,8 @@ "sync": { "message": "Sincronizar" }, - "syncVaultNow": { - "message": "Sincronizar caja fuerte" + "syncNow": { + "message": "Sync now" }, "lastSync": { "message": "Última sincronización:" @@ -455,9 +455,6 @@ "bitWebVaultApp": { "message": "Aplicación web de Bitwarden" }, - "importItems": { - "message": "Importar elementos" - }, "select": { "message": "Seleccionar" }, @@ -1325,8 +1322,11 @@ "exportFrom": { "message": "Exportar desde" }, - "exportVault": { - "message": "Exportar caja fuerte" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "Formato de archivo" @@ -1457,6 +1457,15 @@ "attachmentSaved": { "message": "El adjunto se ha guardado." }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "file": { "message": "Archivo" }, @@ -1466,6 +1475,9 @@ "selectFile": { "message": "Selecciona un archivo." }, + "itemsTransferred": { + "message": "Items transferred" + }, "maxFileSize": { "message": "El tamaño máximo de archivo es de 500MB." }, @@ -3240,9 +3252,6 @@ "copyCustomFieldNameNotUnique": { "message": "Identificador único no encontrado." }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "Ya no es necesaria una contraseña maestra para los miembros de la siguiente organización. Confirma el dominio que aparece a continuación con el administrador de tu organización." - }, "organizationName": { "message": "Nombre de la organización" }, @@ -4206,10 +4215,6 @@ "ignore": { "message": "Ignorar" }, - "importData": { - "message": "Importar datos", - "description": "Used for the header of the import dialog, the import button and within the file-password-prompt" - }, "importError": { "message": "Error al importar" }, @@ -4796,6 +4801,15 @@ "accountSecurity": { "message": "Seguridad de la cuenta" }, + "phishingBlocker": { + "message": "Phishing Blocker" + }, + "enablePhishingDetection": { + "message": "Phishing detection" + }, + "enablePhishingDetectionDesc": { + "message": "Display warning before accessing suspected phishing sites" + }, "notifications": { "message": "Notificaciones" }, @@ -5848,8 +5862,8 @@ "andMoreFeatures": { "message": "And more!" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "upgradeToPremium": { "message": "Upgrade to Premium" @@ -5879,7 +5893,141 @@ "cardNumberLabel": { "message": "Card number" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { "message": "Timeout action" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToImmediately": { + "message": "Your organization has set the default session timeout to Immediately." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Your organization has set the default session timeout to On system lock." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On browser restart." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On browser restart" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/browser/src/_locales/et/messages.json b/apps/browser/src/_locales/et/messages.json index bade6b0dff9..113336c18cb 100644 --- a/apps/browser/src/_locales/et/messages.json +++ b/apps/browser/src/_locales/et/messages.json @@ -436,8 +436,8 @@ "sync": { "message": "Sünkroniseeri" }, - "syncVaultNow": { - "message": "Sünkroniseeri hoidla" + "syncNow": { + "message": "Sync now" }, "lastSync": { "message": "Viimane sünkronisatsioon:" @@ -455,9 +455,6 @@ "bitWebVaultApp": { "message": "Bitwardeni veebirakendus" }, - "importItems": { - "message": "Impordi andmed" - }, "select": { "message": "Vali" }, @@ -1325,8 +1322,11 @@ "exportFrom": { "message": "Export from" }, - "exportVault": { - "message": "Ekspordi hoidla" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "Failivorming" @@ -1457,6 +1457,15 @@ "attachmentSaved": { "message": "Manus on salvestatud." }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "file": { "message": "Fail" }, @@ -1466,6 +1475,9 @@ "selectFile": { "message": "Vali fail." }, + "itemsTransferred": { + "message": "Items transferred" + }, "maxFileSize": { "message": "Maksimaalne faili suurus on 500 MB." }, @@ -3240,9 +3252,6 @@ "copyCustomFieldNameNotUnique": { "message": "Unikaalset identifikaatorit ei leitud." }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "organizationName": { "message": "Organization name" }, @@ -4206,10 +4215,6 @@ "ignore": { "message": "Ignore" }, - "importData": { - "message": "Import data", - "description": "Used for the header of the import dialog, the import button and within the file-password-prompt" - }, "importError": { "message": "Import error" }, @@ -4796,6 +4801,15 @@ "accountSecurity": { "message": "Account security" }, + "phishingBlocker": { + "message": "Phishing Blocker" + }, + "enablePhishingDetection": { + "message": "Phishing detection" + }, + "enablePhishingDetectionDesc": { + "message": "Display warning before accessing suspected phishing sites" + }, "notifications": { "message": "Notifications" }, @@ -5848,8 +5862,8 @@ "andMoreFeatures": { "message": "And more!" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "upgradeToPremium": { "message": "Upgrade to Premium" @@ -5879,7 +5893,141 @@ "cardNumberLabel": { "message": "Card number" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { "message": "Timeout action" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToImmediately": { + "message": "Your organization has set the default session timeout to Immediately." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Your organization has set the default session timeout to On system lock." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On browser restart." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On browser restart" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/browser/src/_locales/eu/messages.json b/apps/browser/src/_locales/eu/messages.json index 90cfc13f6ef..87469554bea 100644 --- a/apps/browser/src/_locales/eu/messages.json +++ b/apps/browser/src/_locales/eu/messages.json @@ -436,8 +436,8 @@ "sync": { "message": "Sinkronizatu" }, - "syncVaultNow": { - "message": "Sinkronizatu kutxa gotorra orain" + "syncNow": { + "message": "Sync now" }, "lastSync": { "message": "Azken sinkronizazioa:" @@ -455,9 +455,6 @@ "bitWebVaultApp": { "message": "Bitwarden web app" }, - "importItems": { - "message": "Inportatu elementuak" - }, "select": { "message": "Hautatu" }, @@ -1325,8 +1322,11 @@ "exportFrom": { "message": "Export from" }, - "exportVault": { - "message": "Esportatu kutxa gotorra" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "Fitxategiaren formatua" @@ -1457,6 +1457,15 @@ "attachmentSaved": { "message": "Eranskina gorde da." }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "file": { "message": "Fitxategia" }, @@ -1466,6 +1475,9 @@ "selectFile": { "message": "Hautatu fitxategia." }, + "itemsTransferred": { + "message": "Items transferred" + }, "maxFileSize": { "message": "Eranskinaren gehienezko tamaina 500MB." }, @@ -3240,9 +3252,6 @@ "copyCustomFieldNameNotUnique": { "message": "Ez da identifikatzaile bakarrik aurkitu." }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "organizationName": { "message": "Organization name" }, @@ -4206,10 +4215,6 @@ "ignore": { "message": "Ezikusi" }, - "importData": { - "message": "Inportatu datuak", - "description": "Used for the header of the import dialog, the import button and within the file-password-prompt" - }, "importError": { "message": "Errorea inportatzerakoan" }, @@ -4796,6 +4801,15 @@ "accountSecurity": { "message": "Account security" }, + "phishingBlocker": { + "message": "Phishing Blocker" + }, + "enablePhishingDetection": { + "message": "Phishing detection" + }, + "enablePhishingDetectionDesc": { + "message": "Display warning before accessing suspected phishing sites" + }, "notifications": { "message": "Notifications" }, @@ -5848,8 +5862,8 @@ "andMoreFeatures": { "message": "And more!" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "upgradeToPremium": { "message": "Upgrade to Premium" @@ -5879,7 +5893,141 @@ "cardNumberLabel": { "message": "Card number" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { "message": "Timeout action" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToImmediately": { + "message": "Your organization has set the default session timeout to Immediately." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Your organization has set the default session timeout to On system lock." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On browser restart." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On browser restart" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/browser/src/_locales/fa/messages.json b/apps/browser/src/_locales/fa/messages.json index 7a4c8744429..5d4ff7a6d2b 100644 --- a/apps/browser/src/_locales/fa/messages.json +++ b/apps/browser/src/_locales/fa/messages.json @@ -436,8 +436,8 @@ "sync": { "message": "همگام‌سازی" }, - "syncVaultNow": { - "message": "همگام‌سازی گاوصندوق" + "syncNow": { + "message": "Sync now" }, "lastSync": { "message": "آخرین همگام‌سازی:" @@ -455,9 +455,6 @@ "bitWebVaultApp": { "message": "برنامه وب Bitwarden" }, - "importItems": { - "message": "درون ریزی موارد" - }, "select": { "message": "انتخاب" }, @@ -1325,8 +1322,11 @@ "exportFrom": { "message": "برون ریزی از" }, - "exportVault": { - "message": "برون ریزی گاوصندوق" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "فرمت پرونده" @@ -1457,6 +1457,15 @@ "attachmentSaved": { "message": "پیوست ذخیره شد" }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "file": { "message": "پرونده" }, @@ -1466,6 +1475,9 @@ "selectFile": { "message": "ﺍﻧﺘﺨﺎﺏ یک ﭘﺮﻭﻧﺪﻩ" }, + "itemsTransferred": { + "message": "Items transferred" + }, "maxFileSize": { "message": "بیشترین حجم پرونده ۵۰۰ مگابایت است." }, @@ -3240,9 +3252,6 @@ "copyCustomFieldNameNotUnique": { "message": "شناسه منحصر به فردی یافت نشد." }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "برای اعضای سازمان زیر، کلمه عبور اصلی دیگر لازم نیست. لطفاً دامنه زیر را با مدیر سازمان خود تأیید کنید." - }, "organizationName": { "message": "نام سازمان" }, @@ -4206,10 +4215,6 @@ "ignore": { "message": "نادیده گرفتن" }, - "importData": { - "message": "وارد کردن اطلاعات", - "description": "Used for the header of the import dialog, the import button and within the file-password-prompt" - }, "importError": { "message": "خطای درون ریزی" }, @@ -4796,6 +4801,15 @@ "accountSecurity": { "message": "امنیت حساب کاربری" }, + "phishingBlocker": { + "message": "Phishing Blocker" + }, + "enablePhishingDetection": { + "message": "Phishing detection" + }, + "enablePhishingDetectionDesc": { + "message": "Display warning before accessing suspected phishing sites" + }, "notifications": { "message": "اعلان‌ها" }, @@ -5848,8 +5862,8 @@ "andMoreFeatures": { "message": "And more!" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "upgradeToPremium": { "message": "Upgrade to Premium" @@ -5879,7 +5893,141 @@ "cardNumberLabel": { "message": "Card number" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { "message": "Timeout action" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToImmediately": { + "message": "Your organization has set the default session timeout to Immediately." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Your organization has set the default session timeout to On system lock." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On browser restart." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On browser restart" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/browser/src/_locales/fi/messages.json b/apps/browser/src/_locales/fi/messages.json index d7f0f600b9e..06157846f90 100644 --- a/apps/browser/src/_locales/fi/messages.json +++ b/apps/browser/src/_locales/fi/messages.json @@ -436,8 +436,8 @@ "sync": { "message": "Synkronointi" }, - "syncVaultNow": { - "message": "Synkronoi holvi nyt" + "syncNow": { + "message": "Sync now" }, "lastSync": { "message": "Viimeisin synkronointi:" @@ -455,9 +455,6 @@ "bitWebVaultApp": { "message": "Bitwarden Verkkosovellus" }, - "importItems": { - "message": "Tuo kohteita" - }, "select": { "message": "Valitse" }, @@ -1325,8 +1322,11 @@ "exportFrom": { "message": "Vie lähteestä" }, - "exportVault": { - "message": "Vie holvi" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "Tiedostomuoto" @@ -1457,6 +1457,15 @@ "attachmentSaved": { "message": "Tiedostoliite tallennettiin" }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "file": { "message": "Tiedosto" }, @@ -1466,6 +1475,9 @@ "selectFile": { "message": "Valitse tiedosto." }, + "itemsTransferred": { + "message": "Items transferred" + }, "maxFileSize": { "message": "Tiedoston enimmäiskoko on 500 Mt." }, @@ -3240,9 +3252,6 @@ "copyCustomFieldNameNotUnique": { "message": "Yksilöllistä tunnistetta ei löytynyt." }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "Pääsalasanaa ei enää tarvita tämän organisaation jäsenille. Ole hyvä ja vahvista alla oleva verkkotunnus organisaation ylläpitäjän kanssa." - }, "organizationName": { "message": "Organisaation nimi" }, @@ -4206,10 +4215,6 @@ "ignore": { "message": "Ohita" }, - "importData": { - "message": "Tuo tietoja", - "description": "Used for the header of the import dialog, the import button and within the file-password-prompt" - }, "importError": { "message": "Tuontivirhe" }, @@ -4796,6 +4801,15 @@ "accountSecurity": { "message": "Tilin suojaus" }, + "phishingBlocker": { + "message": "Phishing Blocker" + }, + "enablePhishingDetection": { + "message": "Phishing detection" + }, + "enablePhishingDetectionDesc": { + "message": "Display warning before accessing suspected phishing sites" + }, "notifications": { "message": "Ilmoitukset" }, @@ -5848,8 +5862,8 @@ "andMoreFeatures": { "message": "And more!" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "upgradeToPremium": { "message": "Upgrade to Premium" @@ -5879,7 +5893,141 @@ "cardNumberLabel": { "message": "Kortin numero" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { "message": "Timeout action" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToImmediately": { + "message": "Your organization has set the default session timeout to Immediately." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Your organization has set the default session timeout to On system lock." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On browser restart." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On browser restart" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/browser/src/_locales/fil/messages.json b/apps/browser/src/_locales/fil/messages.json index 50964716ad0..918acdac775 100644 --- a/apps/browser/src/_locales/fil/messages.json +++ b/apps/browser/src/_locales/fil/messages.json @@ -436,8 +436,8 @@ "sync": { "message": "Ikintal" }, - "syncVaultNow": { - "message": "Isingit ang Vault ngayon" + "syncNow": { + "message": "Sync now" }, "lastSync": { "message": "Huling sinkronisasyon:" @@ -455,9 +455,6 @@ "bitWebVaultApp": { "message": "Bitwarden web app" }, - "importItems": { - "message": "Isingit ang Vault ngayon" - }, "select": { "message": "Piliin" }, @@ -1325,8 +1322,11 @@ "exportFrom": { "message": "Export from" }, - "exportVault": { - "message": "I-export vault" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "Format ng file" @@ -1457,6 +1457,15 @@ "attachmentSaved": { "message": "Attachment na nai-save" }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "file": { "message": "Mag-file" }, @@ -1466,6 +1475,9 @@ "selectFile": { "message": "Pumili ng File" }, + "itemsTransferred": { + "message": "Items transferred" + }, "maxFileSize": { "message": "Maximum na laki ng file ay 500 MB." }, @@ -3240,9 +3252,6 @@ "copyCustomFieldNameNotUnique": { "message": "Walang natagpuang natatanging nag-identipikar." }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "organizationName": { "message": "Organization name" }, @@ -4206,10 +4215,6 @@ "ignore": { "message": "Ignore" }, - "importData": { - "message": "Import data", - "description": "Used for the header of the import dialog, the import button and within the file-password-prompt" - }, "importError": { "message": "Import error" }, @@ -4796,6 +4801,15 @@ "accountSecurity": { "message": "Account security" }, + "phishingBlocker": { + "message": "Phishing Blocker" + }, + "enablePhishingDetection": { + "message": "Phishing detection" + }, + "enablePhishingDetectionDesc": { + "message": "Display warning before accessing suspected phishing sites" + }, "notifications": { "message": "Notifications" }, @@ -5848,8 +5862,8 @@ "andMoreFeatures": { "message": "And more!" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "upgradeToPremium": { "message": "Upgrade to Premium" @@ -5879,7 +5893,141 @@ "cardNumberLabel": { "message": "Card number" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { "message": "Timeout action" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToImmediately": { + "message": "Your organization has set the default session timeout to Immediately." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Your organization has set the default session timeout to On system lock." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On browser restart." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On browser restart" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/browser/src/_locales/fr/messages.json b/apps/browser/src/_locales/fr/messages.json index 6ddf7ea5873..cb611df41a1 100644 --- a/apps/browser/src/_locales/fr/messages.json +++ b/apps/browser/src/_locales/fr/messages.json @@ -436,8 +436,8 @@ "sync": { "message": "Synchronisation" }, - "syncVaultNow": { - "message": "Synchroniser le coffre maintenant" + "syncNow": { + "message": "Synchroniser maintenant" }, "lastSync": { "message": "Dernière synchronisation :" @@ -455,9 +455,6 @@ "bitWebVaultApp": { "message": "Application web Bitwarden" }, - "importItems": { - "message": "Importer des éléments" - }, "select": { "message": "Sélectionner" }, @@ -586,7 +583,7 @@ "message": "Les éléments archivés sont exclus des résultats de recherche généraux et des suggestions de remplissage automatique. Êtes-vous sûr de vouloir archiver cet élément ?" }, "upgradeToUseArchive": { - "message": "A premium membership is required to use Archive." + "message": "Une adhésion premium est requise pour utiliser Archive." }, "edit": { "message": "Modifier" @@ -598,7 +595,7 @@ "message": "Tout afficher" }, "showAll": { - "message": "Show all" + "message": "Tout afficher" }, "viewLess": { "message": "Afficher moins" @@ -1325,8 +1322,11 @@ "exportFrom": { "message": "Exporter à partir de" }, - "exportVault": { - "message": "Exporter le coffre" + "export": { + "message": "Exporter" + }, + "import": { + "message": "Importer" }, "fileFormat": { "message": "Format de fichier" @@ -1407,25 +1407,25 @@ "message": "En savoir plus" }, "migrationsFailed": { - "message": "An error occurred updating the encryption settings." + "message": "Une erreur s'est produite lors de la mise à jour des paramètres de chiffrement." }, "updateEncryptionSettingsTitle": { - "message": "Update your encryption settings" + "message": "Mettre à jour vos paramètres de chiffrement" }, "updateEncryptionSettingsDesc": { - "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + "message": "Les nouveaux paramètres de chiffrement recommandés amélioreront la sécurité de votre compte. Entrez votre mot de passe principal pour faire la mise à jour maintenant." }, "confirmIdentityToContinue": { - "message": "Confirm your identity to continue" + "message": "Confirmez votre identité pour continuer" }, "enterYourMasterPassword": { - "message": "Enter your master password" + "message": "Entrez votre mot de passe principal" }, "updateSettings": { - "message": "Update settings" + "message": "Mettre à jour les paramètres" }, "later": { - "message": "Later" + "message": "Plus tard" }, "authenticatorKeyTotp": { "message": "Clé Authenticator (TOTP)" @@ -1457,6 +1457,15 @@ "attachmentSaved": { "message": "La pièce jointe a été enregistrée." }, + "fixEncryption": { + "message": "Corriger le chiffrement" + }, + "fixEncryptionTooltip": { + "message": "Ce fichier utilise une méthode de chiffrement obsolète." + }, + "attachmentUpdated": { + "message": "Pièce jointe mise à jour" + }, "file": { "message": "Fichier" }, @@ -1466,6 +1475,9 @@ "selectFile": { "message": "Sélectionnez un fichier." }, + "itemsTransferred": { + "message": "Éléments transférés" + }, "maxFileSize": { "message": "La taille maximale du fichier est de 500 Mo." }, @@ -1497,7 +1509,7 @@ "message": "1 Go de stockage chiffré pour les fichiers joints." }, "premiumSignUpStorageV2": { - "message": "$SIZE$ encrypted storage for file attachments.", + "message": "Stockage chiffré de $SIZE$ pour les pièces jointes.", "placeholders": { "size": { "content": "$1", @@ -1904,7 +1916,7 @@ "message": "Année d'expiration" }, "monthly": { - "message": "month" + "message": "mois" }, "expiration": { "message": "Expiration" @@ -3240,9 +3252,6 @@ "copyCustomFieldNameNotUnique": { "message": "Aucun identifiant unique trouvé." }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "Un mot de passe principal n’est plus requis pour les membres de l’organisation suivante. Veuillez confirmer le domaine ci-dessous auprès de l'administrateur de votre organisation." - }, "organizationName": { "message": "Nom de l'organisation" }, @@ -4206,10 +4215,6 @@ "ignore": { "message": "Ignorer" }, - "importData": { - "message": "Importer des données", - "description": "Used for the header of the import dialog, the import button and within the file-password-prompt" - }, "importError": { "message": "Erreur lors de l'importation" }, @@ -4796,6 +4801,15 @@ "accountSecurity": { "message": "Sécurité du compte" }, + "phishingBlocker": { + "message": "Phishing Blocker" + }, + "enablePhishingDetection": { + "message": "Phishing detection" + }, + "enablePhishingDetectionDesc": { + "message": "Display warning before accessing suspected phishing sites" + }, "notifications": { "message": "Notifications" }, @@ -5848,17 +5862,17 @@ "andMoreFeatures": { "message": "Et encore plus !" }, - "planDescPremium": { - "message": "Sécurité en ligne complète" + "advancedOnlineSecurity": { + "message": "Sécurité en ligne avancée" }, "upgradeToPremium": { "message": "Mettre à niveau vers Premium" }, "unlockAdvancedSecurity": { - "message": "Unlock advanced security features" + "message": "Débloquer les fonctionnalités de sécurité avancées" }, "unlockAdvancedSecurityDesc": { - "message": "A Premium subscription gives you more tools to stay secure and in control" + "message": "Un abonnement Premium vous donne plus d'outils pour rester en sécurité et en contrôle" }, "explorePremium": { "message": "Explorer Premium" @@ -5879,7 +5893,141 @@ "cardNumberLabel": { "message": "Numéro de carte" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Votre organisation n'utilise plus les mots de passe principaux pour se connecter à Bitwarden. Pour continuer, vérifiez l'organisation et le domaine." + }, + "continueWithLogIn": { + "message": "Continuer avec la connexion" + }, + "doNotContinue": { + "message": "Ne pas continuer" + }, + "domain": { + "message": "Domaine" + }, + "keyConnectorDomainTooltip": { + "message": "Ce domaine stockera les clés de chiffrement de votre compte, alors assurez-vous que vous lui faites confiance. Si vous n'êtes pas sûr, vérifiez auprès de votre administrateur." + }, + "verifyYourOrganization": { + "message": "Vérifiez votre organisation pour vous connecter" + }, + "organizationVerified": { + "message": "Organisation vérifiée" + }, + "domainVerified": { + "message": "Domaine vérifié" + }, + "leaveOrganizationContent": { + "message": "Si vous ne vérifiez pas votre organisation, votre accès à l'organisation sera révoqué." + }, + "leaveNow": { + "message": "Quitter maintenant" + }, + "verifyYourDomainToLogin": { + "message": "Vérifiez votre domaine pour vous connecter" + }, + "verifyYourDomainDescription": { + "message": "Pour continuer à vous connecter, vérifiez ce domaine." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "Pour continuer à vous connecter, vérifiez l'organisation et le domaine." + }, "sessionTimeoutSettingsAction": { "message": "Action à l’expiration" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "Ce paramètre est géré par votre organisation." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Votre organisation a réglé le délai d'expiration de session maximal à $HOURS$ heure(s) et $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToImmediately": { + "message": "Votre organisation a défini le délai d'expiration de session par défaut sur Immédiat." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Votre organisation a défini le délai d'expiration de session par défaut sur Au verrouillage du système." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Votre organisation a défini le délai d'expiration de session par défaut sur Au redémarrage du navigateur." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Le délai d'expiration de session maximal ne peut pas dépasser $HOURS$ heure(s) et $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "Au redémarrage du navigateur" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Configurez une méthode de déverrouillage pour changer le délai d'expiration de votre coffre" + }, + "upgrade": { + "message": "Mettre à jour" + }, + "leaveConfirmationDialogTitle": { + "message": "Êtes-vous sûr de vouloir quitter ?" + }, + "leaveConfirmationDialogContentOne": { + "message": "En refusant, vos éléments personnels resteront dans votre compte, mais vous perdrez l'accès aux éléments partagés et aux fonctionnalités de l'organisation." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contactez votre administrateur pour regagner l'accès." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Quitter $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "Comment gérer mon coffre ?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transférer les éléments vers $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ exige que tous les éléments soient détenus par l’organisation pour des raisons de sécurité et de conformité. Cliquez sur Accepter pour transférer la propriété de vos éléments.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/browser/src/_locales/gl/messages.json b/apps/browser/src/_locales/gl/messages.json index 5695ae16035..e83baff136f 100644 --- a/apps/browser/src/_locales/gl/messages.json +++ b/apps/browser/src/_locales/gl/messages.json @@ -436,8 +436,8 @@ "sync": { "message": "Sincronizar" }, - "syncVaultNow": { - "message": "Sincronizar caixa forte agora" + "syncNow": { + "message": "Sync now" }, "lastSync": { "message": "Última sincronización:" @@ -455,9 +455,6 @@ "bitWebVaultApp": { "message": "Aplicación web de Bitwarden" }, - "importItems": { - "message": "Importar entradas" - }, "select": { "message": "Seleccionar" }, @@ -1325,8 +1322,11 @@ "exportFrom": { "message": "Exportar dende" }, - "exportVault": { - "message": "Exportar caixa forte" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "Formato de ficheiro" @@ -1457,6 +1457,15 @@ "attachmentSaved": { "message": "Anexo gardado" }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "file": { "message": "Arquivo" }, @@ -1466,6 +1475,9 @@ "selectFile": { "message": "Selecciona un arquivo" }, + "itemsTransferred": { + "message": "Items transferred" + }, "maxFileSize": { "message": "O tamaño máximo é de 500 MB." }, @@ -3240,9 +3252,6 @@ "copyCustomFieldNameNotUnique": { "message": "Non se atopou ningún identificador único." }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "organizationName": { "message": "Organization name" }, @@ -4206,10 +4215,6 @@ "ignore": { "message": "Ignorar" }, - "importData": { - "message": "Importar datos", - "description": "Used for the header of the import dialog, the import button and within the file-password-prompt" - }, "importError": { "message": "Erro ó importar" }, @@ -4796,6 +4801,15 @@ "accountSecurity": { "message": "Seguridade da conta" }, + "phishingBlocker": { + "message": "Phishing Blocker" + }, + "enablePhishingDetection": { + "message": "Phishing detection" + }, + "enablePhishingDetectionDesc": { + "message": "Display warning before accessing suspected phishing sites" + }, "notifications": { "message": "Notificacións" }, @@ -5848,8 +5862,8 @@ "andMoreFeatures": { "message": "And more!" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "upgradeToPremium": { "message": "Upgrade to Premium" @@ -5879,7 +5893,141 @@ "cardNumberLabel": { "message": "Card number" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { "message": "Timeout action" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToImmediately": { + "message": "Your organization has set the default session timeout to Immediately." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Your organization has set the default session timeout to On system lock." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On browser restart." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On browser restart" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/browser/src/_locales/he/messages.json b/apps/browser/src/_locales/he/messages.json index 67ca71338b2..64dddc4db04 100644 --- a/apps/browser/src/_locales/he/messages.json +++ b/apps/browser/src/_locales/he/messages.json @@ -436,8 +436,8 @@ "sync": { "message": "סנכרן" }, - "syncVaultNow": { - "message": "סנכרן את הכספת עכשיו" + "syncNow": { + "message": "Sync now" }, "lastSync": { "message": "סנכרון אחרון:" @@ -455,9 +455,6 @@ "bitWebVaultApp": { "message": "יישום הרשת של Bitwarden" }, - "importItems": { - "message": "ייבא פריטים" - }, "select": { "message": "בחר" }, @@ -1325,8 +1322,11 @@ "exportFrom": { "message": "ייצא מ־" }, - "exportVault": { - "message": "ייצא כספת" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "פורמט הקובץ" @@ -1457,6 +1457,15 @@ "attachmentSaved": { "message": "הצרופה נשמרה" }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "file": { "message": "קובץ" }, @@ -1466,6 +1475,9 @@ "selectFile": { "message": "בחר קובץ" }, + "itemsTransferred": { + "message": "Items transferred" + }, "maxFileSize": { "message": "גודל הקובץ המרבי הוא 500MB." }, @@ -3240,9 +3252,6 @@ "copyCustomFieldNameNotUnique": { "message": "לא נמצא מזהה ייחודי." }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "סיסמה ראשית אינה נדרשת עוד עבור חברים בארגון הבא. נא לאשר את הדומיין שלהלן עם מנהל הארגון שלך." - }, "organizationName": { "message": "שם הארגון" }, @@ -4206,10 +4215,6 @@ "ignore": { "message": "התעלם" }, - "importData": { - "message": "ייבא נתונים", - "description": "Used for the header of the import dialog, the import button and within the file-password-prompt" - }, "importError": { "message": "שגיאת ייבוא" }, @@ -4796,6 +4801,15 @@ "accountSecurity": { "message": "אבטחת החשבון" }, + "phishingBlocker": { + "message": "Phishing Blocker" + }, + "enablePhishingDetection": { + "message": "Phishing detection" + }, + "enablePhishingDetectionDesc": { + "message": "Display warning before accessing suspected phishing sites" + }, "notifications": { "message": "התראות" }, @@ -5848,8 +5862,8 @@ "andMoreFeatures": { "message": "ועוד!" }, - "planDescPremium": { - "message": "השלם אבטחה מקוונת" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "upgradeToPremium": { "message": "שדרג לפרימיום" @@ -5879,7 +5893,141 @@ "cardNumberLabel": { "message": "מספר כרטיס" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { "message": "פעולת פסק זמן" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToImmediately": { + "message": "Your organization has set the default session timeout to Immediately." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Your organization has set the default session timeout to On system lock." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On browser restart." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On browser restart" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/browser/src/_locales/hi/messages.json b/apps/browser/src/_locales/hi/messages.json index b839e31cd96..4602d099b59 100644 --- a/apps/browser/src/_locales/hi/messages.json +++ b/apps/browser/src/_locales/hi/messages.json @@ -436,8 +436,8 @@ "sync": { "message": "सिंक" }, - "syncVaultNow": { - "message": "Sync Vault Now" + "syncNow": { + "message": "Sync now" }, "lastSync": { "message": "Last Sync:" @@ -455,9 +455,6 @@ "bitWebVaultApp": { "message": "Bitwarden web app" }, - "importItems": { - "message": "Import Items" - }, "select": { "message": "चयन करें" }, @@ -1325,8 +1322,11 @@ "exportFrom": { "message": "Export from" }, - "exportVault": { - "message": "Export Vault" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "File Format" @@ -1457,6 +1457,15 @@ "attachmentSaved": { "message": "अटैचमेंट बच गया है।" }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "file": { "message": "फ़ाइल" }, @@ -1466,6 +1475,9 @@ "selectFile": { "message": "फ़ाइल का चयन करें।" }, + "itemsTransferred": { + "message": "Items transferred" + }, "maxFileSize": { "message": "अधिकतम फाइल आकार 500 MB है।" }, @@ -3240,9 +3252,6 @@ "copyCustomFieldNameNotUnique": { "message": "No unique identifier found." }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "organizationName": { "message": "Organization name" }, @@ -4206,10 +4215,6 @@ "ignore": { "message": "Ignore" }, - "importData": { - "message": "Import data", - "description": "Used for the header of the import dialog, the import button and within the file-password-prompt" - }, "importError": { "message": "Import error" }, @@ -4796,6 +4801,15 @@ "accountSecurity": { "message": "Account security" }, + "phishingBlocker": { + "message": "Phishing Blocker" + }, + "enablePhishingDetection": { + "message": "Phishing detection" + }, + "enablePhishingDetectionDesc": { + "message": "Display warning before accessing suspected phishing sites" + }, "notifications": { "message": "Notifications" }, @@ -5848,8 +5862,8 @@ "andMoreFeatures": { "message": "And more!" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "उन्नत ऑनलाइन सुरक्षा" }, "upgradeToPremium": { "message": "Upgrade to Premium" @@ -5879,7 +5893,141 @@ "cardNumberLabel": { "message": "Card number" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { "message": "Timeout action" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToImmediately": { + "message": "Your organization has set the default session timeout to Immediately." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Your organization has set the default session timeout to On system lock." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On browser restart." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On browser restart" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/browser/src/_locales/hr/messages.json b/apps/browser/src/_locales/hr/messages.json index da78924b1eb..3a42b22eb45 100644 --- a/apps/browser/src/_locales/hr/messages.json +++ b/apps/browser/src/_locales/hr/messages.json @@ -436,8 +436,8 @@ "sync": { "message": "Sinkronizacija" }, - "syncVaultNow": { - "message": "Odmah sinkroniziraj trezor" + "syncNow": { + "message": "Sync now" }, "lastSync": { "message": "Posljednja sinkronizacija:" @@ -455,9 +455,6 @@ "bitWebVaultApp": { "message": "Bitwarden web trezor" }, - "importItems": { - "message": "Uvoz stavki" - }, "select": { "message": "Odaberi" }, @@ -1325,8 +1322,11 @@ "exportFrom": { "message": "Izvezi iz" }, - "exportVault": { - "message": "Izvezi trezor" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "Format datoteke" @@ -1457,6 +1457,15 @@ "attachmentSaved": { "message": "Privitak spremljen" }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "file": { "message": "Datoteka" }, @@ -1466,6 +1475,9 @@ "selectFile": { "message": "Odaberi datoteku." }, + "itemsTransferred": { + "message": "Items transferred" + }, "maxFileSize": { "message": "Najveća veličina datoteke je 500 MB." }, @@ -3240,9 +3252,6 @@ "copyCustomFieldNameNotUnique": { "message": "Nije nađen jedinstveni identifikator." }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "Glavna lozinka više nije obavezna za članove sljedeće organizacije. Provjeri prikazanu domenu sa svojim administratorom." - }, "organizationName": { "message": "Naziv Organizacije" }, @@ -4206,10 +4215,6 @@ "ignore": { "message": "Zanemari" }, - "importData": { - "message": "Uvezi podatke", - "description": "Used for the header of the import dialog, the import button and within the file-password-prompt" - }, "importError": { "message": "Greška prilikom uvoza" }, @@ -4796,6 +4801,15 @@ "accountSecurity": { "message": "Sigurnost računa" }, + "phishingBlocker": { + "message": "Phishing Blocker" + }, + "enablePhishingDetection": { + "message": "Phishing detection" + }, + "enablePhishingDetectionDesc": { + "message": "Display warning before accessing suspected phishing sites" + }, "notifications": { "message": "Obavijesti" }, @@ -5848,8 +5862,8 @@ "andMoreFeatures": { "message": "I više!" }, - "planDescPremium": { - "message": "Dovrši online sigurnost" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "upgradeToPremium": { "message": " Nadogradi na Premium" @@ -5879,7 +5893,141 @@ "cardNumberLabel": { "message": "Broj kartice" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { "message": "Radnja kod isteka" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToImmediately": { + "message": "Your organization has set the default session timeout to Immediately." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Your organization has set the default session timeout to On system lock." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On browser restart." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On browser restart" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/browser/src/_locales/hu/messages.json b/apps/browser/src/_locales/hu/messages.json index 388a069e05a..dc34f3b2166 100644 --- a/apps/browser/src/_locales/hu/messages.json +++ b/apps/browser/src/_locales/hu/messages.json @@ -436,8 +436,8 @@ "sync": { "message": "Szinkronizálás" }, - "syncVaultNow": { - "message": "Széf szinkronizálása most" + "syncNow": { + "message": "Szinkronizálás most" }, "lastSync": { "message": "Utolsó szinkronizálás:" @@ -455,9 +455,6 @@ "bitWebVaultApp": { "message": "Bitwarden webes alkalmazás" }, - "importItems": { - "message": "Elemek importálása" - }, "select": { "message": "Kiválaszt" }, @@ -1325,8 +1322,11 @@ "exportFrom": { "message": "Exportálás innen:" }, - "exportVault": { - "message": "Széf exportálása" + "export": { + "message": "Exportálás" + }, + "import": { + "message": "Importálás" }, "fileFormat": { "message": "Fájlformátum" @@ -1457,6 +1457,15 @@ "attachmentSaved": { "message": "A melléklet mentésre került." }, + "fixEncryption": { + "message": "Titkosítás javítása" + }, + "fixEncryptionTooltip": { + "message": "Ez a fájl elavult titkosítási módszert használ." + }, + "attachmentUpdated": { + "message": "A melléklet frissítésre került." + }, "file": { "message": "Fájl" }, @@ -1466,6 +1475,9 @@ "selectFile": { "message": "Válasszunk egy fájlt." }, + "itemsTransferred": { + "message": "Az elemek átvitelre kerültek." + }, "maxFileSize": { "message": "A naximális fájlméret 500 MB." }, @@ -3240,9 +3252,6 @@ "copyCustomFieldNameNotUnique": { "message": "Nincs egyedi azonosító." }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A következő szervezet tagjai számára már nincs szükség mesterjelszóra. Erősítsük meg az alábbi tartományt a szervezet adminisztrátorával." - }, "organizationName": { "message": "Szervezet neve" }, @@ -4206,10 +4215,6 @@ "ignore": { "message": "Mellőz" }, - "importData": { - "message": "Adatok importálása", - "description": "Used for the header of the import dialog, the import button and within the file-password-prompt" - }, "importError": { "message": "Importálási hiba" }, @@ -4796,6 +4801,15 @@ "accountSecurity": { "message": "Fiókbiztonság" }, + "phishingBlocker": { + "message": "Adathalászat blokkoló" + }, + "enablePhishingDetection": { + "message": "Adathalászat érzékelés" + }, + "enablePhishingDetectionDesc": { + "message": "Figyelmeztetés megjelenítése a gyanús adathalász webhelyek elérése előtt." + }, "notifications": { "message": "Értesítések" }, @@ -5848,8 +5862,8 @@ "andMoreFeatures": { "message": "És még több!" }, - "planDescPremium": { - "message": "Teljes körű online biztonság" + "advancedOnlineSecurity": { + "message": "Bővített online biztonság" }, "upgradeToPremium": { "message": "Áttérés Prémium csomagra" @@ -5879,7 +5893,141 @@ "cardNumberLabel": { "message": "Kártya szám" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "A szervezet már nem használ mesterjelszavakat a Bitwardenbe bejelentkezéshez. A folytatáshoz ellenőrizzük a szervezetet és a tartományt." + }, + "continueWithLogIn": { + "message": "Folytatás bejelentkezéssel" + }, + "doNotContinue": { + "message": "Nincs folytatás" + }, + "domain": { + "message": "Tartomány" + }, + "keyConnectorDomainTooltip": { + "message": "Ez a tartomány tárolja a fiók titkosítási kulcsait, ezért győződjünk meg róla, hogy megbízunk-e benne. Ha nem vagyunk biztos benne, érdeklődjünk adminisztrátornál." + }, + "verifyYourOrganization": { + "message": "Szervezet ellenőrzése a bejelentkezéshez" + }, + "organizationVerified": { + "message": "A szervezet ellenőrzésre került." + }, + "domainVerified": { + "message": "A tartomány ellenőrzésre került." + }, + "leaveOrganizationContent": { + "message": "Ha nem ellenőrizzük a szervezetet, a szervezethez hozzáférés visszavonásra kerül." + }, + "leaveNow": { + "message": "Elhagyás most" + }, + "verifyYourDomainToLogin": { + "message": "Tartomány ellenőrzése a bejelentkezéshez" + }, + "verifyYourDomainDescription": { + "message": "A bejelentkezés folytatásához ellenőrizzük ezt a tartományt." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "A bejelentkezés folytatásához ellenőrizzük a szervezetet és a tartományt." + }, "sessionTimeoutSettingsAction": { "message": "Időkifutási művelet" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "Ezt a beállítást a szervezet lezeli." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "A szervezet a munkamenet maximális munkamenet időkifutását $HOURS$ órára és $MINUTES$ percre állította be.", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToImmediately": { + "message": "A szervezet az alapértelmezett munkamenet időkifutást Azonnal értékre állította." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "A szervezet az alapértelmezett munkamenet időkifutástr Rendszerzár be értékre állította." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "A szervezet az alapértelmezett munkamenet időkifutást a Böngésző újraindításakor értékre állította." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "A maximális időtúllépés nem haladhatja meg a $HOURS$ óra és $MINUTES$ perc értéket.", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "Böngésző újraindításkor" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Állítsunk be egy feloldási módot a széf időkifutási műveletének módosításához." + }, + "upgrade": { + "message": "Áttérés" + }, + "leaveConfirmationDialogTitle": { + "message": "Biztosan szeretnénk kilépni?" + }, + "leaveConfirmationDialogContentOne": { + "message": "Az elutasítással a személyes elemek a fiókban maradnak, de elveszítjük hozzáférést a megosztott elemekhez és a szervezeti funkciókhoz." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Lépjünk kapcsolatba az adminisztrátorral a hozzáférés visszaszerzéséért." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "$ORGANIZATION$ elhagyása", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "Hogyan kezeljem a széfet?" + }, + "transferItemsToOrganizationTitle": { + "message": "Elemek átvitele $ORGANIZATION$ szervezethez", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ megköveteli, hogy minden elem a szervezet tulajdonában legyen a biztonság és a megfelelőség érdekében. Kattintás az elfogadásra az elemek tulajdonjogának átruházásához.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Átvitel elfogadása" + }, + "declineAndLeave": { + "message": "Elutasítás és kilépés" + }, + "whyAmISeeingThis": { + "message": "Miért látható ez?" } } diff --git a/apps/browser/src/_locales/id/messages.json b/apps/browser/src/_locales/id/messages.json index d18b25c51ed..36df2e733f8 100644 --- a/apps/browser/src/_locales/id/messages.json +++ b/apps/browser/src/_locales/id/messages.json @@ -436,8 +436,8 @@ "sync": { "message": "Sinkronisasi" }, - "syncVaultNow": { - "message": "Sinkronkan Brankas Sekarang" + "syncNow": { + "message": "Sync now" }, "lastSync": { "message": "Sinkronisasi Terakhir:" @@ -455,9 +455,6 @@ "bitWebVaultApp": { "message": "Aplikasi web Bitwarden" }, - "importItems": { - "message": "Impor Item" - }, "select": { "message": "Pilih" }, @@ -1325,8 +1322,11 @@ "exportFrom": { "message": "Ekspor dari" }, - "exportVault": { - "message": "Ekspor Brankas" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "Format Berkas" @@ -1457,6 +1457,15 @@ "attachmentSaved": { "message": "Lampiran telah disimpan." }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "file": { "message": "Berkas" }, @@ -1466,6 +1475,9 @@ "selectFile": { "message": "Pilih berkas." }, + "itemsTransferred": { + "message": "Items transferred" + }, "maxFileSize": { "message": "Ukuran berkas maksimal adalah 500 MB." }, @@ -3240,9 +3252,6 @@ "copyCustomFieldNameNotUnique": { "message": "Tidak ada pengidentifikasi unik yang ditemukan." }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "Sebuah kata sandi utama tidak lagi dibutuhkan untuk para anggota dari organisasi berikut. Silakan konfirmasi domain berikut kepada pengelola organisasi Anda." - }, "organizationName": { "message": "Nama organisasi" }, @@ -4206,10 +4215,6 @@ "ignore": { "message": "Abaikan" }, - "importData": { - "message": "Impor data", - "description": "Used for the header of the import dialog, the import button and within the file-password-prompt" - }, "importError": { "message": "Kesalahan impor" }, @@ -4796,6 +4801,15 @@ "accountSecurity": { "message": "Keamanan akun" }, + "phishingBlocker": { + "message": "Phishing Blocker" + }, + "enablePhishingDetection": { + "message": "Phishing detection" + }, + "enablePhishingDetectionDesc": { + "message": "Display warning before accessing suspected phishing sites" + }, "notifications": { "message": "Pemberitahuan" }, @@ -5848,8 +5862,8 @@ "andMoreFeatures": { "message": "And more!" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "upgradeToPremium": { "message": "Upgrade to Premium" @@ -5879,7 +5893,141 @@ "cardNumberLabel": { "message": "Card number" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { "message": "Timeout action" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToImmediately": { + "message": "Your organization has set the default session timeout to Immediately." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Your organization has set the default session timeout to On system lock." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On browser restart." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On browser restart" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/browser/src/_locales/it/messages.json b/apps/browser/src/_locales/it/messages.json index 9bb75d7f449..a64b3e0f351 100644 --- a/apps/browser/src/_locales/it/messages.json +++ b/apps/browser/src/_locales/it/messages.json @@ -436,8 +436,8 @@ "sync": { "message": "Sincronizza" }, - "syncVaultNow": { - "message": "Sincronizza cassaforte ora" + "syncNow": { + "message": "Sincronizza" }, "lastSync": { "message": "Ultima sincronizzazione:" @@ -455,9 +455,6 @@ "bitWebVaultApp": { "message": "Bitwarden web app" }, - "importItems": { - "message": "Importa elementi" - }, "select": { "message": "Seleziona" }, @@ -1325,8 +1322,11 @@ "exportFrom": { "message": "Esporta da" }, - "exportVault": { - "message": "Esporta cassaforte" + "export": { + "message": "Esporta" + }, + "import": { + "message": "Importa" }, "fileFormat": { "message": "Formato file" @@ -1457,6 +1457,15 @@ "attachmentSaved": { "message": "Allegato salvato" }, + "fixEncryption": { + "message": "Correggi la crittografia" + }, + "fixEncryptionTooltip": { + "message": "Questo file usa un metodo di crittografia obsoleto." + }, + "attachmentUpdated": { + "message": "Allegato aggiornato" + }, "file": { "message": "File" }, @@ -1466,6 +1475,9 @@ "selectFile": { "message": "Seleziona un file" }, + "itemsTransferred": { + "message": "Elementi trasferiti" + }, "maxFileSize": { "message": "La dimensione massima del file è 500 MB." }, @@ -3240,9 +3252,6 @@ "copyCustomFieldNameNotUnique": { "message": "Nessun identificatore univoco trovato." }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "La password principale non è più richiesta per i membri dell'organizzazione. Per favore, conferma il dominio qui sotto con l'amministratore." - }, "organizationName": { "message": "Nome organizzazione" }, @@ -4206,10 +4215,6 @@ "ignore": { "message": "Ignora" }, - "importData": { - "message": "Importa dati", - "description": "Used for the header of the import dialog, the import button and within the file-password-prompt" - }, "importError": { "message": "Errore di importazione" }, @@ -4796,6 +4801,15 @@ "accountSecurity": { "message": "Sicurezza dell'account" }, + "phishingBlocker": { + "message": "Phishing Blocker" + }, + "enablePhishingDetection": { + "message": "Phishing detection" + }, + "enablePhishingDetectionDesc": { + "message": "Display warning before accessing suspected phishing sites" + }, "notifications": { "message": "Notifiche" }, @@ -5848,8 +5862,8 @@ "andMoreFeatures": { "message": "E molto altro!" }, - "planDescPremium": { - "message": "Sicurezza online completa" + "advancedOnlineSecurity": { + "message": "Sicurezza online avanzata" }, "upgradeToPremium": { "message": "Aggiorna a Premium" @@ -5879,7 +5893,141 @@ "cardNumberLabel": { "message": "Numero di carta" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "La tua organizzazione non utilizza più le password principali per accedere a Bitwarden. Per continuare, verifica l'organizzazione e il dominio." + }, + "continueWithLogIn": { + "message": "Accedi e continua" + }, + "doNotContinue": { + "message": "Non continuare" + }, + "domain": { + "message": "Dominio" + }, + "keyConnectorDomainTooltip": { + "message": "Questo dominio memorizzerà le chiavi di crittografia del tuo account, quindi assicurati di impostarlo come affidabile. Se non hai la certezza che lo sia, verifica con l'amministratore." + }, + "verifyYourOrganization": { + "message": "Verifica la tua organizzazione per accedere" + }, + "organizationVerified": { + "message": "Organizzazione verificata" + }, + "domainVerified": { + "message": "Dominio verificato" + }, + "leaveOrganizationContent": { + "message": "Se non verifichi l'organizzazione, il tuo accesso sarà revocato." + }, + "leaveNow": { + "message": "Abbandona" + }, + "verifyYourDomainToLogin": { + "message": "Verifica il tuo dominio per accedere" + }, + "verifyYourDomainDescription": { + "message": "Per continuare con l'accesso, verifica questo dominio." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "Per continuare con l'accesso, verifica l'organizzazione e il dominio." + }, "sessionTimeoutSettingsAction": { "message": "Azione al timeout" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "Questa impostazione è gestita dalla tua organizzazione." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "La tua organizzazione ha impostato $HOURS$ ora/e e $MINUTES$ minuto/i come durata massima della sessione.", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToImmediately": { + "message": "In base alle impostazioni della tua organizzazione, la sessione terminerà immediatamente." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "In base alle impostazioni della tua organizzazione, la sessione terminerà al blocco del dispositivo." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "In base alle impostazioni della tua organizzazione, la sessione terminerà ogni volta che la pagina sarà chiusa o ricaricata." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "La durata della sessione non può superare $HOURS$ ora/e e $MINUTES$ minuto/i", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "Al riavvio del browser" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Imposta un metodo di sblocco per modificare l'azione al timeout" + }, + "upgrade": { + "message": "Aggiorna" + }, + "leaveConfirmationDialogTitle": { + "message": "Vuoi davvero abbandonare?" + }, + "leaveConfirmationDialogContentOne": { + "message": "Se rifiuti, tutti gli elementi esistenti resteranno nel tuo account, ma perderai l'accesso agli oggetti condivisi e alle funzioni organizzative." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contatta il tuo amministratore per recuperare l'accesso." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Abbandona $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "Come si gestisce la cassaforte?" + }, + "transferItemsToOrganizationTitle": { + "message": "Trasferisci gli elementi in $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ richiede che tutti gli elementi siano di proprietà dell'organizzazione per motivi di conformità e sicurezza. Clicca su 'Accetta' per trasferire la proprietà.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accetta il trasferimento" + }, + "declineAndLeave": { + "message": "Rifiuta e abbandona" + }, + "whyAmISeeingThis": { + "message": "Perché vedo questo avviso?" } } diff --git a/apps/browser/src/_locales/ja/messages.json b/apps/browser/src/_locales/ja/messages.json index 0b0883beaf3..e89c58ae4c3 100644 --- a/apps/browser/src/_locales/ja/messages.json +++ b/apps/browser/src/_locales/ja/messages.json @@ -436,8 +436,8 @@ "sync": { "message": "同期" }, - "syncVaultNow": { - "message": "保管庫を同期する" + "syncNow": { + "message": "Sync now" }, "lastSync": { "message": "前回の同期:" @@ -455,9 +455,6 @@ "bitWebVaultApp": { "message": "Bitwarden ウェブアプリ" }, - "importItems": { - "message": "アイテムのインポート" - }, "select": { "message": "選択" }, @@ -551,7 +548,7 @@ "message": "保管庫を検索" }, "resetSearch": { - "message": "Reset search" + "message": "検索をリセット" }, "archiveNoun": { "message": "アーカイブ", @@ -565,28 +562,28 @@ "message": "アーカイブ解除" }, "itemsInArchive": { - "message": "Items in archive" + "message": "アーカイブ済みのアイテム" }, "noItemsInArchive": { - "message": "No items in archive" + "message": "アーカイブにアイテムがありません" }, "noItemsInArchiveDesc": { - "message": "Archived items will appear here and will be excluded from general search results and autofill suggestions." + "message": "アーカイブされたアイテムはここに表示され、通常の検索結果および自動入力の候補から除外されます。" }, "itemWasSentToArchive": { - "message": "Item was sent to archive" + "message": "アイテムはアーカイブに送信されました" }, "itemUnarchived": { - "message": "Item was unarchived" + "message": "アイテムはアーカイブから解除されました" }, "archiveItem": { - "message": "Archive item" + "message": "アイテムをアーカイブ" }, "archiveItemConfirmDesc": { - "message": "Archived items are excluded from general search results and autofill suggestions. Are you sure you want to archive this item?" + "message": "アーカイブされたアイテムはここに表示され、通常の検索結果および自動入力の候補から除外されます。このアイテムをアーカイブしますか?" }, "upgradeToUseArchive": { - "message": "A premium membership is required to use Archive." + "message": "アーカイブを使用するにはプレミアムメンバーシップが必要です。" }, "edit": { "message": "編集" @@ -595,13 +592,13 @@ "message": "表示" }, "viewAll": { - "message": "View all" + "message": "すべて表示" }, "showAll": { - "message": "Show all" + "message": "すべて表示" }, "viewLess": { - "message": "View less" + "message": "表示を減らす" }, "viewLogin": { "message": "ログイン情報を表示" @@ -749,7 +746,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", @@ -806,10 +803,10 @@ "message": "ロック時" }, "onIdle": { - "message": "On system idle" + "message": "アイドル時" }, "onSleep": { - "message": "On system sleep" + "message": "スリープ時" }, "onRestart": { "message": "ブラウザ再起動時" @@ -955,7 +952,7 @@ "message": "以下の手順に従ってログインを完了してください。" }, "followTheStepsBelowToFinishLoggingInWithSecurityKey": { - "message": "Follow the steps below to finish logging in with your security key." + "message": "セキュリティキーでログインを完了するには、以下の手順に従ってください。" }, "restartRegistration": { "message": "登録を再度始める" @@ -1140,7 +1137,7 @@ "message": "このパスワードを Bitwarden に保存しますか?" }, "notificationAddSave": { - "message": "保存する" + "message": "保存" }, "notificationViewAria": { "message": "View $ITEMNAME$, opens in new window", @@ -1325,8 +1322,11 @@ "exportFrom": { "message": "エクスポート元" }, - "exportVault": { - "message": "保管庫のエクスポート" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "ファイル形式" @@ -1407,25 +1407,25 @@ "message": "さらに詳しく" }, "migrationsFailed": { - "message": "An error occurred updating the encryption settings." + "message": "暗号化設定の更新中にエラーが発生しました。" }, "updateEncryptionSettingsTitle": { - "message": "Update your encryption settings" + "message": "暗号化設定を更新する" }, "updateEncryptionSettingsDesc": { "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." }, "confirmIdentityToContinue": { - "message": "Confirm your identity to continue" + "message": "続行するには本人確認を行ってください" }, "enterYourMasterPassword": { - "message": "Enter your master password" + "message": "マスターパスワードを入力" }, "updateSettings": { - "message": "Update settings" + "message": "設定を更新" }, "later": { - "message": "Later" + "message": "後で" }, "authenticatorKeyTotp": { "message": "認証キー (TOTP)" @@ -1457,6 +1457,15 @@ "attachmentSaved": { "message": "添付ファイルを保存しました。" }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "file": { "message": "ファイル" }, @@ -1466,6 +1475,9 @@ "selectFile": { "message": "ファイルを選択してください。" }, + "itemsTransferred": { + "message": "Items transferred" + }, "maxFileSize": { "message": "最大ファイルサイズ: 500 MB" }, @@ -1473,7 +1485,7 @@ "message": "サービスが利用できません" }, "legacyEncryptionUnsupported": { - "message": "Legacy encryption is no longer supported. Please contact support to recover your account." + "message": "従来の暗号化はサポートされていません。アカウントを復元するにはサポートにお問い合わせください。" }, "premiumMembership": { "message": "プレミアム会員" @@ -1497,7 +1509,7 @@ "message": "1GB の暗号化されたファイルストレージ" }, "premiumSignUpStorageV2": { - "message": "$SIZE$ encrypted storage for file attachments.", + "message": "$SIZE$ の暗号化されたファイルストレージ。", "placeholders": { "size": { "content": "$1", @@ -1740,16 +1752,16 @@ "message": "Confirm autofill" }, "confirmAutofillDesc": { - "message": "This site doesn't match your saved login details. Before you fill in your login credentials, make sure it's a trusted site." + "message": "このサイトは保存されたログイン情報と一致しません。ログイン情報を入力する前に、信頼できるサイトであることを確認してください。" }, "showInlineMenuLabel": { "message": "フォームフィールドに自動入力の候補を表示する" }, "howDoesBitwardenProtectFromPhishing": { - "message": "How does Bitwarden protect your data from phishing?" + "message": "Bitwarden はどのようにフィッシングからデータを保護しますか?" }, "currentWebsite": { - "message": "Current website" + "message": "現在のウェブサイト" }, "autofillAndAddWebsite": { "message": "Autofill and add this website" @@ -1904,7 +1916,7 @@ "message": "有効期限年" }, "monthly": { - "message": "month" + "message": "月" }, "expiration": { "message": "有効期限" @@ -2078,7 +2090,7 @@ "description": "Header for new text send" }, "newItemHeaderFileSend": { - "message": "New File Send", + "message": "新しい Send ファイル", "description": "Header for new file send" }, "editItemHeaderLogin": { @@ -2090,7 +2102,7 @@ "description": "Header for edit card item type" }, "editItemHeaderIdentity": { - "message": "Edit Identity", + "message": "ID を編集", "description": "Header for edit identity item type" }, "editItemHeaderNote": { @@ -2106,7 +2118,7 @@ "description": "Header for edit text send" }, "editItemHeaderFileSend": { - "message": "Edit File Send", + "message": "Send ファイルを編集", "description": "Header for edit file send" }, "viewItemHeaderLogin": { @@ -2118,7 +2130,7 @@ "description": "Header for view card item type" }, "viewItemHeaderIdentity": { - "message": "View Identity", + "message": "ID を表示", "description": "Header for view identity item type" }, "viewItemHeaderNote": { @@ -2252,7 +2264,7 @@ "message": "種類" }, "allItems": { - "message": "全てのアイテム" + "message": "すべてのアイテム" }, "noPasswordsInList": { "message": "表示するパスワードがありません" @@ -2334,7 +2346,7 @@ "message": "Bitwarden のロックを解除するための PIN コードを設定します。アプリから完全にログアウトすると、PIN 設定はリセットされます。" }, "setPinCode": { - "message": "You can use this PIN to unlock Bitwarden. Your PIN will be reset if you ever fully log out of the application." + "message": "Bitwarden のロックを解除するために PIN を使用使用することができます。アプリからログアウトすると PIN はリセットされます。" }, "pinRequired": { "message": "PIN コードが必要です。" @@ -2755,7 +2767,7 @@ } }, "atRiskChangePrompt": { - "message": "Your password for this site is at-risk. $ORGANIZATION$ has requested that you change it.", + "message": "このサイトのパスワードは危険です。$ORGANIZATION$ が変更を要求しています。", "placeholders": { "organization": { "content": "$1", @@ -3100,7 +3112,7 @@ "message": "この機能を使用するにはメールアドレスを確認する必要があります。ウェブ保管庫でメールアドレスを確認できます。" }, "masterPasswordSuccessfullySet": { - "message": "Master password successfully set" + "message": "マスターパスワードを設定しました" }, "updatedMasterPassword": { "message": "マスターパスワードを更新しました" @@ -3240,14 +3252,11 @@ "copyCustomFieldNameNotUnique": { "message": "一意の識別子が見つかりませんでした。" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "organizationName": { "message": "組織名" }, "keyConnectorDomain": { - "message": "Key Connector domain" + "message": "キーコネクタードメイン" }, "leaveOrganization": { "message": "組織から脱退する" @@ -3304,7 +3313,7 @@ } }, "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { - "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "message": "$ORGANIZATION$ に関連付けられた組織の保管庫のみがエクスポートされます。", "placeholders": { "organization": { "content": "$1", @@ -3328,7 +3337,7 @@ "message": "復号エラー" }, "errorGettingAutoFillData": { - "message": "Error getting autofill data" + "message": "自動入力データの取得に失敗しました" }, "couldNotDecryptVaultItemsBelow": { "message": "Bitwarden は以下の保管庫のアイテムを復号できませんでした。" @@ -3883,7 +3892,7 @@ "message": "ログインを完了できません" }, "loginOnTrustedDeviceOrAskAdminToAssignPassword": { - "message": "You need to log in on a trusted device or ask your administrator to assign you a password." + "message": "信頼できるデバイスにログインするか、管理者にパスワードの割り当てを依頼する必要があります。" }, "ssoIdentifierRequired": { "message": "組織の SSO ID が必要です。" @@ -3947,7 +3956,7 @@ "message": "信頼されたデバイス" }, "trustOrganization": { - "message": "Trust organization" + "message": "組織を信頼" }, "trust": { "message": "信頼する" @@ -3968,7 +3977,7 @@ "message": "This organization has an Enterprise policy that will enroll you in account recovery. Enrollment will allow organization administrators to change your password. Only proceed if you recognize this organization and the fingerprint phrase displayed below matches the organization's fingerprint." }, "trustUser": { - "message": "Trust user" + "message": "ユーザーを信頼" }, "sendsTitleNoItems": { "message": "機密情報を安全に送信", @@ -4206,10 +4215,6 @@ "ignore": { "message": "無視" }, - "importData": { - "message": "データのインポート", - "description": "Used for the header of the import dialog, the import button and within the file-password-prompt" - }, "importError": { "message": "インポート エラー" }, @@ -4796,6 +4801,15 @@ "accountSecurity": { "message": "アカウントのセキュリティ" }, + "phishingBlocker": { + "message": "Phishing Blocker" + }, + "enablePhishingDetection": { + "message": "Phishing detection" + }, + "enablePhishingDetectionDesc": { + "message": "Display warning before accessing suspected phishing sites" + }, "notifications": { "message": "通知" }, @@ -4918,7 +4932,7 @@ "message": "モバイルアプリを入手" }, "getTheMobileAppDesc": { - "message": "Access your passwords on the go with the Bitwarden mobile app." + "message": "Bitwarden モバイルアプリで外出先からでもパスワードにアクセスしましょう。" }, "getTheDesktopApp": { "message": "デスクトップアプリを入手" @@ -5305,7 +5319,7 @@ "message": "アカウントへのアクセスが要求されました" }, "confirmAccessAttempt": { - "message": "Confirm access attempt for $EMAIL$", + "message": "$EMAIL$ のログイン試行を確認", "placeholders": { "email": { "content": "$1", @@ -5692,16 +5706,16 @@ "message": "保管庫へようこそ!" }, "phishingPageTitleV2": { - "message": "Phishing attempt detected" + "message": "フィッシングの試行が検出されました" }, "phishingPageSummary": { - "message": "The site you are attempting to visit is a known malicious site and a security risk." + "message": "訪問しようとしているサイトは既知の悪意のあるサイトであり、セキュリティ上のリスクがあります。" }, "phishingPageCloseTabV2": { "message": "このタブを閉じる" }, "phishingPageContinueV2": { - "message": "Continue to this site (not recommended)" + "message": "このサイトに進む (非推奨)" }, "phishingPageExplanation1": { "message": "This site was found in ", @@ -5757,13 +5771,13 @@ "message": "With cards, easily autofill payment forms securely and accurately." }, "newIdentityNudgeTitle": { - "message": "Simplify creating accounts" + "message": "アカウント作成を簡素化" }, "newIdentityNudgeBody": { "message": "With identities, quickly autofill long registration or contact forms." }, "newNoteNudgeTitle": { - "message": "Keep your sensitive data safe" + "message": "機密データを安全に保管" }, "newNoteNudgeBody": { "message": "With notes, securely store sensitive data like banking or insurance details." @@ -5785,7 +5799,7 @@ "message": "パスワードをすばやく作成" }, "generatorNudgeBodyOne": { - "message": "Easily create strong and unique passwords by clicking on", + "message": "クリックすることで、強力でユニークなパスワードを簡単に作成できます", "description": "Two part message", "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." }, @@ -5808,7 +5822,7 @@ "message": "You do not have permissions to view this page. Try logging in with a different account." }, "wasmNotSupported": { - "message": "WebAssembly is not supported on your browser or is not enabled. WebAssembly is required to use the Bitwarden app.", + "message": "WebAssembly はお使いのブラウザでサポートされていないか、有効になっていません。WebAssembly は Bitwarden アプリを使用するために必要です。", "description": "'WebAssembly' is a technical term and should not be translated." }, "showMore": { @@ -5837,7 +5851,7 @@ "message": "認証機を内蔵" }, "secureFileStorage": { - "message": "Secure file storage" + "message": "セキュアなファイルストレージ" }, "emergencyAccess": { "message": "緊急アクセス" @@ -5846,10 +5860,10 @@ "message": "Breach monitoring" }, "andMoreFeatures": { - "message": "And more!" + "message": "などなど!" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "upgradeToPremium": { "message": "プレミアムにアップグレード" @@ -5864,10 +5878,10 @@ "message": "Explore Premium" }, "loadingVault": { - "message": "Loading vault" + "message": "保管庫の読み込み中" }, "vaultLoaded": { - "message": "Vault loaded" + "message": "保管庫が読み込まれました" }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", @@ -5879,7 +5893,141 @@ "cardNumberLabel": { "message": "カード番号" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { - "message": "Timeout action" + "message": "タイムアウト時のアクション" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToImmediately": { + "message": "Your organization has set the default session timeout to Immediately." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Your organization has set the default session timeout to On system lock." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On browser restart." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On browser restart" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/browser/src/_locales/ka/messages.json b/apps/browser/src/_locales/ka/messages.json index ebb01f095f3..6e1a5c556d9 100644 --- a/apps/browser/src/_locales/ka/messages.json +++ b/apps/browser/src/_locales/ka/messages.json @@ -436,8 +436,8 @@ "sync": { "message": "სინქრონიზაცია" }, - "syncVaultNow": { - "message": "Sync vault now" + "syncNow": { + "message": "Sync now" }, "lastSync": { "message": "ბოლო სინქი:" @@ -455,9 +455,6 @@ "bitWebVaultApp": { "message": "Bitwarden web app" }, - "importItems": { - "message": "Import items" - }, "select": { "message": "არჩევა" }, @@ -1325,8 +1322,11 @@ "exportFrom": { "message": "Export from" }, - "exportVault": { - "message": "Export vault" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "File format" @@ -1457,6 +1457,15 @@ "attachmentSaved": { "message": "Attachment saved" }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "file": { "message": "ფაილი" }, @@ -1466,6 +1475,9 @@ "selectFile": { "message": "აირჩიეთ ფაილი" }, + "itemsTransferred": { + "message": "Items transferred" + }, "maxFileSize": { "message": "Maximum file size is 500 MB." }, @@ -3240,9 +3252,6 @@ "copyCustomFieldNameNotUnique": { "message": "No unique identifier found." }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "organizationName": { "message": "Organization name" }, @@ -4206,10 +4215,6 @@ "ignore": { "message": "იგნორი" }, - "importData": { - "message": "მონაცემების შემოტანა", - "description": "Used for the header of the import dialog, the import button and within the file-password-prompt" - }, "importError": { "message": "შემოტანის შეცდომა" }, @@ -4796,6 +4801,15 @@ "accountSecurity": { "message": "ანგარიშის უსაფრთხოება" }, + "phishingBlocker": { + "message": "Phishing Blocker" + }, + "enablePhishingDetection": { + "message": "Phishing detection" + }, + "enablePhishingDetectionDesc": { + "message": "Display warning before accessing suspected phishing sites" + }, "notifications": { "message": "გაფრთხილებები" }, @@ -5848,8 +5862,8 @@ "andMoreFeatures": { "message": "And more!" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "upgradeToPremium": { "message": "Upgrade to Premium" @@ -5879,7 +5893,141 @@ "cardNumberLabel": { "message": "Card number" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { "message": "Timeout action" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToImmediately": { + "message": "Your organization has set the default session timeout to Immediately." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Your organization has set the default session timeout to On system lock." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On browser restart." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On browser restart" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/browser/src/_locales/km/messages.json b/apps/browser/src/_locales/km/messages.json index 684a04d9175..02945c6ff9b 100644 --- a/apps/browser/src/_locales/km/messages.json +++ b/apps/browser/src/_locales/km/messages.json @@ -436,8 +436,8 @@ "sync": { "message": "Sync" }, - "syncVaultNow": { - "message": "Sync vault now" + "syncNow": { + "message": "Sync now" }, "lastSync": { "message": "Last sync:" @@ -455,9 +455,6 @@ "bitWebVaultApp": { "message": "Bitwarden web app" }, - "importItems": { - "message": "Import items" - }, "select": { "message": "Select" }, @@ -1325,8 +1322,11 @@ "exportFrom": { "message": "Export from" }, - "exportVault": { - "message": "Export vault" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "File format" @@ -1457,6 +1457,15 @@ "attachmentSaved": { "message": "Attachment saved" }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "file": { "message": "File" }, @@ -1466,6 +1475,9 @@ "selectFile": { "message": "Select a file" }, + "itemsTransferred": { + "message": "Items transferred" + }, "maxFileSize": { "message": "Maximum file size is 500 MB." }, @@ -3240,9 +3252,6 @@ "copyCustomFieldNameNotUnique": { "message": "No unique identifier found." }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "organizationName": { "message": "Organization name" }, @@ -4206,10 +4215,6 @@ "ignore": { "message": "Ignore" }, - "importData": { - "message": "Import data", - "description": "Used for the header of the import dialog, the import button and within the file-password-prompt" - }, "importError": { "message": "Import error" }, @@ -4796,6 +4801,15 @@ "accountSecurity": { "message": "Account security" }, + "phishingBlocker": { + "message": "Phishing Blocker" + }, + "enablePhishingDetection": { + "message": "Phishing detection" + }, + "enablePhishingDetectionDesc": { + "message": "Display warning before accessing suspected phishing sites" + }, "notifications": { "message": "Notifications" }, @@ -5848,8 +5862,8 @@ "andMoreFeatures": { "message": "And more!" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "upgradeToPremium": { "message": "Upgrade to Premium" @@ -5879,7 +5893,141 @@ "cardNumberLabel": { "message": "Card number" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { "message": "Timeout action" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToImmediately": { + "message": "Your organization has set the default session timeout to Immediately." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Your organization has set the default session timeout to On system lock." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On browser restart." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On browser restart" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/browser/src/_locales/kn/messages.json b/apps/browser/src/_locales/kn/messages.json index 05ea413b522..5d37dbf41b7 100644 --- a/apps/browser/src/_locales/kn/messages.json +++ b/apps/browser/src/_locales/kn/messages.json @@ -436,8 +436,8 @@ "sync": { "message": "ಸಿಂಕ್" }, - "syncVaultNow": { - "message": "ವಾಲ್ಟ್ ಅನ್ನು ಈಗ ಸಿಂಕ್ ಮಾಡಿ" + "syncNow": { + "message": "Sync now" }, "lastSync": { "message": "ಕೊನೆಯ ಸಿಂಕ್" @@ -455,9 +455,6 @@ "bitWebVaultApp": { "message": "Bitwarden web app" }, - "importItems": { - "message": "ವಸ್ತುಗಳನ್ನು ಆಮದು ಮಾಡಿ" - }, "select": { "message": "ಆಯ್ಕೆಮಾಡಿ" }, @@ -1325,8 +1322,11 @@ "exportFrom": { "message": "Export from" }, - "exportVault": { - "message": "ರಫ್ತು ವಾಲ್ಟ್" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "ಕಡತದ ಮಾದರಿ" @@ -1457,6 +1457,15 @@ "attachmentSaved": { "message": "ಲಗತ್ತನ್ನು ಉಳಿಸಲಾಗಿದೆ." }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "file": { "message": "ಫೈಲ್" }, @@ -1466,6 +1475,9 @@ "selectFile": { "message": "ಕಡತವನ್ನು ಆಯ್ಕೆಮಾಡು." }, + "itemsTransferred": { + "message": "Items transferred" + }, "maxFileSize": { "message": "ಗರಿಷ್ಠ ಫೈಲ್ ಗಾತ್ರ 500 ಎಂಬಿ." }, @@ -3240,9 +3252,6 @@ "copyCustomFieldNameNotUnique": { "message": "No unique identifier found." }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "organizationName": { "message": "Organization name" }, @@ -4206,10 +4215,6 @@ "ignore": { "message": "Ignore" }, - "importData": { - "message": "Import data", - "description": "Used for the header of the import dialog, the import button and within the file-password-prompt" - }, "importError": { "message": "Import error" }, @@ -4796,6 +4801,15 @@ "accountSecurity": { "message": "Account security" }, + "phishingBlocker": { + "message": "Phishing Blocker" + }, + "enablePhishingDetection": { + "message": "Phishing detection" + }, + "enablePhishingDetectionDesc": { + "message": "Display warning before accessing suspected phishing sites" + }, "notifications": { "message": "Notifications" }, @@ -5848,8 +5862,8 @@ "andMoreFeatures": { "message": "And more!" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "upgradeToPremium": { "message": "Upgrade to Premium" @@ -5879,7 +5893,141 @@ "cardNumberLabel": { "message": "Card number" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { "message": "Timeout action" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToImmediately": { + "message": "Your organization has set the default session timeout to Immediately." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Your organization has set the default session timeout to On system lock." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On browser restart." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On browser restart" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/browser/src/_locales/ko/messages.json b/apps/browser/src/_locales/ko/messages.json index b35fe8283f7..247133a4295 100644 --- a/apps/browser/src/_locales/ko/messages.json +++ b/apps/browser/src/_locales/ko/messages.json @@ -436,8 +436,8 @@ "sync": { "message": "동기화" }, - "syncVaultNow": { - "message": "지금 보관함 동기화" + "syncNow": { + "message": "Sync now" }, "lastSync": { "message": "마지막 동기화:" @@ -455,9 +455,6 @@ "bitWebVaultApp": { "message": "Bitwarden 웹 앱" }, - "importItems": { - "message": "항목 가져오기" - }, "select": { "message": "선택" }, @@ -1325,8 +1322,11 @@ "exportFrom": { "message": "~(으)로부터 내보내기" }, - "exportVault": { - "message": "보관함 내보내기" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "파일 형식" @@ -1457,6 +1457,15 @@ "attachmentSaved": { "message": "첨부 파일을 저장했습니다." }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "file": { "message": "파일" }, @@ -1466,6 +1475,9 @@ "selectFile": { "message": "파일을 선택하세요." }, + "itemsTransferred": { + "message": "Items transferred" + }, "maxFileSize": { "message": "최대 파일 크기는 500MB입니다." }, @@ -3240,9 +3252,6 @@ "copyCustomFieldNameNotUnique": { "message": "고유 식별자를 찾을 수 없습니다." }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "organizationName": { "message": "Organization name" }, @@ -4206,10 +4215,6 @@ "ignore": { "message": "무시하기" }, - "importData": { - "message": "데이터 가져오기", - "description": "Used for the header of the import dialog, the import button and within the file-password-prompt" - }, "importError": { "message": "가져오기 오류" }, @@ -4796,6 +4801,15 @@ "accountSecurity": { "message": "계정 보안" }, + "phishingBlocker": { + "message": "Phishing Blocker" + }, + "enablePhishingDetection": { + "message": "Phishing detection" + }, + "enablePhishingDetectionDesc": { + "message": "Display warning before accessing suspected phishing sites" + }, "notifications": { "message": "알림" }, @@ -5848,8 +5862,8 @@ "andMoreFeatures": { "message": "And more!" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "upgradeToPremium": { "message": "Upgrade to Premium" @@ -5879,7 +5893,141 @@ "cardNumberLabel": { "message": "Card number" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { "message": "Timeout action" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToImmediately": { + "message": "Your organization has set the default session timeout to Immediately." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Your organization has set the default session timeout to On system lock." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On browser restart." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On browser restart" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/browser/src/_locales/lt/messages.json b/apps/browser/src/_locales/lt/messages.json index 7fcc2df0330..25c830574c6 100644 --- a/apps/browser/src/_locales/lt/messages.json +++ b/apps/browser/src/_locales/lt/messages.json @@ -436,8 +436,8 @@ "sync": { "message": "Sinchronizuoti" }, - "syncVaultNow": { - "message": "Sinchronizuoti saugyklą dabar" + "syncNow": { + "message": "Sync now" }, "lastSync": { "message": "Paskutinis sinchronizavimas:" @@ -455,9 +455,6 @@ "bitWebVaultApp": { "message": "Bitwarden Interneto svetainė" }, - "importItems": { - "message": "Importuoti elementus" - }, "select": { "message": "Pasirinkti" }, @@ -1325,8 +1322,11 @@ "exportFrom": { "message": "Export from" }, - "exportVault": { - "message": "Eksportuoti saugyklą" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "Failo formatas" @@ -1457,6 +1457,15 @@ "attachmentSaved": { "message": "Priedas išsaugotas" }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "file": { "message": "Failas" }, @@ -1466,6 +1475,9 @@ "selectFile": { "message": "Pasirinkite failą." }, + "itemsTransferred": { + "message": "Items transferred" + }, "maxFileSize": { "message": "Didžiausias failo dydis – 500 MB." }, @@ -3240,9 +3252,6 @@ "copyCustomFieldNameNotUnique": { "message": "Unikalus identifikatorius nerastas." }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "organizationName": { "message": "Organization name" }, @@ -4206,10 +4215,6 @@ "ignore": { "message": "Ignoruoti" }, - "importData": { - "message": "Importuoti duomenis", - "description": "Used for the header of the import dialog, the import button and within the file-password-prompt" - }, "importError": { "message": "Importavimo klaida" }, @@ -4796,6 +4801,15 @@ "accountSecurity": { "message": "Paskyros saugumas" }, + "phishingBlocker": { + "message": "Phishing Blocker" + }, + "enablePhishingDetection": { + "message": "Phishing detection" + }, + "enablePhishingDetectionDesc": { + "message": "Display warning before accessing suspected phishing sites" + }, "notifications": { "message": "Pranešimai" }, @@ -5848,8 +5862,8 @@ "andMoreFeatures": { "message": "And more!" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "upgradeToPremium": { "message": "Upgrade to Premium" @@ -5879,7 +5893,141 @@ "cardNumberLabel": { "message": "Card number" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { "message": "Timeout action" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToImmediately": { + "message": "Your organization has set the default session timeout to Immediately." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Your organization has set the default session timeout to On system lock." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On browser restart." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On browser restart" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/browser/src/_locales/lv/messages.json b/apps/browser/src/_locales/lv/messages.json index a719320fc8c..65c4591db80 100644 --- a/apps/browser/src/_locales/lv/messages.json +++ b/apps/browser/src/_locales/lv/messages.json @@ -436,8 +436,8 @@ "sync": { "message": "Sinhronizēt" }, - "syncVaultNow": { - "message": "Sinhronizēt glabātavu" + "syncNow": { + "message": "Sinhronizēt tūlīt" }, "lastSync": { "message": "Pēdējā sinhronizēšana:" @@ -455,9 +455,6 @@ "bitWebVaultApp": { "message": "Bitwarden tīmekļa lietotne" }, - "importItems": { - "message": "Ievietot vienumus" - }, "select": { "message": "Izvēlēties" }, @@ -1325,8 +1322,11 @@ "exportFrom": { "message": "Izgūt no" }, - "exportVault": { - "message": "Izgūt glabātavas saturu" + "export": { + "message": "Izgūt" + }, + "import": { + "message": "Ievietot" }, "fileFormat": { "message": "Datnes veids" @@ -1457,6 +1457,15 @@ "attachmentSaved": { "message": "Pielikums tika saglabāts." }, + "fixEncryption": { + "message": "Salabot šifrēšanu" + }, + "fixEncryptionTooltip": { + "message": "Šī datne izmanto novecojušu šifrēšanas veidu." + }, + "attachmentUpdated": { + "message": "Pielikums atjaunināts" + }, "file": { "message": "Datne" }, @@ -1466,6 +1475,9 @@ "selectFile": { "message": "Atlasīt datni" }, + "itemsTransferred": { + "message": "Vienumi pārcelti" + }, "maxFileSize": { "message": "Lielākais pieļaujamais datnes izmērs ir 500 MB." }, @@ -3240,9 +3252,6 @@ "copyCustomFieldNameNotUnique": { "message": "Nav atrasts neviens neatkārtojams identifikators" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "Galvenā parole vairs nav nepieciešama turpmāk minētās apvienības dalībniekiem. Lūgums saskaņot zemāk esošo domēnu ar savas apvienības pārvaldītāju." - }, "organizationName": { "message": "Apvienības nosaukums" }, @@ -4206,10 +4215,6 @@ "ignore": { "message": "Neņemt vērā" }, - "importData": { - "message": "Ievietot datus", - "description": "Used for the header of the import dialog, the import button and within the file-password-prompt" - }, "importError": { "message": "Ievietošanas kļūda" }, @@ -4796,6 +4801,15 @@ "accountSecurity": { "message": "Konta drošība" }, + "phishingBlocker": { + "message": "Phishing Blocker" + }, + "enablePhishingDetection": { + "message": "Phishing detection" + }, + "enablePhishingDetectionDesc": { + "message": "Display warning before accessing suspected phishing sites" + }, "notifications": { "message": "Paziņojumi" }, @@ -5848,8 +5862,8 @@ "andMoreFeatures": { "message": "Un vēl!" }, - "planDescPremium": { - "message": "Pilnīga drošība tiešsaistē" + "advancedOnlineSecurity": { + "message": "Izvērsta tiešsaistes drošība" }, "upgradeToPremium": { "message": "Uzlabot uz Premium" @@ -5879,7 +5893,141 @@ "cardNumberLabel": { "message": "Kartes numurs" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Apvienība vairs neizmanto galvenās paroles, lai pieteiktos Bitwarden. Lai turpinātu, jāapliecina apvienība un domēns." + }, + "continueWithLogIn": { + "message": "Turpināt ar pieteikšanos" + }, + "doNotContinue": { + "message": "Neturpināt" + }, + "domain": { + "message": "Domēns" + }, + "keyConnectorDomainTooltip": { + "message": "Šajā domēnā tiks glabātas konta šifrēšanas atslēgas, tādēļ jāpārliecinās par uzticamību. Ja nav pārliecības, jāsazinās ar savu pārvaldītāju." + }, + "verifyYourOrganization": { + "message": "Jāapliecina apvienība, lai pieteiktos" + }, + "organizationVerified": { + "message": "Apvienība apliecināta" + }, + "domainVerified": { + "message": "Domēns ir apliecināts" + }, + "leaveOrganizationContent": { + "message": "Ja neapliecināsi apvienību, tiks atsaukta piekļuve tai." + }, + "leaveNow": { + "message": "Pamest tagad" + }, + "verifyYourDomainToLogin": { + "message": "Jāapliecina domēns, lai pieteiktos" + }, + "verifyYourDomainDescription": { + "message": "Lai turpinātu pieteikšanos, jāapliecina šis domēns." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "Lai turpinātu pieteikšanos, jāapliecina apvienība un domēns." + }, "sessionTimeoutSettingsAction": { "message": "Noildzes darbība" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToImmediately": { + "message": "Your organization has set the default session timeout to Immediately." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Your organization has set the default session timeout to On system lock." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On browser restart." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On browser restart" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "upgrade": { + "message": "Uzlabot" + }, + "leaveConfirmationDialogTitle": { + "message": "Vai tiešām pamest?" + }, + "leaveConfirmationDialogContentOne": { + "message": "Pēc noraidīšanas personīgie vienumi paliks Tavā kontā, bet Tu zaudēsi piekļvuvi kopīgotajiem vienumiem un apvienību iespējām." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Jāsazinās ar savu pārvaldītāju, lai atgūtu piekļuvi." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Pamest $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "Kā es varu pārvaldīt savu glabātavu?" + }, + "transferItemsToOrganizationTitle": { + "message": "Pārcelt vienumus uz $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/browser/src/_locales/ml/messages.json b/apps/browser/src/_locales/ml/messages.json index b72d82cd93f..895ae7baa01 100644 --- a/apps/browser/src/_locales/ml/messages.json +++ b/apps/browser/src/_locales/ml/messages.json @@ -436,8 +436,8 @@ "sync": { "message": "സമന്വയിപ്പിക്കുക" }, - "syncVaultNow": { - "message": "വാൾട് ഇപ്പോൾ സമന്വയിപ്പിക്കുക" + "syncNow": { + "message": "Sync now" }, "lastSync": { "message": "അവസാന സമന്വയം:" @@ -455,9 +455,6 @@ "bitWebVaultApp": { "message": "Bitwarden web app" }, - "importItems": { - "message": "ഇനങ്ങൾ ഇമ്പോർട് ചെയ്യുക" - }, "select": { "message": "തിരഞ്ഞെടുക്കുക" }, @@ -1325,8 +1322,11 @@ "exportFrom": { "message": "Export from" }, - "exportVault": { - "message": "വാൾട് എക്സ്പോർട്" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "ഫയൽ ഫോർമാറ്റ്" @@ -1457,6 +1457,15 @@ "attachmentSaved": { "message": "അറ്റാച്ചുമെന്റ് സംരക്ഷിച്ചു." }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "file": { "message": "ഫയൽ" }, @@ -1466,6 +1475,9 @@ "selectFile": { "message": "ഒരു ഫയൽ തിരഞ്ഞെടുക്കുക" }, + "itemsTransferred": { + "message": "Items transferred" + }, "maxFileSize": { "message": "പരമാവധി ഫയൽ വലുപ്പം 500 MB ആണ്." }, @@ -3240,9 +3252,6 @@ "copyCustomFieldNameNotUnique": { "message": "No unique identifier found." }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "organizationName": { "message": "Organization name" }, @@ -4206,10 +4215,6 @@ "ignore": { "message": "Ignore" }, - "importData": { - "message": "Import data", - "description": "Used for the header of the import dialog, the import button and within the file-password-prompt" - }, "importError": { "message": "Import error" }, @@ -4796,6 +4801,15 @@ "accountSecurity": { "message": "Account security" }, + "phishingBlocker": { + "message": "Phishing Blocker" + }, + "enablePhishingDetection": { + "message": "Phishing detection" + }, + "enablePhishingDetectionDesc": { + "message": "Display warning before accessing suspected phishing sites" + }, "notifications": { "message": "Notifications" }, @@ -5848,8 +5862,8 @@ "andMoreFeatures": { "message": "And more!" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "upgradeToPremium": { "message": "Upgrade to Premium" @@ -5879,7 +5893,141 @@ "cardNumberLabel": { "message": "Card number" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { "message": "Timeout action" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToImmediately": { + "message": "Your organization has set the default session timeout to Immediately." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Your organization has set the default session timeout to On system lock." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On browser restart." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On browser restart" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/browser/src/_locales/mr/messages.json b/apps/browser/src/_locales/mr/messages.json index 03c3b4a70ae..2f2f9bbefe8 100644 --- a/apps/browser/src/_locales/mr/messages.json +++ b/apps/browser/src/_locales/mr/messages.json @@ -436,8 +436,8 @@ "sync": { "message": "संकालन" }, - "syncVaultNow": { - "message": "तिजोरी संकालन आता करा" + "syncNow": { + "message": "Sync now" }, "lastSync": { "message": "शेवटचे संकालन:" @@ -455,9 +455,6 @@ "bitWebVaultApp": { "message": "Bitwarden web app" }, - "importItems": { - "message": "वस्तू आयात करा" - }, "select": { "message": "Select" }, @@ -1325,8 +1322,11 @@ "exportFrom": { "message": "Export from" }, - "exportVault": { - "message": "Export vault" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "File format" @@ -1457,6 +1457,15 @@ "attachmentSaved": { "message": "Attachment saved" }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "file": { "message": "File" }, @@ -1466,6 +1475,9 @@ "selectFile": { "message": "Select a file" }, + "itemsTransferred": { + "message": "Items transferred" + }, "maxFileSize": { "message": "Maximum file size is 500 MB." }, @@ -3240,9 +3252,6 @@ "copyCustomFieldNameNotUnique": { "message": "No unique identifier found." }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "organizationName": { "message": "Organization name" }, @@ -4206,10 +4215,6 @@ "ignore": { "message": "Ignore" }, - "importData": { - "message": "Import data", - "description": "Used for the header of the import dialog, the import button and within the file-password-prompt" - }, "importError": { "message": "Import error" }, @@ -4796,6 +4801,15 @@ "accountSecurity": { "message": "Account security" }, + "phishingBlocker": { + "message": "Phishing Blocker" + }, + "enablePhishingDetection": { + "message": "Phishing detection" + }, + "enablePhishingDetectionDesc": { + "message": "Display warning before accessing suspected phishing sites" + }, "notifications": { "message": "Notifications" }, @@ -5848,8 +5862,8 @@ "andMoreFeatures": { "message": "And more!" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "upgradeToPremium": { "message": "Upgrade to Premium" @@ -5879,7 +5893,141 @@ "cardNumberLabel": { "message": "Card number" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { "message": "Timeout action" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToImmediately": { + "message": "Your organization has set the default session timeout to Immediately." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Your organization has set the default session timeout to On system lock." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On browser restart." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On browser restart" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/browser/src/_locales/my/messages.json b/apps/browser/src/_locales/my/messages.json index 684a04d9175..02945c6ff9b 100644 --- a/apps/browser/src/_locales/my/messages.json +++ b/apps/browser/src/_locales/my/messages.json @@ -436,8 +436,8 @@ "sync": { "message": "Sync" }, - "syncVaultNow": { - "message": "Sync vault now" + "syncNow": { + "message": "Sync now" }, "lastSync": { "message": "Last sync:" @@ -455,9 +455,6 @@ "bitWebVaultApp": { "message": "Bitwarden web app" }, - "importItems": { - "message": "Import items" - }, "select": { "message": "Select" }, @@ -1325,8 +1322,11 @@ "exportFrom": { "message": "Export from" }, - "exportVault": { - "message": "Export vault" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "File format" @@ -1457,6 +1457,15 @@ "attachmentSaved": { "message": "Attachment saved" }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "file": { "message": "File" }, @@ -1466,6 +1475,9 @@ "selectFile": { "message": "Select a file" }, + "itemsTransferred": { + "message": "Items transferred" + }, "maxFileSize": { "message": "Maximum file size is 500 MB." }, @@ -3240,9 +3252,6 @@ "copyCustomFieldNameNotUnique": { "message": "No unique identifier found." }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "organizationName": { "message": "Organization name" }, @@ -4206,10 +4215,6 @@ "ignore": { "message": "Ignore" }, - "importData": { - "message": "Import data", - "description": "Used for the header of the import dialog, the import button and within the file-password-prompt" - }, "importError": { "message": "Import error" }, @@ -4796,6 +4801,15 @@ "accountSecurity": { "message": "Account security" }, + "phishingBlocker": { + "message": "Phishing Blocker" + }, + "enablePhishingDetection": { + "message": "Phishing detection" + }, + "enablePhishingDetectionDesc": { + "message": "Display warning before accessing suspected phishing sites" + }, "notifications": { "message": "Notifications" }, @@ -5848,8 +5862,8 @@ "andMoreFeatures": { "message": "And more!" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "upgradeToPremium": { "message": "Upgrade to Premium" @@ -5879,7 +5893,141 @@ "cardNumberLabel": { "message": "Card number" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { "message": "Timeout action" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToImmediately": { + "message": "Your organization has set the default session timeout to Immediately." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Your organization has set the default session timeout to On system lock." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On browser restart." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On browser restart" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/browser/src/_locales/nb/messages.json b/apps/browser/src/_locales/nb/messages.json index 6226d26312f..027e28d1f2c 100644 --- a/apps/browser/src/_locales/nb/messages.json +++ b/apps/browser/src/_locales/nb/messages.json @@ -436,8 +436,8 @@ "sync": { "message": "Synkroniser" }, - "syncVaultNow": { - "message": "Synkroniser hvelvet nå" + "syncNow": { + "message": "Sync now" }, "lastSync": { "message": "Forrige synkronisering:" @@ -455,9 +455,6 @@ "bitWebVaultApp": { "message": "Bitwardens nett-app" }, - "importItems": { - "message": "Importer elementer" - }, "select": { "message": "Velg" }, @@ -1325,8 +1322,11 @@ "exportFrom": { "message": "Eksporter fra" }, - "exportVault": { - "message": "Eksporter hvelvet" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "Filformat" @@ -1457,6 +1457,15 @@ "attachmentSaved": { "message": "Vedlegget har blitt lagret." }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "file": { "message": "Fil" }, @@ -1466,6 +1475,9 @@ "selectFile": { "message": "Velg en fil." }, + "itemsTransferred": { + "message": "Items transferred" + }, "maxFileSize": { "message": "Den maksimale filstørrelsen er 500 MB." }, @@ -3240,9 +3252,6 @@ "copyCustomFieldNameNotUnique": { "message": "Ingen unik identifikator ble funnet." }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "organizationName": { "message": "Organisasjonens navn" }, @@ -4206,10 +4215,6 @@ "ignore": { "message": "Ignorer" }, - "importData": { - "message": "Importer data", - "description": "Used for the header of the import dialog, the import button and within the file-password-prompt" - }, "importError": { "message": "Importeringsfeil" }, @@ -4796,6 +4801,15 @@ "accountSecurity": { "message": "Kontosikkerhet" }, + "phishingBlocker": { + "message": "Phishing Blocker" + }, + "enablePhishingDetection": { + "message": "Phishing detection" + }, + "enablePhishingDetectionDesc": { + "message": "Display warning before accessing suspected phishing sites" + }, "notifications": { "message": "Varsler" }, @@ -5848,8 +5862,8 @@ "andMoreFeatures": { "message": "And more!" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "upgradeToPremium": { "message": "Upgrade to Premium" @@ -5879,7 +5893,141 @@ "cardNumberLabel": { "message": "Card number" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { "message": "Timeout action" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToImmediately": { + "message": "Your organization has set the default session timeout to Immediately." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Your organization has set the default session timeout to On system lock." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On browser restart." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On browser restart" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/browser/src/_locales/ne/messages.json b/apps/browser/src/_locales/ne/messages.json index 684a04d9175..02945c6ff9b 100644 --- a/apps/browser/src/_locales/ne/messages.json +++ b/apps/browser/src/_locales/ne/messages.json @@ -436,8 +436,8 @@ "sync": { "message": "Sync" }, - "syncVaultNow": { - "message": "Sync vault now" + "syncNow": { + "message": "Sync now" }, "lastSync": { "message": "Last sync:" @@ -455,9 +455,6 @@ "bitWebVaultApp": { "message": "Bitwarden web app" }, - "importItems": { - "message": "Import items" - }, "select": { "message": "Select" }, @@ -1325,8 +1322,11 @@ "exportFrom": { "message": "Export from" }, - "exportVault": { - "message": "Export vault" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "File format" @@ -1457,6 +1457,15 @@ "attachmentSaved": { "message": "Attachment saved" }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "file": { "message": "File" }, @@ -1466,6 +1475,9 @@ "selectFile": { "message": "Select a file" }, + "itemsTransferred": { + "message": "Items transferred" + }, "maxFileSize": { "message": "Maximum file size is 500 MB." }, @@ -3240,9 +3252,6 @@ "copyCustomFieldNameNotUnique": { "message": "No unique identifier found." }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "organizationName": { "message": "Organization name" }, @@ -4206,10 +4215,6 @@ "ignore": { "message": "Ignore" }, - "importData": { - "message": "Import data", - "description": "Used for the header of the import dialog, the import button and within the file-password-prompt" - }, "importError": { "message": "Import error" }, @@ -4796,6 +4801,15 @@ "accountSecurity": { "message": "Account security" }, + "phishingBlocker": { + "message": "Phishing Blocker" + }, + "enablePhishingDetection": { + "message": "Phishing detection" + }, + "enablePhishingDetectionDesc": { + "message": "Display warning before accessing suspected phishing sites" + }, "notifications": { "message": "Notifications" }, @@ -5848,8 +5862,8 @@ "andMoreFeatures": { "message": "And more!" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "upgradeToPremium": { "message": "Upgrade to Premium" @@ -5879,7 +5893,141 @@ "cardNumberLabel": { "message": "Card number" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { "message": "Timeout action" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToImmediately": { + "message": "Your organization has set the default session timeout to Immediately." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Your organization has set the default session timeout to On system lock." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On browser restart." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On browser restart" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/browser/src/_locales/nl/messages.json b/apps/browser/src/_locales/nl/messages.json index 04e6e1bb7f3..f91f28e5c1c 100644 --- a/apps/browser/src/_locales/nl/messages.json +++ b/apps/browser/src/_locales/nl/messages.json @@ -436,8 +436,8 @@ "sync": { "message": "Synchroniseren" }, - "syncVaultNow": { - "message": "Kluis nu synchroniseren" + "syncNow": { + "message": "Nu synchroniseren" }, "lastSync": { "message": "Laatste synchronisatie:" @@ -455,9 +455,6 @@ "bitWebVaultApp": { "message": "Bitwarden webapp" }, - "importItems": { - "message": "Items importeren" - }, "select": { "message": "Selecteren" }, @@ -1325,8 +1322,11 @@ "exportFrom": { "message": "Exporteren vanuit" }, - "exportVault": { - "message": "Kluis exporteren" + "export": { + "message": "Exporteren" + }, + "import": { + "message": "Importeren" }, "fileFormat": { "message": "Bestandsindeling" @@ -1457,6 +1457,15 @@ "attachmentSaved": { "message": "Bijlage opgeslagen" }, + "fixEncryption": { + "message": "Versleuteling repareren" + }, + "fixEncryptionTooltip": { + "message": "Dit bestand gebruikt een verouderde versleutelingsmethode." + }, + "attachmentUpdated": { + "message": "Bijlagen bijgewerkt" + }, "file": { "message": "Bestand" }, @@ -1466,6 +1475,9 @@ "selectFile": { "message": "Selecteer een bestand" }, + "itemsTransferred": { + "message": "Items overgedragen" + }, "maxFileSize": { "message": "Maximale bestandsgrootte is 500 MB." }, @@ -3240,9 +3252,6 @@ "copyCustomFieldNameNotUnique": { "message": "Geen unieke id gevonden." }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "Voor leden van de volgende organisatie is een hoofdwachtwoord niet langer nodig. Bevestig het domein hieronder met de beheerder van je organisatie." - }, "organizationName": { "message": "Organisatienaam" }, @@ -4206,10 +4215,6 @@ "ignore": { "message": "Negeren" }, - "importData": { - "message": "Data importeren", - "description": "Used for the header of the import dialog, the import button and within the file-password-prompt" - }, "importError": { "message": "Fout bij importeren" }, @@ -4796,6 +4801,15 @@ "accountSecurity": { "message": "Accountbeveiliging" }, + "phishingBlocker": { + "message": "Phishing-blocker" + }, + "enablePhishingDetection": { + "message": "Phishingdetectie" + }, + "enablePhishingDetectionDesc": { + "message": "Waarschuwing weergeven voor het benaderen van vermoedelijke phishingsites" + }, "notifications": { "message": "Meldingen" }, @@ -5848,8 +5862,8 @@ "andMoreFeatures": { "message": "En meer!" }, - "planDescPremium": { - "message": "Online beveiliging voltooien" + "advancedOnlineSecurity": { + "message": "Geavanceerde online beveiliging" }, "upgradeToPremium": { "message": "Opwaarderen naar Premium" @@ -5879,7 +5893,141 @@ "cardNumberLabel": { "message": "Kaartnummer" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Je organisatie maakt niet langer gebruik van hoofdwachtwoorden om in te loggen op Bitwarden. Controleer de organisatie en het domein om door te gaan." + }, + "continueWithLogIn": { + "message": "Doorgaan met inloggen" + }, + "doNotContinue": { + "message": "Niet verder gaan" + }, + "domain": { + "message": "Domein" + }, + "keyConnectorDomainTooltip": { + "message": "Dit domein zal de encryptiesleutels van je account opslaan, dus zorg ervoor dat je het vertrouwt. Als je het niet zeker weet, controleer dan bij je beheerder." + }, + "verifyYourOrganization": { + "message": "Verifieer je organisatie om in te loggen" + }, + "organizationVerified": { + "message": "Organisatie geverifieerd" + }, + "domainVerified": { + "message": "Domein geverifieerd" + }, + "leaveOrganizationContent": { + "message": "Als je je organisatie niet verifieert, wordt je toegang tot de organisatie ingetrokken." + }, + "leaveNow": { + "message": "Nu verlaten" + }, + "verifyYourDomainToLogin": { + "message": "Verifieer je domein om in te loggen" + }, + "verifyYourDomainDescription": { + "message": "Bevestig dit domein om verder te gaan met inloggen." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "Bevestig organisatie en domein om verder te gaan met inloggen." + }, "sessionTimeoutSettingsAction": { "message": "Time-out actie" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "Deze instelling wordt beheerd door je organisatie." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Je organisatie heeft de maximale sessietime-out ingesteld op $HOURS$ uur en $MINUTES$ minuten.", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToImmediately": { + "message": "Je organisatie heeft de standaard sessie time-out ingesteld op Onmiddellijk." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Je organisatie heeft de standaard sessietime-out ingesteld op Systeem vergrendelen." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Je organisatie heeft de standaard sessietime-out ingesteld op Browser verversen." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximale time-out kan niet langer zijn dan $HOURS$ uur en $MINUTES$ minu(u) t(en)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "Browser herstart" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Stel een ontgrendelingsmethode in om je kluis time-out actie te wijzigen" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Weet je zeker dat je wilt verlaten?" + }, + "leaveConfirmationDialogContentOne": { + "message": "Door te weigeren, blijven je persoonlijke items in je account, maar verlies je toegang tot gedeelde items en organisatiefunctionaliteit." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Neem contact op met je beheerder om weer toegang te krijgen." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "$ORGANIZATION$ verlaten", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "Hoe beheer ik mijn kluis?" + }, + "transferItemsToOrganizationTitle": { + "message": "Items overdragen aan $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ vereist dat alle items eigendom zijn van de organisatie voor veiligheid en naleving. Klik op accepteren voor het overdragen van eigendom van je items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Overdacht accepteren" + }, + "declineAndLeave": { + "message": "Weigeren en verlaten" + }, + "whyAmISeeingThis": { + "message": "Waarom zie ik dit?" } } diff --git a/apps/browser/src/_locales/nn/messages.json b/apps/browser/src/_locales/nn/messages.json index 684a04d9175..02945c6ff9b 100644 --- a/apps/browser/src/_locales/nn/messages.json +++ b/apps/browser/src/_locales/nn/messages.json @@ -436,8 +436,8 @@ "sync": { "message": "Sync" }, - "syncVaultNow": { - "message": "Sync vault now" + "syncNow": { + "message": "Sync now" }, "lastSync": { "message": "Last sync:" @@ -455,9 +455,6 @@ "bitWebVaultApp": { "message": "Bitwarden web app" }, - "importItems": { - "message": "Import items" - }, "select": { "message": "Select" }, @@ -1325,8 +1322,11 @@ "exportFrom": { "message": "Export from" }, - "exportVault": { - "message": "Export vault" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "File format" @@ -1457,6 +1457,15 @@ "attachmentSaved": { "message": "Attachment saved" }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "file": { "message": "File" }, @@ -1466,6 +1475,9 @@ "selectFile": { "message": "Select a file" }, + "itemsTransferred": { + "message": "Items transferred" + }, "maxFileSize": { "message": "Maximum file size is 500 MB." }, @@ -3240,9 +3252,6 @@ "copyCustomFieldNameNotUnique": { "message": "No unique identifier found." }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "organizationName": { "message": "Organization name" }, @@ -4206,10 +4215,6 @@ "ignore": { "message": "Ignore" }, - "importData": { - "message": "Import data", - "description": "Used for the header of the import dialog, the import button and within the file-password-prompt" - }, "importError": { "message": "Import error" }, @@ -4796,6 +4801,15 @@ "accountSecurity": { "message": "Account security" }, + "phishingBlocker": { + "message": "Phishing Blocker" + }, + "enablePhishingDetection": { + "message": "Phishing detection" + }, + "enablePhishingDetectionDesc": { + "message": "Display warning before accessing suspected phishing sites" + }, "notifications": { "message": "Notifications" }, @@ -5848,8 +5862,8 @@ "andMoreFeatures": { "message": "And more!" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "upgradeToPremium": { "message": "Upgrade to Premium" @@ -5879,7 +5893,141 @@ "cardNumberLabel": { "message": "Card number" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { "message": "Timeout action" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToImmediately": { + "message": "Your organization has set the default session timeout to Immediately." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Your organization has set the default session timeout to On system lock." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On browser restart." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On browser restart" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/browser/src/_locales/or/messages.json b/apps/browser/src/_locales/or/messages.json index 684a04d9175..02945c6ff9b 100644 --- a/apps/browser/src/_locales/or/messages.json +++ b/apps/browser/src/_locales/or/messages.json @@ -436,8 +436,8 @@ "sync": { "message": "Sync" }, - "syncVaultNow": { - "message": "Sync vault now" + "syncNow": { + "message": "Sync now" }, "lastSync": { "message": "Last sync:" @@ -455,9 +455,6 @@ "bitWebVaultApp": { "message": "Bitwarden web app" }, - "importItems": { - "message": "Import items" - }, "select": { "message": "Select" }, @@ -1325,8 +1322,11 @@ "exportFrom": { "message": "Export from" }, - "exportVault": { - "message": "Export vault" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "File format" @@ -1457,6 +1457,15 @@ "attachmentSaved": { "message": "Attachment saved" }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "file": { "message": "File" }, @@ -1466,6 +1475,9 @@ "selectFile": { "message": "Select a file" }, + "itemsTransferred": { + "message": "Items transferred" + }, "maxFileSize": { "message": "Maximum file size is 500 MB." }, @@ -3240,9 +3252,6 @@ "copyCustomFieldNameNotUnique": { "message": "No unique identifier found." }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "organizationName": { "message": "Organization name" }, @@ -4206,10 +4215,6 @@ "ignore": { "message": "Ignore" }, - "importData": { - "message": "Import data", - "description": "Used for the header of the import dialog, the import button and within the file-password-prompt" - }, "importError": { "message": "Import error" }, @@ -4796,6 +4801,15 @@ "accountSecurity": { "message": "Account security" }, + "phishingBlocker": { + "message": "Phishing Blocker" + }, + "enablePhishingDetection": { + "message": "Phishing detection" + }, + "enablePhishingDetectionDesc": { + "message": "Display warning before accessing suspected phishing sites" + }, "notifications": { "message": "Notifications" }, @@ -5848,8 +5862,8 @@ "andMoreFeatures": { "message": "And more!" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "upgradeToPremium": { "message": "Upgrade to Premium" @@ -5879,7 +5893,141 @@ "cardNumberLabel": { "message": "Card number" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { "message": "Timeout action" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToImmediately": { + "message": "Your organization has set the default session timeout to Immediately." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Your organization has set the default session timeout to On system lock." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On browser restart." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On browser restart" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/browser/src/_locales/pl/messages.json b/apps/browser/src/_locales/pl/messages.json index 24729a2331b..c8e452d7190 100644 --- a/apps/browser/src/_locales/pl/messages.json +++ b/apps/browser/src/_locales/pl/messages.json @@ -436,8 +436,8 @@ "sync": { "message": "Synchronizacja" }, - "syncVaultNow": { - "message": "Synchronizuj sejf" + "syncNow": { + "message": "Sync now" }, "lastSync": { "message": "Ostatnia synchronizacja:" @@ -455,9 +455,6 @@ "bitWebVaultApp": { "message": "Aplikacja internetowa Bitwarden" }, - "importItems": { - "message": "Importuj elementy" - }, "select": { "message": "Wybierz" }, @@ -598,7 +595,7 @@ "message": "Pokaż wszystko" }, "showAll": { - "message": "Show all" + "message": "Pokaż wszystko" }, "viewLess": { "message": "Pokaż mniej" @@ -1325,8 +1322,11 @@ "exportFrom": { "message": "Eksportuj z" }, - "exportVault": { - "message": "Eksportuj sejf" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "Format pliku" @@ -1407,10 +1407,10 @@ "message": "Dowiedz się więcej" }, "migrationsFailed": { - "message": "An error occurred updating the encryption settings." + "message": "Wystąpił błąd podczas aktualizacji ustawień szyfrowania." }, "updateEncryptionSettingsTitle": { - "message": "Update your encryption settings" + "message": "Zaktualizuj ustawienia szyfrowania" }, "updateEncryptionSettingsDesc": { "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." @@ -1425,7 +1425,7 @@ "message": "Update settings" }, "later": { - "message": "Later" + "message": "Później" }, "authenticatorKeyTotp": { "message": "Klucz uwierzytelniający (TOTP)" @@ -1457,6 +1457,15 @@ "attachmentSaved": { "message": "Załącznik został zapisany" }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "file": { "message": "Plik" }, @@ -1466,6 +1475,9 @@ "selectFile": { "message": "Wybierz plik" }, + "itemsTransferred": { + "message": "Items transferred" + }, "maxFileSize": { "message": "Maksymalny rozmiar pliku to 500 MB." }, @@ -1904,7 +1916,7 @@ "message": "Rok wygaśnięcia" }, "monthly": { - "message": "month" + "message": "miesiąc" }, "expiration": { "message": "Data wygaśnięcia" @@ -3240,9 +3252,6 @@ "copyCustomFieldNameNotUnique": { "message": "Nie znaleziono unikatowego identyfikatora." }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "Hasło główne nie jest już wymagane dla członków następującej organizacji. Potwierdź poniższą domenę z administratorem organizacji." - }, "organizationName": { "message": "Nazwa organizacji" }, @@ -4206,10 +4215,6 @@ "ignore": { "message": "Ignoruj" }, - "importData": { - "message": "Importuj dane", - "description": "Used for the header of the import dialog, the import button and within the file-password-prompt" - }, "importError": { "message": "Błąd importowania" }, @@ -4796,6 +4801,15 @@ "accountSecurity": { "message": "Bezpieczeństwo konta" }, + "phishingBlocker": { + "message": "Phishing Blocker" + }, + "enablePhishingDetection": { + "message": "Phishing detection" + }, + "enablePhishingDetectionDesc": { + "message": "Display warning before accessing suspected phishing sites" + }, "notifications": { "message": "Powiadomienia" }, @@ -5848,8 +5862,8 @@ "andMoreFeatures": { "message": "I jeszcze więcej!" }, - "planDescPremium": { - "message": "Pełne bezpieczeństwo w Internecie" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "upgradeToPremium": { "message": "Ulepsz do Premium" @@ -5879,7 +5893,141 @@ "cardNumberLabel": { "message": "Numer karty" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domena" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { "message": "Akcja przekroczenia limitu czasu" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToImmediately": { + "message": "Your organization has set the default session timeout to Immediately." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Your organization has set the default session timeout to On system lock." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On browser restart." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On browser restart" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/browser/src/_locales/pt_BR/messages.json b/apps/browser/src/_locales/pt_BR/messages.json index c7a5300a873..20c1a7e098d 100644 --- a/apps/browser/src/_locales/pt_BR/messages.json +++ b/apps/browser/src/_locales/pt_BR/messages.json @@ -68,7 +68,7 @@ "message": "Uma dica de senha principal pode ajudá-lo(a) a lembrá-la caso você esqueça." }, "masterPassHintText": { - "message": "Se você esquecer sua senha, a dica de senha pode ser enviada ao seu e-mail. $CURRENT$/$MAXIMUM$ caracteres máximos.", + "message": "Se você esquecer sua senha, a dica da senha pode ser enviada ao seu e-mail. $CURRENT$ do máximo de $MAXIMUM$ caracteres.", "placeholders": { "current": { "content": "$1", @@ -436,8 +436,8 @@ "sync": { "message": "Sincronizar" }, - "syncVaultNow": { - "message": "Sincronizar cofre agora" + "syncNow": { + "message": "Sincronizar agora" }, "lastSync": { "message": "Última sincronização:" @@ -455,9 +455,6 @@ "bitWebVaultApp": { "message": "Aplicativo web do Bitwarden" }, - "importItems": { - "message": "Importar itens" - }, "select": { "message": "Selecionar" }, @@ -688,16 +685,16 @@ "message": "Opções de desbloqueio" }, "unlockMethodNeededToChangeTimeoutActionDesc": { - "message": "Configure um método de desbloqueio para alterar a ação do tempo limite do cofre." + "message": "Configure um método de desbloqueio para alterar a ação do limite de tempo do cofre." }, "unlockMethodNeeded": { "message": "Configure um método de desbloqueio nas Configurações" }, "sessionTimeoutHeader": { - "message": "Tempo limite da sessão" + "message": "Limite de tempo da sessão" }, "vaultTimeoutHeader": { - "message": "Tempo limite do cofre" + "message": "Limite de tempo do cofre" }, "otherOptions": { "message": "Outras opções" @@ -758,10 +755,10 @@ } }, "vaultTimeout": { - "message": "Tempo limite do cofre" + "message": "Limite de tempo do cofre" }, "vaultTimeout1": { - "message": "Tempo limite" + "message": "Limite de tempo" }, "lockNow": { "message": "Bloquear agora" @@ -1126,7 +1123,7 @@ "message": "Clique em itens na tela do Cofre para preencher automaticamente" }, "clickToAutofill": { - "message": "Clique em um item para preenchê-lo automaticamente" + "message": "Clicar itens na sugestão para preenchê-lo" }, "clearClipboard": { "message": "Limpar área de transferência", @@ -1325,8 +1322,11 @@ "exportFrom": { "message": "Exportar de" }, - "exportVault": { - "message": "Exportar cofre" + "export": { + "message": "Exportar" + }, + "import": { + "message": "Importar" }, "fileFormat": { "message": "Formato do arquivo" @@ -1367,7 +1367,7 @@ "message": "Confirmar exportação do cofre" }, "exportWarningDesc": { - "message": "Esta exportação contém os dados do seu cofre em um formato não criptografado. Você não deve armazenar ou enviar o arquivo exportado por canais inseguros (como e-mail). Apague o arquivo imediatamente após terminar de usá-lo." + "message": "Esta exportação contém os dados do seu cofre em um formato sem criptografia. Você não deve armazenar ou enviar o arquivo exportado por canais inseguros (como e-mail). Apague o arquivo imediatamente após terminar de usá-lo." }, "encExportKeyWarningDesc": { "message": "Esta exportação criptografa seus dados usando a chave de criptografia da sua conta. Se você rotacionar a chave de criptografia da sua conta, você deve exportar novamente, já que você não será capaz de descriptografar este arquivo de exportação." @@ -1457,6 +1457,15 @@ "attachmentSaved": { "message": "Anexo salvo" }, + "fixEncryption": { + "message": "Corrigir criptografia" + }, + "fixEncryptionTooltip": { + "message": "Este arquivo está usando um método de criptografia desatualizado." + }, + "attachmentUpdated": { + "message": "Anexo atualizado" + }, "file": { "message": "Arquivo" }, @@ -1466,6 +1475,9 @@ "selectFile": { "message": "Selecione um arquivo" }, + "itemsTransferred": { + "message": "Itens transferidos" + }, "maxFileSize": { "message": "O tamanho máximo do arquivo é de 500 MB." }, @@ -1569,7 +1581,7 @@ "message": "Pedir biometria ao abrir" }, "authenticationTimeout": { - "message": "Tempo limite da autenticação atingido" + "message": "Limite de tempo da autenticação atingido" }, "authenticationSessionTimedOut": { "message": "A sessão de autenticação expirou. Reinicie o processo de autenticação." @@ -2405,10 +2417,10 @@ "message": "Personalização do cofre" }, "vaultTimeoutAction": { - "message": "Ação do tempo limite do cofre" + "message": "Ação do limite de tempo do cofre" }, "vaultTimeoutAction1": { - "message": "Ação do tempo limite" + "message": "Ação do limite de tempo" }, "lock": { "message": "Bloquear", @@ -2440,10 +2452,10 @@ "message": "Já tem uma conta?" }, "vaultTimeoutLogOutConfirmation": { - "message": "Desconectar-se irá remover todo o acesso ao seu cofre e requirirá autenticação online após o período de tempo limite. Tem certeza de que deseja usar esta configuração?" + "message": "Desconectar-se irá remover todo o acesso ao seu cofre e requirirá autenticação online após o período de limite de tempo. Tem certeza de que deseja usar esta configuração?" }, "vaultTimeoutLogOutConfirmationTitle": { - "message": "Confirmação da ação do tempo limite" + "message": "Confirmação da ação do limite de tempo" }, "autoFillAndSave": { "message": "Preencher automaticamente e salvar" @@ -3158,10 +3170,10 @@ "message": "Minutos" }, "vaultTimeoutPolicyAffectingOptions": { - "message": "Os requisitos de política empresarial foram aplicados às suas opções de tempo limite" + "message": "Os requisitos de política empresarial foram aplicados às suas opções de limite de tempo" }, "vaultTimeoutPolicyInEffect": { - "message": "As políticas da sua organização configuraram o seu máximo permitido do tempo limite do cofre para $HOURS$ hora(s) e $MINUTES$ minuto(s).", + "message": "As políticas da sua organização configuraram o seu máximo permitido do limite de tempo do cofre para $HOURS$ hora(s) e $MINUTES$ minuto(s).", "placeholders": { "hours": { "content": "$1", @@ -3187,7 +3199,7 @@ } }, "vaultTimeoutPolicyMaximumError": { - "message": "Tempo limite excede a restrição definida pela sua organização: máximo de $HOURS$ hora(s) e $MINUTES$ minuto(s)", + "message": "Limite de tempo excede a restrição definida pela sua organização: máximo de $HOURS$ hora(s) e $MINUTES$ minuto(s)", "placeholders": { "hours": { "content": "$1", @@ -3200,7 +3212,7 @@ } }, "vaultTimeoutPolicyWithActionInEffect": { - "message": "As políticas da sua organização estão afetando o tempo limite do seu cofre. \nO tempo limite máximo permitido para o cofre é $HOURS$ hora(s) e $MINUTES$ minuto(s). A ação de tempo limite do seu cofre está configurada como $ACTION$.", + "message": "As políticas da sua organização estão afetando o limite de tempo do seu cofre. \nO limite de tempo máximo permitido para o cofre é $HOURS$ hora(s) e $MINUTES$ minuto(s). A ação de limite de tempo do seu cofre está configurada como $ACTION$.", "placeholders": { "hours": { "content": "$1", @@ -3217,7 +3229,7 @@ } }, "vaultTimeoutActionPolicyInEffect": { - "message": "As políticas da sua organização configuraram a ação do tempo limite do seu cofre para $ACTION$.", + "message": "As políticas da sua organização configuraram a ação do limite de tempo do seu cofre para $ACTION$.", "placeholders": { "action": { "content": "$1", @@ -3226,7 +3238,7 @@ } }, "vaultTimeoutTooLarge": { - "message": "O tempo limite do seu cofre excede as restrições estabelecidas pela sua organização." + "message": "O limite de tempo do seu cofre excede as restrições estabelecidas pela sua organização." }, "vaultExportDisabled": { "message": "Exportação de cofre indisponível" @@ -3240,9 +3252,6 @@ "copyCustomFieldNameNotUnique": { "message": "Nenhum identificador único encontrado." }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "Uma senha principal não é mais necessária para os membros da seguinte organização. Confirme o domínio abaixo com o administrador da sua organização." - }, "organizationName": { "message": "Nome da organização" }, @@ -3497,7 +3506,7 @@ } }, "forwarderNoAccountId": { - "message": "Não foi possível obter o ID da conta de e-mail mascarado $SERVICENAME$.", + "message": "Não é possível obter o ID da conta de e-mail mascarado do $SERVICENAME$.", "description": "Displayed when the forwarding service fails to return an account ID.", "placeholders": { "servicename": { @@ -3656,7 +3665,7 @@ "message": "Solicitação enviada" }, "loginRequestApprovedForEmailOnDevice": { - "message": "Solicitação de autenticação aprovada para $EMAIL$ em $DEVICE$", + "message": "Solicitação de acesso aprovada para $EMAIL$ em $DEVICE$", "placeholders": { "email": { "content": "$1", @@ -3669,7 +3678,7 @@ } }, "youDeniedLoginAttemptFromAnotherDevice": { - "message": "Você negou uma tentativa de autenticação de outro dispositivo. Se era você, tente se conectar com o dispositivo novamente." + "message": "Você negou uma tentativa de acesso de outro dispositivo. Se era você, tente se conectar com o dispositivo novamente." }, "device": { "message": "Dispositivo" @@ -3693,7 +3702,7 @@ "message": "Senha fraca identificada e encontrada em um vazamento de dados. Use uma senha forte e única para proteger a sua conta. Tem certeza de que deseja usar essa senha?" }, "checkForBreaches": { - "message": "Conferir vazamentos de dados conhecidos por esta senha" + "message": "Conferir se esta senha vazou ao público" }, "important": { "message": "Importante:" @@ -3838,13 +3847,13 @@ "message": "Tipo do dispositivo" }, "loginRequest": { - "message": "Solicitação de autenticação" + "message": "Solicitação de acesso" }, "thisRequestIsNoLongerValid": { "message": "Esta solicitação não é mais válida." }, "loginRequestHasAlreadyExpired": { - "message": "A solicitação de autenticação já expirou." + "message": "A solicitação de acesso já expirou." }, "justNow": { "message": "Agora há pouco" @@ -3935,7 +3944,7 @@ "message": "Problemas para acessar?" }, "loginApproved": { - "message": "Autenticação aprovada" + "message": "Acesso aprovado" }, "userEmailMissing": { "message": "E-mail do usuário ausente" @@ -4206,10 +4215,6 @@ "ignore": { "message": "Ignorar" }, - "importData": { - "message": "Importar dados", - "description": "Used for the header of the import dialog, the import button and within the file-password-prompt" - }, "importError": { "message": "Erro ao importar" }, @@ -4796,6 +4801,15 @@ "accountSecurity": { "message": "Segurança da conta" }, + "phishingBlocker": { + "message": "Bloqueador de phishing" + }, + "enablePhishingDetection": { + "message": "Detecção de phishing" + }, + "enablePhishingDetectionDesc": { + "message": "Exiba um aviso antes de acessar sites suspeitos de phishing" + }, "notifications": { "message": "Notificações" }, @@ -5299,7 +5313,7 @@ "message": "Ações da conta" }, "showNumberOfAutofillSuggestions": { - "message": "Mostrar número de sugestões de preenchimento automático de credenciais no ícone da extensão" + "message": "Mostrar número de sugestões de preenchimento no ícone da extensão" }, "accountAccessRequested": { "message": "Acesso à conta solicitado" @@ -5350,7 +5364,7 @@ "message": "Tentar novamente" }, "vaultCustomTimeoutMinimum": { - "message": "Tempo limite mínimo personalizado é 1 minuto." + "message": "Limite de tempo mínimo personalizado é 1 minuto." }, "fileSavedToDevice": { "message": "Arquivo salvo no dispositivo. Gerencie a partir dos downloads do seu dispositivo." @@ -5413,7 +5427,7 @@ "message": "Desbloqueie seu cofre em segundos" }, "unlockVaultDesc": { - "message": "Você pode personalizar suas configurações de desbloqueio e tempo limite para acessar seu cofre mais rapidamente." + "message": "Você pode personalizar suas configurações de desbloqueio e limite de tempo para acessar seu cofre mais rapidamente." }, "unlockPinSet": { "message": "PIN de desbloqueio configurado" @@ -5848,8 +5862,8 @@ "andMoreFeatures": { "message": "E mais!" }, - "planDescPremium": { - "message": "Segurança on-line completa" + "advancedOnlineSecurity": { + "message": "Segurança on-line avançada" }, "upgradeToPremium": { "message": "Faça upgrade para o Premium" @@ -5879,7 +5893,141 @@ "cardNumberLabel": { "message": "Número do cartão" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "A sua organização não está mais usando senhas principais para se conectar ao Bitwarden. Para continuar, verifique a organização e o domínio." + }, + "continueWithLogIn": { + "message": "Continuar acessando" + }, + "doNotContinue": { + "message": "Não continuar" + }, + "domain": { + "message": "Domínio" + }, + "keyConnectorDomainTooltip": { + "message": "Este domínio armazenará as chaves de criptografia da sua conta, então certifique-se que confia nele. Se não tiver certeza, verifique com o seu administrador." + }, + "verifyYourOrganization": { + "message": "Verifique sua organização para se conectar" + }, + "organizationVerified": { + "message": "Organização verificada" + }, + "domainVerified": { + "message": "Domínio verificado" + }, + "leaveOrganizationContent": { + "message": "Se você não verificar a sua organização, o seu acesso à organização será revogado." + }, + "leaveNow": { + "message": "Sair agora" + }, + "verifyYourDomainToLogin": { + "message": "Verifique seu domínio para se conectar" + }, + "verifyYourDomainDescription": { + "message": "Para continuar se conectando, verifique este domínio." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "Para continuar se conectando, verifique a organização e o domínio." + }, "sessionTimeoutSettingsAction": { - "message": "Ação do tempo limite" + "message": "Ação do limite de tempo" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "Esta configuração é gerenciada pela sua organização." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "A sua organização configurou o limite de tempo máximo da sessão para $HOURS$ hora(s) e $MINUTES$ minuto(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToImmediately": { + "message": "A sua organização configurou o limite de tempo padrão da sessão para imediatamente." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "A sua organização configurou o limite de tempo padrão da sessão para ser no bloqueio do sistema." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "A sua organização configurou o limite de tempo padrão da sessão para ser no reinício do navegador." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "O limite de tempo máximo não pode exceder $HOURS$ hora(s) e $MINUTES$ minuto(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "No reinício do navegador" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Configure um método de desbloqueio para alterar a ação do limite de tempo" + }, + "upgrade": { + "message": "Fazer upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Tem certeza de que quer sair?" + }, + "leaveConfirmationDialogContentOne": { + "message": "Se recusar, seus itens pessoais continuarão na sua conta, mas você perderá o acesso aos itens compartilhados e os recursos de organização." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Entre em contato com o seu administrador para recuperar o acesso." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Sair de $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "Como gerencio meu cofre?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transferir itens para $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ exige que todos os itens sejam propriedade da organização por segurança e conformidade. Clique em aceitar para transferir a propriedade dos seus itens.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Aceitar transferência" + }, + "declineAndLeave": { + "message": "Recusar e sair" + }, + "whyAmISeeingThis": { + "message": "Por que estou vendo isso?" } } diff --git a/apps/browser/src/_locales/pt_PT/messages.json b/apps/browser/src/_locales/pt_PT/messages.json index c03f3038f98..e0cbf43a813 100644 --- a/apps/browser/src/_locales/pt_PT/messages.json +++ b/apps/browser/src/_locales/pt_PT/messages.json @@ -200,7 +200,7 @@ "description": "This string is used on the vault page to indicate autofilling. Horizontal space is limited in the interface here so try and keep translations as concise as possible." }, "autoFill": { - "message": "Preencher automaticamente" + "message": "Preenchimento automático" }, "autoFillLogin": { "message": "Preencher automaticamente credencial" @@ -436,8 +436,8 @@ "sync": { "message": "Sincronizar" }, - "syncVaultNow": { - "message": "Sincronizar o cofre agora" + "syncNow": { + "message": "Sincronizar agora" }, "lastSync": { "message": "Última sincronização:" @@ -455,9 +455,6 @@ "bitWebVaultApp": { "message": "Aplicação web Bitwarden" }, - "importItems": { - "message": "Importar itens" - }, "select": { "message": "Selecionar" }, @@ -1325,8 +1322,11 @@ "exportFrom": { "message": "Exportar de" }, - "exportVault": { - "message": "Exportar cofre" + "export": { + "message": "Exportar" + }, + "import": { + "message": "Importar" }, "fileFormat": { "message": "Formato do ficheiro" @@ -1457,6 +1457,15 @@ "attachmentSaved": { "message": "Anexo guardado" }, + "fixEncryption": { + "message": "Corrigir encriptação" + }, + "fixEncryptionTooltip": { + "message": "Este ficheiro está a utilizar um método de encriptação desatualizado." + }, + "attachmentUpdated": { + "message": "Anexo atualizado" + }, "file": { "message": "Ficheiro" }, @@ -1466,6 +1475,9 @@ "selectFile": { "message": "Selecionar um ficheiro" }, + "itemsTransferred": { + "message": "Itens transferidos" + }, "maxFileSize": { "message": "O tamanho máximo do ficheiro é de 500 MB." }, @@ -3240,9 +3252,6 @@ "copyCustomFieldNameNotUnique": { "message": "Não foi encontrado um identificador único." }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "Já não é necessária uma palavra-passe mestra para os membros da seguinte organização. Por favor, confirme o domínio abaixo com o administrador da sua organização." - }, "organizationName": { "message": "Nome da organização" }, @@ -4206,10 +4215,6 @@ "ignore": { "message": "Ignorar" }, - "importData": { - "message": "Importar dados", - "description": "Used for the header of the import dialog, the import button and within the file-password-prompt" - }, "importError": { "message": "Erro de importação" }, @@ -4796,6 +4801,15 @@ "accountSecurity": { "message": "Segurança da conta" }, + "phishingBlocker": { + "message": "Bloqueador de phishing" + }, + "enablePhishingDetection": { + "message": "Deteção de phishing" + }, + "enablePhishingDetectionDesc": { + "message": "Mostrar aviso antes de aceder a sites suspeitos de phishing" + }, "notifications": { "message": "Notificações" }, @@ -5848,8 +5862,8 @@ "andMoreFeatures": { "message": "E muito mais!" }, - "planDescPremium": { - "message": "Segurança total online" + "advancedOnlineSecurity": { + "message": "Segurança online avançada" }, "upgradeToPremium": { "message": "Atualizar para o Premium" @@ -5879,7 +5893,141 @@ "cardNumberLabel": { "message": "Número do cartão" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "A sua organização já não utiliza palavras-passe mestras para iniciar sessão no Bitwarden. Para continuar, verifique a organização e o domínio." + }, + "continueWithLogIn": { + "message": "Continuar com o início de sessão" + }, + "doNotContinue": { + "message": "Não continuar" + }, + "domain": { + "message": "Domínio" + }, + "keyConnectorDomainTooltip": { + "message": "Este domínio armazenará as chaves de encriptação da sua conta, portanto certifique-se de que confia nele. Se não tiver a certeza, verifique com o seu administrador." + }, + "verifyYourOrganization": { + "message": "Verifique a sua organização para iniciar sessão" + }, + "organizationVerified": { + "message": "Organização verificada" + }, + "domainVerified": { + "message": "Domínio verificado" + }, + "leaveOrganizationContent": { + "message": "Se não verificar a sua organização, o seu acesso à organização será revogado." + }, + "leaveNow": { + "message": "Sair agora" + }, + "verifyYourDomainToLogin": { + "message": "Verifique o seu domínio para iniciar sessão" + }, + "verifyYourDomainDescription": { + "message": "Para continuar com o início de sessão, verifique este domínio." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "Para continuar com o início de sessão, verifique a organização e o domínio." + }, "sessionTimeoutSettingsAction": { "message": "Ação de tempo limite" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "Esta configuração é gerida pela sua organização." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "A sua organização definiu o tempo limite máximo da sessão para $HOURS$ hora(s) e $MINUTES$ minuto(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToImmediately": { + "message": "A sua organização definiu o tempo limite predefinido da sessão como Imediatamente." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "A sua organização definiu o tempo limite de sessão predefinido para Ao bloquear o sistema." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "A sua organização definiu o tempo limite de sessão predefinido para Ao reiniciar o navegador." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "O tempo limite máximo não pode ser superior a $HOURS$ hora(s) e $MINUTES$ minuto(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "Ao reiniciar o navegador" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Configure um método de desbloqueio para alterar a sua ação de tempo limite" + }, + "upgrade": { + "message": "Atualizar" + }, + "leaveConfirmationDialogTitle": { + "message": "Tem a certeza de que pretende sair?" + }, + "leaveConfirmationDialogContentOne": { + "message": "Ao recusar, os seus itens pessoais permanecerão na sua conta, mas perderá o acesso aos itens partilhados e às funcionalidades da organização." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Entre em contacto com o seu administrador para recuperar o acesso." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Sair de $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "Como posso gerir o meu cofre?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transferir itens para $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ exige que todos os itens sejam propriedade da organização por motivos de segurança e conformidade. Clique em Aceitar para transferir a propriedade dos seus itens.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Aceitar transferência" + }, + "declineAndLeave": { + "message": "Recusar e sair" + }, + "whyAmISeeingThis": { + "message": "Porque é que estou a ver isto?" } } diff --git a/apps/browser/src/_locales/ro/messages.json b/apps/browser/src/_locales/ro/messages.json index 85db8d8a4f3..d6d32804dcb 100644 --- a/apps/browser/src/_locales/ro/messages.json +++ b/apps/browser/src/_locales/ro/messages.json @@ -436,8 +436,8 @@ "sync": { "message": "Sincronizare" }, - "syncVaultNow": { - "message": "Sincronizare seif acum" + "syncNow": { + "message": "Sync now" }, "lastSync": { "message": "Ultima sincronizare:" @@ -455,9 +455,6 @@ "bitWebVaultApp": { "message": "Aplicația web Bitwarden" }, - "importItems": { - "message": "Import de articole" - }, "select": { "message": "Selectare" }, @@ -1325,8 +1322,11 @@ "exportFrom": { "message": "Export from" }, - "exportVault": { - "message": "Export seif" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "Format fișier" @@ -1457,6 +1457,15 @@ "attachmentSaved": { "message": "Atașamentul a fost salvat" }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "file": { "message": "Fișier" }, @@ -1466,6 +1475,9 @@ "selectFile": { "message": "Selectare fișier" }, + "itemsTransferred": { + "message": "Items transferred" + }, "maxFileSize": { "message": "Mărimea maximă a fișierului este de 500 MB." }, @@ -3240,9 +3252,6 @@ "copyCustomFieldNameNotUnique": { "message": "Nu a fost găsit niciun identificator unic." }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "organizationName": { "message": "Organization name" }, @@ -4206,10 +4215,6 @@ "ignore": { "message": "Ignore" }, - "importData": { - "message": "Import data", - "description": "Used for the header of the import dialog, the import button and within the file-password-prompt" - }, "importError": { "message": "Import error" }, @@ -4796,6 +4801,15 @@ "accountSecurity": { "message": "Account security" }, + "phishingBlocker": { + "message": "Phishing Blocker" + }, + "enablePhishingDetection": { + "message": "Phishing detection" + }, + "enablePhishingDetectionDesc": { + "message": "Display warning before accessing suspected phishing sites" + }, "notifications": { "message": "Notifications" }, @@ -5848,8 +5862,8 @@ "andMoreFeatures": { "message": "And more!" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "upgradeToPremium": { "message": "Upgrade to Premium" @@ -5879,7 +5893,141 @@ "cardNumberLabel": { "message": "Card number" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { "message": "Timeout action" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToImmediately": { + "message": "Your organization has set the default session timeout to Immediately." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Your organization has set the default session timeout to On system lock." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On browser restart." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On browser restart" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/browser/src/_locales/ru/messages.json b/apps/browser/src/_locales/ru/messages.json index 58f2b372459..575b29350fd 100644 --- a/apps/browser/src/_locales/ru/messages.json +++ b/apps/browser/src/_locales/ru/messages.json @@ -436,7 +436,7 @@ "sync": { "message": "Синхронизация" }, - "syncVaultNow": { + "syncNow": { "message": "Синхронизировать" }, "lastSync": { @@ -455,9 +455,6 @@ "bitWebVaultApp": { "message": "Веб-приложение Bitwarden" }, - "importItems": { - "message": "Импорт элементов" - }, "select": { "message": "Выбрать" }, @@ -931,7 +928,7 @@ "message": "Вы вышли из своего аккаунта." }, "loginExpired": { - "message": "Истек срок действия вашего сеанса." + "message": "Истек срок действия вашей сессии." }, "logIn": { "message": "Войти" @@ -1325,8 +1322,11 @@ "exportFrom": { "message": "Экспорт из" }, - "exportVault": { - "message": "Экспорт хранилища" + "export": { + "message": "Экспорт" + }, + "import": { + "message": "Импорт" }, "fileFormat": { "message": "Формат файла" @@ -1457,6 +1457,15 @@ "attachmentSaved": { "message": "Вложение сохранено." }, + "fixEncryption": { + "message": "Исправить шифрование" + }, + "fixEncryptionTooltip": { + "message": "Этот файл использует устаревший метод шифрования." + }, + "attachmentUpdated": { + "message": "Вложение обновлено" + }, "file": { "message": "Файл" }, @@ -1466,6 +1475,9 @@ "selectFile": { "message": "Выбрать файл" }, + "itemsTransferred": { + "message": "Элементы переданы" + }, "maxFileSize": { "message": "Максимальный размер файла 500 МБ." }, @@ -1572,7 +1584,7 @@ "message": "Таймаут аутентификации" }, "authenticationSessionTimedOut": { - "message": "Сеанс аутентификации завершился по времени. Пожалуйста, попробуйте войти еще раз." + "message": "Сессия аутентификации завершилась по времени. Пожалуйста, перезапустите процесс авторизации." }, "verificationCodeEmailSent": { "message": "Отправлено письмо подтверждения на $EMAIL$.", @@ -3109,10 +3121,10 @@ "message": "Обновить мастер-пароль" }, "updateMasterPasswordWarning": { - "message": "Мастер-пароль недавно был изменен администратором вашей организации. Чтобы получить доступ к хранилищу, вы должны обновить его сейчас. В результате текущий сеанс будет завершен, потребуется повторный вход. Сеансы на других устройствах могут оставаться активными в течение одного часа." + "message": "Мастер-пароль недавно был изменен администратором вашей организации. Чтобы получить доступ к хранилищу, вы должны обновить его сейчас. В результате текущая сессия будет завершена, потребуется повторный вход. Сессии на других устройствах могут оставаться активными в течение одного часа." }, "updateWeakMasterPasswordWarning": { - "message": "Ваш мастер-пароль не соответствует требованиям политики вашей организации. Для доступа к хранилищу вы должны обновить свой мастер-пароль прямо сейчас. При этом текущий сеанс будет завершен и потребуется повторная авторизация. Сеансы на других устройствах могут оставаться активными в течение часа." + "message": "Ваш мастер-пароль не соответствует требованиям политики вашей организации. Для доступа к хранилищу вы должны обновить свой мастер-пароль прямо сейчас. При этом текущая сессия будет завершена и потребуется повторная авторизация. Сессии на других устройствах могут оставаться активными в течение часа." }, "tdeDisabledMasterPasswordRequired": { "message": "В вашей организации отключено шифрование доверенных устройств. Пожалуйста, установите мастер-пароль для доступа к вашему хранилищу." @@ -3240,9 +3252,6 @@ "copyCustomFieldNameNotUnique": { "message": "Уникальный идентификатор не найден." }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "Мастер-пароль больше не требуется для членов следующей организации. Пожалуйста, подтвердите указанный ниже домен у администратора вашей организации." - }, "organizationName": { "message": "Название организации" }, @@ -3268,7 +3277,7 @@ "message": "Показать количество символов" }, "sessionTimeout": { - "message": "Время вашего сеанса истекло. Пожалуйста, вернитесь и попробуйте войти снова." + "message": "Время вашей сессии истекло. Пожалуйста, вернитесь и попробуйте войти снова." }, "exportingPersonalVaultTitle": { "message": "Экспорт личного хранилища" @@ -4206,10 +4215,6 @@ "ignore": { "message": "Игнорировать" }, - "importData": { - "message": "Импорт данных", - "description": "Used for the header of the import dialog, the import button and within the file-password-prompt" - }, "importError": { "message": "Ошибка импорта" }, @@ -4796,6 +4801,15 @@ "accountSecurity": { "message": "Безопасность аккаунта" }, + "phishingBlocker": { + "message": "Блокировщик фишинга" + }, + "enablePhishingDetection": { + "message": "Обнаружение фишинга" + }, + "enablePhishingDetectionDesc": { + "message": "Отображать предупреждение перед доступом к подозрительным фишинговым сайтам" + }, "notifications": { "message": "Уведомления" }, @@ -5848,8 +5862,8 @@ "andMoreFeatures": { "message": "И многое другое!" }, - "planDescPremium": { - "message": "Полная онлайн-защищенность" + "advancedOnlineSecurity": { + "message": "Расширенная онлайн-безопасность" }, "upgradeToPremium": { "message": "Обновить до Премиум" @@ -5879,7 +5893,141 @@ "cardNumberLabel": { "message": "Номер карты" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Ваша организация больше не использует мастер-пароли для входа в Bitwarden. Чтобы продолжить, подтвердите организацию и домен." + }, + "continueWithLogIn": { + "message": "Продолжить с логином" + }, + "doNotContinue": { + "message": "Не продолжать" + }, + "domain": { + "message": "Домен" + }, + "keyConnectorDomainTooltip": { + "message": "В этом домене будут храниться ключи шифрования вашего аккаунта, поэтому убедитесь, что вы ему доверяете. Если вы не уверены, обратитесь к своему администратору." + }, + "verifyYourOrganization": { + "message": "Подтвердите свою организацию для входа" + }, + "organizationVerified": { + "message": "Организация подтверждена" + }, + "domainVerified": { + "message": "Домен верифицирован" + }, + "leaveOrganizationContent": { + "message": "Если вы не подтвердите свою организацию, ваш доступ к ней будет аннулирован." + }, + "leaveNow": { + "message": "Покинуть" + }, + "verifyYourDomainToLogin": { + "message": "Подтвердите свой домен для входа" + }, + "verifyYourDomainDescription": { + "message": "Чтобы продолжить с логином, подтвердите этот домен." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "Чтобы продолжить с логином, подтвердите организацию и домен." + }, "sessionTimeoutSettingsAction": { "message": "Тайм-аут действия" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "Эта настройка управляется вашей организацией." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "В вашей организации максимальный тайм-аут сессии установлен равным $HOURS$ час. и $MINUTES$ мин.", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToImmediately": { + "message": "Ваша организация не установила тайм-аут сессии на Немедленно." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Ваша организация установила тайм-аут сессии по умолчанию на При блокировке системы." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Ваша организация установила тайм-аут сессии по умолчанию на При перезапуске браузера." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Максимальный тайм-аут не может превышать $HOURS$ час. и $MINUTES$ мин.", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "При перезапуске браузера" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Установите способ разблокировки для изменения действия при истечении тайм-аута" + }, + "upgrade": { + "message": "Перейти" + }, + "leaveConfirmationDialogTitle": { + "message": "Вы уверены, что хотите покинуть?" + }, + "leaveConfirmationDialogContentOne": { + "message": "В случае отказа ваши личные данные останутся в вашем аккаунте, но вы потеряете доступ к общим элементам и возможностям организации." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Свяжитесь с вашим администратором для восстановления доступа." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Покинуть $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "Как я могу управлять своим хранилищем?" + }, + "transferItemsToOrganizationTitle": { + "message": "Перенести элементы в $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ требует, чтобы все элементы принадлежали организации для обеспечения безопасности и соответствия требованиям. Нажмите Принять, чтобы передать собственность на ваши элементы.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Принять передачу" + }, + "declineAndLeave": { + "message": "Отклонить и покинуть" + }, + "whyAmISeeingThis": { + "message": "Почему я это вижу?" } } diff --git a/apps/browser/src/_locales/si/messages.json b/apps/browser/src/_locales/si/messages.json index ead646d0ac0..d4fb646f471 100644 --- a/apps/browser/src/_locales/si/messages.json +++ b/apps/browser/src/_locales/si/messages.json @@ -436,8 +436,8 @@ "sync": { "message": "සමමුහූර්තනය" }, - "syncVaultNow": { - "message": "සුරක්ෂිතාගාරය දැන් සමමුහුර්ත කරන්න" + "syncNow": { + "message": "Sync now" }, "lastSync": { "message": "අවසන් සමමුහුර්ත කරන්න:" @@ -455,9 +455,6 @@ "bitWebVaultApp": { "message": "Bitwarden web app" }, - "importItems": { - "message": "ආනයන අයිතම" - }, "select": { "message": "තෝරන්න" }, @@ -1325,8 +1322,11 @@ "exportFrom": { "message": "Export from" }, - "exportVault": { - "message": "අපනයන සුරක්ෂිතාගාරය" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "ගොනු ආකෘතිය" @@ -1457,6 +1457,15 @@ "attachmentSaved": { "message": "ඇමුණුම ගැලවීම කර ඇත." }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "file": { "message": "ගොනුව" }, @@ -1466,6 +1475,9 @@ "selectFile": { "message": "ගොනුවක් තෝරන්න." }, + "itemsTransferred": { + "message": "Items transferred" + }, "maxFileSize": { "message": "උපරිම ගොනු ප්රමාණය 500 MB වේ." }, @@ -3240,9 +3252,6 @@ "copyCustomFieldNameNotUnique": { "message": "අද්විතීය හඳුනාගැනීමක් සොයාගත නොහැකි විය." }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "organizationName": { "message": "Organization name" }, @@ -4206,10 +4215,6 @@ "ignore": { "message": "Ignore" }, - "importData": { - "message": "Import data", - "description": "Used for the header of the import dialog, the import button and within the file-password-prompt" - }, "importError": { "message": "Import error" }, @@ -4796,6 +4801,15 @@ "accountSecurity": { "message": "Account security" }, + "phishingBlocker": { + "message": "Phishing Blocker" + }, + "enablePhishingDetection": { + "message": "Phishing detection" + }, + "enablePhishingDetectionDesc": { + "message": "Display warning before accessing suspected phishing sites" + }, "notifications": { "message": "Notifications" }, @@ -5848,8 +5862,8 @@ "andMoreFeatures": { "message": "And more!" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "upgradeToPremium": { "message": "Upgrade to Premium" @@ -5879,7 +5893,141 @@ "cardNumberLabel": { "message": "Card number" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { "message": "Timeout action" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToImmediately": { + "message": "Your organization has set the default session timeout to Immediately." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Your organization has set the default session timeout to On system lock." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On browser restart." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On browser restart" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/browser/src/_locales/sk/messages.json b/apps/browser/src/_locales/sk/messages.json index 70620e6c5e5..c32da5e7adb 100644 --- a/apps/browser/src/_locales/sk/messages.json +++ b/apps/browser/src/_locales/sk/messages.json @@ -436,8 +436,8 @@ "sync": { "message": "Synchronizácia" }, - "syncVaultNow": { - "message": "Synchronizovať trezor teraz" + "syncNow": { + "message": "Synchronizovať teraz" }, "lastSync": { "message": "Posledná synchronizácia:" @@ -455,9 +455,6 @@ "bitWebVaultApp": { "message": "Webová aplikácia Bitwarden" }, - "importItems": { - "message": "Importovať položky" - }, "select": { "message": "Vybrať" }, @@ -803,7 +800,7 @@ "message": "4 hodiny" }, "onLocked": { - "message": "Keď je systém uzamknutý" + "message": "Pri uzamknutí systému" }, "onIdle": { "message": "Pri nečinnosti systému" @@ -812,7 +809,7 @@ "message": "V režime spánku" }, "onRestart": { - "message": "Po reštarte prehliadača" + "message": "Pri reštarte prehliadača" }, "never": { "message": "Nikdy" @@ -1325,8 +1322,11 @@ "exportFrom": { "message": "Exportovať z" }, - "exportVault": { - "message": "Export trezoru" + "export": { + "message": "Exportovať" + }, + "import": { + "message": "Importovať" }, "fileFormat": { "message": "Formát súboru" @@ -1457,6 +1457,15 @@ "attachmentSaved": { "message": "Príloha bola uložená" }, + "fixEncryption": { + "message": "Opraviť šifrovanie" + }, + "fixEncryptionTooltip": { + "message": "Tento súbor používa zastaranú metódu šifrovania." + }, + "attachmentUpdated": { + "message": "Príloha bola aktualizovaná" + }, "file": { "message": "Súbor" }, @@ -1466,6 +1475,9 @@ "selectFile": { "message": "Vyberte súbor" }, + "itemsTransferred": { + "message": "Items transferred" + }, "maxFileSize": { "message": "Maximálna veľkosť súboru je 500 MB." }, @@ -3240,9 +3252,6 @@ "copyCustomFieldNameNotUnique": { "message": "Nenašiel sa žiadny jedinečný identifikátor." }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "Hlavné heslo sa už nevyžaduje pre členov tejto organizácie. Nižšie uvedenú doménu potvrďte u správcu organizácie." - }, "organizationName": { "message": "Názov organizácie" }, @@ -4206,10 +4215,6 @@ "ignore": { "message": "Ignorovať" }, - "importData": { - "message": "Import údajov", - "description": "Used for the header of the import dialog, the import button and within the file-password-prompt" - }, "importError": { "message": "Chyba importu" }, @@ -4796,6 +4801,15 @@ "accountSecurity": { "message": "Zabezpečenie účtu" }, + "phishingBlocker": { + "message": "Blokovač phishingu" + }, + "enablePhishingDetection": { + "message": "Detekcia phishingu" + }, + "enablePhishingDetectionDesc": { + "message": "Zobrazí upozornenie pred prístupom na podozrivé phishingové stránky" + }, "notifications": { "message": "Upozornenia" }, @@ -5848,8 +5862,8 @@ "andMoreFeatures": { "message": "A ešte viac!" }, - "planDescPremium": { - "message": "Úplné online zabezpečenie" + "advancedOnlineSecurity": { + "message": "Pokročilá online ochrana" }, "upgradeToPremium": { "message": "Upgradovať na Prémium" @@ -5879,7 +5893,141 @@ "cardNumberLabel": { "message": "Číslo karty" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Vaša organizácia už nepoužíva hlavné heslá na prihlásenie do Bitwardenu. Ak chcete pokračovať, overte organizáciu a doménu." + }, + "continueWithLogIn": { + "message": "Pokračujte prihlásením" + }, + "doNotContinue": { + "message": "Nepokračovať" + }, + "domain": { + "message": "Doména" + }, + "keyConnectorDomainTooltip": { + "message": "Táto doména bude ukladať šifrovacie kľúče vášho účtu, takže sa uistite, že jej dôverujete. Ak si nie ste istí, overte si to u správcu." + }, + "verifyYourOrganization": { + "message": "Na prihlásenie overte organizáciu" + }, + "organizationVerified": { + "message": "Organizácia je overená" + }, + "domainVerified": { + "message": "Doména je overená" + }, + "leaveOrganizationContent": { + "message": "Ak organizáciu neoveríte, váš prístup k nej bude zrušený." + }, + "leaveNow": { + "message": "Opustiť teraz" + }, + "verifyYourDomainToLogin": { + "message": "Na prihlásenie overte doménu" + }, + "verifyYourDomainDescription": { + "message": "Na pokračovanie prihlásením, overte túto doménu." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "Na pokračovanie prihlásením, overte organizáciu a doménu." + }, "sessionTimeoutSettingsAction": { "message": "Akcia pri vypršaní časového limitu" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "Toto nastavenie spravuje vaša organizácia." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Vaša organizácia nastavila maximálny časový limit relácie na $HOURS$ hod. a $MINUTES$ min.", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToImmediately": { + "message": "Vaša organizácia nastavila predvolený časový limit relácie na Okamžite." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Vaša organizácia nastavila predvolený časový limit relácie na Pri uzamknutí systému." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Vaša organizácia nastavila predvolený časový limit relácie na Pri reštarte prehliadača." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximálny časový limit nesmie prekročiť $HOURS$ hod. a $MINUTES$ min.", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "Pri reštarte prehliadača" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Nastavte metódu odomknutia, aby ste zmenili akciu pri vypršaní časového limitu" + }, + "upgrade": { + "message": "Upgradovať" + }, + "leaveConfirmationDialogTitle": { + "message": "Naozaj chcete odísť?" + }, + "leaveConfirmationDialogContentOne": { + "message": "Ak odmietnete, vaše osobné položky zostanú vo vašom účte, ale stratíte prístup k zdieľaným položkám a funkciám organizácie." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Ak chcete obnoviť prístup, obráťte sa na správcu." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Opustiť $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "Ako môžem spravovať svoj trezor?" + }, + "transferItemsToOrganizationTitle": { + "message": "Prenos položiek do $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ vyžaduje, aby všetky položky boli vo vlastníctve organizácie z dôvodu bezpečnosti a dodržiavania predpisov. Ak chcete previesť vlastníctvo položiek, kliknite na tlačidlo Prijať.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Prijať prenos" + }, + "declineAndLeave": { + "message": "Zamietnuť a odísť" + }, + "whyAmISeeingThis": { + "message": "Prečo to vidím?" } } diff --git a/apps/browser/src/_locales/sl/messages.json b/apps/browser/src/_locales/sl/messages.json index 4867f4b89f2..fcfa6857588 100644 --- a/apps/browser/src/_locales/sl/messages.json +++ b/apps/browser/src/_locales/sl/messages.json @@ -436,8 +436,8 @@ "sync": { "message": "Sinhronizacija" }, - "syncVaultNow": { - "message": "Sinhroniziraj trezor zdaj" + "syncNow": { + "message": "Sync now" }, "lastSync": { "message": "Zadnja sinhronizacija:" @@ -455,9 +455,6 @@ "bitWebVaultApp": { "message": "Bitwarden web app" }, - "importItems": { - "message": "Uvozi elemente" - }, "select": { "message": "Izberi" }, @@ -1325,8 +1322,11 @@ "exportFrom": { "message": "Export from" }, - "exportVault": { - "message": "Izvoz trezorja" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "Format datoteke" @@ -1457,6 +1457,15 @@ "attachmentSaved": { "message": "Priponka je bila shranjena." }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "file": { "message": "Datoteka" }, @@ -1466,6 +1475,9 @@ "selectFile": { "message": "Izberite datoteko." }, + "itemsTransferred": { + "message": "Items transferred" + }, "maxFileSize": { "message": "Največja velikost datoteke je 500 MB." }, @@ -3240,9 +3252,6 @@ "copyCustomFieldNameNotUnique": { "message": "No unique identifier found." }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "organizationName": { "message": "Organization name" }, @@ -4206,10 +4215,6 @@ "ignore": { "message": "Ignore" }, - "importData": { - "message": "Import data", - "description": "Used for the header of the import dialog, the import button and within the file-password-prompt" - }, "importError": { "message": "Import error" }, @@ -4796,6 +4801,15 @@ "accountSecurity": { "message": "Account security" }, + "phishingBlocker": { + "message": "Phishing Blocker" + }, + "enablePhishingDetection": { + "message": "Phishing detection" + }, + "enablePhishingDetectionDesc": { + "message": "Display warning before accessing suspected phishing sites" + }, "notifications": { "message": "Notifications" }, @@ -5848,8 +5862,8 @@ "andMoreFeatures": { "message": "And more!" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "upgradeToPremium": { "message": "Upgrade to Premium" @@ -5879,7 +5893,141 @@ "cardNumberLabel": { "message": "Card number" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { "message": "Timeout action" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToImmediately": { + "message": "Your organization has set the default session timeout to Immediately." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Your organization has set the default session timeout to On system lock." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On browser restart." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On browser restart" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/browser/src/_locales/sr/messages.json b/apps/browser/src/_locales/sr/messages.json index c04c113caca..fa3e918bc01 100644 --- a/apps/browser/src/_locales/sr/messages.json +++ b/apps/browser/src/_locales/sr/messages.json @@ -436,8 +436,8 @@ "sync": { "message": "Синхронизација" }, - "syncVaultNow": { - "message": "Одмах синхронизуј сеф" + "syncNow": { + "message": "Синхронизуј сада" }, "lastSync": { "message": "Задња синронизација:" @@ -455,9 +455,6 @@ "bitWebVaultApp": { "message": "Bitwarden веб апликација" }, - "importItems": { - "message": "Увоз ставки" - }, "select": { "message": "Изабери" }, @@ -586,7 +583,7 @@ "message": "Архивиране ставке су искључене из општих резултата претраге и предлога за ауто попуњавање. Јесте ли сигурни да желите да архивирате ову ставку?" }, "upgradeToUseArchive": { - "message": "A premium membership is required to use Archive." + "message": "Премијум чланство је неопходно за употребу Архиве." }, "edit": { "message": "Уреди" @@ -598,10 +595,10 @@ "message": "Прегледај све" }, "showAll": { - "message": "Show all" + "message": "Прикажи све" }, "viewLess": { - "message": "View less" + "message": "Прикажи мање" }, "viewLogin": { "message": "Преглед пријаве" @@ -806,10 +803,10 @@ "message": "На закључавање система" }, "onIdle": { - "message": "On system idle" + "message": "На мировање система" }, "onSleep": { - "message": "On system sleep" + "message": "Након спавања система" }, "onRestart": { "message": "На покретање прегледача" @@ -1325,8 +1322,11 @@ "exportFrom": { "message": "Извоз од" }, - "exportVault": { - "message": "Извоз сефа" + "export": { + "message": "Извези" + }, + "import": { + "message": "Увоз" }, "fileFormat": { "message": "Формат датотеке" @@ -1407,25 +1407,25 @@ "message": "Сазнај више" }, "migrationsFailed": { - "message": "An error occurred updating the encryption settings." + "message": "Дошло је до грешке при ажурирању подешавања шифровања." }, "updateEncryptionSettingsTitle": { - "message": "Update your encryption settings" + "message": "Ажурирај своје поставке за шифровање" }, "updateEncryptionSettingsDesc": { - "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + "message": "Нова препоручена подешавања шифрирања побољшаће вашу сигурност налога. Унесите своју главну лозинку за ажурирање." }, "confirmIdentityToContinue": { - "message": "Confirm your identity to continue" + "message": "Да бисте наставили потврдите ваш идентитет" }, "enterYourMasterPassword": { - "message": "Enter your master password" + "message": "Унети вашу главну лозинку" }, "updateSettings": { - "message": "Update settings" + "message": "Ажурирај подешавања" }, "later": { - "message": "Later" + "message": "Касније" }, "authenticatorKeyTotp": { "message": "Једнократни код" @@ -1457,6 +1457,15 @@ "attachmentSaved": { "message": "Прилог је сачуван." }, + "fixEncryption": { + "message": "Поправи шифровање" + }, + "fixEncryptionTooltip": { + "message": "Ова датотека користи застарели метод шифровања." + }, + "attachmentUpdated": { + "message": "Прилог је ажуриран" + }, "file": { "message": "Датотека" }, @@ -1466,6 +1475,9 @@ "selectFile": { "message": "Изабери датотеку." }, + "itemsTransferred": { + "message": "Пренете ставке" + }, "maxFileSize": { "message": "Максимална величина је 500МБ." }, @@ -1497,7 +1509,7 @@ "message": "1ГБ шифровано складиште за прилоге." }, "premiumSignUpStorageV2": { - "message": "$SIZE$ encrypted storage for file attachments.", + "message": "$SIZE$ шифровано складиште за прилоге.", "placeholders": { "size": { "content": "$1", @@ -1904,7 +1916,7 @@ "message": "Година истека" }, "monthly": { - "message": "month" + "message": "месец" }, "expiration": { "message": "Истек" @@ -3240,9 +3252,6 @@ "copyCustomFieldNameNotUnique": { "message": "Није пронађен ниједан јединствени идентификатор." }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "Главна лозинка више није потребна за чланове следеће организације. Молимо потврдите домен са администратором организације." - }, "organizationName": { "message": "Назив организације" }, @@ -4206,10 +4215,6 @@ "ignore": { "message": "Игнориши" }, - "importData": { - "message": "Увези податке", - "description": "Used for the header of the import dialog, the import button and within the file-password-prompt" - }, "importError": { "message": "Грешка при увозу" }, @@ -4796,6 +4801,15 @@ "accountSecurity": { "message": "Безбедност налога" }, + "phishingBlocker": { + "message": "Phishing Blocker" + }, + "enablePhishingDetection": { + "message": "Phishing detection" + }, + "enablePhishingDetectionDesc": { + "message": "Display warning before accessing suspected phishing sites" + }, "notifications": { "message": "Обавештења" }, @@ -4942,7 +4956,7 @@ "message": "Премијум" }, "unlockFeaturesWithPremium": { - "message": "Unlock reporting, emergency access, and more security features with Premium." + "message": "Откључајте извештавање, приступ хитним случајевима и више безбедносних функција уз Премиум." }, "freeOrgsCannotUseAttachments": { "message": "Бесплатне организације не могу да користе прилоге" @@ -5848,26 +5862,26 @@ "andMoreFeatures": { "message": "И још више!" }, - "planDescPremium": { - "message": "Потпуна онлајн безбедност" + "advancedOnlineSecurity": { + "message": "Напредна онлајн безбедност" }, "upgradeToPremium": { "message": "Надоградите на Premium" }, "unlockAdvancedSecurity": { - "message": "Unlock advanced security features" + "message": "Откључајте напредне безбедносне функције" }, "unlockAdvancedSecurityDesc": { - "message": "A Premium subscription gives you more tools to stay secure and in control" + "message": "Премиум претплата вам даје више алата да останете сигурни и под контролом" }, "explorePremium": { - "message": "Explore Premium" + "message": "Прегледати Премијум" }, "loadingVault": { - "message": "Loading vault" + "message": "Учитавање сефа" }, "vaultLoaded": { - "message": "Vault loaded" + "message": "Сеф учитан" }, "settingDisabledByPolicy": { "message": "Ово подешавање је онемогућено смерницама ваше организације.", @@ -5879,7 +5893,141 @@ "cardNumberLabel": { "message": "Број картице" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Ваша организација више не користи главне лозинке за пријаву на Bitwarden. Да бисте наставили, верификујте организацију и домен." + }, + "continueWithLogIn": { + "message": "Наставити са пријавом" + }, + "doNotContinue": { + "message": "Не настави" + }, + "domain": { + "message": "Домен" + }, + "keyConnectorDomainTooltip": { + "message": "Овај домен ће чувати кључеве за шифровање вашег налога, па се уверите да му верујете. Ако нисте сигурни, проверите код свог администратора." + }, + "verifyYourOrganization": { + "message": "Верификујте своју организацију да бисте се пријавили" + }, + "organizationVerified": { + "message": "Организација верификована" + }, + "domainVerified": { + "message": "Домен верификован" + }, + "leaveOrganizationContent": { + "message": "Ако не верификујете своју организацију, ваш приступ организацији ће бити опозван." + }, + "leaveNow": { + "message": "Напусти сада" + }, + "verifyYourDomainToLogin": { + "message": "Верификујте домен да бисте се пријавили" + }, + "verifyYourDomainDescription": { + "message": "Да бисте наставили са пријављивањем, верификујте овај домен." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "Да бисте наставили са пријављивањем, верификујте организацију и домен." + }, "sessionTimeoutSettingsAction": { - "message": "Timeout action" + "message": "Акција тајмаута" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "Овим подешавањем управља ваша организација." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Ваша организација је подесила максимално временско ограничење сесије на $HOURS$ сати и $MINUTES$ минута.", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToImmediately": { + "message": "Ваша организација је поставила подразумевано временско ограничење сесије на Одмах." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Ваша организација је поставила подразумевано временско ограничење сесије на Блокирање система." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Ваша организација је поставила подразумевано временско ограничење сесије на При рестартовању прегледача." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Максимално временско ограничење не може да пређе $HOURS$ сат(а) и $MINUTES$ минут(а)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "На покретање прегледача" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Подесите метод откључавања да бисте променили радњу временског ограничења" + }, + "upgrade": { + "message": "Надогради" + }, + "leaveConfirmationDialogTitle": { + "message": "Да ли сте сигурни да желите да напустите?" + }, + "leaveConfirmationDialogContentOne": { + "message": "Ако одбијете, ваши лични предмети ће остати на вашем налогу, али ћете изгубити приступ дељеним ставкама и организационим функцијама." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Контактирајте свог администратора да бисте поново добили приступ." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Напустити $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "Како да управљам својим сефом?" + }, + "transferItemsToOrganizationTitle": { + "message": "Премести ставке у $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ захтева да све ставке буду у власништву организације ради безбедности и усклађености. Кликните на прихвати да бисте пренели власништво над својим ставкама.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Прихвати трансфер" + }, + "declineAndLeave": { + "message": "Одбиј и напусти" + }, + "whyAmISeeingThis": { + "message": "Зашто видите ово?" } } diff --git a/apps/browser/src/_locales/sv/messages.json b/apps/browser/src/_locales/sv/messages.json index a6cf12541fd..bb1e65f82fa 100644 --- a/apps/browser/src/_locales/sv/messages.json +++ b/apps/browser/src/_locales/sv/messages.json @@ -436,8 +436,8 @@ "sync": { "message": "Synkronisera" }, - "syncVaultNow": { - "message": "Synkronisera valv nu" + "syncNow": { + "message": "Synkronisera nu" }, "lastSync": { "message": "Senaste synkronisering:" @@ -455,9 +455,6 @@ "bitWebVaultApp": { "message": "Bitwarden webbapp" }, - "importItems": { - "message": "Importera objekt" - }, "select": { "message": "Välj" }, @@ -1325,8 +1322,11 @@ "exportFrom": { "message": "Exportera från" }, - "exportVault": { - "message": "Exportera valv" + "export": { + "message": "Exportera" + }, + "import": { + "message": "Importera" }, "fileFormat": { "message": "Filformat" @@ -1457,6 +1457,15 @@ "attachmentSaved": { "message": "Bilaga sparad" }, + "fixEncryption": { + "message": "Fixa kryptering" + }, + "fixEncryptionTooltip": { + "message": "Denna fil använder en föråldrad krypteringsmetod." + }, + "attachmentUpdated": { + "message": "Bilaga uppdaterad" + }, "file": { "message": "Fil" }, @@ -1466,6 +1475,9 @@ "selectFile": { "message": "Välj en fil" }, + "itemsTransferred": { + "message": "Objekt överförda" + }, "maxFileSize": { "message": "Filen får vara maximalt 500 MB." }, @@ -3240,9 +3252,6 @@ "copyCustomFieldNameNotUnique": { "message": "Ingen unik identifierare hittades." }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "Ett huvudlösenord krävs inte längre för medlemmar i följande organisation. Vänligen bekräfta domänen nedan med din organisationsadministratör." - }, "organizationName": { "message": "Organisationsnamn" }, @@ -4206,10 +4215,6 @@ "ignore": { "message": "Ignorera" }, - "importData": { - "message": "Importera data", - "description": "Used for the header of the import dialog, the import button and within the file-password-prompt" - }, "importError": { "message": "Fel vid import" }, @@ -4796,6 +4801,15 @@ "accountSecurity": { "message": "Kontosäkerhet" }, + "phishingBlocker": { + "message": "Phishing Blocker" + }, + "enablePhishingDetection": { + "message": "Phishing detection" + }, + "enablePhishingDetectionDesc": { + "message": "Display warning before accessing suspected phishing sites" + }, "notifications": { "message": "Aviseringar" }, @@ -5848,8 +5862,8 @@ "andMoreFeatures": { "message": "och mer!" }, - "planDescPremium": { - "message": "Komplett säkerhet online" + "advancedOnlineSecurity": { + "message": "Avancerad säkerhet online" }, "upgradeToPremium": { "message": "Uppgradera till Premium" @@ -5879,7 +5893,141 @@ "cardNumberLabel": { "message": "Kortnummer" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Din organisation använder inte längre huvudlösenord för att logga in på Bitwarden. För att fortsätta, verifiera organisationen och domänen." + }, + "continueWithLogIn": { + "message": "Fortsätt med inloggning" + }, + "doNotContinue": { + "message": "Fortsätt inte" + }, + "domain": { + "message": "Domän" + }, + "keyConnectorDomainTooltip": { + "message": "Denna domän kommer att lagra dina krypteringsnycklar, så se till att du litar på den. Om du inte är säker, kontrollera med din administratör." + }, + "verifyYourOrganization": { + "message": "Verifiera din organisation för att logga in" + }, + "organizationVerified": { + "message": "Organisation verifierad" + }, + "domainVerified": { + "message": "Domän verifierad" + }, + "leaveOrganizationContent": { + "message": "Om du inte verifierar din organisation kommer din åtkomst till organisationen att återkallas." + }, + "leaveNow": { + "message": "Lämna nu" + }, + "verifyYourDomainToLogin": { + "message": "Verifiera din domän för att logga in" + }, + "verifyYourDomainDescription": { + "message": "För att fortsätta med inloggning, verifiera denna domän." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "För att fortsätta logga in, verifiera organisationen och domänen." + }, "sessionTimeoutSettingsAction": { "message": "Tidsgränsåtgärd" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "Den här inställningen hanteras av din organisation." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Din organisation har ställt in maximal sessionstidsgräns till $HOURS$ timmar och $MINUTES$ minut(er).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToImmediately": { + "message": "Din organisation har ställt in tidsgräns för standardsessionen till Omedelbart." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Din organisation har ställt in tidsgräns för standardsessionen till Vid systemlåsning." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Din organisation har ställt in tidsgräns för standardsessionen till Vid omstart av webbläsaren." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximal tidsgräns får inte överstiga $HOURS$ timmar och $MINUTES$ minut(er)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "Vid omstart av webbläsaren" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Ställ in en upplåsningsmetod för att ändra din tidsgränsåtgärd" + }, + "upgrade": { + "message": "Uppgradera" + }, + "leaveConfirmationDialogTitle": { + "message": "Är du säker på att du vill lämna?" + }, + "leaveConfirmationDialogContentOne": { + "message": "Genom att avböja kommer dina personliga objekt att stanna på ditt konto, men du kommer att förlora åtkomst till delade objekt och organisationsfunktioner." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Kontakta administratören för att återfå åtkomst." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Lämna $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "Hur hanterar jag mitt valv?" + }, + "transferItemsToOrganizationTitle": { + "message": "Överför objekt till $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ kräver att alla objekt ägs av organisationen för säkerhet och efterlevnad. Klicka på godkänn för att överföra ägarskapet för dina objekt.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Godkänn överföring" + }, + "declineAndLeave": { + "message": "Avböj och lämna" + }, + "whyAmISeeingThis": { + "message": "Varför ser jag det här?" } } diff --git a/apps/browser/src/_locales/ta/messages.json b/apps/browser/src/_locales/ta/messages.json index 934cb8e1a01..9856e53591d 100644 --- a/apps/browser/src/_locales/ta/messages.json +++ b/apps/browser/src/_locales/ta/messages.json @@ -32,7 +32,7 @@ "message": "ஒற்றை உள்நுழைவைப் பயன்படுத்தவும்" }, "yourOrganizationRequiresSingleSignOn": { - "message": "Your organization requires single sign-on." + "message": "உங்கள் நிறுவனத்திற்கு ஒற்றை உள்நுழைவு தேவை." }, "welcomeBack": { "message": "மீண்டும் வருக" @@ -436,8 +436,8 @@ "sync": { "message": "ஒத்திசை" }, - "syncVaultNow": { - "message": "இப்போது வால்ட்டை ஒத்திசை" + "syncNow": { + "message": "Sync now" }, "lastSync": { "message": "கடைசி ஒத்திசைவு:" @@ -455,9 +455,6 @@ "bitWebVaultApp": { "message": "Bitwarden வலை பயன்பாடு" }, - "importItems": { - "message": "உருப்படிகளை இறக்குமதிசெய்" - }, "select": { "message": "தேர்ந்தெடு" }, @@ -554,39 +551,39 @@ "message": "தேடலை மீட்டமை" }, "archiveNoun": { - "message": "Archive", + "message": "காப்பகம்", "description": "Noun" }, "archiveVerb": { - "message": "Archive", + "message": "காப்பகப்படுத்து", "description": "Verb" }, "unArchive": { - "message": "Unarchive" + "message": "காப்பகத்தை அகற்று" }, "itemsInArchive": { - "message": "Items in archive" + "message": "காப்பகத்தில் உள்ள உருப்படிகள்" }, "noItemsInArchive": { - "message": "No items in archive" + "message": "காப்பகத்தில் எந்த உருப்படிகளும் இல்லை" }, "noItemsInArchiveDesc": { - "message": "Archived items will appear here and will be excluded from general search results and autofill suggestions." + "message": "காப்பகப்படுத்தப்பட்ட உருப்படிகள் இங்கே தோன்றும், மேலும் அவை பொதுவான தேடல் முடிவுகள் மற்றும் தானியங்குநிரப்பு பரிந்துரைகளிலிருந்து விலக்கப்படும்." }, "itemWasSentToArchive": { - "message": "Item was sent to archive" + "message": "ஆவணம் காப்பகத்திற்கு அனுப்பப்பட்டது" }, "itemUnarchived": { - "message": "Item was unarchived" + "message": "காப்பகம் மீட்டெடுக்கப்பட்டது" }, "archiveItem": { - "message": "Archive item" + "message": "உருப்படியைக் காப்பகப்படுத்து" }, "archiveItemConfirmDesc": { - "message": "Archived items are excluded from general search results and autofill suggestions. Are you sure you want to archive this item?" + "message": "காப்பகப்படுத்தப்பட்ட உருப்படிகள் பொதுவான தேடல் முடிவுகள் மற்றும் தானியங்குநிரப்பு பரிந்துரைகளிலிருந்து விலக்கப்பட்டுள்ளன. இந்த உருப்படியை காப்பகப்படுத்த விரும்புகிறீர்களா?" }, "upgradeToUseArchive": { - "message": "A premium membership is required to use Archive." + "message": "காப்பகத்தைப் பயன்படுத்த பிரீமியம் உறுப்பினர் தேவை." }, "edit": { "message": "திருத்து" @@ -595,13 +592,13 @@ "message": "காண்" }, "viewAll": { - "message": "View all" + "message": "அனைத்தையும் காண்க" }, "showAll": { - "message": "Show all" + "message": "அனைத்தையும் காட்டு" }, "viewLess": { - "message": "View less" + "message": "குறைவாகக் காண்க" }, "viewLogin": { "message": "உள்நுழைவைக் காண்க" @@ -749,7 +746,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", @@ -806,10 +803,10 @@ "message": "சிஸ்டம் பூட்டப்பட்டவுடன்" }, "onIdle": { - "message": "On system idle" + "message": "கணினி செயலற்ற நிலையில்" }, "onSleep": { - "message": "On system sleep" + "message": "கணினி உறக்கநிலையில்" }, "onRestart": { "message": "உலாவி மறுதொடக்கம் செய்யப்பட்டவுடன்" @@ -1050,10 +1047,10 @@ "message": "உருப்படி சேமிக்கப்பட்டது" }, "savedWebsite": { - "message": "Saved website" + "message": "சேமிக்கப்பட்ட வலைத்தளம்" }, "savedWebsites": { - "message": "Saved websites ( $COUNT$ )", + "message": "சேமிக்கப்பட்ட வலைத்தளங்கள் ( $COUNT$ )", "placeholders": { "count": { "content": "$1", @@ -1325,8 +1322,11 @@ "exportFrom": { "message": "இதிலிருந்து ஏற்றுமதிசெய்" }, - "exportVault": { - "message": "வால்ட்டை ஏற்றுமதிசெய்" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "கோப்பு வடிவம்" @@ -1407,25 +1407,25 @@ "message": "மேலும் அறிக" }, "migrationsFailed": { - "message": "An error occurred updating the encryption settings." + "message": "குறியாக்க அமைப்புகளைப் புதுப்பிக்கும்போது பிழை ஏற்பட்டது." }, "updateEncryptionSettingsTitle": { - "message": "Update your encryption settings" + "message": "உங்கள் குறியாக்க அமைப்புகளைப் புதுப்பிக்கவும்" }, "updateEncryptionSettingsDesc": { - "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + "message": "பரிந்துரைக்கப்பட்ட புதிய குறியாக்க அமைப்புகள் உங்கள் கணக்கு பாதுகாப்பை மேம்படுத்தும். இப்போதே புதுப்பிக்க உங்கள் முதன்மை கடவுச்சொல்லை உள்ளிடவும்." }, "confirmIdentityToContinue": { - "message": "Confirm your identity to continue" + "message": "தொடர உங்கள் அடையாளத்தை உறுதிப்படுத்தவும்" }, "enterYourMasterPassword": { - "message": "Enter your master password" + "message": "உங்கள் முதன்மை கடவுச்சொல்லை உள்ளிடவும்" }, "updateSettings": { - "message": "Update settings" + "message": "அமைப்புகளைப் புதுப்பிக்கவும்" }, "later": { - "message": "Later" + "message": "பின்னர்" }, "authenticatorKeyTotp": { "message": "அங்கீகரிப்பு விசை (TOTP)" @@ -1457,6 +1457,15 @@ "attachmentSaved": { "message": "இணைப்பு சேமிக்கப்பட்டது" }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "file": { "message": "கோப்பு" }, @@ -1466,6 +1475,9 @@ "selectFile": { "message": "ஒரு கோப்பைத் தேர்ந்தெடுக்கவும்" }, + "itemsTransferred": { + "message": "Items transferred" + }, "maxFileSize": { "message": "அதிகபட்ச கோப்பு அளவு 500 MB ஆகும்." }, @@ -1497,7 +1509,7 @@ "message": "கோப்பு இணைப்புகளுக்கு 1 GB என்க்ரிப்ட் செய்யப்பட்ட ஸ்டோரேஜ்." }, "premiumSignUpStorageV2": { - "message": "$SIZE$ encrypted storage for file attachments.", + "message": "கோப்பு இணைப்புகளுக்கான $SIZE$ மறைகுறியாக்கப்பட்ட சேமிப்பிடம்.", "placeholders": { "size": { "content": "$1", @@ -1606,13 +1618,13 @@ "message": "பாதுகாப்பு விசையைப் படி" }, "readingPasskeyLoading": { - "message": "Reading passkey..." + "message": "கடவுச்சொற்களைப் படிக்கிறது..." }, "passkeyAuthenticationFailed": { - "message": "Passkey authentication failed" + "message": "கடவுச்சொல் அங்கீகாரம் தோல்வியடைந்தது" }, "useADifferentLogInMethod": { - "message": "Use a different log in method" + "message": "வேறு உள்நுழைவு முறையைப் பயன்படுத்தவும்" }, "awaitingSecurityKeyInteraction": { "message": "பாதுகாப்பு விசை தொடர்புகொள்ளக் காத்திருக்கிறது..." @@ -1681,7 +1693,7 @@ "message": "நீங்கள் பேஸ் சர்வர் URL-ஐ அல்லது குறைந்தது ஒரு தனிப்பயன் சூழலைச் சேர்க்க வேண்டும்." }, "selfHostedEnvMustUseHttps": { - "message": "URLs must use HTTPS." + "message": "URLகள் HTTPS ஐப் பயன்படுத்த வேண்டும்." }, "customEnvironment": { "message": "தனிப்பயன் சூழல்" @@ -1737,28 +1749,28 @@ "message": "ஆட்டோஃபில்லை முடக்கு" }, "confirmAutofill": { - "message": "Confirm autofill" + "message": "தானியங்கு நிரப்புதலை உறுதிப்படுத்தவும்" }, "confirmAutofillDesc": { - "message": "This site doesn't match your saved login details. Before you fill in your login credentials, make sure it's a trusted site." + "message": "இந்த தளம் உங்கள் சேமிக்கப்பட்ட உள்நுழைவு விவரங்களுடன் பொருந்தவில்லை. உங்கள் உள்நுழைவு சான்றுகளை நிரப்புவதற்கு முன், அது நம்பகமான தளம்தானா என்பதை உறுதிப்படுத்திக் கொள்ளுங்கள்." }, "showInlineMenuLabel": { "message": "படிவப் புலங்களில் ஆட்டோஃபில் பரிந்துரைகளைக் காட்டு" }, "howDoesBitwardenProtectFromPhishing": { - "message": "How does Bitwarden protect your data from phishing?" + "message": "Bitwarden உங்கள் தரவை ஃபிஷிங்கிலிருந்து எவ்வாறு பாதுகாக்கிறது?" }, "currentWebsite": { - "message": "Current website" + "message": "தற்போதைய வலைத்தளம்" }, "autofillAndAddWebsite": { - "message": "Autofill and add this website" + "message": "இந்த வலைத்தளத்தைத் தானாக நிரப்பிச் சேர்க்கவும்" }, "autofillWithoutAdding": { - "message": "Autofill without adding" + "message": "சேர்க்காமல் தானாக நிரப்பு" }, "doNotAutofill": { - "message": "Do not autofill" + "message": "தானாக நிரப்ப வேண்டாம்" }, "showInlineMenuIdentitiesLabel": { "message": "பரிந்துரைகளாக அடையாளங்களைக் காட்டு" @@ -1886,7 +1898,7 @@ "message": "உங்கள் சரிபார்ப்புக் குறியீட்டிற்கான உங்கள் மின்னஞ்சலைச் சரிபார்க்க, பாப்அப் சாளரத்திற்கு வெளியே கிளிக் செய்வதால், இந்த பாப்அப் மூடப்படும். இந்த பாப்அப் மூடாமல் இருக்க, புதிய சாளரத்தில் திறக்க விரும்புகிறீர்களா?" }, "showIconsChangePasswordUrls": { - "message": "Show website icons and retrieve change password URLs" + "message": "வலைத்தள ஐகான்களைக் காண்பி, கடவுச்சொல் மாற்று URLகளை மீட்டெடுக்கவும்" }, "cardholderName": { "message": "அட்டைதாரர் பெயர்" @@ -1904,7 +1916,7 @@ "message": "காலாவதி ஆண்டு" }, "monthly": { - "message": "month" + "message": "மாதம்" }, "expiration": { "message": "காலாவதி" @@ -2054,79 +2066,79 @@ "message": "குறிப்பு" }, "newItemHeaderLogin": { - "message": "New Login", + "message": "புதிய உள்நுழைவு", "description": "Header for new login item type" }, "newItemHeaderCard": { - "message": "New Card", + "message": "புதிய அட்டை", "description": "Header for new card item type" }, "newItemHeaderIdentity": { - "message": "New Identity", + "message": "புதிய அடையாளம்", "description": "Header for new identity item type" }, "newItemHeaderNote": { - "message": "New Note", + "message": "புதிய குறிப்பு", "description": "Header for new note item type" }, "newItemHeaderSshKey": { - "message": "New SSH key", + "message": "புதிய SSH விசை", "description": "Header for new SSH key item type" }, "newItemHeaderTextSend": { - "message": "New Text Send", + "message": "புதிய உரை அனுப்புதல்", "description": "Header for new text send" }, "newItemHeaderFileSend": { - "message": "New File Send", + "message": "புதிய கோப்பு அனுப்புதல்", "description": "Header for new file send" }, "editItemHeaderLogin": { - "message": "Edit Login", + "message": "உள்நுழைவைத் திருத்து", "description": "Header for edit login item type" }, "editItemHeaderCard": { - "message": "Edit Card", + "message": "கார்டைத் திருத்து", "description": "Header for edit card item type" }, "editItemHeaderIdentity": { - "message": "Edit Identity", + "message": "அடையாளத்தைத் திருத்து", "description": "Header for edit identity item type" }, "editItemHeaderNote": { - "message": "Edit Note", + "message": "குறிப்பைத் திருத்து", "description": "Header for edit note item type" }, "editItemHeaderSshKey": { - "message": "Edit SSH key", + "message": "SSH விசையைத் திருத்து", "description": "Header for edit SSH key item type" }, "editItemHeaderTextSend": { - "message": "Edit Text Send", + "message": "உரை அனுப்புதலைத் திருத்து", "description": "Header for edit text send" }, "editItemHeaderFileSend": { - "message": "Edit File Send", + "message": "கோப்பைத் திருத்து அனுப்பு", "description": "Header for edit file send" }, "viewItemHeaderLogin": { - "message": "View Login", + "message": "உள்நுழைவைக் காண்க", "description": "Header for view login item type" }, "viewItemHeaderCard": { - "message": "View Card", + "message": "கார்டைப் பார்க்கவும்", "description": "Header for view card item type" }, "viewItemHeaderIdentity": { - "message": "View Identity", + "message": "அடையாளத்தைக் காண்க", "description": "Header for view identity item type" }, "viewItemHeaderNote": { - "message": "View Note", + "message": "குறிப்பைக் காண்க", "description": "Header for view note item type" }, "viewItemHeaderSshKey": { - "message": "View SSH key", + "message": "SSH விசையைக் காண்க", "description": "Header for view SSH key item type" }, "passwordHistory": { @@ -2476,7 +2488,7 @@ } }, "topLayerHijackWarning": { - "message": "This page is interfering with the Bitwarden experience. The Bitwarden inline menu has been temporarily disabled as a safety measure." + "message": "இந்தப் பக்கம் பிட்வார்டன் அனுபவத்தில் குறுக்கிடுகிறது. பாதுகாப்பு நடவடிக்கையாக பிட்வார்டன் இன்லைன் மெனு தற்காலிகமாக முடக்கப்பட்டுள்ளது." }, "setMasterPassword": { "message": "முதன்மை கடவுச்சொல்லை அமை" @@ -3240,9 +3252,6 @@ "copyCustomFieldNameNotUnique": { "message": "தனிப்பட்ட அடையாளங்காட்டி எதுவும் இல்லை." }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "பின்வரும் நிறுவனத்தின் உறுப்பினர்களுக்கு இனி ஒரு முதன்மை கடவுச்சொல் தேவையில்லை. உங்கள் நிறுவன நிர்வாகியுடன் கீழே உள்ள டொமைனை உறுதிப்படுத்தவும்." - }, "organizationName": { "message": "நிறுவன பெயர்" }, @@ -3304,7 +3313,7 @@ } }, "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { - "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "message": "$ORGANIZATION$ உடன் தொடர்புடைய நிறுவன பெட்டகம் மட்டுமே ஏற்றுமதி செய்யப்படும்.", "placeholders": { "organization": { "content": "$1", @@ -3313,7 +3322,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", @@ -3328,7 +3337,7 @@ "message": "குறியாக்கம் நீக்கப் பிழை" }, "errorGettingAutoFillData": { - "message": "Error getting autofill data" + "message": "தானியங்குநிரப்பு தரவைப் பெறுவதில் பிழை" }, "couldNotDecryptVaultItemsBelow": { "message": "Bitwarden கீழே பட்டியலிடப்பட்ட பெட்டக பொருளை குறியாக்கம் நீக்க முடியவில்லை." @@ -4102,13 +4111,13 @@ "description": "Toast message for informing the user that autofill on page load has been set to the default setting." }, "cannotAutofill": { - "message": "Cannot autofill" + "message": "தானாக நிரப்ப முடியாது" }, "cannotAutofillExactMatch": { - "message": "Default matching is set to 'Exact Match'. The current website does not exactly match the saved login details for this item." + "message": "இயல்புநிலை பொருத்தம் 'சரியான பொருத்தம்' என அமைக்கப்பட்டுள்ளது. தற்போதைய வலைத்தளம் இந்த உருப்படிக்கான சேமிக்கப்பட்ட உள்நுழைவு விவரங்களுடன் சரியாகப் பொருந்தவில்லை." }, "okay": { - "message": "Okay" + "message": "சரி" }, "toggleSideNavigation": { "message": "பக்க வழிசெலுத்தலை மாற்று" @@ -4206,10 +4215,6 @@ "ignore": { "message": "புறக்கணி" }, - "importData": { - "message": "தரவை இறக்குமதி செய்", - "description": "Used for the header of the import dialog, the import button and within the file-password-prompt" - }, "importError": { "message": "இறக்குமதி பிழை" }, @@ -4796,6 +4801,15 @@ "accountSecurity": { "message": "கணக்கு பாதுகாப்பு" }, + "phishingBlocker": { + "message": "Phishing Blocker" + }, + "enablePhishingDetection": { + "message": "Phishing detection" + }, + "enablePhishingDetectionDesc": { + "message": "Display warning before accessing suspected phishing sites" + }, "notifications": { "message": "அறிவிப்புகள்" }, @@ -4942,7 +4956,7 @@ "message": "பிரீமியம்" }, "unlockFeaturesWithPremium": { - "message": "Unlock reporting, emergency access, and more security features with Premium." + "message": "Premium மூலம் அறிக்கையிடல், அவசரகால அணுகல் மற்றும் கூடுதல் பாதுகாப்பு அம்சங்களைத் திறக்கவும்." }, "freeOrgsCannotUseAttachments": { "message": "இலவச நிறுவனங்கள் இணைப்புகளைப் பயன்படுத்த முடியாது" @@ -5029,7 +5043,7 @@ } }, "defaultLabelWithValue": { - "message": "Default ( $VALUE$ )", + "message": "இயல்புநிலை ($VALUE$ )", "description": "A label that indicates the default value for a field with the current default value in parentheses.", "placeholders": { "value": { @@ -5692,30 +5706,30 @@ "message": "உங்கள் சேமிப்பு பெட்டகத்திற்கு நல்வரவு!" }, "phishingPageTitleV2": { - "message": "Phishing attempt detected" + "message": "ஃபிஷிங் முயற்சி கண்டறியப்பட்டது" }, "phishingPageSummary": { - "message": "The site you are attempting to visit is a known malicious site and a security risk." + "message": "நீங்கள் பார்வையிட முயற்சிக்கும் தளம் ஒரு அறியப்பட்ட தீங்கிழைக்கும் தளம் மற்றும் பாதுகாப்பு அபாயத்தைக் கொண்டுள்ளது." }, "phishingPageCloseTabV2": { - "message": "Close this tab" + "message": "இந்த தாவலை மூடு" }, "phishingPageContinueV2": { - "message": "Continue to this site (not recommended)" + "message": "இந்த தளத்திற்குத் தொடரவும் (பரிந்துரைக்கப்படவில்லை)" }, "phishingPageExplanation1": { - "message": "This site was found in ", + "message": "இந்த தளம் காணப்பட்ட இடம் ", "description": "This is in multiple parts to allow for bold text in the middle of the sentence. A proper name follows this." }, "phishingPageExplanation2": { - "message": ", an open-source list of known phishing sites used for stealing personal and sensitive information.", + "message": ", தனிப்பட்ட மற்றும் முக்கியமான தகவல்களைத் திருடப் பயன்படுத்தப்படும் அறியப்பட்ட ஃபிஷிங் தளங்களின் திறந்த மூல பட்டியல்.", "description": "This is in multiple parts to allow for bold text in the middle of the sentence. A proper name precedes this." }, "phishingPageLearnMore": { - "message": "Learn more about phishing detection" + "message": "ஃபிஷிங் கண்டறிதல் பற்றி மேலும் அறிக" }, "protectedBy": { - "message": "Protected by $PRODUCT$", + "message": "$PRODUCT$ ஆல் பாதுகாக்கப்பட்டது", "placeholders": { "product": { "content": "$1", @@ -5799,10 +5813,10 @@ "description": "Aria label for the body content of the generator nudge" }, "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": "உங்கள் அனுபவத்தை மேம்படுத்த எந்த ஐகான் அல்லது கடவுச்சொல்லை மாற்ற URL ஐப் பயன்படுத்த வேண்டும் என்பதை அடையாளம் காண பிட்வார்டன் சேமிக்கப்பட்ட உள்நுழைவு URIகளைப் பயன்படுத்தும். நீங்கள் இந்த சேவையைப் பயன்படுத்தும்போது எந்த தகவலும் சேகரிக்கப்படாது அல்லது சேமிக்கப்படாது." }, "noPermissionsViewPage": { "message": "இந்த பக்கத்தைக் காண உங்களுக்கு அனுமதிகள் இல்லை. வேறு கணக்குடன் உள்நுழைய முயற்சிக்கவும்." @@ -5828,58 +5842,192 @@ "message": "Key Connector டொமைனை உறுதிப்படுத்து" }, "atRiskLoginsSecured": { - "message": "Great job securing your at-risk logins!" + "message": "உங்கள் ஆபத்தில் உள்ள உள்நுழைவுகளைப் பாதுகாப்பது மிகச் சிறந்த வேலை!" }, "upgradeNow": { - "message": "Upgrade now" + "message": "இப்போதே மேம்படுத்து" }, "builtInAuthenticator": { - "message": "Built-in authenticator" + "message": "உள்ளமைக்கப்பட்ட அங்கீகரிப்பான்" }, "secureFileStorage": { - "message": "Secure file storage" + "message": "பாதுகாப்பான கோப்பு சேமிப்பு" }, "emergencyAccess": { - "message": "Emergency access" + "message": "அவசர அணுகல்" }, "breachMonitoring": { - "message": "Breach monitoring" + "message": "மீறல் கண்காணிப்பு" }, "andMoreFeatures": { - "message": "And more!" + "message": "இன்னமும் அதிகமாக!" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "மேம்பட்ட ஆன்லைன் பாதுகாப்பு" }, "upgradeToPremium": { - "message": "Upgrade to Premium" + "message": "பிரீமியத்திற்கு மேம்படுத்து" }, "unlockAdvancedSecurity": { - "message": "Unlock advanced security features" + "message": "மேம்பட்ட பாதுகாப்பு அம்சங்களைத் திறக்கவும்" }, "unlockAdvancedSecurityDesc": { - "message": "A Premium subscription gives you more tools to stay secure and in control" + "message": "பிரீமியம் சந்தா உங்களுக்குப் பாதுகாப்பாகவும் கட்டுப்பாட்டிலும் இருக்க கூடுதல் கருவிகளை வழங்குகிறது" }, "explorePremium": { - "message": "Explore Premium" + "message": "பிரீமியத்தை ஆராயுங்கள்" }, "loadingVault": { - "message": "Loading vault" + "message": "பெட்டகத்தை ஏற்றுகிறது" }, "vaultLoaded": { - "message": "Vault loaded" + "message": "பெட்டகம் ஏற்றப்பட்டது" }, "settingDisabledByPolicy": { - "message": "This setting is disabled by your organization's policy.", + "message": "இந்த அமைப்பு உங்கள் நிறுவனத்தின் கொள்கையால் முடக்கப்பட்டுள்ளது.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." }, "zipPostalCodeLabel": { - "message": "ZIP / Postal code" + "message": "அஞ்சல் குறியீடு" }, "cardNumberLabel": { - "message": "Card number" + "message": "அட்டை எண்" + }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." }, "sessionTimeoutSettingsAction": { - "message": "Timeout action" + "message": "காலாவதி நடவடிக்கை" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "இந்த அமைப்பை உங்கள் நிறுவனம் நிர்வகிக்கிறது." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "உங்கள் நிறுவனம் அதிகபட்ச அமர்வு நேர முடிவை $HOURS$ மணிநேரம்(கள்) மற்றும் $MINUTES$ நிமிடம்(கள்) என அமைத்துள்ளது.", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToImmediately": { + "message": "உங்கள் நிறுவனம் இயல்புநிலை அமர்வு நேர முடிவை உடனடியாக அமைத்துள்ளது." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "உங்கள் நிறுவனம் இயல்புநிலை அமர்வு நேர முடிவை கணினி பூட்டிற்கு அமைத்துள்ளது." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "உங்கள் நிறுவனம் இயல்புநிலை அமர்வு நேர முடிவை உலாவி மறுதொடக்கம் ஆன் என அமைத்துள்ளது." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "அதிகபட்ச நேர முடிவு $HOURS$ மணிநேரம்(கள்) மற்றும் $MINUTES$ நிமிடம்(கள்) ஐ விட அதிகமாக இருக்கக்கூடாது", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "உலாவியை மறுதொடக்கம் செய்யும்போது" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "உங்கள் காலாவதி செயலை மாற்ற ஒரு திறத்தல் முறையை அமைக்கவும்" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/browser/src/_locales/te/messages.json b/apps/browser/src/_locales/te/messages.json index 684a04d9175..02945c6ff9b 100644 --- a/apps/browser/src/_locales/te/messages.json +++ b/apps/browser/src/_locales/te/messages.json @@ -436,8 +436,8 @@ "sync": { "message": "Sync" }, - "syncVaultNow": { - "message": "Sync vault now" + "syncNow": { + "message": "Sync now" }, "lastSync": { "message": "Last sync:" @@ -455,9 +455,6 @@ "bitWebVaultApp": { "message": "Bitwarden web app" }, - "importItems": { - "message": "Import items" - }, "select": { "message": "Select" }, @@ -1325,8 +1322,11 @@ "exportFrom": { "message": "Export from" }, - "exportVault": { - "message": "Export vault" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "File format" @@ -1457,6 +1457,15 @@ "attachmentSaved": { "message": "Attachment saved" }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "file": { "message": "File" }, @@ -1466,6 +1475,9 @@ "selectFile": { "message": "Select a file" }, + "itemsTransferred": { + "message": "Items transferred" + }, "maxFileSize": { "message": "Maximum file size is 500 MB." }, @@ -3240,9 +3252,6 @@ "copyCustomFieldNameNotUnique": { "message": "No unique identifier found." }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "organizationName": { "message": "Organization name" }, @@ -4206,10 +4215,6 @@ "ignore": { "message": "Ignore" }, - "importData": { - "message": "Import data", - "description": "Used for the header of the import dialog, the import button and within the file-password-prompt" - }, "importError": { "message": "Import error" }, @@ -4796,6 +4801,15 @@ "accountSecurity": { "message": "Account security" }, + "phishingBlocker": { + "message": "Phishing Blocker" + }, + "enablePhishingDetection": { + "message": "Phishing detection" + }, + "enablePhishingDetectionDesc": { + "message": "Display warning before accessing suspected phishing sites" + }, "notifications": { "message": "Notifications" }, @@ -5848,8 +5862,8 @@ "andMoreFeatures": { "message": "And more!" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "upgradeToPremium": { "message": "Upgrade to Premium" @@ -5879,7 +5893,141 @@ "cardNumberLabel": { "message": "Card number" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { "message": "Timeout action" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToImmediately": { + "message": "Your organization has set the default session timeout to Immediately." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Your organization has set the default session timeout to On system lock." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On browser restart." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On browser restart" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/browser/src/_locales/th/messages.json b/apps/browser/src/_locales/th/messages.json index bade59ad99b..a07b23793b7 100644 --- a/apps/browser/src/_locales/th/messages.json +++ b/apps/browser/src/_locales/th/messages.json @@ -1,6 +1,6 @@ { "appName": { - "message": "bitwarden" + "message": "Bitwarden" }, "appLogoLabel": { "message": "โลโก้ Bitwarden" @@ -436,8 +436,8 @@ "sync": { "message": "ซิงค์" }, - "syncVaultNow": { - "message": "Sync Vault Now" + "syncNow": { + "message": "Sync now" }, "lastSync": { "message": "Last Sync:" @@ -455,9 +455,6 @@ "bitWebVaultApp": { "message": "Bitwarden web app" }, - "importItems": { - "message": "Import Items" - }, "select": { "message": "เลือก" }, @@ -1325,8 +1322,11 @@ "exportFrom": { "message": "Export from" }, - "exportVault": { - "message": "Export Vault" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "File Format" @@ -1457,6 +1457,15 @@ "attachmentSaved": { "message": "บันทึกไฟล์แนบแล้ว" }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "file": { "message": "ไฟล์" }, @@ -1466,6 +1475,9 @@ "selectFile": { "message": "เลือกไฟล์" }, + "itemsTransferred": { + "message": "Items transferred" + }, "maxFileSize": { "message": "ขนาดไฟล์สูงสุด คือ 500 MB" }, @@ -3240,9 +3252,6 @@ "copyCustomFieldNameNotUnique": { "message": "No unique identifier found." }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "organizationName": { "message": "Organization name" }, @@ -4206,10 +4215,6 @@ "ignore": { "message": "Ignore" }, - "importData": { - "message": "Import data", - "description": "Used for the header of the import dialog, the import button and within the file-password-prompt" - }, "importError": { "message": "Import error" }, @@ -4796,6 +4801,15 @@ "accountSecurity": { "message": "ความปลอดภัยของบัญชี" }, + "phishingBlocker": { + "message": "Phishing Blocker" + }, + "enablePhishingDetection": { + "message": "Phishing detection" + }, + "enablePhishingDetectionDesc": { + "message": "Display warning before accessing suspected phishing sites" + }, "notifications": { "message": "Notifications" }, @@ -5848,8 +5862,8 @@ "andMoreFeatures": { "message": "And more!" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "upgradeToPremium": { "message": "Upgrade to Premium" @@ -5879,7 +5893,141 @@ "cardNumberLabel": { "message": "Card number" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { "message": "Timeout action" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToImmediately": { + "message": "Your organization has set the default session timeout to Immediately." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Your organization has set the default session timeout to On system lock." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On browser restart." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On browser restart" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/browser/src/_locales/tr/messages.json b/apps/browser/src/_locales/tr/messages.json index 0ba7e2ffec3..70e9a5c0afe 100644 --- a/apps/browser/src/_locales/tr/messages.json +++ b/apps/browser/src/_locales/tr/messages.json @@ -436,8 +436,8 @@ "sync": { "message": "Eşitle" }, - "syncVaultNow": { - "message": "Kasayı şimdi eşitle" + "syncNow": { + "message": "Şimdi eşitle" }, "lastSync": { "message": "Son eşitleme:" @@ -455,9 +455,6 @@ "bitWebVaultApp": { "message": "Bitwarden web uygulaması" }, - "importItems": { - "message": "Hesapları içe aktar" - }, "select": { "message": "Seç" }, @@ -1325,8 +1322,11 @@ "exportFrom": { "message": "Dışa aktarılacak konum" }, - "exportVault": { - "message": "Kasayı dışa aktar" + "export": { + "message": "Dışa aktar" + }, + "import": { + "message": "İçe aktar" }, "fileFormat": { "message": "Dosya biçimi" @@ -1407,25 +1407,25 @@ "message": "Daha fazla bilgi al" }, "migrationsFailed": { - "message": "An error occurred updating the encryption settings." + "message": "Şifreleme ayarları güncellenirken bir hata oluştu." }, "updateEncryptionSettingsTitle": { - "message": "Update your encryption settings" + "message": "Şifreleme ayarlarınızı güncelleyin" }, "updateEncryptionSettingsDesc": { - "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + "message": "Önerilen yeni şifreleme ayarları hesap güvenliğinizi artıracaktır. Şimdi güncellemek için ana parolanızı girin." }, "confirmIdentityToContinue": { - "message": "Confirm your identity to continue" + "message": "Devam etmek için kimliğinizi doğrulayın" }, "enterYourMasterPassword": { - "message": "Enter your master password" + "message": "Ana parolanızı girin" }, "updateSettings": { - "message": "Update settings" + "message": "Ayarları güncelle" }, "later": { - "message": "Later" + "message": "Daha sonra" }, "authenticatorKeyTotp": { "message": "Kimlik doğrulama anahtarı (TOTP)" @@ -1457,6 +1457,15 @@ "attachmentSaved": { "message": "Dosya kaydedildi" }, + "fixEncryption": { + "message": "Şifrelemeyi düzelt" + }, + "fixEncryptionTooltip": { + "message": "Bu dosya eski bir şifreleme yöntemi kullanıyor." + }, + "attachmentUpdated": { + "message": "Ek güncellendi" + }, "file": { "message": "Dosya" }, @@ -1466,6 +1475,9 @@ "selectFile": { "message": "Bir dosya seçin" }, + "itemsTransferred": { + "message": "Kayıtlar aktarıldı" + }, "maxFileSize": { "message": "Maksimum dosya boyutu 500 MB'dir." }, @@ -3240,9 +3252,6 @@ "copyCustomFieldNameNotUnique": { "message": "Benzersiz tanımlayıcı bulunamadı." }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "Aşağıdaki organizasyonun üyeleri için artık ana parola gerekmemektedir. Lütfen alan adını organizasyon yöneticinizle doğrulayın." - }, "organizationName": { "message": "Kuruluş adı" }, @@ -4105,7 +4114,7 @@ "message": "Otomatik doldurulamıyor" }, "cannotAutofillExactMatch": { - "message": "Default matching is set to 'Exact Match'. The current website does not exactly match the saved login details for this item." + "message": "Varsayılan eşleştirme \"Tam eşleşme\" olarak ayarlı. Geçerli web sitesi, bu kayıt için kaydedilen hesap bilgileriyle tam olarak eşleşmiyor." }, "okay": { "message": "Tamam" @@ -4206,10 +4215,6 @@ "ignore": { "message": "Yok say" }, - "importData": { - "message": "Verileri içe aktar", - "description": "Used for the header of the import dialog, the import button and within the file-password-prompt" - }, "importError": { "message": "İçe aktarma hatası" }, @@ -4796,6 +4801,15 @@ "accountSecurity": { "message": "Hesap güvenliği" }, + "phishingBlocker": { + "message": "Phishing Blocker" + }, + "enablePhishingDetection": { + "message": "Phishing detection" + }, + "enablePhishingDetectionDesc": { + "message": "Display warning before accessing suspected phishing sites" + }, "notifications": { "message": "Bildirimler" }, @@ -4942,7 +4956,7 @@ "message": "Premium" }, "unlockFeaturesWithPremium": { - "message": "Unlock reporting, emergency access, and more security features with Premium." + "message": "Raporlama, acil erişim ve daha fazla güvenlik özelliğinin kilidini Premium ile açın." }, "freeOrgsCannotUseAttachments": { "message": "Ücretsiz kuruluşlar dosya eklerini kullanamaz" @@ -5704,18 +5718,18 @@ "message": "Siteye devam et (önerilmez)" }, "phishingPageExplanation1": { - "message": "This site was found in ", + "message": "Bu site şurada bulundu ", "description": "This is in multiple parts to allow for bold text in the middle of the sentence. A proper name follows this." }, "phishingPageExplanation2": { - "message": ", an open-source list of known phishing sites used for stealing personal and sensitive information.", + "message": ", kişisel ve hassas bilgileri çalmak için kullanılan bilinen oltalama sitelerini içeren açık kaynaklı bir liste.", "description": "This is in multiple parts to allow for bold text in the middle of the sentence. A proper name precedes this." }, "phishingPageLearnMore": { - "message": "Learn more about phishing detection" + "message": "Oltalama tespiti hakkında daha fazla bilgi edinin" }, "protectedBy": { - "message": "Protected by $PRODUCT$", + "message": "$PRODUCT$ tarafından korunuyor", "placeholders": { "product": { "content": "$1", @@ -5828,7 +5842,7 @@ "message": "Key Connector alan adını doğrulayın" }, "atRiskLoginsSecured": { - "message": "Great job securing your at-risk logins!" + "message": "Risk altındaki hesaplarınızı güvene alarak harika bir iş çıkardınız!" }, "upgradeNow": { "message": "Şimdi yükselt" @@ -5848,8 +5862,8 @@ "andMoreFeatures": { "message": "Ve daha fazlası!" }, - "planDescPremium": { - "message": "Eksiksiz çevrimiçi güvenlik" + "advancedOnlineSecurity": { + "message": "Gelişmiş çevrimiçi güvenlik" }, "upgradeToPremium": { "message": "Premium'a yükselt" @@ -5861,7 +5875,7 @@ "message": "Premium abonelik size daha fazla güvenlik ve kontrol olanağı sunan ek araçlara erişmenizi sağlar" }, "explorePremium": { - "message": "Explore Premium" + "message": "Premium’u keşfet" }, "loadingVault": { "message": "Kasa yükleniyor" @@ -5870,7 +5884,7 @@ "message": "Kasa yüklendi" }, "settingDisabledByPolicy": { - "message": "This setting is disabled by your organization's policy.", + "message": "Bu ayar, kuruluşunuzun ilkesi tarafından devre dışı bırakıldı.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." }, "zipPostalCodeLabel": { @@ -5879,7 +5893,141 @@ "cardNumberLabel": { "message": "Kart numarası" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { "message": "Zaman aşımı eylemi" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "Bu ayar kuruluşunuz tarafından yönetiliyor." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Kuruluşunuz maksimum kasa zaman aşımını $HOURS$ saat $MINUTES$ dakika olarak belirlemiş.", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToImmediately": { + "message": "Kuruluşunuz varsayılan oturum zaman aşımını “Hemen” olarak ayarlamış." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Kuruluşunuz varsayılan oturum zaman aşımını “Sistem kilitlenince” olarak ayarlamış." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Kuruluşunuz varsayılan oturum zaman aşımını “Tarayıcı yeniden başlatılınca” olarak ayarlamış." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maksimum zaman aşımı en fazla $HOURS$ saat $MINUTES$ dakika olabilir", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "Tarayıcı yeniden başlatılınca" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Zaman aşımı eyleminizi değiştirmek için kilit açma yönteminizi ayarlayın" + }, + "upgrade": { + "message": "Yükselt" + }, + "leaveConfirmationDialogTitle": { + "message": "Ayrılmak istediğinizden emin misiniz?" + }, + "leaveConfirmationDialogContentOne": { + "message": "Reddederseniz kişisel kayıtlarınız hesabınızda kalır, ancak paylaşılan kayıtlara ve kuruluş özelliklerine erişiminizi kaybedersiniz." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Erişiminizi yeniden kazanmak için yöneticinizle iletişime geçin." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "$ORGANIZATION$ kuruluşundan ayrıl", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "Kasamı nasıl yönetebilirim?" + }, + "transferItemsToOrganizationTitle": { + "message": "Kayıtları $ORGANIZATION$ kuruluşuna aktar", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$, güvenlik ve mevzuata uyum amacıyla tüm kayıtların kuruluşa ait olmasını zorunlu kılıyor. Kayıtlarınızın sahipliğini devretmek için \"Kabul et\"e tıklayın.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Aktarımı kabul et" + }, + "declineAndLeave": { + "message": "Reddet ve ayrıl" + }, + "whyAmISeeingThis": { + "message": "Bunu neden görüyorum?" } } diff --git a/apps/browser/src/_locales/uk/messages.json b/apps/browser/src/_locales/uk/messages.json index 147e63cce02..8d040513d67 100644 --- a/apps/browser/src/_locales/uk/messages.json +++ b/apps/browser/src/_locales/uk/messages.json @@ -436,8 +436,8 @@ "sync": { "message": "Синхронізація" }, - "syncVaultNow": { - "message": "Синхронізувати зараз" + "syncNow": { + "message": "Sync now" }, "lastSync": { "message": "Остання синхронізація:" @@ -455,9 +455,6 @@ "bitWebVaultApp": { "message": "Вебпрограма Bitwarden" }, - "importItems": { - "message": "Імпортувати записи" - }, "select": { "message": "Обрати" }, @@ -1325,8 +1322,11 @@ "exportFrom": { "message": "Експортувати з" }, - "exportVault": { - "message": "Експортувати сховище" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "Формат файлу" @@ -1457,6 +1457,15 @@ "attachmentSaved": { "message": "Вкладення збережено" }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "file": { "message": "Файл" }, @@ -1466,6 +1475,9 @@ "selectFile": { "message": "Оберіть файл" }, + "itemsTransferred": { + "message": "Items transferred" + }, "maxFileSize": { "message": "Максимальний розмір файлу 500 МБ." }, @@ -3240,9 +3252,6 @@ "copyCustomFieldNameNotUnique": { "message": "Не знайдено унікальний ідентифікатор." }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "Головний пароль більше не є обов'язковим для учасників зазначеної організації. Підтвердьте вказаний нижче домен з адміністратором вашої організації." - }, "organizationName": { "message": "Назва організації" }, @@ -4206,10 +4215,6 @@ "ignore": { "message": "Ігнорувати" }, - "importData": { - "message": "Імпортувати дані", - "description": "Used for the header of the import dialog, the import button and within the file-password-prompt" - }, "importError": { "message": "Помилка імпорту" }, @@ -4796,6 +4801,15 @@ "accountSecurity": { "message": "Безпека облікового запису" }, + "phishingBlocker": { + "message": "Phishing Blocker" + }, + "enablePhishingDetection": { + "message": "Phishing detection" + }, + "enablePhishingDetectionDesc": { + "message": "Display warning before accessing suspected phishing sites" + }, "notifications": { "message": "Сповіщення" }, @@ -5848,8 +5862,8 @@ "andMoreFeatures": { "message": "Інші можливості!" }, - "planDescPremium": { - "message": "Повна онлайн-безпека" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "upgradeToPremium": { "message": "Покращити до Premium" @@ -5879,7 +5893,141 @@ "cardNumberLabel": { "message": "Номер картки" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { "message": "Timeout action" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToImmediately": { + "message": "Your organization has set the default session timeout to Immediately." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Your organization has set the default session timeout to On system lock." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On browser restart." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On browser restart" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/browser/src/_locales/vi/messages.json b/apps/browser/src/_locales/vi/messages.json index a94a38ffb63..4049af21e06 100644 --- a/apps/browser/src/_locales/vi/messages.json +++ b/apps/browser/src/_locales/vi/messages.json @@ -436,8 +436,8 @@ "sync": { "message": "Đồng bộ" }, - "syncVaultNow": { - "message": "Đồng bộ kho lưu trữ ngay" + "syncNow": { + "message": "Sync now" }, "lastSync": { "message": "Đồng bộ lần cuối:" @@ -455,9 +455,6 @@ "bitWebVaultApp": { "message": "Ứng dụng web Bitwarden" }, - "importItems": { - "message": "Nhập vào kho" - }, "select": { "message": "Chọn" }, @@ -586,7 +583,7 @@ "message": "Các mục đã lưu trữ sẽ bị loại khỏi kết quả tìm kiếm chung và gợi ý tự động điền. Bạn có chắc chắn muốn lưu trữ mục này không?" }, "upgradeToUseArchive": { - "message": "Cần là thành viên premium để sử dụng Archive." + "message": "Cần là thành viên cao cấp để sử dụng tính năng Lưu trữ." }, "edit": { "message": "Sửa" @@ -806,7 +803,7 @@ "message": "Mỗi khi khóa máy" }, "onIdle": { - "message": "Khi hệ thống rãnh rỗi" + "message": "Khi hệ thống nhàn rỗi" }, "onSleep": { "message": "Khi hệ thống ngủ" @@ -1325,8 +1322,11 @@ "exportFrom": { "message": "Xuất từ" }, - "exportVault": { - "message": "Xuất kho" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "Định dạng tập tin" @@ -1407,25 +1407,25 @@ "message": "Tìm hiểu thêm" }, "migrationsFailed": { - "message": "An error occurred updating the encryption settings." + "message": "Đã xảy ra lỗi khi cập nhật cài đặt mã hóa." }, "updateEncryptionSettingsTitle": { - "message": "Update your encryption settings" + "message": "Cập nhật cài đặt mã hóa của bạn" }, "updateEncryptionSettingsDesc": { - "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + "message": "Cài đặt mã hóa được khuyến nghị sẽ cải thiện bảo mật cho tài khoản của bạn. Nhập mật khẩu chính để cập nhật ngay." }, "confirmIdentityToContinue": { - "message": "Confirm your identity to continue" + "message": "Xác minh danh tính để tiếp tục" }, "enterYourMasterPassword": { - "message": "Enter your master password" + "message": "Nhập mật khẩu chính của bạn" }, "updateSettings": { - "message": "Update settings" + "message": "Cập nhật cài đặt" }, "later": { - "message": "Later" + "message": "Để sau" }, "authenticatorKeyTotp": { "message": "Khóa xác thực (TOTP)" @@ -1457,6 +1457,15 @@ "attachmentSaved": { "message": "Đã lưu tệp đính kèm" }, + "fixEncryption": { + "message": "Sửa mã hóa" + }, + "fixEncryptionTooltip": { + "message": "Tệp này đang sử dụng phương pháp mã hóa lỗi thời." + }, + "attachmentUpdated": { + "message": "Tệp đính kèm đã được cập nhật" + }, "file": { "message": "Tập tin" }, @@ -1466,6 +1475,9 @@ "selectFile": { "message": "Chọn tập tin" }, + "itemsTransferred": { + "message": "Các mục đã chuyển" + }, "maxFileSize": { "message": "Kích thước tối đa của tập tin là 500MB." }, @@ -2476,7 +2488,7 @@ } }, "topLayerHijackWarning": { - "message": "Trang này đang cản trở trải nghiệm Bitwarden. Menu nội tuyến Bitwarden đã tạm thời bị vô hiệu hóa như một biện pháp an toàn." + "message": "Trang này đang làm gián đoạn trải nghiệm Bitwarden. Menu nội tuyến của Bitwarden đã tạm thời bị tắt để đảm bảo an toàn." }, "setMasterPassword": { "message": "Đặt mật khẩu chính" @@ -3240,9 +3252,6 @@ "copyCustomFieldNameNotUnique": { "message": "Không tìm thấy danh tính duy nhất." }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "Mật khẩu chính không còn được yêu cầu đối với các thành viên của tổ chức sau đây. Vui lòng xác nhận tên miền bên dưới với quản trị viên của tổ chức." - }, "organizationName": { "message": "Tên tổ chức" }, @@ -4206,10 +4215,6 @@ "ignore": { "message": "Bỏ qua" }, - "importData": { - "message": "Nhập dữ liệu", - "description": "Used for the header of the import dialog, the import button and within the file-password-prompt" - }, "importError": { "message": "Lỗi khi nhập" }, @@ -4796,6 +4801,15 @@ "accountSecurity": { "message": "Bảo mật tài khoản" }, + "phishingBlocker": { + "message": "Phishing Blocker" + }, + "enablePhishingDetection": { + "message": "Phishing detection" + }, + "enablePhishingDetectionDesc": { + "message": "Display warning before accessing suspected phishing sites" + }, "notifications": { "message": "Thông báo" }, @@ -4942,7 +4956,7 @@ "message": "Cao cấp" }, "unlockFeaturesWithPremium": { - "message": "Mở khóa tính năng báo cáo, quyền truy cập khẩn cấp và nhiều tính năng bảo mật khác với Premium." + "message": "Mở khóa tính năng báo cáo, quyền truy cập khẩn cấp và nhiều tính năng bảo mật khác với gói Cao cấp." }, "freeOrgsCannotUseAttachments": { "message": "Các tổ chức miễn phí không thể sử dụng tệp đính kèm" @@ -5848,8 +5862,8 @@ "andMoreFeatures": { "message": "Và nhiều hơn nữa!" }, - "planDescPremium": { - "message": "Bảo mật trực tuyến toàn diện" + "advancedOnlineSecurity": { + "message": "Bảo mật trực tuyến nâng cao" }, "upgradeToPremium": { "message": "Nâng cấp lên gói Cao cấp" @@ -5858,10 +5872,10 @@ "message": "Mở khóa các tính năng bảo mật nâng cao" }, "unlockAdvancedSecurityDesc": { - "message": "Đăng ký Premium cung cấp cho bạn nhiều công cụ hơn để luôn an toàn và kiểm soát" + "message": "Đăng ký gói Cao cấp cung cấp nhiều công cụ để bạn luôn an toàn và kiểm soát tốt hơn" }, "explorePremium": { - "message": "Khám phá Premium" + "message": "Khám phá gói Cao cấp" }, "loadingVault": { "message": "Đang tải kho" @@ -5879,7 +5893,141 @@ "cardNumberLabel": { "message": "Số thẻ" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { "message": "Hành động sau khi đóng kho" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "Cài đặt này do tổ chức của bạn quản lý." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Tổ chức của bạn đã đặt thời gian chờ phiên tối đa là $HOURS$ giờ và $MINUTES$ phút.", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToImmediately": { + "message": "Tổ chức của bạn đã đặt thời gian chờ phiên mặc định là Ngay lập tức." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Tổ chức của bạn đã đặt thời gian chờ phiên mặc định là Mỗi khi khóa máy." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Tổ chức của bạn đã đặt thời gian chờ phiên mặc định là Mỗi khi khởi động lại trình duyệt." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Thời gian chờ tối đa không thể vượt quá $HOURS$ giờ và $MINUTES$ phút", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "Mỗi khi khởi động lại trình duyệt" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Đặt phương thức mở khóa để thay đổi hành động khi hết thời gian chờ" + }, + "upgrade": { + "message": "Nâng cấp" + }, + "leaveConfirmationDialogTitle": { + "message": "Bạn có chắc chắn muốn rời đi không?" + }, + "leaveConfirmationDialogContentOne": { + "message": "Bằng việc từ chối, các mục cá nhân sẽ vẫn nằm trong tài khoản của bạn, nhưng bạn sẽ mất quyền truy cập vào các mục được chia sẻ và tính năng tổ chức." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Liên hệ quản trị viên của bạn để lấy lại quyền truy cập." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Rời khỏi $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "Tôi quản lý kho của mình như thế nào?" + }, + "transferItemsToOrganizationTitle": { + "message": "Chuyển các mục đến $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ yêu cầu tất cả các mục phải thuộc sở hữu của tổ chức để đảm bảo an ninh và tuân thủ. Nhấp chấp nhận để chuyển quyền sở hữu các mục của bạn.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Chấp nhận chuyển" + }, + "declineAndLeave": { + "message": "Từ chối và rời đi" + }, + "whyAmISeeingThis": { + "message": "Tại sao tôi thấy điều này?" } } diff --git a/apps/browser/src/_locales/zh_CN/messages.json b/apps/browser/src/_locales/zh_CN/messages.json index fd4b6380b1b..4ebfaca7ef8 100644 --- a/apps/browser/src/_locales/zh_CN/messages.json +++ b/apps/browser/src/_locales/zh_CN/messages.json @@ -436,8 +436,8 @@ "sync": { "message": "同步" }, - "syncVaultNow": { - "message": "立即同步密码库" + "syncNow": { + "message": "立即同步" }, "lastSync": { "message": "上次同步:" @@ -455,9 +455,6 @@ "bitWebVaultApp": { "message": "Bitwarden 网页 App" }, - "importItems": { - "message": "导入项目" - }, "select": { "message": "选择" }, @@ -601,7 +598,7 @@ "message": "显示全部" }, "viewLess": { - "message": "查看更少" + "message": "显示更少" }, "viewLogin": { "message": "查看登录" @@ -991,7 +988,7 @@ "message": "文件夹已添加" }, "twoStepLoginConfirmation": { - "message": "两步登录要求您从其他设备(例如安全密钥、验证器 App、短信、电话或者电子邮件)来验证您的登录,这能使您的账户更加安全。两步登录需要在 bitwarden.com 网页版密码库中设置。现在访问此网站吗?" + "message": "两步登录要求您从其他设备(例如安全密钥、验证器 App、短信、电话或者电子邮件)来验证您的登录,这能使您的账户更加安全。两步登录需要在 bitwarden.com 网页版密码库中设置。现在要访问此网站吗?" }, "twoStepLoginConfirmationContent": { "message": "在 Bitwarden 网页 App 中设置两步登录,让您的账户更加安全。" @@ -1275,7 +1272,7 @@ "message": "询问保存新的通行密钥或使用存储在密码库中的通行密钥登录。适用于所有已登录的账户。" }, "notificationChangeDesc": { - "message": "是否要在 Bitwarden 中更新此密码?" + "message": "要在 Bitwarden 中更新此密码吗?" }, "notificationChangeSave": { "message": "更新" @@ -1325,8 +1322,11 @@ "exportFrom": { "message": "导出自" }, - "exportVault": { - "message": "导出密码库" + "export": { + "message": "导出" + }, + "import": { + "message": "导入" }, "fileFormat": { "message": "文件格式" @@ -1407,25 +1407,25 @@ "message": "进一步了解" }, "migrationsFailed": { - "message": "An error occurred updating the encryption settings." + "message": "更新加密设置时发生错误。" }, "updateEncryptionSettingsTitle": { - "message": "Update your encryption settings" + "message": "更新您的加密设置" }, "updateEncryptionSettingsDesc": { - "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + "message": "新推荐的加密设置将提高您的账户安全性。输入您的主密码以立即更新。" }, "confirmIdentityToContinue": { - "message": "Confirm your identity to continue" + "message": "确认您的身份以继续" }, "enterYourMasterPassword": { - "message": "Enter your master password" + "message": "输入您的主密码" }, "updateSettings": { - "message": "Update settings" + "message": "更新设置" }, "later": { - "message": "Later" + "message": "稍后" }, "authenticatorKeyTotp": { "message": "验证器密钥 (TOTP)" @@ -1457,6 +1457,15 @@ "attachmentSaved": { "message": "附件已保存" }, + "fixEncryption": { + "message": "修复加密" + }, + "fixEncryptionTooltip": { + "message": "此文件正在使用过时的加密方式。" + }, + "attachmentUpdated": { + "message": "附件已更新" + }, "file": { "message": "文件" }, @@ -1466,6 +1475,9 @@ "selectFile": { "message": "选择一个文件" }, + "itemsTransferred": { + "message": "项目已传输" + }, "maxFileSize": { "message": "文件最大为 500 MB。" }, @@ -1482,7 +1494,7 @@ "message": "管理会员资格" }, "premiumManageAlert": { - "message": "您可以在 bitwarden.com 网页版密码库管理您的会员资格。现在要访问吗?" + "message": "您可以在 bitwarden.com 网页版密码库管理您的会员资格。现在要访问此网站吗?" }, "premiumRefresh": { "message": "刷新会员资格" @@ -3240,9 +3252,6 @@ "copyCustomFieldNameNotUnique": { "message": "未找到唯一的标识符。" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "以下组织的成员不再需要主密码。请与您的组织管理员确认下面的域名。" - }, "organizationName": { "message": "组织名称" }, @@ -3895,10 +3904,10 @@ "message": "检查您的电子邮箱" }, "followTheLinkInTheEmailSentTo": { - "message": "点击发送到电子邮件中的链接" + "message": "点击发送到" }, "andContinueCreatingYourAccount": { - "message": "然后继续创建您的账户。" + "message": "的电子邮件中的链接,然后继续创建您的账户。" }, "noEmail": { "message": "没收到电子邮件吗?" @@ -4206,10 +4215,6 @@ "ignore": { "message": "忽略" }, - "importData": { - "message": "导入数据", - "description": "Used for the header of the import dialog, the import button and within the file-password-prompt" - }, "importError": { "message": "导入出错" }, @@ -4395,7 +4400,7 @@ "message": "此站点没有匹配的登录" }, "searchSavePasskeyNewLogin": { - "message": "搜索或将通行密钥保存为一个新的登录" + "message": "搜索或将通行密钥保存为新的登录" }, "confirm": { "message": "确认" @@ -4404,7 +4409,7 @@ "message": "保存通行密钥" }, "savePasskeyNewLogin": { - "message": "作为新的登录项目保存通行密钥" + "message": "将通行密钥保存为新的登录" }, "chooseCipherForPasskeySave": { "message": "选择一个用于保存此通行密钥的登录项目" @@ -4796,6 +4801,15 @@ "accountSecurity": { "message": "账户安全" }, + "phishingBlocker": { + "message": "Phishing Blocker" + }, + "enablePhishingDetection": { + "message": "Phishing detection" + }, + "enablePhishingDetectionDesc": { + "message": "Display warning before accessing suspected phishing sites" + }, "notifications": { "message": "通知" }, @@ -5848,8 +5862,8 @@ "andMoreFeatures": { "message": "以及更多!" }, - "planDescPremium": { - "message": "全面的在线安全防护" + "advancedOnlineSecurity": { + "message": "高级在线安全防护" }, "upgradeToPremium": { "message": "升级为高级版" @@ -5879,7 +5893,141 @@ "cardNumberLabel": { "message": "卡号" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "您的组织已不再使用主密码登录 Bitwarden。要继续,请验证组织和域名。" + }, + "continueWithLogIn": { + "message": "继续登录" + }, + "doNotContinue": { + "message": "不要继续" + }, + "domain": { + "message": "域名" + }, + "keyConnectorDomainTooltip": { + "message": "此域名将存储您的账户加密密钥,所以请确保您信任它。如果您不确定,请与您的管理员联系。" + }, + "verifyYourOrganization": { + "message": "验证您的组织以登录" + }, + "organizationVerified": { + "message": "组织已验证" + }, + "domainVerified": { + "message": "域名已验证" + }, + "leaveOrganizationContent": { + "message": "如果不验证您的组织,您对组织的访问权限将被撤销。" + }, + "leaveNow": { + "message": "立即退出" + }, + "verifyYourDomainToLogin": { + "message": "验证您的域名以登录" + }, + "verifyYourDomainDescription": { + "message": "要继续登录,请验证此域名。" + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "要继续登录,请验证组织和域名。" + }, "sessionTimeoutSettingsAction": { "message": "超时动作" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "此设置由您的组织管理。" + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "您的组织已将最大会话超时设置为 $HOURS$ 小时 $MINUTES$ 分钟。", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToImmediately": { + "message": "您的组织已将默认会话超时设置为「立即」。" + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "您的组织已将默认会话超时设置为「系统锁定时」。" + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "您的组织已将默认会话超时设置为「浏览器重启时」。" + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "最大超时不能超过 $HOURS$ 小时 $MINUTES$ 分钟", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "浏览器重启时" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "设置一个解锁方式以更改您的超时动作" + }, + "upgrade": { + "message": "升级" + }, + "leaveConfirmationDialogTitle": { + "message": "确定要退出吗?" + }, + "leaveConfirmationDialogContentOne": { + "message": "拒绝后,您的个人项目将保留在您的账户中,但您将失去对共享项目和组织功能的访问权限。" + }, + "leaveConfirmationDialogContentTwo": { + "message": "联系您的管理员以重新获取访问权限。" + }, + "leaveConfirmationDialogConfirmButton": { + "message": "退出 $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "我该如何管理我的密码库?" + }, + "transferItemsToOrganizationTitle": { + "message": "传输项目到 $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "出于安全和合规考虑,$ORGANIZATION$ 要求所有项目归组织所有。点击「接受」以传输您的项目的所有权。", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "接受传输" + }, + "declineAndLeave": { + "message": "拒绝并退出" + }, + "whyAmISeeingThis": { + "message": "为什么我会看到这个?" } } diff --git a/apps/browser/src/_locales/zh_TW/messages.json b/apps/browser/src/_locales/zh_TW/messages.json index de78e415fa6..e0b69c212b5 100644 --- a/apps/browser/src/_locales/zh_TW/messages.json +++ b/apps/browser/src/_locales/zh_TW/messages.json @@ -436,8 +436,8 @@ "sync": { "message": "同步" }, - "syncVaultNow": { - "message": "立即同步密碼庫" + "syncNow": { + "message": "Sync now" }, "lastSync": { "message": "上次同步於:" @@ -455,9 +455,6 @@ "bitWebVaultApp": { "message": "Bitwarden 網頁應用程式" }, - "importItems": { - "message": "匯入項目" - }, "select": { "message": "選擇" }, @@ -586,7 +583,7 @@ "message": "封存的項目將不會出現在一般搜尋結果或自動填入建議中。確定要封存此項目嗎?" }, "upgradeToUseArchive": { - "message": "A premium membership is required to use Archive." + "message": "需要進階版會員才能使用封存功能。" }, "edit": { "message": "編輯" @@ -598,7 +595,7 @@ "message": "檢視全部" }, "showAll": { - "message": "Show all" + "message": "顯示全部" }, "viewLess": { "message": "顯示較少" @@ -1325,8 +1322,11 @@ "exportFrom": { "message": "匯出自" }, - "exportVault": { - "message": "匯出密碼庫" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "檔案格式" @@ -1407,25 +1407,25 @@ "message": "深入了解" }, "migrationsFailed": { - "message": "An error occurred updating the encryption settings." + "message": "更新加密設定時發生錯誤。" }, "updateEncryptionSettingsTitle": { - "message": "Update your encryption settings" + "message": "更新您的加密設定" }, "updateEncryptionSettingsDesc": { - "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + "message": "新的建議加密設定將提升您的帳戶安全性。請輸入主密碼以立即更新。" }, "confirmIdentityToContinue": { - "message": "Confirm your identity to continue" + "message": "請先確認身分後再繼續" }, "enterYourMasterPassword": { - "message": "Enter your master password" + "message": "輸入您的主密碼" }, "updateSettings": { - "message": "Update settings" + "message": "更新設定" }, "later": { - "message": "Later" + "message": "以後再說" }, "authenticatorKeyTotp": { "message": "驗證器金鑰 (TOTP)" @@ -1457,6 +1457,15 @@ "attachmentSaved": { "message": "附件已儲存" }, + "fixEncryption": { + "message": "修正加密" + }, + "fixEncryptionTooltip": { + "message": "此檔案使用了過時的加密方式。" + }, + "attachmentUpdated": { + "message": "附件已更新" + }, "file": { "message": "檔案" }, @@ -1466,6 +1475,9 @@ "selectFile": { "message": "選取檔案" }, + "itemsTransferred": { + "message": "Items transferred" + }, "maxFileSize": { "message": "檔案最大為 500MB。" }, @@ -1497,7 +1509,7 @@ "message": "用於檔案附件的 1 GB 加密儲存空間。" }, "premiumSignUpStorageV2": { - "message": "$SIZE$ encrypted storage for file attachments.", + "message": "用於檔案附件的 $SIZE$ 加密儲存空間。", "placeholders": { "size": { "content": "$1", @@ -1904,7 +1916,7 @@ "message": "逾期年份" }, "monthly": { - "message": "month" + "message": "月" }, "expiration": { "message": "逾期" @@ -3240,9 +3252,6 @@ "copyCustomFieldNameNotUnique": { "message": "找不到唯一識別碼。" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "以下組織的成員已不再需要主密碼。請與你的組織管理員確認下方的網域。" - }, "organizationName": { "message": "組織名稱" }, @@ -4206,10 +4215,6 @@ "ignore": { "message": "忽略" }, - "importData": { - "message": "匯入資料", - "description": "Used for the header of the import dialog, the import button and within the file-password-prompt" - }, "importError": { "message": "匯入時發生錯誤" }, @@ -4796,6 +4801,15 @@ "accountSecurity": { "message": "帳戶安全性" }, + "phishingBlocker": { + "message": "Phishing Blocker" + }, + "enablePhishingDetection": { + "message": "Phishing detection" + }, + "enablePhishingDetectionDesc": { + "message": "Display warning before accessing suspected phishing sites" + }, "notifications": { "message": "通知" }, @@ -4942,7 +4956,7 @@ "message": "進階版" }, "unlockFeaturesWithPremium": { - "message": "Unlock reporting, emergency access, and more security features with Premium." + "message": "使用進階版解鎖報告、緊急存取及更多安全功能。" }, "freeOrgsCannotUseAttachments": { "message": "免費組織無法使用附檔" @@ -5848,17 +5862,17 @@ "andMoreFeatures": { "message": "以及其他功能功能!" }, - "planDescPremium": { - "message": "完整的線上安全" + "advancedOnlineSecurity": { + "message": "進階線上安全防護" }, "upgradeToPremium": { "message": "升級到 Premium" }, "unlockAdvancedSecurity": { - "message": "Unlock advanced security features" + "message": "解鎖進階安全功能" }, "unlockAdvancedSecurityDesc": { - "message": "A Premium subscription gives you more tools to stay secure and in control" + "message": "進階版訂閱可為您提供更多工具,協助維持安全並掌握主控權" }, "explorePremium": { "message": "探索進階版" @@ -5879,7 +5893,141 @@ "cardNumberLabel": { "message": "支付卡號碼" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { "message": "逾時後動作" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "此設定由您的組織管理。" + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "您的組織已將最長工作階段逾時設為 $HOURS$ 小時與 $MINUTES$ 分鐘。", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToImmediately": { + "message": "您的組織已將預設工作階段逾時設定為「立即」。" + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "您的組織已將預設工作階段逾時設定為「在系統鎖定時」。" + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "您的組織已將預設工作階段逾時設定為「在瀏覽器重新啟動時」。" + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "最長逾時時間不可超過 $HOURS$ 小時 $MINUTES$ 分鐘", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "於瀏覽器重新啟動時" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "設定一個解鎖方式來變更您的密碼庫逾時動作。" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } From a987494300fc2d745efc4a6c675f86a8eedb797b Mon Sep 17 00:00:00 2001 From: Alex Morask <144709477+amorask-bitwarden@users.noreply.github.com> Date: Tue, 16 Dec 2025 10:13:18 -0600 Subject: [PATCH 084/188] [PM-29607] Create `@bitwarden/subscription` (#17997) * Create @bitwarden/subscription * Fix changed import in tsconfig.base.json --- .github/CODEOWNERS | 1 + jest.config.js | 1 + libs/subscription/README.md | 5 ++++ libs/subscription/eslint.config.mjs | 3 ++ libs/subscription/jest.config.js | 10 +++++++ libs/subscription/package.json | 11 +++++++ libs/subscription/project.json | 34 ++++++++++++++++++++++ libs/subscription/src/index.ts | 1 + libs/subscription/src/subscription.spec.ts | 8 +++++ libs/subscription/tsconfig.eslint.json | 6 ++++ libs/subscription/tsconfig.json | 13 +++++++++ libs/subscription/tsconfig.lib.json | 10 +++++++ libs/subscription/tsconfig.spec.json | 10 +++++++ package-lock.json | 9 ++++++ tsconfig.base.json | 1 + 15 files changed, 123 insertions(+) create mode 100644 libs/subscription/README.md create mode 100644 libs/subscription/eslint.config.mjs create mode 100644 libs/subscription/jest.config.js create mode 100644 libs/subscription/package.json create mode 100644 libs/subscription/project.json create mode 100644 libs/subscription/src/index.ts create mode 100644 libs/subscription/src/subscription.spec.ts create mode 100644 libs/subscription/tsconfig.eslint.json create mode 100644 libs/subscription/tsconfig.json create mode 100644 libs/subscription/tsconfig.lib.json create mode 100644 libs/subscription/tsconfig.spec.json diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 89fff27b217..99efec2fbbb 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -232,3 +232,4 @@ libs/pricing @bitwarden/team-billing-dev .claude/ @bitwarden/team-ai-sme .github/workflows/respond.yml @bitwarden/team-ai-sme .github/workflows/review-code.yml @bitwarden/team-ai-sme +libs/subscription @bitwarden/team-billing-dev diff --git a/jest.config.js b/jest.config.js index e5aeb536172..37d15eb8f92 100644 --- a/jest.config.js +++ b/jest.config.js @@ -59,6 +59,7 @@ module.exports = { "<rootDir>/libs/tools/send/send-ui/jest.config.js", "<rootDir>/libs/user-core/jest.config.js", "<rootDir>/libs/vault/jest.config.js", + "<rootDir>/libs/subscription/jest.config.js", ], // Workaround for a memory leak that crashes tests in CI: diff --git a/libs/subscription/README.md b/libs/subscription/README.md new file mode 100644 index 00000000000..be3d5044b4f --- /dev/null +++ b/libs/subscription/README.md @@ -0,0 +1,5 @@ +# Subscription + +Owned by: billing + +Components and services for managing Bitwarden subscriptions. diff --git a/libs/subscription/eslint.config.mjs b/libs/subscription/eslint.config.mjs new file mode 100644 index 00000000000..9c37d10e3ff --- /dev/null +++ b/libs/subscription/eslint.config.mjs @@ -0,0 +1,3 @@ +import baseConfig from "../../eslint.config.mjs"; + +export default [...baseConfig]; diff --git a/libs/subscription/jest.config.js b/libs/subscription/jest.config.js new file mode 100644 index 00000000000..a78ad5c2fc3 --- /dev/null +++ b/libs/subscription/jest.config.js @@ -0,0 +1,10 @@ +module.exports = { + displayName: "subscription", + preset: "../../jest.preset.js", + testEnvironment: "node", + transform: { + "^.+\\.[tj]s$": ["ts-jest", { tsconfig: "<rootDir>/tsconfig.spec.json" }], + }, + moduleFileExtensions: ["ts", "js", "html"], + coverageDirectory: "../../coverage/libs/subscription", +}; diff --git a/libs/subscription/package.json b/libs/subscription/package.json new file mode 100644 index 00000000000..67861a8891f --- /dev/null +++ b/libs/subscription/package.json @@ -0,0 +1,11 @@ +{ + "name": "@bitwarden/subscription", + "version": "0.0.1", + "description": "Components and services for managing Bitwarden subscriptions.", + "private": true, + "type": "commonjs", + "main": "index.js", + "types": "index.d.ts", + "license": "GPL-3.0", + "author": "billing" +} diff --git a/libs/subscription/project.json b/libs/subscription/project.json new file mode 100644 index 00000000000..ebd7b1cb73e --- /dev/null +++ b/libs/subscription/project.json @@ -0,0 +1,34 @@ +{ + "name": "subscription", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "libs/subscription/src", + "projectType": "library", + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": ["{options.outputPath}"], + "options": { + "outputPath": "dist/libs/subscription", + "main": "libs/subscription/src/index.ts", + "tsConfig": "libs/subscription/tsconfig.lib.json", + "assets": ["libs/subscription/*.md"], + "rootDir": "libs/subscription/src" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": ["{options.outputFile}"], + "options": { + "lintFilePatterns": ["libs/subscription/**/*.ts"] + } + }, + "test": { + "executor": "@nx/jest:jest", + "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], + "options": { + "jestConfig": "libs/subscription/jest.config.js" + } + } + } +} diff --git a/libs/subscription/src/index.ts b/libs/subscription/src/index.ts new file mode 100644 index 00000000000..3deb7c89d41 --- /dev/null +++ b/libs/subscription/src/index.ts @@ -0,0 +1 @@ +export type Placeholder = unknown; diff --git a/libs/subscription/src/subscription.spec.ts b/libs/subscription/src/subscription.spec.ts new file mode 100644 index 00000000000..7f0836a5063 --- /dev/null +++ b/libs/subscription/src/subscription.spec.ts @@ -0,0 +1,8 @@ +import * as lib from "./index"; + +describe("subscription", () => { + // This test will fail until something is exported from index.ts + it("should work", () => { + expect(lib).toBeDefined(); + }); +}); diff --git a/libs/subscription/tsconfig.eslint.json b/libs/subscription/tsconfig.eslint.json new file mode 100644 index 00000000000..3daf120441a --- /dev/null +++ b/libs/subscription/tsconfig.eslint.json @@ -0,0 +1,6 @@ +{ + "extends": "../../tsconfig.base.json", + "files": [], + "include": ["src/**/*.ts", "src/**/*.js"], + "exclude": ["**/build", "**/dist"] +} diff --git a/libs/subscription/tsconfig.json b/libs/subscription/tsconfig.json new file mode 100644 index 00000000000..62ebbd94647 --- /dev/null +++ b/libs/subscription/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + }, + { + "path": "./tsconfig.spec.json" + } + ] +} diff --git a/libs/subscription/tsconfig.lib.json b/libs/subscription/tsconfig.lib.json new file mode 100644 index 00000000000..9cbf6736007 --- /dev/null +++ b/libs/subscription/tsconfig.lib.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "include": ["src/**/*.ts"], + "exclude": ["jest.config.js", "src/**/*.spec.ts"] +} diff --git a/libs/subscription/tsconfig.spec.json b/libs/subscription/tsconfig.spec.json new file mode 100644 index 00000000000..1275f148a18 --- /dev/null +++ b/libs/subscription/tsconfig.spec.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "module": "commonjs", + "moduleResolution": "node10", + "types": ["jest", "node"] + }, + "include": ["jest.config.ts", "src/**/*.test.ts", "src/**/*.spec.ts", "src/**/*.d.ts"] +} diff --git a/package-lock.json b/package-lock.json index 879cda31ec7..6d67a0172bf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -628,6 +628,11 @@ "version": "0.0.1", "license": "GPL-3.0" }, + "libs/subscription": { + "name": "@bitwarden/subscription", + "version": "0.0.1", + "license": "GPL-3.0" + }, "libs/tools/export/vault-export/vault-export-core": { "name": "@bitwarden/vault-export-core", "version": "0.0.0", @@ -5011,6 +5016,10 @@ "resolved": "libs/storage-test-utils", "link": true }, + "node_modules/@bitwarden/subscription": { + "resolved": "libs/subscription", + "link": true + }, "node_modules/@bitwarden/ui-common": { "resolved": "libs/ui/common", "link": true diff --git a/tsconfig.base.json b/tsconfig.base.json index ae4b9f5f601..2f6499eb374 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -58,6 +58,7 @@ "@bitwarden/state-test-utils": ["libs/state-test-utils/src/index.ts"], "@bitwarden/storage-core": ["libs/storage-core/src/index.ts"], "@bitwarden/storage-test-utils": ["libs/storage-test-utils/src/index.ts"], + "@bitwarden/subscription": ["libs/subscription/src/index.ts"], "@bitwarden/ui-common": ["./libs/ui/common/src"], "@bitwarden/ui-common/setup-jest": ["./libs/ui/common/src/setup-jest"], "@bitwarden/user-core": ["libs/user-core/src/index.ts"], From 25bd2a4c347719aa641378db43591141ad9733a9 Mon Sep 17 00:00:00 2001 From: Nick Krantz <125900171+nick-livefront@users.noreply.github.com> Date: Tue, 16 Dec 2025 12:08:03 -0600 Subject: [PATCH 085/188] move archive check into conditional to avoid undefined error (#18000) --- .../vault-item-dialog/vault-item-dialog.component.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts b/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts index d7b9ee97123..15e62eaf93e 100644 --- a/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts +++ b/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts @@ -253,7 +253,7 @@ export class VaultItemDialogComponent implements OnInit, OnDestroy { protected showRestore: boolean; - protected cipherIsArchived: boolean; + protected cipherIsArchived: boolean = false; protected get loadingForm() { return this.loadForm && !this.formReady; @@ -350,6 +350,8 @@ export class VaultItemDialogComponent implements OnInit, OnDestroy { return; } + this.cipherIsArchived = this.cipher.isArchived; + this.collections = this.formConfig.collections.filter((c) => this.cipher.collectionIds?.includes(c.id), ); @@ -375,7 +377,6 @@ export class VaultItemDialogComponent implements OnInit, OnDestroy { this.filter = await firstValueFrom(this.routedVaultFilterService.filter$); this.showRestore = await this.canUserRestore(); - this.cipherIsArchived = this.cipher.isArchived; this.performingInitialLoad = false; } From 78784f509d34749c17562ba44f49c0ce29a9105f Mon Sep 17 00:00:00 2001 From: "bw-ghapp[bot]" <178206702+bw-ghapp[bot]@users.noreply.github.com> Date: Tue, 16 Dec 2025 19:11:06 +0100 Subject: [PATCH 086/188] Autosync the updated translations (#17935) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/desktop/src/locales/af/messages.json | 240 ++++++++++++++- apps/desktop/src/locales/ar/messages.json | 240 ++++++++++++++- apps/desktop/src/locales/az/messages.json | 254 ++++++++++++++-- apps/desktop/src/locales/be/messages.json | 240 ++++++++++++++- apps/desktop/src/locales/bg/messages.json | 240 ++++++++++++++- apps/desktop/src/locales/bn/messages.json | 240 ++++++++++++++- apps/desktop/src/locales/bs/messages.json | 240 ++++++++++++++- apps/desktop/src/locales/ca/messages.json | 240 ++++++++++++++- apps/desktop/src/locales/cs/messages.json | 240 ++++++++++++++- apps/desktop/src/locales/cy/messages.json | 240 ++++++++++++++- apps/desktop/src/locales/da/messages.json | 240 ++++++++++++++- apps/desktop/src/locales/de/messages.json | 256 ++++++++++++++-- apps/desktop/src/locales/el/messages.json | 240 ++++++++++++++- apps/desktop/src/locales/en_GB/messages.json | 240 ++++++++++++++- apps/desktop/src/locales/en_IN/messages.json | 240 ++++++++++++++- apps/desktop/src/locales/eo/messages.json | 240 ++++++++++++++- apps/desktop/src/locales/es/messages.json | 240 ++++++++++++++- apps/desktop/src/locales/et/messages.json | 240 ++++++++++++++- apps/desktop/src/locales/eu/messages.json | 240 ++++++++++++++- apps/desktop/src/locales/fa/messages.json | 240 ++++++++++++++- apps/desktop/src/locales/fi/messages.json | 240 ++++++++++++++- apps/desktop/src/locales/fil/messages.json | 240 ++++++++++++++- apps/desktop/src/locales/fr/messages.json | 240 ++++++++++++++- apps/desktop/src/locales/gl/messages.json | 240 ++++++++++++++- apps/desktop/src/locales/he/messages.json | 240 ++++++++++++++- apps/desktop/src/locales/hi/messages.json | 240 ++++++++++++++- apps/desktop/src/locales/hr/messages.json | 240 ++++++++++++++- apps/desktop/src/locales/hu/messages.json | 240 ++++++++++++++- apps/desktop/src/locales/id/messages.json | 240 ++++++++++++++- apps/desktop/src/locales/it/messages.json | 240 ++++++++++++++- apps/desktop/src/locales/ja/messages.json | 240 ++++++++++++++- apps/desktop/src/locales/ka/messages.json | 240 ++++++++++++++- apps/desktop/src/locales/km/messages.json | 240 ++++++++++++++- apps/desktop/src/locales/kn/messages.json | 240 ++++++++++++++- apps/desktop/src/locales/ko/messages.json | 240 ++++++++++++++- apps/desktop/src/locales/lt/messages.json | 240 ++++++++++++++- apps/desktop/src/locales/lv/messages.json | 240 ++++++++++++++- apps/desktop/src/locales/me/messages.json | 240 ++++++++++++++- apps/desktop/src/locales/ml/messages.json | 240 ++++++++++++++- apps/desktop/src/locales/mr/messages.json | 240 ++++++++++++++- apps/desktop/src/locales/my/messages.json | 240 ++++++++++++++- apps/desktop/src/locales/nb/messages.json | 240 ++++++++++++++- apps/desktop/src/locales/ne/messages.json | 240 ++++++++++++++- apps/desktop/src/locales/nl/messages.json | 240 ++++++++++++++- apps/desktop/src/locales/nn/messages.json | 240 ++++++++++++++- apps/desktop/src/locales/or/messages.json | 240 ++++++++++++++- apps/desktop/src/locales/pl/messages.json | 240 ++++++++++++++- apps/desktop/src/locales/pt_BR/messages.json | 294 ++++++++++++++++--- apps/desktop/src/locales/pt_PT/messages.json | 240 ++++++++++++++- apps/desktop/src/locales/ro/messages.json | 240 ++++++++++++++- apps/desktop/src/locales/ru/messages.json | 256 ++++++++++++++-- apps/desktop/src/locales/si/messages.json | 240 ++++++++++++++- apps/desktop/src/locales/sk/messages.json | 242 ++++++++++++++- apps/desktop/src/locales/sl/messages.json | 240 ++++++++++++++- apps/desktop/src/locales/sr/messages.json | 258 ++++++++++++++-- apps/desktop/src/locales/sv/messages.json | 240 ++++++++++++++- apps/desktop/src/locales/ta/messages.json | 240 ++++++++++++++- apps/desktop/src/locales/te/messages.json | 240 ++++++++++++++- apps/desktop/src/locales/th/messages.json | 240 ++++++++++++++- apps/desktop/src/locales/tr/messages.json | 290 +++++++++++++++--- apps/desktop/src/locales/uk/messages.json | 240 ++++++++++++++- apps/desktop/src/locales/vi/messages.json | 262 +++++++++++++++-- apps/desktop/src/locales/zh_CN/messages.json | 268 +++++++++++++++-- apps/desktop/src/locales/zh_TW/messages.json | 254 ++++++++++++++-- 64 files changed, 14645 insertions(+), 949 deletions(-) diff --git a/apps/desktop/src/locales/af/messages.json b/apps/desktop/src/locales/af/messages.json index a4230a128c5..73a05ce79a6 100644 --- a/apps/desktop/src/locales/af/messages.json +++ b/apps/desktop/src/locales/af/messages.json @@ -708,6 +708,18 @@ "addAttachment": { "message": "Add attachment" }, + "itemsTransferred": { + "message": "Items transferred" + }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "maxFileSizeSansPunctuation": { "message": "Maximum file size is 500 MB" }, @@ -908,6 +920,12 @@ "unexpectedError": { "message": "'n Onverwagte fout het voorgekom." }, + "unexpectedErrorShort": { + "message": "Unexpected error" + }, + "closeThisBitwardenWindow": { + "message": "Close this Bitwarden window and try again." + }, "itemInformation": { "message": "Iteminligting" }, @@ -1180,8 +1198,8 @@ "followUs": { "message": "Volg Ons" }, - "syncVault": { - "message": "Sichroniseer Kluis" + "syncNow": { + "message": "Sync now" }, "changeMasterPass": { "message": "Verander Hoofwagwoord" @@ -1757,8 +1775,11 @@ "exportFrom": { "message": "Export from" }, - "exportVault": { - "message": "Stuur Kluis Uit" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "Lêerformaat" @@ -2619,9 +2640,6 @@ "removedMasterPassword": { "message": "Hoofwagwoord is verwyder" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "organizationName": { "message": "Organization name" }, @@ -3477,10 +3495,6 @@ "aliasDomain": { "message": "Alias domain" }, - "importData": { - "message": "Invoer data", - "description": "Used for the desktop menu item and the header of the import dialog" - }, "importError": { "message": "Invoer fout" }, @@ -3886,6 +3900,75 @@ "fileSavedToDevice": { "message": "File saved to device. Manage from your device downloads." }, + "importantNotice": { + "message": "Important notice" + }, + "setupTwoStepLogin": { + "message": "Set up two-step login" + }, + "newDeviceVerificationNoticeContentPage1": { + "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." + }, + "newDeviceVerificationNoticeContentPage2": { + "message": "You can set up two-step login as an alternative way to protect your account or change your email to one you can access." + }, + "remindMeLater": { + "message": "Remind me later" + }, + "newDeviceVerificationNoticePageOneFormContent": { + "message": "Do you have reliable access to your email, $EMAIL$?", + "placeholders": { + "email": { + "content": "$1", + "example": "your_name@email.com" + } + } + }, + "newDeviceVerificationNoticePageOneEmailAccessNo": { + "message": "No, I do not" + }, + "newDeviceVerificationNoticePageOneEmailAccessYes": { + "message": "Yes, I can reliably access my email" + }, + "turnOnTwoStepLogin": { + "message": "Turn on two-step login" + }, + "changeAcctEmail": { + "message": "Change account email" + }, + "passkeyLogin": { + "message": "Log in with passkey?" + }, + "savePasskeyQuestion": { + "message": "Save passkey?" + }, + "saveNewPasskey": { + "message": "Save as new login" + }, + "savePasskeyNewLogin": { + "message": "Save passkey as new login" + }, + "noMatchingLoginsForSite": { + "message": "No matching logins for this site" + }, + "overwritePasskey": { + "message": "Overwrite passkey?" + }, + "unableToSavePasskey": { + "message": "Unable to save passkey" + }, + "alreadyContainsPasskey": { + "message": "This item already contains a passkey. Are you sure you want to overwrite the current passkey?" + }, + "passkeyAlreadyExists": { + "message": "A passkey already exists for this application." + }, + "applicationDoesNotSupportDuplicates": { + "message": "This application does not support duplicates." + }, + "closeThisWindow": { + "message": "Close this window" + }, "allowScreenshots": { "message": "Allow screen capture" }, @@ -4244,16 +4327,147 @@ "andMoreFeatures": { "message": "And more!" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "upgradeToPremium": { "message": "Upgrade to Premium" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { "message": "Timeout action" }, "sessionTimeoutHeader": { "message": "Session timeout" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Your organization has set the default session timeout to On system lock." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On restart." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On restart" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/desktop/src/locales/ar/messages.json b/apps/desktop/src/locales/ar/messages.json index 54a9425c901..7373bb671fa 100644 --- a/apps/desktop/src/locales/ar/messages.json +++ b/apps/desktop/src/locales/ar/messages.json @@ -708,6 +708,18 @@ "addAttachment": { "message": "Add attachment" }, + "itemsTransferred": { + "message": "Items transferred" + }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "maxFileSizeSansPunctuation": { "message": "Maximum file size is 500 MB" }, @@ -908,6 +920,12 @@ "unexpectedError": { "message": "حدث خطأ غير متوقع." }, + "unexpectedErrorShort": { + "message": "Unexpected error" + }, + "closeThisBitwardenWindow": { + "message": "Close this Bitwarden window and try again." + }, "itemInformation": { "message": "معلومات العنصر" }, @@ -1180,8 +1198,8 @@ "followUs": { "message": "تابعنا" }, - "syncVault": { - "message": "مزامنة الخزانة" + "syncNow": { + "message": "Sync now" }, "changeMasterPass": { "message": "تغيير كلمة المرور الرئيسية" @@ -1757,8 +1775,11 @@ "exportFrom": { "message": "تصدير من" }, - "exportVault": { - "message": "تصدير الخزانة" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "صيغة الملف" @@ -2619,9 +2640,6 @@ "removedMasterPassword": { "message": "تمت إزالة كلمة المرور الرئيسية." }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "organizationName": { "message": "Organization name" }, @@ -3477,10 +3495,6 @@ "aliasDomain": { "message": "الاسم البديل للنطاق" }, - "importData": { - "message": "استيراد البيانات", - "description": "Used for the desktop menu item and the header of the import dialog" - }, "importError": { "message": "خطأ في الاستيراد" }, @@ -3886,6 +3900,75 @@ "fileSavedToDevice": { "message": "تم حفظ الملف على الجهاز. إدارة من تنزيلات جهازك." }, + "importantNotice": { + "message": "Important notice" + }, + "setupTwoStepLogin": { + "message": "Set up two-step login" + }, + "newDeviceVerificationNoticeContentPage1": { + "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." + }, + "newDeviceVerificationNoticeContentPage2": { + "message": "You can set up two-step login as an alternative way to protect your account or change your email to one you can access." + }, + "remindMeLater": { + "message": "Remind me later" + }, + "newDeviceVerificationNoticePageOneFormContent": { + "message": "Do you have reliable access to your email, $EMAIL$?", + "placeholders": { + "email": { + "content": "$1", + "example": "your_name@email.com" + } + } + }, + "newDeviceVerificationNoticePageOneEmailAccessNo": { + "message": "No, I do not" + }, + "newDeviceVerificationNoticePageOneEmailAccessYes": { + "message": "Yes, I can reliably access my email" + }, + "turnOnTwoStepLogin": { + "message": "Turn on two-step login" + }, + "changeAcctEmail": { + "message": "Change account email" + }, + "passkeyLogin": { + "message": "Log in with passkey?" + }, + "savePasskeyQuestion": { + "message": "Save passkey?" + }, + "saveNewPasskey": { + "message": "Save as new login" + }, + "savePasskeyNewLogin": { + "message": "Save passkey as new login" + }, + "noMatchingLoginsForSite": { + "message": "No matching logins for this site" + }, + "overwritePasskey": { + "message": "Overwrite passkey?" + }, + "unableToSavePasskey": { + "message": "Unable to save passkey" + }, + "alreadyContainsPasskey": { + "message": "This item already contains a passkey. Are you sure you want to overwrite the current passkey?" + }, + "passkeyAlreadyExists": { + "message": "A passkey already exists for this application." + }, + "applicationDoesNotSupportDuplicates": { + "message": "This application does not support duplicates." + }, + "closeThisWindow": { + "message": "Close this window" + }, "allowScreenshots": { "message": "Allow screen capture" }, @@ -4244,16 +4327,147 @@ "andMoreFeatures": { "message": "And more!" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "upgradeToPremium": { "message": "Upgrade to Premium" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { "message": "Timeout action" }, "sessionTimeoutHeader": { "message": "Session timeout" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Your organization has set the default session timeout to On system lock." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On restart." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On restart" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/desktop/src/locales/az/messages.json b/apps/desktop/src/locales/az/messages.json index 9e8c77aa76a..fb818fac642 100644 --- a/apps/desktop/src/locales/az/messages.json +++ b/apps/desktop/src/locales/az/messages.json @@ -708,6 +708,18 @@ "addAttachment": { "message": "Qoşma əlavə et" }, + "itemsTransferred": { + "message": "Elementlər köçürüldü" + }, + "fixEncryption": { + "message": "Şifrələməni düzəlt" + }, + "fixEncryptionTooltip": { + "message": "Bu fayl, köhnə bir şifrələmə üsulunu istifadə edir." + }, + "attachmentUpdated": { + "message": "Qoşma güncəllənib" + }, "maxFileSizeSansPunctuation": { "message": "Maksimal fayl həcmi 500 MB-dır" }, @@ -908,6 +920,12 @@ "unexpectedError": { "message": "Gözlənilməz bir səhv baş verdi." }, + "unexpectedErrorShort": { + "message": "Gözlənilməz xəta" + }, + "closeThisBitwardenWindow": { + "message": "Bu Bitwarden pəncərəsini bağlayıb yenidən sınayın." + }, "itemInformation": { "message": "Element məlumatları" }, @@ -1094,22 +1112,22 @@ "message": "Daha ətraflı" }, "migrationsFailed": { - "message": "An error occurred updating the encryption settings." + "message": "Şifrələmə ayarlarını güncəlləyərkən bir xəta baş verdi." }, "updateEncryptionSettingsTitle": { - "message": "Update your encryption settings" + "message": "Şifrələmə ayarlarınızı güncəlləyin" }, "updateEncryptionSettingsDesc": { - "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + "message": "Tövsiyə edilən yeni şifrələmə ayarları, hesabınızın təhlükəsizliyini artıracaq. İndi güncəlləmək üçün ana parolunuzu daxil edin." }, "confirmIdentityToContinue": { - "message": "Confirm your identity to continue" + "message": "Davam etmək üçün kimliyinizi təsdiqləyin" }, "enterYourMasterPassword": { - "message": "Enter your master password" + "message": "Ana parolunuzu daxil edin" }, "updateSettings": { - "message": "Update settings" + "message": "Ayarları güncəllə" }, "featureUnavailable": { "message": "Özəllik əlçatmazdır" @@ -1180,8 +1198,8 @@ "followUs": { "message": "Bizi izləyin" }, - "syncVault": { - "message": "Seyfi sinxronlaşdır" + "syncNow": { + "message": "İndi sinxr." }, "changeMasterPass": { "message": "Ana parolu dəyişdir" @@ -1509,7 +1527,7 @@ "message": "Fayl qoşmaları üçün 1 GB şifrələnmiş saxlama sahəsi." }, "premiumSignUpStorageV2": { - "message": "$SIZE$ encrypted storage for file attachments.", + "message": "Fayl qoşmaları üçün $SIZE$ şifrələnmiş anbar sahəsi.", "placeholders": { "size": { "content": "$1", @@ -1757,8 +1775,11 @@ "exportFrom": { "message": "Buradan xaricə köçür" }, - "exportVault": { - "message": "Seyfi xaricə köçür" + "export": { + "message": "Xaricə köçür" + }, + "import": { + "message": "Daxilə köçür" }, "fileFormat": { "message": "Fayl formatı" @@ -2619,9 +2640,6 @@ "removedMasterPassword": { "message": "Ana parol silindi." }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "Aşağıdakı təşkilatların üzvləri üçün artıq ana parol tələb olunmur. Lütfən aşağıdakı domeni təşkilatınızın inzibatçısı ilə təsdiqləyin." - }, "organizationName": { "message": "Təşkilat adı" }, @@ -3477,10 +3495,6 @@ "aliasDomain": { "message": "Domen ləqəbi" }, - "importData": { - "message": "Veriləri daxilə köçür", - "description": "Used for the desktop menu item and the header of the import dialog" - }, "importError": { "message": "Daxilə köçürmə xətası" }, @@ -3886,6 +3900,75 @@ "fileSavedToDevice": { "message": "Fayl cihazda saxlanıldı. Endirilənləri cihazınızdan idarə edin." }, + "importantNotice": { + "message": "Vacib bildiriş" + }, + "setupTwoStepLogin": { + "message": "İki addımlı girişi qur" + }, + "newDeviceVerificationNoticeContentPage1": { + "message": "Bitwarden, 2025-ci ilin Fevral ayından etibarən yeni cihazlardan gələn girişləri doğrulamaq üçün hesabınızın e-poçtuna bir kod göndərəcək." + }, + "newDeviceVerificationNoticeContentPage2": { + "message": "Hesabınızı qorumaq üçün alternativ bir yol kimi iki addımlı girişi qura və ya e-poçtunuzu erişə biləcəyiniz bir e-poçtla dəyişdirə bilərsiniz." + }, + "remindMeLater": { + "message": "Daha sonra xatırlat" + }, + "newDeviceVerificationNoticePageOneFormContent": { + "message": "$EMAIL$ e-poçtunuza güvənli şəkildə erişə bilirsiniz?", + "placeholders": { + "email": { + "content": "$1", + "example": "your_name@email.com" + } + } + }, + "newDeviceVerificationNoticePageOneEmailAccessNo": { + "message": "Xeyr, bilmirəm" + }, + "newDeviceVerificationNoticePageOneEmailAccessYes": { + "message": "Bəli, e-poçtuma güvənli şəkildə erişə bilirəm" + }, + "turnOnTwoStepLogin": { + "message": "İki addımlı girişi işə sal" + }, + "changeAcctEmail": { + "message": "Hesabın e-poçtunu dəyişdir" + }, + "passkeyLogin": { + "message": "Keçid açarı ilə giriş edilsin?" + }, + "savePasskeyQuestion": { + "message": "Keçid açarı saxlanılsın?" + }, + "saveNewPasskey": { + "message": "Yeni giriş kimi saxla" + }, + "savePasskeyNewLogin": { + "message": "Keçid açarını yeni bir giriş olaraq saxla" + }, + "noMatchingLoginsForSite": { + "message": "Bu sayt üçün uyuşan giriş məlumatı yoxdur" + }, + "overwritePasskey": { + "message": "Keçid açarının üzərinə yazılsın?" + }, + "unableToSavePasskey": { + "message": "Keçid açarı saxlanıla bilmir" + }, + "alreadyContainsPasskey": { + "message": "Bu elementdə artıq bir keçid açarı var. Hazırkı keçid açarının üzərinə yazmaq istədiyinizə əminsiniz?" + }, + "passkeyAlreadyExists": { + "message": "Bu tətbiq üçün bir keçid açarı artıq mövcuddur." + }, + "applicationDoesNotSupportDuplicates": { + "message": "Bu tətbiq, təkrarları dəstəkləmir." + }, + "closeThisWindow": { + "message": "Bu pəncərəni bağla" + }, "allowScreenshots": { "message": "Ekranı çəkməyə icazə ver" }, @@ -4244,16 +4327,147 @@ "andMoreFeatures": { "message": "Və daha çoxu!" }, - "planDescPremium": { - "message": "Tam onlayn təhlükəsizlik" + "advancedOnlineSecurity": { + "message": "Qabaqcıl onlayn təhlükəsizlik" }, "upgradeToPremium": { "message": "\"Premium\"a yüksəlt" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Təşkilatınız, artıq Bitwarden-ə giriş etmək üçün ana parol istifadə etmir. Davam etmək üçün təşkilatı və domeni doğrulayın." + }, + "continueWithLogIn": { + "message": "Giriş etməyə davam" + }, + "doNotContinue": { + "message": "Davam etmə" + }, + "domain": { + "message": "Domen" + }, + "keyConnectorDomainTooltip": { + "message": "Bu domen, hesabınızın şifrələmə açarlarını saxlayacaq, ona görə də, bu domenə güvəndiyinizə əmin olun. Əmin deyilsinizsə, adminizlə əlaqə saxlayın." + }, + "verifyYourOrganization": { + "message": "Giriş etmək üçün təşkilatınızı doğrulayın" + }, + "organizationVerified": { + "message": "Təşkilat doğrulandı" + }, + "domainVerified": { + "message": "Domen doğrulandı" + }, + "leaveOrganizationContent": { + "message": "Təşkilatınızı doğrulamasanız, təşkilata erişiminiz ləğv ediləcək." + }, + "leaveNow": { + "message": "İndi tərk et" + }, + "verifyYourDomainToLogin": { + "message": "Giriş etmək üçün domeninizi doğrulayın" + }, + "verifyYourDomainDescription": { + "message": "Giriş prosesini davam etdirmək üçün bu domeni doğrulayın." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "Giriş prosesini davam etdirmək üçün bu təşkilatı və domeni doğrulayın." + }, "sessionTimeoutSettingsAction": { "message": "Vaxt bitmə əməliyyatı" }, "sessionTimeoutHeader": { "message": "Sessiya vaxt bitməsi" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "Bu ayar, təşkilatınız tərəfindən idarə olunur." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Təşkilatınız, maksimum seyf bitmə vaxtını $HOURS$ saat $MINUTES$ dəqiqə olaraq ayarladı.", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Təşkilatınız, seansın ilkin bitmə vaxtını Sistem kilidi açılanda olaraq təyin etdi." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Təşkilatınız, seansın ilkin bitmə vaxtını Yenidən başladılanda olaraq təyin etdi." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maksimum bitmə vaxtı $HOURS$ saat $MINUTES$ dəqiqə dəyərini aşa bilməz", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "Yenidən başladılanda" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Vaxt bitmə əməliyyatınızı dəyişdirmək üçün bir kilid açma üsulu qurun" + }, + "upgrade": { + "message": "Yüksəlt" + }, + "leaveConfirmationDialogTitle": { + "message": "Tərk etmək istədiyinizə əminsiniz?" + }, + "leaveConfirmationDialogContentOne": { + "message": "Rədd cavabı versəniz, fərdi elementləriniz hesabınızda qalacaq, paylaşılan elementlərə və təşkilat özəlliklərinə erişimi itirəcəksiniz." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Erişimi təkrar qazanmaq üçün admininizlə əlaqə saxlayın." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Tərk et: $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "Seyfimi necə idarə edim?" + }, + "transferItemsToOrganizationTitle": { + "message": "Elementləri bura köçür: $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$, təhlükəsizlik və riayətlilik üçün bütün elementlərin təşkilata aid olmasını tələb edir. Elementlərinizin sahibliyini transfer etmək üçün qəbul edin.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Transferi qəbul et" + }, + "declineAndLeave": { + "message": "Rədd et və tərk et" + }, + "whyAmISeeingThis": { + "message": "Bunu niyə görürəm?" } } diff --git a/apps/desktop/src/locales/be/messages.json b/apps/desktop/src/locales/be/messages.json index e9b3834a9f7..982c5ba2956 100644 --- a/apps/desktop/src/locales/be/messages.json +++ b/apps/desktop/src/locales/be/messages.json @@ -708,6 +708,18 @@ "addAttachment": { "message": "Add attachment" }, + "itemsTransferred": { + "message": "Items transferred" + }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "maxFileSizeSansPunctuation": { "message": "Maximum file size is 500 MB" }, @@ -908,6 +920,12 @@ "unexpectedError": { "message": "Адбылася нечаканая памылка." }, + "unexpectedErrorShort": { + "message": "Unexpected error" + }, + "closeThisBitwardenWindow": { + "message": "Close this Bitwarden window and try again." + }, "itemInformation": { "message": "Звесткі аб элеменце" }, @@ -1180,8 +1198,8 @@ "followUs": { "message": "Сачыце за намі" }, - "syncVault": { - "message": "Сінхранізаваць сховішча" + "syncNow": { + "message": "Sync now" }, "changeMasterPass": { "message": "Змяніць асноўны пароль" @@ -1757,8 +1775,11 @@ "exportFrom": { "message": "Export from" }, - "exportVault": { - "message": "Экспартаваць сховішча" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "Фармат файла" @@ -2619,9 +2640,6 @@ "removedMasterPassword": { "message": "Асноўны пароль выдалены." }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "organizationName": { "message": "Organization name" }, @@ -3477,10 +3495,6 @@ "aliasDomain": { "message": "Alias domain" }, - "importData": { - "message": "Import data", - "description": "Used for the desktop menu item and the header of the import dialog" - }, "importError": { "message": "Import error" }, @@ -3886,6 +3900,75 @@ "fileSavedToDevice": { "message": "File saved to device. Manage from your device downloads." }, + "importantNotice": { + "message": "Important notice" + }, + "setupTwoStepLogin": { + "message": "Set up two-step login" + }, + "newDeviceVerificationNoticeContentPage1": { + "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." + }, + "newDeviceVerificationNoticeContentPage2": { + "message": "You can set up two-step login as an alternative way to protect your account or change your email to one you can access." + }, + "remindMeLater": { + "message": "Remind me later" + }, + "newDeviceVerificationNoticePageOneFormContent": { + "message": "Do you have reliable access to your email, $EMAIL$?", + "placeholders": { + "email": { + "content": "$1", + "example": "your_name@email.com" + } + } + }, + "newDeviceVerificationNoticePageOneEmailAccessNo": { + "message": "No, I do not" + }, + "newDeviceVerificationNoticePageOneEmailAccessYes": { + "message": "Yes, I can reliably access my email" + }, + "turnOnTwoStepLogin": { + "message": "Turn on two-step login" + }, + "changeAcctEmail": { + "message": "Change account email" + }, + "passkeyLogin": { + "message": "Log in with passkey?" + }, + "savePasskeyQuestion": { + "message": "Save passkey?" + }, + "saveNewPasskey": { + "message": "Save as new login" + }, + "savePasskeyNewLogin": { + "message": "Save passkey as new login" + }, + "noMatchingLoginsForSite": { + "message": "No matching logins for this site" + }, + "overwritePasskey": { + "message": "Overwrite passkey?" + }, + "unableToSavePasskey": { + "message": "Unable to save passkey" + }, + "alreadyContainsPasskey": { + "message": "This item already contains a passkey. Are you sure you want to overwrite the current passkey?" + }, + "passkeyAlreadyExists": { + "message": "A passkey already exists for this application." + }, + "applicationDoesNotSupportDuplicates": { + "message": "This application does not support duplicates." + }, + "closeThisWindow": { + "message": "Close this window" + }, "allowScreenshots": { "message": "Allow screen capture" }, @@ -4244,16 +4327,147 @@ "andMoreFeatures": { "message": "And more!" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "upgradeToPremium": { "message": "Upgrade to Premium" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { "message": "Timeout action" }, "sessionTimeoutHeader": { "message": "Session timeout" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Your organization has set the default session timeout to On system lock." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On restart." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On restart" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/desktop/src/locales/bg/messages.json b/apps/desktop/src/locales/bg/messages.json index f27fb467c0c..b223a4d42b3 100644 --- a/apps/desktop/src/locales/bg/messages.json +++ b/apps/desktop/src/locales/bg/messages.json @@ -708,6 +708,18 @@ "addAttachment": { "message": "Добавяне на прикачен файл" }, + "itemsTransferred": { + "message": "Елементите са прехвърлени" + }, + "fixEncryption": { + "message": "Поправяне на шифроването" + }, + "fixEncryptionTooltip": { + "message": "Този файл използва остарял метод на шифроване." + }, + "attachmentUpdated": { + "message": "Прикаченият файл е актуализиран" + }, "maxFileSizeSansPunctuation": { "message": "Максималният размер на файла е 500 MB" }, @@ -908,6 +920,12 @@ "unexpectedError": { "message": "Неочаквана грешка." }, + "unexpectedErrorShort": { + "message": "Неочаквана грешка" + }, + "closeThisBitwardenWindow": { + "message": "Затворете прозореца на Битуорден и опитайте отново." + }, "itemInformation": { "message": "Сведения за елемента" }, @@ -1180,8 +1198,8 @@ "followUs": { "message": "Следвайте ни" }, - "syncVault": { - "message": "Синхронизиране" + "syncNow": { + "message": "Синхронизиране сега" }, "changeMasterPass": { "message": "Промяна на главната парола" @@ -1757,8 +1775,11 @@ "exportFrom": { "message": "Изнасяне от" }, - "exportVault": { - "message": "Изнасяне на трезора" + "export": { + "message": "Изнасяне" + }, + "import": { + "message": "Внасяне" }, "fileFormat": { "message": "Формат на файла" @@ -2619,9 +2640,6 @@ "removedMasterPassword": { "message": "Главната парола е премахната." }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "За членовете на следната организация вече не се изисква главна парола. Потвърдете домейна по-долу с администратора на организацията си." - }, "organizationName": { "message": "Име на организацията" }, @@ -3477,10 +3495,6 @@ "aliasDomain": { "message": "Псевдонимен домейн" }, - "importData": { - "message": "Внасяне на данни", - "description": "Used for the desktop menu item and the header of the import dialog" - }, "importError": { "message": "Грешка при внасянето" }, @@ -3886,6 +3900,75 @@ "fileSavedToDevice": { "message": "Файлът е запазен на устройството. Можете да го намерите в мястото за сваляния на устройството." }, + "importantNotice": { + "message": "Важно съобщение" + }, + "setupTwoStepLogin": { + "message": "Настройте двустепенно удостоверяване" + }, + "newDeviceVerificationNoticeContentPage1": { + "message": "Битуорден ще изпрати код до е-пощата Ви, за потвърждаване на вписването от нови устройства. Това ще започне от февруари 2025." + }, + "newDeviceVerificationNoticeContentPage2": { + "message": "Можете да настроите двустепенно удостоверяване, като различен метод на защита, или ако е необходимо да промените е-пощата си с такава, до която имате достъп." + }, + "remindMeLater": { + "message": "Напомнете ми по-късно" + }, + "newDeviceVerificationNoticePageOneFormContent": { + "message": "Имате ли сигурен достъп до е-пощата си – $EMAIL$?", + "placeholders": { + "email": { + "content": "$1", + "example": "your_name@email.com" + } + } + }, + "newDeviceVerificationNoticePageOneEmailAccessNo": { + "message": "Не, нямам" + }, + "newDeviceVerificationNoticePageOneEmailAccessYes": { + "message": "Да, имам достъп до е-пощата си" + }, + "turnOnTwoStepLogin": { + "message": "Включване на двустепенното удостоверяване" + }, + "changeAcctEmail": { + "message": "Промяна на е-пощата" + }, + "passkeyLogin": { + "message": "Вписване със секретен ключ?" + }, + "savePasskeyQuestion": { + "message": "Да се запази на секретният ключ?" + }, + "saveNewPasskey": { + "message": "Запазване като нов елемент за вписване" + }, + "savePasskeyNewLogin": { + "message": "Запазване на секретния ключ като нов елемент за вписване" + }, + "noMatchingLoginsForSite": { + "message": "Няма записи за вписване отговарящи на този уеб сайт" + }, + "overwritePasskey": { + "message": "Да се замени ли секретният ключ?" + }, + "unableToSavePasskey": { + "message": "Секретният ключ не може да бъде запазен" + }, + "alreadyContainsPasskey": { + "message": "Този елемент вече съдържа секретен ключ. Наистина ли искате да замените текущия секретен ключ?" + }, + "passkeyAlreadyExists": { + "message": "За това приложение вече съществува секретен ключ." + }, + "applicationDoesNotSupportDuplicates": { + "message": "Това приложение не поддържа дубликати." + }, + "closeThisWindow": { + "message": "Затворете този прозорец" + }, "allowScreenshots": { "message": "Позволяване на заснемането на екрана" }, @@ -4244,16 +4327,147 @@ "andMoreFeatures": { "message": "И още!" }, - "planDescPremium": { - "message": "Пълна сигурност в Интернет" + "advancedOnlineSecurity": { + "message": "Разширена сигурност в Интернет" }, "upgradeToPremium": { "message": "Надградете до Платения план" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Вашата организация вече не използва главни пароли за вписване в Битуорден. За да продължите, потвърдете организацията и домейна." + }, + "continueWithLogIn": { + "message": "Продължаване с вписването" + }, + "doNotContinue": { + "message": "Не продължавам" + }, + "domain": { + "message": "Домейн" + }, + "keyConnectorDomainTooltip": { + "message": "Този домейн ще съхранява ключовете за шифроване на акаунта Ви, така че се уверете, че му имате доверие. Ако имате съмнения, свържете се с администратора си." + }, + "verifyYourOrganization": { + "message": "Потвърдете организацията си, за да се впишете" + }, + "organizationVerified": { + "message": "Организацията е потвърдена" + }, + "domainVerified": { + "message": "Домейнът е потвърден" + }, + "leaveOrganizationContent": { + "message": "Ако не потвърдите организацията, достъпът Ви до нея ще бъде преустановен." + }, + "leaveNow": { + "message": "Напускане сега" + }, + "verifyYourDomainToLogin": { + "message": "Потвърдете домейна си, за да се впишете" + }, + "verifyYourDomainDescription": { + "message": "За да продължите с вписването, потвърдете този домейн." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "За да продължите с вписването, потвърдете организацията и домейна." + }, "sessionTimeoutSettingsAction": { "message": "Действие при изтичането на времето за достъп" }, "sessionTimeoutHeader": { "message": "Изтичане на времето за сесията" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "Тази настройка се управлява от организацията Ви." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Организацията Ви е настроила максималното разрешено време за достъп на [%1$i] час(а) и [%2$i] минути.", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Организацията Ви е настроила стандартното разрешено време за достъп да бъде до заключване на системата." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Организацията Ви е настроила стандартното разрешено време за достъп да бъде до рестартиране." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Максималното време на достъп не може да превишава $HOURS$ час(а) и $MINUTES$ минути", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "При рестартиране" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Задайте метод за отключване, за да може да промените действието при изтичане на времето за достъп" + }, + "upgrade": { + "message": "Надграждане" + }, + "leaveConfirmationDialogTitle": { + "message": "Наистина ли искате да напуснете?" + }, + "leaveConfirmationDialogContentOne": { + "message": "Ако откажете, Вашите собствени елементи ще останат в акаунта Ви, но ще загубите достъп до споделените елементи и функционалностите на организацията." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Свържете се с администратор, за да получите достъп отново." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Напускане на $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "Как да управлявам трезора си?" + }, + "transferItemsToOrganizationTitle": { + "message": "Прехвърляне на елементи към $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ изисква всички елементи да станат притежание на организацията, за по-добра сигурност и съвместимост. Изберете, че приемате, за да прехвърлите собствеността на елементите си към организацията.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Приемане на прехвърлянето" + }, + "declineAndLeave": { + "message": "Отказване и напускане" + }, + "whyAmISeeingThis": { + "message": "Защо виждам това?" } } diff --git a/apps/desktop/src/locales/bn/messages.json b/apps/desktop/src/locales/bn/messages.json index f9f072f290e..07233587ba8 100644 --- a/apps/desktop/src/locales/bn/messages.json +++ b/apps/desktop/src/locales/bn/messages.json @@ -708,6 +708,18 @@ "addAttachment": { "message": "Add attachment" }, + "itemsTransferred": { + "message": "Items transferred" + }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "maxFileSizeSansPunctuation": { "message": "Maximum file size is 500 MB" }, @@ -908,6 +920,12 @@ "unexpectedError": { "message": "একটি অপ্রত্যাশিত ত্রুটি ঘটেছে।" }, + "unexpectedErrorShort": { + "message": "Unexpected error" + }, + "closeThisBitwardenWindow": { + "message": "Close this Bitwarden window and try again." + }, "itemInformation": { "message": "বস্তু তথ্য" }, @@ -1180,8 +1198,8 @@ "followUs": { "message": "Follow us" }, - "syncVault": { - "message": "ভল্ট সিঙ্ক করুন" + "syncNow": { + "message": "Sync now" }, "changeMasterPass": { "message": "মূল পাসওয়ার্ড পরিবর্তন" @@ -1757,8 +1775,11 @@ "exportFrom": { "message": "Export from" }, - "exportVault": { - "message": "ভল্ট রফতানি" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "ফাইলের ধরণ" @@ -2619,9 +2640,6 @@ "removedMasterPassword": { "message": "Master password removed" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "organizationName": { "message": "Organization name" }, @@ -3477,10 +3495,6 @@ "aliasDomain": { "message": "Alias domain" }, - "importData": { - "message": "Import data", - "description": "Used for the desktop menu item and the header of the import dialog" - }, "importError": { "message": "Import error" }, @@ -3886,6 +3900,75 @@ "fileSavedToDevice": { "message": "File saved to device. Manage from your device downloads." }, + "importantNotice": { + "message": "Important notice" + }, + "setupTwoStepLogin": { + "message": "Set up two-step login" + }, + "newDeviceVerificationNoticeContentPage1": { + "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." + }, + "newDeviceVerificationNoticeContentPage2": { + "message": "You can set up two-step login as an alternative way to protect your account or change your email to one you can access." + }, + "remindMeLater": { + "message": "Remind me later" + }, + "newDeviceVerificationNoticePageOneFormContent": { + "message": "Do you have reliable access to your email, $EMAIL$?", + "placeholders": { + "email": { + "content": "$1", + "example": "your_name@email.com" + } + } + }, + "newDeviceVerificationNoticePageOneEmailAccessNo": { + "message": "No, I do not" + }, + "newDeviceVerificationNoticePageOneEmailAccessYes": { + "message": "Yes, I can reliably access my email" + }, + "turnOnTwoStepLogin": { + "message": "Turn on two-step login" + }, + "changeAcctEmail": { + "message": "Change account email" + }, + "passkeyLogin": { + "message": "Log in with passkey?" + }, + "savePasskeyQuestion": { + "message": "Save passkey?" + }, + "saveNewPasskey": { + "message": "Save as new login" + }, + "savePasskeyNewLogin": { + "message": "Save passkey as new login" + }, + "noMatchingLoginsForSite": { + "message": "No matching logins for this site" + }, + "overwritePasskey": { + "message": "Overwrite passkey?" + }, + "unableToSavePasskey": { + "message": "Unable to save passkey" + }, + "alreadyContainsPasskey": { + "message": "This item already contains a passkey. Are you sure you want to overwrite the current passkey?" + }, + "passkeyAlreadyExists": { + "message": "A passkey already exists for this application." + }, + "applicationDoesNotSupportDuplicates": { + "message": "This application does not support duplicates." + }, + "closeThisWindow": { + "message": "Close this window" + }, "allowScreenshots": { "message": "Allow screen capture" }, @@ -4244,16 +4327,147 @@ "andMoreFeatures": { "message": "And more!" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "upgradeToPremium": { "message": "Upgrade to Premium" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { "message": "Timeout action" }, "sessionTimeoutHeader": { "message": "Session timeout" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Your organization has set the default session timeout to On system lock." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On restart." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On restart" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/desktop/src/locales/bs/messages.json b/apps/desktop/src/locales/bs/messages.json index b8798320a98..432f466ebe1 100644 --- a/apps/desktop/src/locales/bs/messages.json +++ b/apps/desktop/src/locales/bs/messages.json @@ -708,6 +708,18 @@ "addAttachment": { "message": "Add attachment" }, + "itemsTransferred": { + "message": "Items transferred" + }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "maxFileSizeSansPunctuation": { "message": "Maximum file size is 500 MB" }, @@ -908,6 +920,12 @@ "unexpectedError": { "message": "Neočekivana greška se dogodila." }, + "unexpectedErrorShort": { + "message": "Unexpected error" + }, + "closeThisBitwardenWindow": { + "message": "Close this Bitwarden window and try again." + }, "itemInformation": { "message": "Informacije o stavki" }, @@ -1180,8 +1198,8 @@ "followUs": { "message": "Pratite nas" }, - "syncVault": { - "message": "Sinhronizujte trezor sada" + "syncNow": { + "message": "Sync now" }, "changeMasterPass": { "message": "Promijenite glavnu lozinku" @@ -1757,8 +1775,11 @@ "exportFrom": { "message": "Export from" }, - "exportVault": { - "message": "Izvezi trezor" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "Format datoteke" @@ -2619,9 +2640,6 @@ "removedMasterPassword": { "message": "Master password removed" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "organizationName": { "message": "Organization name" }, @@ -3477,10 +3495,6 @@ "aliasDomain": { "message": "Alias domain" }, - "importData": { - "message": "Uvoz podataka", - "description": "Used for the desktop menu item and the header of the import dialog" - }, "importError": { "message": "Greška pri uvozu" }, @@ -3886,6 +3900,75 @@ "fileSavedToDevice": { "message": "File saved to device. Manage from your device downloads." }, + "importantNotice": { + "message": "Important notice" + }, + "setupTwoStepLogin": { + "message": "Set up two-step login" + }, + "newDeviceVerificationNoticeContentPage1": { + "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." + }, + "newDeviceVerificationNoticeContentPage2": { + "message": "You can set up two-step login as an alternative way to protect your account or change your email to one you can access." + }, + "remindMeLater": { + "message": "Remind me later" + }, + "newDeviceVerificationNoticePageOneFormContent": { + "message": "Do you have reliable access to your email, $EMAIL$?", + "placeholders": { + "email": { + "content": "$1", + "example": "your_name@email.com" + } + } + }, + "newDeviceVerificationNoticePageOneEmailAccessNo": { + "message": "No, I do not" + }, + "newDeviceVerificationNoticePageOneEmailAccessYes": { + "message": "Yes, I can reliably access my email" + }, + "turnOnTwoStepLogin": { + "message": "Turn on two-step login" + }, + "changeAcctEmail": { + "message": "Change account email" + }, + "passkeyLogin": { + "message": "Log in with passkey?" + }, + "savePasskeyQuestion": { + "message": "Save passkey?" + }, + "saveNewPasskey": { + "message": "Save as new login" + }, + "savePasskeyNewLogin": { + "message": "Save passkey as new login" + }, + "noMatchingLoginsForSite": { + "message": "No matching logins for this site" + }, + "overwritePasskey": { + "message": "Overwrite passkey?" + }, + "unableToSavePasskey": { + "message": "Unable to save passkey" + }, + "alreadyContainsPasskey": { + "message": "This item already contains a passkey. Are you sure you want to overwrite the current passkey?" + }, + "passkeyAlreadyExists": { + "message": "A passkey already exists for this application." + }, + "applicationDoesNotSupportDuplicates": { + "message": "This application does not support duplicates." + }, + "closeThisWindow": { + "message": "Close this window" + }, "allowScreenshots": { "message": "Allow screen capture" }, @@ -4244,16 +4327,147 @@ "andMoreFeatures": { "message": "And more!" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "upgradeToPremium": { "message": "Upgrade to Premium" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { "message": "Timeout action" }, "sessionTimeoutHeader": { "message": "Session timeout" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Your organization has set the default session timeout to On system lock." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On restart." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On restart" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/desktop/src/locales/ca/messages.json b/apps/desktop/src/locales/ca/messages.json index a7674cbc753..b91438416dd 100644 --- a/apps/desktop/src/locales/ca/messages.json +++ b/apps/desktop/src/locales/ca/messages.json @@ -708,6 +708,18 @@ "addAttachment": { "message": "Afig adjunt" }, + "itemsTransferred": { + "message": "Items transferred" + }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "maxFileSizeSansPunctuation": { "message": "La mida màxima del fitxer és de 500 MB" }, @@ -908,6 +920,12 @@ "unexpectedError": { "message": "S'ha produït un error inesperat." }, + "unexpectedErrorShort": { + "message": "Unexpected error" + }, + "closeThisBitwardenWindow": { + "message": "Close this Bitwarden window and try again." + }, "itemInformation": { "message": "Informació de l'element" }, @@ -1180,8 +1198,8 @@ "followUs": { "message": "Seguiu-nos" }, - "syncVault": { - "message": "Sincronitza la caixa forta" + "syncNow": { + "message": "Sync now" }, "changeMasterPass": { "message": "Canvia la contrasenya mestra" @@ -1757,8 +1775,11 @@ "exportFrom": { "message": "Exporta des de" }, - "exportVault": { - "message": "Exporta caixa forta" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "Format de fitxer" @@ -2619,9 +2640,6 @@ "removedMasterPassword": { "message": "S'ha suprimit la contrasenya mestra." }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "Ja no cal contrasenya mestra per als membres de la següent organització. Confirmeu el domini següent amb l'administrador de l'organització." - }, "organizationName": { "message": "Nom de l'organització" }, @@ -3477,10 +3495,6 @@ "aliasDomain": { "message": "Domini d'àlies" }, - "importData": { - "message": "Importa dades", - "description": "Used for the desktop menu item and the header of the import dialog" - }, "importError": { "message": "Error d'importació" }, @@ -3886,6 +3900,75 @@ "fileSavedToDevice": { "message": "File saved to device. Manage from your device downloads." }, + "importantNotice": { + "message": "Important notice" + }, + "setupTwoStepLogin": { + "message": "Set up two-step login" + }, + "newDeviceVerificationNoticeContentPage1": { + "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." + }, + "newDeviceVerificationNoticeContentPage2": { + "message": "You can set up two-step login as an alternative way to protect your account or change your email to one you can access." + }, + "remindMeLater": { + "message": "Remind me later" + }, + "newDeviceVerificationNoticePageOneFormContent": { + "message": "Do you have reliable access to your email, $EMAIL$?", + "placeholders": { + "email": { + "content": "$1", + "example": "your_name@email.com" + } + } + }, + "newDeviceVerificationNoticePageOneEmailAccessNo": { + "message": "No, I do not" + }, + "newDeviceVerificationNoticePageOneEmailAccessYes": { + "message": "Yes, I can reliably access my email" + }, + "turnOnTwoStepLogin": { + "message": "Turn on two-step login" + }, + "changeAcctEmail": { + "message": "Change account email" + }, + "passkeyLogin": { + "message": "Log in with passkey?" + }, + "savePasskeyQuestion": { + "message": "Save passkey?" + }, + "saveNewPasskey": { + "message": "Save as new login" + }, + "savePasskeyNewLogin": { + "message": "Save passkey as new login" + }, + "noMatchingLoginsForSite": { + "message": "No matching logins for this site" + }, + "overwritePasskey": { + "message": "Overwrite passkey?" + }, + "unableToSavePasskey": { + "message": "Unable to save passkey" + }, + "alreadyContainsPasskey": { + "message": "This item already contains a passkey. Are you sure you want to overwrite the current passkey?" + }, + "passkeyAlreadyExists": { + "message": "A passkey already exists for this application." + }, + "applicationDoesNotSupportDuplicates": { + "message": "This application does not support duplicates." + }, + "closeThisWindow": { + "message": "Close this window" + }, "allowScreenshots": { "message": "Permet capturar la pantalla" }, @@ -4244,16 +4327,147 @@ "andMoreFeatures": { "message": "And more!" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "upgradeToPremium": { "message": "Upgrade to Premium" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { "message": "Timeout action" }, "sessionTimeoutHeader": { "message": "Session timeout" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Your organization has set the default session timeout to On system lock." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On restart." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On restart" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/desktop/src/locales/cs/messages.json b/apps/desktop/src/locales/cs/messages.json index b93e5d0f513..dffe7b34f8f 100644 --- a/apps/desktop/src/locales/cs/messages.json +++ b/apps/desktop/src/locales/cs/messages.json @@ -708,6 +708,18 @@ "addAttachment": { "message": "Přidat přílohu" }, + "itemsTransferred": { + "message": "Převedené položky" + }, + "fixEncryption": { + "message": "Opravit šifrování" + }, + "fixEncryptionTooltip": { + "message": "Tento soubor používá zastaralou šifrovací metodu." + }, + "attachmentUpdated": { + "message": "Příloha byla aktualizována" + }, "maxFileSizeSansPunctuation": { "message": "Maximální velikost souboru je 500 MB" }, @@ -908,6 +920,12 @@ "unexpectedError": { "message": "Vyskytla se neočekávaná chyba." }, + "unexpectedErrorShort": { + "message": "Neočekávaná chyba" + }, + "closeThisBitwardenWindow": { + "message": "Zavřete toto okno Bitwardenu a zkuste to znovu." + }, "itemInformation": { "message": "Informace o položce" }, @@ -1180,8 +1198,8 @@ "followUs": { "message": "Sledujte nás" }, - "syncVault": { - "message": "Synchronizovat trezor" + "syncNow": { + "message": "Synchronizovat nyní" }, "changeMasterPass": { "message": "Změnit hlavní heslo" @@ -1757,8 +1775,11 @@ "exportFrom": { "message": "Exportovat z" }, - "exportVault": { - "message": "Exportovat trezor" + "export": { + "message": "Exportovat" + }, + "import": { + "message": "Importovat" }, "fileFormat": { "message": "Formát souboru" @@ -2619,9 +2640,6 @@ "removedMasterPassword": { "message": "Hlavní heslo bylo odebráno" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "Hlavní heslo již není vyžadováno pro členy následující organizace. Potvrďte níže uvedenou doménu u správce Vaší organizace." - }, "organizationName": { "message": "Název organizace" }, @@ -3477,10 +3495,6 @@ "aliasDomain": { "message": "Doména aliasu" }, - "importData": { - "message": "Importovat data", - "description": "Used for the desktop menu item and the header of the import dialog" - }, "importError": { "message": "Chyba importu" }, @@ -3886,6 +3900,75 @@ "fileSavedToDevice": { "message": "Soubor byl uložen. Můžete jej nalézt ve stažené složce v zařízení." }, + "importantNotice": { + "message": "Důležité upozornění" + }, + "setupTwoStepLogin": { + "message": "Nastavit dvoufázové přihlášení" + }, + "newDeviceVerificationNoticeContentPage1": { + "message": "Bitwarden odešle kód na e-mail Vašeho účtu pro ověření přihlášení z nových zařízení počínaje únorem 2025." + }, + "newDeviceVerificationNoticeContentPage2": { + "message": "Dvoufázové přihlášení můžete nastavit jako alternativní způsob ochrany Vašeho účtu nebo změnit svůj e-mail na ten, k němuž můžete přistupovat." + }, + "remindMeLater": { + "message": "Připomenout později" + }, + "newDeviceVerificationNoticePageOneFormContent": { + "message": "Máte spolehlivý přístup ke svému e-mailu $EMAIL$?", + "placeholders": { + "email": { + "content": "$1", + "example": "your_name@email.com" + } + } + }, + "newDeviceVerificationNoticePageOneEmailAccessNo": { + "message": "Ne, nemám" + }, + "newDeviceVerificationNoticePageOneEmailAccessYes": { + "message": "Ano, ke svému e-mailu mám přístup" + }, + "turnOnTwoStepLogin": { + "message": "Zapnout dvoufázové přihlášení" + }, + "changeAcctEmail": { + "message": "Změnit e-mail účtu" + }, + "passkeyLogin": { + "message": "Přihlásit se pomocí přístupového klíče?" + }, + "savePasskeyQuestion": { + "message": "Uložit přístupový klíč?" + }, + "saveNewPasskey": { + "message": "Uložit jako nové přihlašovací údaje" + }, + "savePasskeyNewLogin": { + "message": "Uložit přístupový klíč jako nové přihlášení" + }, + "noMatchingLoginsForSite": { + "message": "Žádné odpovídající přihlašovací údaje pro tento web" + }, + "overwritePasskey": { + "message": "Přepsat přístupový klíč?" + }, + "unableToSavePasskey": { + "message": "Nelze uložit přístupový klíč" + }, + "alreadyContainsPasskey": { + "message": "Tato položka již obsahuje přístupový klíč. Jste si jisti, že chcete přepsat aktuální přístupový klíč?" + }, + "passkeyAlreadyExists": { + "message": "Přístupový klíč pro tuto aplikaci již existuje." + }, + "applicationDoesNotSupportDuplicates": { + "message": "Tato aplikace nepodporuje duplikáty." + }, + "closeThisWindow": { + "message": "Zavřít toto okno" + }, "allowScreenshots": { "message": "Povolit záznam obrazovky" }, @@ -4244,16 +4327,147 @@ "andMoreFeatures": { "message": "A ještě více!" }, - "planDescPremium": { - "message": "Dokončit online zabezpečení" + "advancedOnlineSecurity": { + "message": "Pokročilé zabezpečení online" }, "upgradeToPremium": { "message": "Aktualizovat na Premium" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Vaše organizace již k přihlášení do Bitwardenu nepoužívá hlavní hesla. Chcete-li pokračovat, ověřte organizaci a doménu." + }, + "continueWithLogIn": { + "message": "Pokračovat s přihlášením" + }, + "doNotContinue": { + "message": "Nepokračovat" + }, + "domain": { + "message": "Doména" + }, + "keyConnectorDomainTooltip": { + "message": "Tato doména uloží šifrovací klíče Vašeho účtu, takže se ujistěte, že jí věříte. Pokud si nejste jisti, kontaktujte Vašeho správce." + }, + "verifyYourOrganization": { + "message": "Ověřte svou organizaci pro přihlášení" + }, + "organizationVerified": { + "message": "Organizace byla ověřena" + }, + "domainVerified": { + "message": "Doména byla ověřena" + }, + "leaveOrganizationContent": { + "message": "Pokud neověříte svou organizaci, Váš přístup k organizaci bude zrušen." + }, + "leaveNow": { + "message": "Opustit hned" + }, + "verifyYourDomainToLogin": { + "message": "Ověřte svou doménu pro přihlášení" + }, + "verifyYourDomainDescription": { + "message": "Chcete-li pokračovat v přihlášení, ověřte tuto doménu." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "Chcete-li pokračovat v přihlášení, ověřte organizaci a doménu." + }, "sessionTimeoutSettingsAction": { "message": "Akce vypršení časového limitu" }, "sessionTimeoutHeader": { "message": "Časový limit relace" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "Tato nastavení je spravováno Vaší organizací." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Vaše organizace nastavila maximální časový limit relace na $HOURS$ hodin a $MINUTES$ minut.", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Vaše organizace nastavila výchozí časový limit relace na Při uzamknutí systému." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Vaše organizace nastavila výchozí časový limit relace na Při restartu." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximální časový limit nesmí překročit $HOURS$ hodin a $MINUTES$ minut", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "Při restartu" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Nastavte metodu odemknutí, abyste změnili akci při vypršení časového limitu" + }, + "upgrade": { + "message": "Aktualizovat" + }, + "leaveConfirmationDialogTitle": { + "message": "Opravdu chcete odejít?" + }, + "leaveConfirmationDialogContentOne": { + "message": "Odmítnutím zůstanou Vaše osobní položky ve Vašem účtu, ale ztratíte přístup ke sdíleným položkám a funkcím organizace." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Obraťte se na svého správce, abyste znovu získali přístup." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Opustit $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "Jak mohu spravovat svůj trezor?" + }, + "transferItemsToOrganizationTitle": { + "message": "Přenést položky do $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ vyžaduje, aby byly všechny položky vlastněny organizací z důvodu bezpečnosti a shody. Klepnutím na tlačítko pro převod vlastnictví Vašich položek.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Přijmout převod" + }, + "declineAndLeave": { + "message": "Odmítnout a odejít" + }, + "whyAmISeeingThis": { + "message": "Proč se mi toto zobrazuje?" } } diff --git a/apps/desktop/src/locales/cy/messages.json b/apps/desktop/src/locales/cy/messages.json index 5a00ad90bbd..45461838f58 100644 --- a/apps/desktop/src/locales/cy/messages.json +++ b/apps/desktop/src/locales/cy/messages.json @@ -708,6 +708,18 @@ "addAttachment": { "message": "Add attachment" }, + "itemsTransferred": { + "message": "Items transferred" + }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "maxFileSizeSansPunctuation": { "message": "Maximum file size is 500 MB" }, @@ -908,6 +920,12 @@ "unexpectedError": { "message": "An unexpected error has occurred." }, + "unexpectedErrorShort": { + "message": "Unexpected error" + }, + "closeThisBitwardenWindow": { + "message": "Close this Bitwarden window and try again." + }, "itemInformation": { "message": "Item information" }, @@ -1180,8 +1198,8 @@ "followUs": { "message": "Follow us" }, - "syncVault": { - "message": "Sync vault" + "syncNow": { + "message": "Sync now" }, "changeMasterPass": { "message": "Change master password" @@ -1757,8 +1775,11 @@ "exportFrom": { "message": "Export from" }, - "exportVault": { - "message": "Export vault" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "File format" @@ -2619,9 +2640,6 @@ "removedMasterPassword": { "message": "Master password removed" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "organizationName": { "message": "Organization name" }, @@ -3477,10 +3495,6 @@ "aliasDomain": { "message": "Alias domain" }, - "importData": { - "message": "Import data", - "description": "Used for the desktop menu item and the header of the import dialog" - }, "importError": { "message": "Import error" }, @@ -3886,6 +3900,75 @@ "fileSavedToDevice": { "message": "File saved to device. Manage from your device downloads." }, + "importantNotice": { + "message": "Important notice" + }, + "setupTwoStepLogin": { + "message": "Set up two-step login" + }, + "newDeviceVerificationNoticeContentPage1": { + "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." + }, + "newDeviceVerificationNoticeContentPage2": { + "message": "You can set up two-step login as an alternative way to protect your account or change your email to one you can access." + }, + "remindMeLater": { + "message": "Remind me later" + }, + "newDeviceVerificationNoticePageOneFormContent": { + "message": "Do you have reliable access to your email, $EMAIL$?", + "placeholders": { + "email": { + "content": "$1", + "example": "your_name@email.com" + } + } + }, + "newDeviceVerificationNoticePageOneEmailAccessNo": { + "message": "No, I do not" + }, + "newDeviceVerificationNoticePageOneEmailAccessYes": { + "message": "Yes, I can reliably access my email" + }, + "turnOnTwoStepLogin": { + "message": "Turn on two-step login" + }, + "changeAcctEmail": { + "message": "Change account email" + }, + "passkeyLogin": { + "message": "Log in with passkey?" + }, + "savePasskeyQuestion": { + "message": "Save passkey?" + }, + "saveNewPasskey": { + "message": "Save as new login" + }, + "savePasskeyNewLogin": { + "message": "Save passkey as new login" + }, + "noMatchingLoginsForSite": { + "message": "No matching logins for this site" + }, + "overwritePasskey": { + "message": "Overwrite passkey?" + }, + "unableToSavePasskey": { + "message": "Unable to save passkey" + }, + "alreadyContainsPasskey": { + "message": "This item already contains a passkey. Are you sure you want to overwrite the current passkey?" + }, + "passkeyAlreadyExists": { + "message": "A passkey already exists for this application." + }, + "applicationDoesNotSupportDuplicates": { + "message": "This application does not support duplicates." + }, + "closeThisWindow": { + "message": "Close this window" + }, "allowScreenshots": { "message": "Allow screen capture" }, @@ -4244,16 +4327,147 @@ "andMoreFeatures": { "message": "And more!" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "upgradeToPremium": { "message": "Upgrade to Premium" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { "message": "Timeout action" }, "sessionTimeoutHeader": { "message": "Session timeout" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Your organization has set the default session timeout to On system lock." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On restart." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On restart" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/desktop/src/locales/da/messages.json b/apps/desktop/src/locales/da/messages.json index 622c9e9187d..38396658400 100644 --- a/apps/desktop/src/locales/da/messages.json +++ b/apps/desktop/src/locales/da/messages.json @@ -708,6 +708,18 @@ "addAttachment": { "message": "Add attachment" }, + "itemsTransferred": { + "message": "Items transferred" + }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "maxFileSizeSansPunctuation": { "message": "Maximum file size is 500 MB" }, @@ -908,6 +920,12 @@ "unexpectedError": { "message": "En uventet fejl opstod." }, + "unexpectedErrorShort": { + "message": "Unexpected error" + }, + "closeThisBitwardenWindow": { + "message": "Close this Bitwarden window and try again." + }, "itemInformation": { "message": "Emneinformation" }, @@ -1180,8 +1198,8 @@ "followUs": { "message": "Følg os" }, - "syncVault": { - "message": "Synk boks" + "syncNow": { + "message": "Sync now" }, "changeMasterPass": { "message": "Skift hovedadgangskode" @@ -1757,8 +1775,11 @@ "exportFrom": { "message": "Eksportér fra" }, - "exportVault": { - "message": "Eksportér boks" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "Filformat" @@ -2619,9 +2640,6 @@ "removedMasterPassword": { "message": "Hovedadgangskode fjernet." }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "organizationName": { "message": "Organization name" }, @@ -3477,10 +3495,6 @@ "aliasDomain": { "message": "Aliasdomæne" }, - "importData": { - "message": "Dataimport", - "description": "Used for the desktop menu item and the header of the import dialog" - }, "importError": { "message": "Importfejl" }, @@ -3886,6 +3900,75 @@ "fileSavedToDevice": { "message": "Fil gemt på enheden. Håndtér fra enhedens downloads." }, + "importantNotice": { + "message": "Important notice" + }, + "setupTwoStepLogin": { + "message": "Set up two-step login" + }, + "newDeviceVerificationNoticeContentPage1": { + "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." + }, + "newDeviceVerificationNoticeContentPage2": { + "message": "You can set up two-step login as an alternative way to protect your account or change your email to one you can access." + }, + "remindMeLater": { + "message": "Remind me later" + }, + "newDeviceVerificationNoticePageOneFormContent": { + "message": "Do you have reliable access to your email, $EMAIL$?", + "placeholders": { + "email": { + "content": "$1", + "example": "your_name@email.com" + } + } + }, + "newDeviceVerificationNoticePageOneEmailAccessNo": { + "message": "No, I do not" + }, + "newDeviceVerificationNoticePageOneEmailAccessYes": { + "message": "Yes, I can reliably access my email" + }, + "turnOnTwoStepLogin": { + "message": "Turn on two-step login" + }, + "changeAcctEmail": { + "message": "Change account email" + }, + "passkeyLogin": { + "message": "Log in with passkey?" + }, + "savePasskeyQuestion": { + "message": "Save passkey?" + }, + "saveNewPasskey": { + "message": "Save as new login" + }, + "savePasskeyNewLogin": { + "message": "Save passkey as new login" + }, + "noMatchingLoginsForSite": { + "message": "No matching logins for this site" + }, + "overwritePasskey": { + "message": "Overwrite passkey?" + }, + "unableToSavePasskey": { + "message": "Unable to save passkey" + }, + "alreadyContainsPasskey": { + "message": "This item already contains a passkey. Are you sure you want to overwrite the current passkey?" + }, + "passkeyAlreadyExists": { + "message": "A passkey already exists for this application." + }, + "applicationDoesNotSupportDuplicates": { + "message": "This application does not support duplicates." + }, + "closeThisWindow": { + "message": "Close this window" + }, "allowScreenshots": { "message": "Allow screen capture" }, @@ -4244,16 +4327,147 @@ "andMoreFeatures": { "message": "And more!" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "upgradeToPremium": { "message": "Upgrade to Premium" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { "message": "Timeout action" }, "sessionTimeoutHeader": { "message": "Session timeout" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Your organization has set the default session timeout to On system lock." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On restart." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On restart" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/desktop/src/locales/de/messages.json b/apps/desktop/src/locales/de/messages.json index 2743ec21c8a..52e92a9dbfe 100644 --- a/apps/desktop/src/locales/de/messages.json +++ b/apps/desktop/src/locales/de/messages.json @@ -70,7 +70,7 @@ } }, "noEditPermissions": { - "message": "Keine Berechtigung zum Bearbeiten dieses Eintrags" + "message": "Du bist nicht berechtigt, diesen Eintrag zu bearbeiten" }, "welcomeBack": { "message": "Willkommen zurück" @@ -708,6 +708,18 @@ "addAttachment": { "message": "Anhang hinzufügen" }, + "itemsTransferred": { + "message": "Einträge wurden übertragen" + }, + "fixEncryption": { + "message": "Verschlüsselung reparieren" + }, + "fixEncryptionTooltip": { + "message": "Diese Datei verwendet eine veraltete Verschlüsselungsmethode." + }, + "attachmentUpdated": { + "message": "Anhang aktualisiert" + }, "maxFileSizeSansPunctuation": { "message": "Die maximale Dateigröße beträgt 500 MB" }, @@ -908,6 +920,12 @@ "unexpectedError": { "message": "Ein unerwarteter Fehler ist aufgetreten." }, + "unexpectedErrorShort": { + "message": "Unerwarteter Fehler" + }, + "closeThisBitwardenWindow": { + "message": "Schließe dieses Bitwarden-Fenster und versuche es erneut." + }, "itemInformation": { "message": "Eintragsinformationen" }, @@ -1094,22 +1112,22 @@ "message": "Mehr erfahren" }, "migrationsFailed": { - "message": "An error occurred updating the encryption settings." + "message": "Beim Aktualisieren der Verschlüsselungseinstellungen ist ein Fehler aufgetreten." }, "updateEncryptionSettingsTitle": { - "message": "Update your encryption settings" + "message": "Aktualisiere deine Verschlüsselungseinstellungen" }, "updateEncryptionSettingsDesc": { - "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + "message": "Die neuen empfohlenen Verschlüsselungseinstellungen verbessern deine Kontosicherheit. Gib dein Master-Passwort ein, um sie zu aktualisieren." }, "confirmIdentityToContinue": { - "message": "Confirm your identity to continue" + "message": "Bestätige deine Identität, um fortzufahren" }, "enterYourMasterPassword": { - "message": "Enter your master password" + "message": "Gib dein Master-Passwort ein" }, "updateSettings": { - "message": "Update settings" + "message": "Einstellungen aktualisieren" }, "featureUnavailable": { "message": "Funktion nicht verfügbar" @@ -1180,8 +1198,8 @@ "followUs": { "message": "Folge uns" }, - "syncVault": { - "message": "Tresor synchronisieren" + "syncNow": { + "message": "Jetzt synchronisieren" }, "changeMasterPass": { "message": "Master-Passwort ändern" @@ -1757,8 +1775,11 @@ "exportFrom": { "message": "Export aus" }, - "exportVault": { - "message": "Tresor exportieren" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "Dateiformat" @@ -2619,9 +2640,6 @@ "removedMasterPassword": { "message": "Master-Passwort entfernt" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "Für Mitglieder der folgenden Organisation ist kein Master-Passwort mehr erforderlich. Bitte bestätige die folgende Domain bei deinem Organisations-Administrator." - }, "organizationName": { "message": "Name der Organisation" }, @@ -3477,10 +3495,6 @@ "aliasDomain": { "message": "Alias-Domain" }, - "importData": { - "message": "Daten importieren", - "description": "Used for the desktop menu item and the header of the import dialog" - }, "importError": { "message": "Importfehler" }, @@ -3886,6 +3900,75 @@ "fileSavedToDevice": { "message": "Datei auf Gerät gespeichert. Greife darauf über die Downloads deines Geräts zu." }, + "importantNotice": { + "message": "Wichtiger Hinweis" + }, + "setupTwoStepLogin": { + "message": "Zwei-Faktor-Authentifizierung einrichten" + }, + "newDeviceVerificationNoticeContentPage1": { + "message": "Ab Februar 2025 wird Bitwarden einen Code an deine Konto-E-Mail-Adresse senden, um Anmeldungen von neuen Geräten zu verifizieren." + }, + "newDeviceVerificationNoticeContentPage2": { + "message": "Du kannst die Zwei-Faktor-Authentifizierung als eine alternative Methode einrichten, um dein Konto zu schützen, oder deine E-Mail-Adresse zu einer anderen ändern, auf die du zugreifen kannst." + }, + "remindMeLater": { + "message": "Erinnere mich später" + }, + "newDeviceVerificationNoticePageOneFormContent": { + "message": "Hast du zuverlässigen Zugriff auf deine E-Mail-Adresse $EMAIL$?", + "placeholders": { + "email": { + "content": "$1", + "example": "your_name@email.com" + } + } + }, + "newDeviceVerificationNoticePageOneEmailAccessNo": { + "message": "Nein, habe ich nicht" + }, + "newDeviceVerificationNoticePageOneEmailAccessYes": { + "message": "Ja, ich kann zuverlässig auf meine E-Mails zugreifen" + }, + "turnOnTwoStepLogin": { + "message": "Zwei-Faktor-Authentifizierung aktivieren" + }, + "changeAcctEmail": { + "message": "E-Mail-Adresse des Kontos ändern" + }, + "passkeyLogin": { + "message": "Mit Passkey anmelden?" + }, + "savePasskeyQuestion": { + "message": "Passkey speichern?" + }, + "saveNewPasskey": { + "message": "Als neue Zugangsdaten speichern" + }, + "savePasskeyNewLogin": { + "message": "Passkey als neue Zugangsdaten speichern" + }, + "noMatchingLoginsForSite": { + "message": "Keine passenden Zugangsdaten für diese Seite" + }, + "overwritePasskey": { + "message": "Passkey überschreiben?" + }, + "unableToSavePasskey": { + "message": "Passkey konnte nicht gespeichert werden" + }, + "alreadyContainsPasskey": { + "message": "Dieser Eintrag enthält bereits einen Passkey. Bist du sicher, dass du den aktuellen Passkey überschreiben möchtest?" + }, + "passkeyAlreadyExists": { + "message": "Für diese Anwendung existiert bereits ein Passkey." + }, + "applicationDoesNotSupportDuplicates": { + "message": "Diese Anwendung unterstützt keine mehrfachen Instanzen." + }, + "closeThisWindow": { + "message": "Dieses Fenster schließen" + }, "allowScreenshots": { "message": "Bildschirmaufnahme erlauben" }, @@ -4212,7 +4295,7 @@ "message": "Eintrag wurde archiviert" }, "itemWasUnarchived": { - "message": "Eintrag wurde wiederhergestellt" + "message": "Eintrag wird nicht mehr archiviert" }, "archiveItem": { "message": "Eintrag archivieren" @@ -4244,16 +4327,147 @@ "andMoreFeatures": { "message": "Und vieles mehr!" }, - "planDescPremium": { - "message": "Kompletter Online-Sicherheitsplan" + "advancedOnlineSecurity": { + "message": "Erweiterte Online-Sicherheit" }, "upgradeToPremium": { "message": "Auf Premium upgraden" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Deine Organisation verwendet keine Master-Passwörter mehr, um sich bei Bitwarden anzumelden. Verifiziere die Organisation und Domain, um fortzufahren." + }, + "continueWithLogIn": { + "message": "Mit der Anmeldung fortfahren" + }, + "doNotContinue": { + "message": "Nicht fortfahren" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "Diese Domain speichert die Verschlüsselungsschlüssel deines Kontos. Stelle daher sicher, dass du ihr vertraust. Wenn du dir nicht sicher bist, wende dich an deinen Administrator." + }, + "verifyYourOrganization": { + "message": "Verifiziere deine Organisation, um dich anzumelden" + }, + "organizationVerified": { + "message": "Organisation verifiziert" + }, + "domainVerified": { + "message": "Domain verifiziert" + }, + "leaveOrganizationContent": { + "message": "Wenn du deine Organisation nicht verifizierst, wird dein Zugriff auf die Organisation widerrufen." + }, + "leaveNow": { + "message": "Jetzt verlassen" + }, + "verifyYourDomainToLogin": { + "message": "Verifiziere deine Domain, um dich anzumelden" + }, + "verifyYourDomainDescription": { + "message": "Verifiziere diese Domain, um mit der Anmeldung fortzufahren." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "Um mit der Anmeldung fortzufahren, verifiziere die Organisation und Domain." + }, "sessionTimeoutSettingsAction": { "message": "Timeout-Aktion" }, "sessionTimeoutHeader": { "message": "Sitzungs-Timeout" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "Diese Einstellung wird von deiner Organisation verwaltet." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Deine Organisation hat das maximale Sitzungs-Timeout auf $HOURS$ Stunde(n) und $MINUTES$ Minute(n) festgelegt.", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Deine Organisation hat das Standard-Sitzungs-Timeout auf \"Wenn System gesperrt\" gesetzt." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Deine Organisation hat das Standard-Sitzungs-Timeout auf \"Beim Neustart der App\" gesetzt." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Das maximale Timeout darf $HOURS$ Stunde(n) und $MINUTES$ Minute(n) nicht überschreiten", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "Beim Neustart der App" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Stell eine Entsperrmethode ein, um deine Timeout-Aktion zu ändern" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Bist du sicher, dass du gehen willst?" + }, + "leaveConfirmationDialogContentOne": { + "message": "Wenn du ablehnst, bleiben deine persönlichen Einträge in deinem Konto erhalten, aber du wirst den Zugriff auf geteilte Einträge und Organisationsfunktionen verlieren." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Kontaktiere deinen Administrator, um wieder Zugriff zu erhalten." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "$ORGANIZATION$ verlassen", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "Wie kann ich meinen Tresor verwalten?" + }, + "transferItemsToOrganizationTitle": { + "message": "Einträge zu $ORGANIZATION$ übertragen", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ erfordert zur Sicherheit und Compliance, dass alle Einträge der Organisation gehören. Klicke auf Akzeptieren, um den Besitz deiner Einträge zu übertragen.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Übertragung annehmen" + }, + "declineAndLeave": { + "message": "Ablehnen und verlassen" + }, + "whyAmISeeingThis": { + "message": "Warum wird mir das angezeigt?" } } diff --git a/apps/desktop/src/locales/el/messages.json b/apps/desktop/src/locales/el/messages.json index bd8269db4ea..cc6d1909e94 100644 --- a/apps/desktop/src/locales/el/messages.json +++ b/apps/desktop/src/locales/el/messages.json @@ -708,6 +708,18 @@ "addAttachment": { "message": "Προσθήκη συνημμένου" }, + "itemsTransferred": { + "message": "Items transferred" + }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "maxFileSizeSansPunctuation": { "message": "Το μέγιστο μέγεθος αρχείου είναι 500 MB" }, @@ -908,6 +920,12 @@ "unexpectedError": { "message": "Παρουσιάστηκε ένα μη αναμενόμενο σφάλμα." }, + "unexpectedErrorShort": { + "message": "Unexpected error" + }, + "closeThisBitwardenWindow": { + "message": "Close this Bitwarden window and try again." + }, "itemInformation": { "message": "Πληροφορίες αντικειμένου" }, @@ -1180,8 +1198,8 @@ "followUs": { "message": "Ακολουθήστε μας" }, - "syncVault": { - "message": "Συγχρονισμός κρύπτης" + "syncNow": { + "message": "Sync now" }, "changeMasterPass": { "message": "Αλλαγή κύριου κωδικού πρόσβασης" @@ -1757,8 +1775,11 @@ "exportFrom": { "message": "Εξαγωγή από" }, - "exportVault": { - "message": "Εξαγωγή κρύπτης" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "Τύπος αρχείου" @@ -2619,9 +2640,6 @@ "removedMasterPassword": { "message": "Ο κύριος κωδικός αφαιρέθηκε" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "organizationName": { "message": "Organization name" }, @@ -3477,10 +3495,6 @@ "aliasDomain": { "message": "Ψευδώνυμο τομέα" }, - "importData": { - "message": "Εισαγωγή δεδομένων", - "description": "Used for the desktop menu item and the header of the import dialog" - }, "importError": { "message": "Σφάλμα κατά την εισαγωγή" }, @@ -3886,6 +3900,75 @@ "fileSavedToDevice": { "message": "Το αρχείο αποθηκεύτηκε στη συσκευή. Διαχείριση από τις λήψεις της συσκευής σας." }, + "importantNotice": { + "message": "Important notice" + }, + "setupTwoStepLogin": { + "message": "Set up two-step login" + }, + "newDeviceVerificationNoticeContentPage1": { + "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." + }, + "newDeviceVerificationNoticeContentPage2": { + "message": "You can set up two-step login as an alternative way to protect your account or change your email to one you can access." + }, + "remindMeLater": { + "message": "Remind me later" + }, + "newDeviceVerificationNoticePageOneFormContent": { + "message": "Do you have reliable access to your email, $EMAIL$?", + "placeholders": { + "email": { + "content": "$1", + "example": "your_name@email.com" + } + } + }, + "newDeviceVerificationNoticePageOneEmailAccessNo": { + "message": "No, I do not" + }, + "newDeviceVerificationNoticePageOneEmailAccessYes": { + "message": "Yes, I can reliably access my email" + }, + "turnOnTwoStepLogin": { + "message": "Turn on two-step login" + }, + "changeAcctEmail": { + "message": "Change account email" + }, + "passkeyLogin": { + "message": "Log in with passkey?" + }, + "savePasskeyQuestion": { + "message": "Save passkey?" + }, + "saveNewPasskey": { + "message": "Save as new login" + }, + "savePasskeyNewLogin": { + "message": "Save passkey as new login" + }, + "noMatchingLoginsForSite": { + "message": "No matching logins for this site" + }, + "overwritePasskey": { + "message": "Overwrite passkey?" + }, + "unableToSavePasskey": { + "message": "Unable to save passkey" + }, + "alreadyContainsPasskey": { + "message": "This item already contains a passkey. Are you sure you want to overwrite the current passkey?" + }, + "passkeyAlreadyExists": { + "message": "A passkey already exists for this application." + }, + "applicationDoesNotSupportDuplicates": { + "message": "This application does not support duplicates." + }, + "closeThisWindow": { + "message": "Close this window" + }, "allowScreenshots": { "message": "Allow screen capture" }, @@ -4244,16 +4327,147 @@ "andMoreFeatures": { "message": "And more!" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "upgradeToPremium": { "message": "Upgrade to Premium" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { "message": "Timeout action" }, "sessionTimeoutHeader": { "message": "Session timeout" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Your organization has set the default session timeout to On system lock." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On restart." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On restart" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/desktop/src/locales/en_GB/messages.json b/apps/desktop/src/locales/en_GB/messages.json index 4e1f8569caf..66840673b77 100644 --- a/apps/desktop/src/locales/en_GB/messages.json +++ b/apps/desktop/src/locales/en_GB/messages.json @@ -708,6 +708,18 @@ "addAttachment": { "message": "Add attachment" }, + "itemsTransferred": { + "message": "Items transferred" + }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "maxFileSizeSansPunctuation": { "message": "Maximum file size is 500 MB" }, @@ -908,6 +920,12 @@ "unexpectedError": { "message": "An unexpected error has occurred." }, + "unexpectedErrorShort": { + "message": "Unexpected error" + }, + "closeThisBitwardenWindow": { + "message": "Close this Bitwarden window and try again." + }, "itemInformation": { "message": "Item information" }, @@ -1180,8 +1198,8 @@ "followUs": { "message": "Follow us" }, - "syncVault": { - "message": "Sync vault" + "syncNow": { + "message": "Sync now" }, "changeMasterPass": { "message": "Change master password" @@ -1757,8 +1775,11 @@ "exportFrom": { "message": "Export from" }, - "exportVault": { - "message": "Export vault" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "File format" @@ -2619,9 +2640,6 @@ "removedMasterPassword": { "message": "Master password removed" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organisation. Please confirm the domain below with your organisation administrator." - }, "organizationName": { "message": "Organisation name" }, @@ -3477,10 +3495,6 @@ "aliasDomain": { "message": "Alias domain" }, - "importData": { - "message": "Import data", - "description": "Used for the desktop menu item and the header of the import dialog" - }, "importError": { "message": "Import error" }, @@ -3886,6 +3900,75 @@ "fileSavedToDevice": { "message": "File saved to device. Manage from your device downloads." }, + "importantNotice": { + "message": "Important notice" + }, + "setupTwoStepLogin": { + "message": "Set up two-step login" + }, + "newDeviceVerificationNoticeContentPage1": { + "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." + }, + "newDeviceVerificationNoticeContentPage2": { + "message": "You can set up two-step login as an alternative way to protect your account or change your email to one you can access." + }, + "remindMeLater": { + "message": "Remind me later" + }, + "newDeviceVerificationNoticePageOneFormContent": { + "message": "Do you have reliable access to your email, $EMAIL$?", + "placeholders": { + "email": { + "content": "$1", + "example": "your_name@email.com" + } + } + }, + "newDeviceVerificationNoticePageOneEmailAccessNo": { + "message": "No, I do not" + }, + "newDeviceVerificationNoticePageOneEmailAccessYes": { + "message": "Yes, I can reliably access my email" + }, + "turnOnTwoStepLogin": { + "message": "Turn on two-step login" + }, + "changeAcctEmail": { + "message": "Change account email" + }, + "passkeyLogin": { + "message": "Log in with passkey?" + }, + "savePasskeyQuestion": { + "message": "Save passkey?" + }, + "saveNewPasskey": { + "message": "Save as new login" + }, + "savePasskeyNewLogin": { + "message": "Save passkey as new login" + }, + "noMatchingLoginsForSite": { + "message": "No matching logins for this site" + }, + "overwritePasskey": { + "message": "Overwrite passkey?" + }, + "unableToSavePasskey": { + "message": "Unable to save passkey" + }, + "alreadyContainsPasskey": { + "message": "This item already contains a passkey. Are you sure you want to overwrite the current passkey?" + }, + "passkeyAlreadyExists": { + "message": "A passkey already exists for this application." + }, + "applicationDoesNotSupportDuplicates": { + "message": "This application does not support duplicates." + }, + "closeThisWindow": { + "message": "Close this window" + }, "allowScreenshots": { "message": "Allow screen capture" }, @@ -4244,16 +4327,147 @@ "andMoreFeatures": { "message": "And more!" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "upgradeToPremium": { "message": "Upgrade to Premium" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organisation is no longer using master passwords to log into Bitwarden. To continue, verify the organisation and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organisation to log in" + }, + "organizationVerified": { + "message": "Organisation verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organisation, your access to the organisation will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organisation and domain." + }, "sessionTimeoutSettingsAction": { "message": "Timeout action" }, "sessionTimeoutHeader": { "message": "Session timeout" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organisation." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organisation has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Your organisation has set the default session timeout to On system lock." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organisation has set the default session timeout to On restart." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On restart" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organisation features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organisation for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/desktop/src/locales/en_IN/messages.json b/apps/desktop/src/locales/en_IN/messages.json index 1413debe49c..50e6671b2fa 100644 --- a/apps/desktop/src/locales/en_IN/messages.json +++ b/apps/desktop/src/locales/en_IN/messages.json @@ -708,6 +708,18 @@ "addAttachment": { "message": "Add attachment" }, + "itemsTransferred": { + "message": "Items transferred" + }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "maxFileSizeSansPunctuation": { "message": "Maximum file size is 500 MB" }, @@ -908,6 +920,12 @@ "unexpectedError": { "message": "An unexpected error has occurred." }, + "unexpectedErrorShort": { + "message": "Unexpected error" + }, + "closeThisBitwardenWindow": { + "message": "Close this Bitwarden window and try again." + }, "itemInformation": { "message": "Item information" }, @@ -1180,8 +1198,8 @@ "followUs": { "message": "Follow us" }, - "syncVault": { - "message": "Sync vault" + "syncNow": { + "message": "Sync now" }, "changeMasterPass": { "message": "Change master password" @@ -1757,8 +1775,11 @@ "exportFrom": { "message": "Export from" }, - "exportVault": { - "message": "Export vault" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "File format" @@ -2619,9 +2640,6 @@ "removedMasterPassword": { "message": "Master password removed" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organisation. Please confirm the domain below with your organisation administrator." - }, "organizationName": { "message": "Organisation name" }, @@ -3477,10 +3495,6 @@ "aliasDomain": { "message": "Alias domain" }, - "importData": { - "message": "Import data", - "description": "Used for the desktop menu item and the header of the import dialog" - }, "importError": { "message": "Import error" }, @@ -3886,6 +3900,75 @@ "fileSavedToDevice": { "message": "File saved to device. Manage from your device downloads." }, + "importantNotice": { + "message": "Important notice" + }, + "setupTwoStepLogin": { + "message": "Set up two-step login" + }, + "newDeviceVerificationNoticeContentPage1": { + "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." + }, + "newDeviceVerificationNoticeContentPage2": { + "message": "You can set up two-step login as an alternative way to protect your account or change your email to one you can access." + }, + "remindMeLater": { + "message": "Remind me later" + }, + "newDeviceVerificationNoticePageOneFormContent": { + "message": "Do you have reliable access to your email, $EMAIL$?", + "placeholders": { + "email": { + "content": "$1", + "example": "your_name@email.com" + } + } + }, + "newDeviceVerificationNoticePageOneEmailAccessNo": { + "message": "No, I do not" + }, + "newDeviceVerificationNoticePageOneEmailAccessYes": { + "message": "Yes, I can reliably access my email" + }, + "turnOnTwoStepLogin": { + "message": "Turn on two-step login" + }, + "changeAcctEmail": { + "message": "Change account email" + }, + "passkeyLogin": { + "message": "Log in with passkey?" + }, + "savePasskeyQuestion": { + "message": "Save passkey?" + }, + "saveNewPasskey": { + "message": "Save as new login" + }, + "savePasskeyNewLogin": { + "message": "Save passkey as new login" + }, + "noMatchingLoginsForSite": { + "message": "No matching logins for this site" + }, + "overwritePasskey": { + "message": "Overwrite passkey?" + }, + "unableToSavePasskey": { + "message": "Unable to save passkey" + }, + "alreadyContainsPasskey": { + "message": "This item already contains a passkey. Are you sure you want to overwrite the current passkey?" + }, + "passkeyAlreadyExists": { + "message": "A passkey already exists for this application." + }, + "applicationDoesNotSupportDuplicates": { + "message": "This application does not support duplicates." + }, + "closeThisWindow": { + "message": "Close this window" + }, "allowScreenshots": { "message": "Allow screen capture" }, @@ -4244,16 +4327,147 @@ "andMoreFeatures": { "message": "And more!" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "upgradeToPremium": { "message": "Upgrade to Premium" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organisation is no longer using master passwords to log into Bitwarden. To continue, verify the organisation and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organisation to log in" + }, + "organizationVerified": { + "message": "Organisation verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organisation, your access to the organisation will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organisation and domain." + }, "sessionTimeoutSettingsAction": { "message": "Timeout action" }, "sessionTimeoutHeader": { "message": "Session timeout" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organisation." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organisation has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Your organisation has set the default session timeout to On system lock." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organisation has set the default session timeout to On restart." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On restart" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organisation features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organisation for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/desktop/src/locales/eo/messages.json b/apps/desktop/src/locales/eo/messages.json index 5cb6995eeb6..a9ed68bffd8 100644 --- a/apps/desktop/src/locales/eo/messages.json +++ b/apps/desktop/src/locales/eo/messages.json @@ -708,6 +708,18 @@ "addAttachment": { "message": "Add attachment" }, + "itemsTransferred": { + "message": "Items transferred" + }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "maxFileSizeSansPunctuation": { "message": "Maximum file size is 500 MB" }, @@ -908,6 +920,12 @@ "unexpectedError": { "message": "An unexpected error has occurred." }, + "unexpectedErrorShort": { + "message": "Unexpected error" + }, + "closeThisBitwardenWindow": { + "message": "Close this Bitwarden window and try again." + }, "itemInformation": { "message": "Informo de ero" }, @@ -1180,8 +1198,8 @@ "followUs": { "message": "Sekvu nin" }, - "syncVault": { - "message": "Speguli la trezorejon" + "syncNow": { + "message": "Sync now" }, "changeMasterPass": { "message": "Ŝanĝi la ĉefan pasvorton" @@ -1757,8 +1775,11 @@ "exportFrom": { "message": "Elporti el" }, - "exportVault": { - "message": "Export vault" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "Dosierformato" @@ -2619,9 +2640,6 @@ "removedMasterPassword": { "message": "La ĉefa pasvorto foriĝis" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "organizationName": { "message": "Nomo de la organizo" }, @@ -3477,10 +3495,6 @@ "aliasDomain": { "message": "Alias domain" }, - "importData": { - "message": "Enporti datumon", - "description": "Used for the desktop menu item and the header of the import dialog" - }, "importError": { "message": "Enporti eraron" }, @@ -3886,6 +3900,75 @@ "fileSavedToDevice": { "message": "La dosiero konserviĝis en la aparato. La elŝutojn administru de via aparato." }, + "importantNotice": { + "message": "Important notice" + }, + "setupTwoStepLogin": { + "message": "Set up two-step login" + }, + "newDeviceVerificationNoticeContentPage1": { + "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." + }, + "newDeviceVerificationNoticeContentPage2": { + "message": "You can set up two-step login as an alternative way to protect your account or change your email to one you can access." + }, + "remindMeLater": { + "message": "Remind me later" + }, + "newDeviceVerificationNoticePageOneFormContent": { + "message": "Do you have reliable access to your email, $EMAIL$?", + "placeholders": { + "email": { + "content": "$1", + "example": "your_name@email.com" + } + } + }, + "newDeviceVerificationNoticePageOneEmailAccessNo": { + "message": "No, I do not" + }, + "newDeviceVerificationNoticePageOneEmailAccessYes": { + "message": "Yes, I can reliably access my email" + }, + "turnOnTwoStepLogin": { + "message": "Turn on two-step login" + }, + "changeAcctEmail": { + "message": "Change account email" + }, + "passkeyLogin": { + "message": "Log in with passkey?" + }, + "savePasskeyQuestion": { + "message": "Save passkey?" + }, + "saveNewPasskey": { + "message": "Save as new login" + }, + "savePasskeyNewLogin": { + "message": "Save passkey as new login" + }, + "noMatchingLoginsForSite": { + "message": "No matching logins for this site" + }, + "overwritePasskey": { + "message": "Overwrite passkey?" + }, + "unableToSavePasskey": { + "message": "Unable to save passkey" + }, + "alreadyContainsPasskey": { + "message": "This item already contains a passkey. Are you sure you want to overwrite the current passkey?" + }, + "passkeyAlreadyExists": { + "message": "A passkey already exists for this application." + }, + "applicationDoesNotSupportDuplicates": { + "message": "This application does not support duplicates." + }, + "closeThisWindow": { + "message": "Close this window" + }, "allowScreenshots": { "message": "Allow screen capture" }, @@ -4244,16 +4327,147 @@ "andMoreFeatures": { "message": "And more!" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "upgradeToPremium": { "message": "Upgrade to Premium" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { "message": "Timeout action" }, "sessionTimeoutHeader": { "message": "Session timeout" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Your organization has set the default session timeout to On system lock." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On restart." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On restart" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/desktop/src/locales/es/messages.json b/apps/desktop/src/locales/es/messages.json index af90b40af65..e8bf822b015 100644 --- a/apps/desktop/src/locales/es/messages.json +++ b/apps/desktop/src/locales/es/messages.json @@ -708,6 +708,18 @@ "addAttachment": { "message": "Add attachment" }, + "itemsTransferred": { + "message": "Items transferred" + }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "maxFileSizeSansPunctuation": { "message": "Maximum file size is 500 MB" }, @@ -908,6 +920,12 @@ "unexpectedError": { "message": "Ha ocurrido un error inesperado." }, + "unexpectedErrorShort": { + "message": "Unexpected error" + }, + "closeThisBitwardenWindow": { + "message": "Close this Bitwarden window and try again." + }, "itemInformation": { "message": "Información del elemento" }, @@ -1180,8 +1198,8 @@ "followUs": { "message": "Síguenos" }, - "syncVault": { - "message": "Sincronizar caja fuerte" + "syncNow": { + "message": "Sync now" }, "changeMasterPass": { "message": "Cambiar contraseña maestra" @@ -1757,8 +1775,11 @@ "exportFrom": { "message": "Exportar desde" }, - "exportVault": { - "message": "Exportar caja fuerte" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "Formato de archivo" @@ -2619,9 +2640,6 @@ "removedMasterPassword": { "message": "Contraseña maestra eliminada." }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "Ya no es necesaria una contraseña maestra para los miembros de la siguiente organización. Por favor, confirma el dominio que aparece a continuación con el administrador de tu organización." - }, "organizationName": { "message": "Nombre de la organización" }, @@ -3477,10 +3495,6 @@ "aliasDomain": { "message": "Alias de dominio" }, - "importData": { - "message": "Importar datos", - "description": "Used for the desktop menu item and the header of the import dialog" - }, "importError": { "message": "Error de importación" }, @@ -3886,6 +3900,75 @@ "fileSavedToDevice": { "message": "File saved to device. Manage from your device downloads." }, + "importantNotice": { + "message": "Important notice" + }, + "setupTwoStepLogin": { + "message": "Set up two-step login" + }, + "newDeviceVerificationNoticeContentPage1": { + "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." + }, + "newDeviceVerificationNoticeContentPage2": { + "message": "You can set up two-step login as an alternative way to protect your account or change your email to one you can access." + }, + "remindMeLater": { + "message": "Remind me later" + }, + "newDeviceVerificationNoticePageOneFormContent": { + "message": "Do you have reliable access to your email, $EMAIL$?", + "placeholders": { + "email": { + "content": "$1", + "example": "your_name@email.com" + } + } + }, + "newDeviceVerificationNoticePageOneEmailAccessNo": { + "message": "No, I do not" + }, + "newDeviceVerificationNoticePageOneEmailAccessYes": { + "message": "Yes, I can reliably access my email" + }, + "turnOnTwoStepLogin": { + "message": "Turn on two-step login" + }, + "changeAcctEmail": { + "message": "Change account email" + }, + "passkeyLogin": { + "message": "Log in with passkey?" + }, + "savePasskeyQuestion": { + "message": "Save passkey?" + }, + "saveNewPasskey": { + "message": "Save as new login" + }, + "savePasskeyNewLogin": { + "message": "Save passkey as new login" + }, + "noMatchingLoginsForSite": { + "message": "No matching logins for this site" + }, + "overwritePasskey": { + "message": "Overwrite passkey?" + }, + "unableToSavePasskey": { + "message": "Unable to save passkey" + }, + "alreadyContainsPasskey": { + "message": "This item already contains a passkey. Are you sure you want to overwrite the current passkey?" + }, + "passkeyAlreadyExists": { + "message": "A passkey already exists for this application." + }, + "applicationDoesNotSupportDuplicates": { + "message": "This application does not support duplicates." + }, + "closeThisWindow": { + "message": "Close this window" + }, "allowScreenshots": { "message": "Permitir captura de pantalla" }, @@ -4244,16 +4327,147 @@ "andMoreFeatures": { "message": "And more!" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "upgradeToPremium": { "message": "Upgrade to Premium" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { "message": "Timeout action" }, "sessionTimeoutHeader": { "message": "Session timeout" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Your organization has set the default session timeout to On system lock." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On restart." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On restart" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/desktop/src/locales/et/messages.json b/apps/desktop/src/locales/et/messages.json index 8ab2818f7ea..3b92748908b 100644 --- a/apps/desktop/src/locales/et/messages.json +++ b/apps/desktop/src/locales/et/messages.json @@ -708,6 +708,18 @@ "addAttachment": { "message": "Add attachment" }, + "itemsTransferred": { + "message": "Items transferred" + }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "maxFileSizeSansPunctuation": { "message": "Maximum file size is 500 MB" }, @@ -908,6 +920,12 @@ "unexpectedError": { "message": "Tekkis ootamatu viga." }, + "unexpectedErrorShort": { + "message": "Unexpected error" + }, + "closeThisBitwardenWindow": { + "message": "Close this Bitwarden window and try again." + }, "itemInformation": { "message": "Kirje andmed" }, @@ -1180,8 +1198,8 @@ "followUs": { "message": "Jälgi meid" }, - "syncVault": { - "message": "Sünkroniseeri hoidla" + "syncNow": { + "message": "Sync now" }, "changeMasterPass": { "message": "Muuda ülemparooli" @@ -1757,8 +1775,11 @@ "exportFrom": { "message": "Ekspordi asukohast" }, - "exportVault": { - "message": "Ekspordi hoidla" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "Failivorming" @@ -2619,9 +2640,6 @@ "removedMasterPassword": { "message": "Ülemparool on eemaldatud." }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "organizationName": { "message": "Organization name" }, @@ -3477,10 +3495,6 @@ "aliasDomain": { "message": "Varidomeen" }, - "importData": { - "message": "Impordi andmed", - "description": "Used for the desktop menu item and the header of the import dialog" - }, "importError": { "message": "Tõrge importimisel" }, @@ -3886,6 +3900,75 @@ "fileSavedToDevice": { "message": "Fail salvestatud. Halda oma seadmesse allalaaditud faile." }, + "importantNotice": { + "message": "Important notice" + }, + "setupTwoStepLogin": { + "message": "Set up two-step login" + }, + "newDeviceVerificationNoticeContentPage1": { + "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." + }, + "newDeviceVerificationNoticeContentPage2": { + "message": "You can set up two-step login as an alternative way to protect your account or change your email to one you can access." + }, + "remindMeLater": { + "message": "Remind me later" + }, + "newDeviceVerificationNoticePageOneFormContent": { + "message": "Do you have reliable access to your email, $EMAIL$?", + "placeholders": { + "email": { + "content": "$1", + "example": "your_name@email.com" + } + } + }, + "newDeviceVerificationNoticePageOneEmailAccessNo": { + "message": "No, I do not" + }, + "newDeviceVerificationNoticePageOneEmailAccessYes": { + "message": "Yes, I can reliably access my email" + }, + "turnOnTwoStepLogin": { + "message": "Turn on two-step login" + }, + "changeAcctEmail": { + "message": "Change account email" + }, + "passkeyLogin": { + "message": "Log in with passkey?" + }, + "savePasskeyQuestion": { + "message": "Save passkey?" + }, + "saveNewPasskey": { + "message": "Save as new login" + }, + "savePasskeyNewLogin": { + "message": "Save passkey as new login" + }, + "noMatchingLoginsForSite": { + "message": "No matching logins for this site" + }, + "overwritePasskey": { + "message": "Overwrite passkey?" + }, + "unableToSavePasskey": { + "message": "Unable to save passkey" + }, + "alreadyContainsPasskey": { + "message": "This item already contains a passkey. Are you sure you want to overwrite the current passkey?" + }, + "passkeyAlreadyExists": { + "message": "A passkey already exists for this application." + }, + "applicationDoesNotSupportDuplicates": { + "message": "This application does not support duplicates." + }, + "closeThisWindow": { + "message": "Close this window" + }, "allowScreenshots": { "message": "Allow screen capture" }, @@ -4244,16 +4327,147 @@ "andMoreFeatures": { "message": "And more!" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "upgradeToPremium": { "message": "Upgrade to Premium" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { "message": "Timeout action" }, "sessionTimeoutHeader": { "message": "Session timeout" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Your organization has set the default session timeout to On system lock." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On restart." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On restart" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/desktop/src/locales/eu/messages.json b/apps/desktop/src/locales/eu/messages.json index 451e77c4ec0..831636ee240 100644 --- a/apps/desktop/src/locales/eu/messages.json +++ b/apps/desktop/src/locales/eu/messages.json @@ -708,6 +708,18 @@ "addAttachment": { "message": "Add attachment" }, + "itemsTransferred": { + "message": "Items transferred" + }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "maxFileSizeSansPunctuation": { "message": "Maximum file size is 500 MB" }, @@ -908,6 +920,12 @@ "unexpectedError": { "message": "Ustekabeko akatsa gertatu da." }, + "unexpectedErrorShort": { + "message": "Unexpected error" + }, + "closeThisBitwardenWindow": { + "message": "Close this Bitwarden window and try again." + }, "itemInformation": { "message": "Elementuaren informazioa" }, @@ -1180,8 +1198,8 @@ "followUs": { "message": "Jarraitu gaitzazu" }, - "syncVault": { - "message": "Sinkronizatu kutxa gotorra" + "syncNow": { + "message": "Sync now" }, "changeMasterPass": { "message": "Aldatu pasahitz nagusia" @@ -1757,8 +1775,11 @@ "exportFrom": { "message": "Export from" }, - "exportVault": { - "message": "Esportatu kutxa gotorra" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "Fitxategiaren formatua" @@ -2619,9 +2640,6 @@ "removedMasterPassword": { "message": "Pasahitz nagusia ezabatua." }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "organizationName": { "message": "Organization name" }, @@ -3477,10 +3495,6 @@ "aliasDomain": { "message": "Alias domain" }, - "importData": { - "message": "Import data", - "description": "Used for the desktop menu item and the header of the import dialog" - }, "importError": { "message": "Import error" }, @@ -3886,6 +3900,75 @@ "fileSavedToDevice": { "message": "File saved to device. Manage from your device downloads." }, + "importantNotice": { + "message": "Important notice" + }, + "setupTwoStepLogin": { + "message": "Set up two-step login" + }, + "newDeviceVerificationNoticeContentPage1": { + "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." + }, + "newDeviceVerificationNoticeContentPage2": { + "message": "You can set up two-step login as an alternative way to protect your account or change your email to one you can access." + }, + "remindMeLater": { + "message": "Remind me later" + }, + "newDeviceVerificationNoticePageOneFormContent": { + "message": "Do you have reliable access to your email, $EMAIL$?", + "placeholders": { + "email": { + "content": "$1", + "example": "your_name@email.com" + } + } + }, + "newDeviceVerificationNoticePageOneEmailAccessNo": { + "message": "No, I do not" + }, + "newDeviceVerificationNoticePageOneEmailAccessYes": { + "message": "Yes, I can reliably access my email" + }, + "turnOnTwoStepLogin": { + "message": "Turn on two-step login" + }, + "changeAcctEmail": { + "message": "Change account email" + }, + "passkeyLogin": { + "message": "Log in with passkey?" + }, + "savePasskeyQuestion": { + "message": "Save passkey?" + }, + "saveNewPasskey": { + "message": "Save as new login" + }, + "savePasskeyNewLogin": { + "message": "Save passkey as new login" + }, + "noMatchingLoginsForSite": { + "message": "No matching logins for this site" + }, + "overwritePasskey": { + "message": "Overwrite passkey?" + }, + "unableToSavePasskey": { + "message": "Unable to save passkey" + }, + "alreadyContainsPasskey": { + "message": "This item already contains a passkey. Are you sure you want to overwrite the current passkey?" + }, + "passkeyAlreadyExists": { + "message": "A passkey already exists for this application." + }, + "applicationDoesNotSupportDuplicates": { + "message": "This application does not support duplicates." + }, + "closeThisWindow": { + "message": "Close this window" + }, "allowScreenshots": { "message": "Allow screen capture" }, @@ -4244,16 +4327,147 @@ "andMoreFeatures": { "message": "And more!" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "upgradeToPremium": { "message": "Upgrade to Premium" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { "message": "Timeout action" }, "sessionTimeoutHeader": { "message": "Session timeout" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Your organization has set the default session timeout to On system lock." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On restart." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On restart" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/desktop/src/locales/fa/messages.json b/apps/desktop/src/locales/fa/messages.json index f7dedc42542..3f2b28cece0 100644 --- a/apps/desktop/src/locales/fa/messages.json +++ b/apps/desktop/src/locales/fa/messages.json @@ -708,6 +708,18 @@ "addAttachment": { "message": "افزودن پیوست" }, + "itemsTransferred": { + "message": "Items transferred" + }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "maxFileSizeSansPunctuation": { "message": "حداکثر حجم فایل ۵۰۰ مگابایت است" }, @@ -908,6 +920,12 @@ "unexpectedError": { "message": "یک خطای غیر منتظره رخ داده است." }, + "unexpectedErrorShort": { + "message": "Unexpected error" + }, + "closeThisBitwardenWindow": { + "message": "Close this Bitwarden window and try again." + }, "itemInformation": { "message": "اطلاعات مورد" }, @@ -1180,8 +1198,8 @@ "followUs": { "message": "ﻣﺎ ﺭﺍ ﺩﻧﺒﺎﻝ ﮐﻨﻴﺪ" }, - "syncVault": { - "message": "همگام‌سازی گاوصندوق" + "syncNow": { + "message": "Sync now" }, "changeMasterPass": { "message": "تغییر کلمه عبور اصلی" @@ -1757,8 +1775,11 @@ "exportFrom": { "message": "برون ریزی از" }, - "exportVault": { - "message": "برون ریزی گاوصندوق" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "فرمت پرونده" @@ -2619,9 +2640,6 @@ "removedMasterPassword": { "message": "کلمه عبور اصلی حذف شد" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "برای اعضای سازمان زیر، کلمه عبور اصلی دیگر لازم نیست. لطفاً دامنه زیر را با مدیر سازمان خود تأیید کنید." - }, "organizationName": { "message": "نام سازمان" }, @@ -3477,10 +3495,6 @@ "aliasDomain": { "message": "دامنه مستعار" }, - "importData": { - "message": "درون ریزی داده", - "description": "Used for the desktop menu item and the header of the import dialog" - }, "importError": { "message": "خطای درون ریزی" }, @@ -3886,6 +3900,75 @@ "fileSavedToDevice": { "message": "پرونده در دستگاه ذخیره شد. از بخش بارگیری‌های دستگاه خود مدیریت کنید." }, + "importantNotice": { + "message": "Important notice" + }, + "setupTwoStepLogin": { + "message": "Set up two-step login" + }, + "newDeviceVerificationNoticeContentPage1": { + "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." + }, + "newDeviceVerificationNoticeContentPage2": { + "message": "You can set up two-step login as an alternative way to protect your account or change your email to one you can access." + }, + "remindMeLater": { + "message": "Remind me later" + }, + "newDeviceVerificationNoticePageOneFormContent": { + "message": "Do you have reliable access to your email, $EMAIL$?", + "placeholders": { + "email": { + "content": "$1", + "example": "your_name@email.com" + } + } + }, + "newDeviceVerificationNoticePageOneEmailAccessNo": { + "message": "No, I do not" + }, + "newDeviceVerificationNoticePageOneEmailAccessYes": { + "message": "Yes, I can reliably access my email" + }, + "turnOnTwoStepLogin": { + "message": "Turn on two-step login" + }, + "changeAcctEmail": { + "message": "Change account email" + }, + "passkeyLogin": { + "message": "Log in with passkey?" + }, + "savePasskeyQuestion": { + "message": "Save passkey?" + }, + "saveNewPasskey": { + "message": "Save as new login" + }, + "savePasskeyNewLogin": { + "message": "Save passkey as new login" + }, + "noMatchingLoginsForSite": { + "message": "No matching logins for this site" + }, + "overwritePasskey": { + "message": "Overwrite passkey?" + }, + "unableToSavePasskey": { + "message": "Unable to save passkey" + }, + "alreadyContainsPasskey": { + "message": "This item already contains a passkey. Are you sure you want to overwrite the current passkey?" + }, + "passkeyAlreadyExists": { + "message": "A passkey already exists for this application." + }, + "applicationDoesNotSupportDuplicates": { + "message": "This application does not support duplicates." + }, + "closeThisWindow": { + "message": "Close this window" + }, "allowScreenshots": { "message": "اجازه ضبط صفحه" }, @@ -4244,16 +4327,147 @@ "andMoreFeatures": { "message": "و بیشتر!" }, - "planDescPremium": { - "message": "امنیت آنلاین کامل" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "upgradeToPremium": { "message": "ارتقا به نسخه پرمیوم" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { "message": "اقدام وقفه زمانی" }, "sessionTimeoutHeader": { "message": "وقفه زمانی نشست" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Your organization has set the default session timeout to On system lock." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On restart." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On restart" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/desktop/src/locales/fi/messages.json b/apps/desktop/src/locales/fi/messages.json index 2021248bae4..d93e08e759c 100644 --- a/apps/desktop/src/locales/fi/messages.json +++ b/apps/desktop/src/locales/fi/messages.json @@ -708,6 +708,18 @@ "addAttachment": { "message": "Add attachment" }, + "itemsTransferred": { + "message": "Items transferred" + }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "maxFileSizeSansPunctuation": { "message": "Maximum file size is 500 MB" }, @@ -908,6 +920,12 @@ "unexpectedError": { "message": "Tapahtui odottamaton virhe." }, + "unexpectedErrorShort": { + "message": "Unexpected error" + }, + "closeThisBitwardenWindow": { + "message": "Close this Bitwarden window and try again." + }, "itemInformation": { "message": "Kohteen tiedot" }, @@ -1180,8 +1198,8 @@ "followUs": { "message": "Seuraa meitä" }, - "syncVault": { - "message": "Synkronoi holvi" + "syncNow": { + "message": "Sync now" }, "changeMasterPass": { "message": "Vaihda pääsalasana" @@ -1757,8 +1775,11 @@ "exportFrom": { "message": "Vie lähteestä" }, - "exportVault": { - "message": "Vie holvi" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "Tiedostomuoto" @@ -2619,9 +2640,6 @@ "removedMasterPassword": { "message": "Pääsalasana poistettiin" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "organizationName": { "message": "Organization name" }, @@ -3477,10 +3495,6 @@ "aliasDomain": { "message": "Aliaksen verkkotunnus" }, - "importData": { - "message": "Tuo tietoja", - "description": "Used for the desktop menu item and the header of the import dialog" - }, "importError": { "message": "Tuontivirhe" }, @@ -3886,6 +3900,75 @@ "fileSavedToDevice": { "message": "Tiedosto tallennettiin laitteelle. Hallitse sitä laitteesi latauksista." }, + "importantNotice": { + "message": "Important notice" + }, + "setupTwoStepLogin": { + "message": "Set up two-step login" + }, + "newDeviceVerificationNoticeContentPage1": { + "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." + }, + "newDeviceVerificationNoticeContentPage2": { + "message": "You can set up two-step login as an alternative way to protect your account or change your email to one you can access." + }, + "remindMeLater": { + "message": "Remind me later" + }, + "newDeviceVerificationNoticePageOneFormContent": { + "message": "Do you have reliable access to your email, $EMAIL$?", + "placeholders": { + "email": { + "content": "$1", + "example": "your_name@email.com" + } + } + }, + "newDeviceVerificationNoticePageOneEmailAccessNo": { + "message": "No, I do not" + }, + "newDeviceVerificationNoticePageOneEmailAccessYes": { + "message": "Yes, I can reliably access my email" + }, + "turnOnTwoStepLogin": { + "message": "Turn on two-step login" + }, + "changeAcctEmail": { + "message": "Change account email" + }, + "passkeyLogin": { + "message": "Log in with passkey?" + }, + "savePasskeyQuestion": { + "message": "Save passkey?" + }, + "saveNewPasskey": { + "message": "Save as new login" + }, + "savePasskeyNewLogin": { + "message": "Save passkey as new login" + }, + "noMatchingLoginsForSite": { + "message": "No matching logins for this site" + }, + "overwritePasskey": { + "message": "Overwrite passkey?" + }, + "unableToSavePasskey": { + "message": "Unable to save passkey" + }, + "alreadyContainsPasskey": { + "message": "This item already contains a passkey. Are you sure you want to overwrite the current passkey?" + }, + "passkeyAlreadyExists": { + "message": "A passkey already exists for this application." + }, + "applicationDoesNotSupportDuplicates": { + "message": "This application does not support duplicates." + }, + "closeThisWindow": { + "message": "Close this window" + }, "allowScreenshots": { "message": "Salli kuvankaappaus" }, @@ -4244,16 +4327,147 @@ "andMoreFeatures": { "message": "And more!" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "upgradeToPremium": { "message": "Upgrade to Premium" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { "message": "Timeout action" }, "sessionTimeoutHeader": { "message": "Session timeout" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Your organization has set the default session timeout to On system lock." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On restart." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On restart" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/desktop/src/locales/fil/messages.json b/apps/desktop/src/locales/fil/messages.json index 9c8bbaf0a34..e82e5698f9f 100644 --- a/apps/desktop/src/locales/fil/messages.json +++ b/apps/desktop/src/locales/fil/messages.json @@ -708,6 +708,18 @@ "addAttachment": { "message": "Add attachment" }, + "itemsTransferred": { + "message": "Items transferred" + }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "maxFileSizeSansPunctuation": { "message": "Maximum file size is 500 MB" }, @@ -908,6 +920,12 @@ "unexpectedError": { "message": "Isang hindi inaasahang error ang naganap." }, + "unexpectedErrorShort": { + "message": "Unexpected error" + }, + "closeThisBitwardenWindow": { + "message": "Close this Bitwarden window and try again." + }, "itemInformation": { "message": "Impormasyon ng item" }, @@ -1180,8 +1198,8 @@ "followUs": { "message": "Sundin mo kami" }, - "syncVault": { - "message": "Vault ng Sync" + "syncNow": { + "message": "Sync now" }, "changeMasterPass": { "message": "Palitan ang master password" @@ -1757,8 +1775,11 @@ "exportFrom": { "message": "Export from" }, - "exportVault": { - "message": "I-export vault" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "Format ng file" @@ -2619,9 +2640,6 @@ "removedMasterPassword": { "message": "Tinanggal ang password ng master" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "organizationName": { "message": "Organization name" }, @@ -3477,10 +3495,6 @@ "aliasDomain": { "message": "Alias domain" }, - "importData": { - "message": "Import data", - "description": "Used for the desktop menu item and the header of the import dialog" - }, "importError": { "message": "Import error" }, @@ -3886,6 +3900,75 @@ "fileSavedToDevice": { "message": "File saved to device. Manage from your device downloads." }, + "importantNotice": { + "message": "Important notice" + }, + "setupTwoStepLogin": { + "message": "Set up two-step login" + }, + "newDeviceVerificationNoticeContentPage1": { + "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." + }, + "newDeviceVerificationNoticeContentPage2": { + "message": "You can set up two-step login as an alternative way to protect your account or change your email to one you can access." + }, + "remindMeLater": { + "message": "Remind me later" + }, + "newDeviceVerificationNoticePageOneFormContent": { + "message": "Do you have reliable access to your email, $EMAIL$?", + "placeholders": { + "email": { + "content": "$1", + "example": "your_name@email.com" + } + } + }, + "newDeviceVerificationNoticePageOneEmailAccessNo": { + "message": "No, I do not" + }, + "newDeviceVerificationNoticePageOneEmailAccessYes": { + "message": "Yes, I can reliably access my email" + }, + "turnOnTwoStepLogin": { + "message": "Turn on two-step login" + }, + "changeAcctEmail": { + "message": "Change account email" + }, + "passkeyLogin": { + "message": "Log in with passkey?" + }, + "savePasskeyQuestion": { + "message": "Save passkey?" + }, + "saveNewPasskey": { + "message": "Save as new login" + }, + "savePasskeyNewLogin": { + "message": "Save passkey as new login" + }, + "noMatchingLoginsForSite": { + "message": "No matching logins for this site" + }, + "overwritePasskey": { + "message": "Overwrite passkey?" + }, + "unableToSavePasskey": { + "message": "Unable to save passkey" + }, + "alreadyContainsPasskey": { + "message": "This item already contains a passkey. Are you sure you want to overwrite the current passkey?" + }, + "passkeyAlreadyExists": { + "message": "A passkey already exists for this application." + }, + "applicationDoesNotSupportDuplicates": { + "message": "This application does not support duplicates." + }, + "closeThisWindow": { + "message": "Close this window" + }, "allowScreenshots": { "message": "Allow screen capture" }, @@ -4244,16 +4327,147 @@ "andMoreFeatures": { "message": "And more!" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "upgradeToPremium": { "message": "Upgrade to Premium" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { "message": "Timeout action" }, "sessionTimeoutHeader": { "message": "Session timeout" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Your organization has set the default session timeout to On system lock." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On restart." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On restart" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/desktop/src/locales/fr/messages.json b/apps/desktop/src/locales/fr/messages.json index 8f7bfc39c4a..b7792522567 100644 --- a/apps/desktop/src/locales/fr/messages.json +++ b/apps/desktop/src/locales/fr/messages.json @@ -708,6 +708,18 @@ "addAttachment": { "message": "Ajouter une pièce jointe" }, + "itemsTransferred": { + "message": "Items transferred" + }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "maxFileSizeSansPunctuation": { "message": "La taille maximale des fichiers est de 500 Mo" }, @@ -908,6 +920,12 @@ "unexpectedError": { "message": "Une erreur inattendue est survenue." }, + "unexpectedErrorShort": { + "message": "Unexpected error" + }, + "closeThisBitwardenWindow": { + "message": "Close this Bitwarden window and try again." + }, "itemInformation": { "message": "Informations sur l'élément" }, @@ -1180,8 +1198,8 @@ "followUs": { "message": "Suivez-nous" }, - "syncVault": { - "message": "Synchroniser le coffre" + "syncNow": { + "message": "Sync now" }, "changeMasterPass": { "message": "Changer le mot de passe principal" @@ -1757,8 +1775,11 @@ "exportFrom": { "message": "Exporter depuis" }, - "exportVault": { - "message": "Exporter le coffre" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "Format de fichier" @@ -2619,9 +2640,6 @@ "removedMasterPassword": { "message": "Mot de passe principal supprimé" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "Un mot de passe maître n'est plus requis pour les membres de l'organisation suivante. Veuillez confirmer le domaine ci-dessous avec l'administrateur de votre organisation." - }, "organizationName": { "message": "Nom de l'organisation" }, @@ -3477,10 +3495,6 @@ "aliasDomain": { "message": "Domaine de l'alias" }, - "importData": { - "message": "Importer des données", - "description": "Used for the desktop menu item and the header of the import dialog" - }, "importError": { "message": "Erreur lors de l'importation" }, @@ -3886,6 +3900,75 @@ "fileSavedToDevice": { "message": "Fichier enregistré sur l'appareil. Gérez à partir des téléchargements de votre appareil." }, + "importantNotice": { + "message": "Important notice" + }, + "setupTwoStepLogin": { + "message": "Set up two-step login" + }, + "newDeviceVerificationNoticeContentPage1": { + "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." + }, + "newDeviceVerificationNoticeContentPage2": { + "message": "You can set up two-step login as an alternative way to protect your account or change your email to one you can access." + }, + "remindMeLater": { + "message": "Remind me later" + }, + "newDeviceVerificationNoticePageOneFormContent": { + "message": "Do you have reliable access to your email, $EMAIL$?", + "placeholders": { + "email": { + "content": "$1", + "example": "your_name@email.com" + } + } + }, + "newDeviceVerificationNoticePageOneEmailAccessNo": { + "message": "No, I do not" + }, + "newDeviceVerificationNoticePageOneEmailAccessYes": { + "message": "Yes, I can reliably access my email" + }, + "turnOnTwoStepLogin": { + "message": "Turn on two-step login" + }, + "changeAcctEmail": { + "message": "Change account email" + }, + "passkeyLogin": { + "message": "Log in with passkey?" + }, + "savePasskeyQuestion": { + "message": "Save passkey?" + }, + "saveNewPasskey": { + "message": "Save as new login" + }, + "savePasskeyNewLogin": { + "message": "Save passkey as new login" + }, + "noMatchingLoginsForSite": { + "message": "No matching logins for this site" + }, + "overwritePasskey": { + "message": "Overwrite passkey?" + }, + "unableToSavePasskey": { + "message": "Unable to save passkey" + }, + "alreadyContainsPasskey": { + "message": "This item already contains a passkey. Are you sure you want to overwrite the current passkey?" + }, + "passkeyAlreadyExists": { + "message": "A passkey already exists for this application." + }, + "applicationDoesNotSupportDuplicates": { + "message": "This application does not support duplicates." + }, + "closeThisWindow": { + "message": "Close this window" + }, "allowScreenshots": { "message": "Autoriser les captures d'écran" }, @@ -4244,16 +4327,147 @@ "andMoreFeatures": { "message": "Et encore plus !" }, - "planDescPremium": { - "message": "Sécurité en ligne complète" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "upgradeToPremium": { "message": "Mettre à niveau vers Premium" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { "message": "Action à l’expiration" }, "sessionTimeoutHeader": { "message": "Délai d'expiration de la session" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Your organization has set the default session timeout to On system lock." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On restart." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On restart" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/desktop/src/locales/gl/messages.json b/apps/desktop/src/locales/gl/messages.json index 0211550b08d..315272ae464 100644 --- a/apps/desktop/src/locales/gl/messages.json +++ b/apps/desktop/src/locales/gl/messages.json @@ -708,6 +708,18 @@ "addAttachment": { "message": "Add attachment" }, + "itemsTransferred": { + "message": "Items transferred" + }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "maxFileSizeSansPunctuation": { "message": "Maximum file size is 500 MB" }, @@ -908,6 +920,12 @@ "unexpectedError": { "message": "An unexpected error has occurred." }, + "unexpectedErrorShort": { + "message": "Unexpected error" + }, + "closeThisBitwardenWindow": { + "message": "Close this Bitwarden window and try again." + }, "itemInformation": { "message": "Item information" }, @@ -1180,8 +1198,8 @@ "followUs": { "message": "Follow us" }, - "syncVault": { - "message": "Sync vault" + "syncNow": { + "message": "Sync now" }, "changeMasterPass": { "message": "Change master password" @@ -1757,8 +1775,11 @@ "exportFrom": { "message": "Export from" }, - "exportVault": { - "message": "Export vault" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "File format" @@ -2619,9 +2640,6 @@ "removedMasterPassword": { "message": "Master password removed" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "organizationName": { "message": "Organization name" }, @@ -3477,10 +3495,6 @@ "aliasDomain": { "message": "Alias domain" }, - "importData": { - "message": "Import data", - "description": "Used for the desktop menu item and the header of the import dialog" - }, "importError": { "message": "Import error" }, @@ -3886,6 +3900,75 @@ "fileSavedToDevice": { "message": "File saved to device. Manage from your device downloads." }, + "importantNotice": { + "message": "Important notice" + }, + "setupTwoStepLogin": { + "message": "Set up two-step login" + }, + "newDeviceVerificationNoticeContentPage1": { + "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." + }, + "newDeviceVerificationNoticeContentPage2": { + "message": "You can set up two-step login as an alternative way to protect your account or change your email to one you can access." + }, + "remindMeLater": { + "message": "Remind me later" + }, + "newDeviceVerificationNoticePageOneFormContent": { + "message": "Do you have reliable access to your email, $EMAIL$?", + "placeholders": { + "email": { + "content": "$1", + "example": "your_name@email.com" + } + } + }, + "newDeviceVerificationNoticePageOneEmailAccessNo": { + "message": "No, I do not" + }, + "newDeviceVerificationNoticePageOneEmailAccessYes": { + "message": "Yes, I can reliably access my email" + }, + "turnOnTwoStepLogin": { + "message": "Turn on two-step login" + }, + "changeAcctEmail": { + "message": "Change account email" + }, + "passkeyLogin": { + "message": "Log in with passkey?" + }, + "savePasskeyQuestion": { + "message": "Save passkey?" + }, + "saveNewPasskey": { + "message": "Save as new login" + }, + "savePasskeyNewLogin": { + "message": "Save passkey as new login" + }, + "noMatchingLoginsForSite": { + "message": "No matching logins for this site" + }, + "overwritePasskey": { + "message": "Overwrite passkey?" + }, + "unableToSavePasskey": { + "message": "Unable to save passkey" + }, + "alreadyContainsPasskey": { + "message": "This item already contains a passkey. Are you sure you want to overwrite the current passkey?" + }, + "passkeyAlreadyExists": { + "message": "A passkey already exists for this application." + }, + "applicationDoesNotSupportDuplicates": { + "message": "This application does not support duplicates." + }, + "closeThisWindow": { + "message": "Close this window" + }, "allowScreenshots": { "message": "Allow screen capture" }, @@ -4244,16 +4327,147 @@ "andMoreFeatures": { "message": "And more!" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "upgradeToPremium": { "message": "Upgrade to Premium" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { "message": "Timeout action" }, "sessionTimeoutHeader": { "message": "Session timeout" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Your organization has set the default session timeout to On system lock." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On restart." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On restart" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/desktop/src/locales/he/messages.json b/apps/desktop/src/locales/he/messages.json index 5881405c190..5a6d486d723 100644 --- a/apps/desktop/src/locales/he/messages.json +++ b/apps/desktop/src/locales/he/messages.json @@ -708,6 +708,18 @@ "addAttachment": { "message": "הוסף צרופה" }, + "itemsTransferred": { + "message": "Items transferred" + }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "maxFileSizeSansPunctuation": { "message": "גודל הקובץ המרבי הוא 500MB" }, @@ -908,6 +920,12 @@ "unexpectedError": { "message": "אירעה שגיאה לא צפויה." }, + "unexpectedErrorShort": { + "message": "Unexpected error" + }, + "closeThisBitwardenWindow": { + "message": "Close this Bitwarden window and try again." + }, "itemInformation": { "message": "מידע על הפריט" }, @@ -1180,8 +1198,8 @@ "followUs": { "message": "עקוב אחרינו" }, - "syncVault": { - "message": "סנכרון כספת" + "syncNow": { + "message": "Sync now" }, "changeMasterPass": { "message": "החלף סיסמה ראשית" @@ -1757,8 +1775,11 @@ "exportFrom": { "message": "ייצוא מ־" }, - "exportVault": { - "message": "יצוא כספת" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "תבנית קובץ" @@ -2619,9 +2640,6 @@ "removedMasterPassword": { "message": "הסיסמה הראשית הוסרה" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "סיסמה ראשית אינה נדרשת עוד עבור חברים בארגון הבא. נא לאשר את הדומיין שלהלן עם מנהל הארגון שלך." - }, "organizationName": { "message": "שם הארגון" }, @@ -3477,10 +3495,6 @@ "aliasDomain": { "message": "דומיין כינוי" }, - "importData": { - "message": "ייבא נתונים", - "description": "Used for the desktop menu item and the header of the import dialog" - }, "importError": { "message": "שגיאת ייבוא" }, @@ -3886,6 +3900,75 @@ "fileSavedToDevice": { "message": "הקובץ נשמר למכשיר. נהל מהורדות המכשיר שלך." }, + "importantNotice": { + "message": "Important notice" + }, + "setupTwoStepLogin": { + "message": "Set up two-step login" + }, + "newDeviceVerificationNoticeContentPage1": { + "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." + }, + "newDeviceVerificationNoticeContentPage2": { + "message": "You can set up two-step login as an alternative way to protect your account or change your email to one you can access." + }, + "remindMeLater": { + "message": "Remind me later" + }, + "newDeviceVerificationNoticePageOneFormContent": { + "message": "Do you have reliable access to your email, $EMAIL$?", + "placeholders": { + "email": { + "content": "$1", + "example": "your_name@email.com" + } + } + }, + "newDeviceVerificationNoticePageOneEmailAccessNo": { + "message": "No, I do not" + }, + "newDeviceVerificationNoticePageOneEmailAccessYes": { + "message": "Yes, I can reliably access my email" + }, + "turnOnTwoStepLogin": { + "message": "Turn on two-step login" + }, + "changeAcctEmail": { + "message": "Change account email" + }, + "passkeyLogin": { + "message": "Log in with passkey?" + }, + "savePasskeyQuestion": { + "message": "Save passkey?" + }, + "saveNewPasskey": { + "message": "Save as new login" + }, + "savePasskeyNewLogin": { + "message": "Save passkey as new login" + }, + "noMatchingLoginsForSite": { + "message": "No matching logins for this site" + }, + "overwritePasskey": { + "message": "Overwrite passkey?" + }, + "unableToSavePasskey": { + "message": "Unable to save passkey" + }, + "alreadyContainsPasskey": { + "message": "This item already contains a passkey. Are you sure you want to overwrite the current passkey?" + }, + "passkeyAlreadyExists": { + "message": "A passkey already exists for this application." + }, + "applicationDoesNotSupportDuplicates": { + "message": "This application does not support duplicates." + }, + "closeThisWindow": { + "message": "Close this window" + }, "allowScreenshots": { "message": "אפשר לכידת מסך" }, @@ -4244,16 +4327,147 @@ "andMoreFeatures": { "message": "ועוד!" }, - "planDescPremium": { - "message": "השלם אבטחה מקוונת" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "upgradeToPremium": { "message": "שדרג לפרימיום" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { "message": "פעולת פסק זמן" }, "sessionTimeoutHeader": { "message": "פסק זמן להפעלה" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Your organization has set the default session timeout to On system lock." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On restart." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On restart" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/desktop/src/locales/hi/messages.json b/apps/desktop/src/locales/hi/messages.json index 2fac0a369fd..b193e645425 100644 --- a/apps/desktop/src/locales/hi/messages.json +++ b/apps/desktop/src/locales/hi/messages.json @@ -708,6 +708,18 @@ "addAttachment": { "message": "Add attachment" }, + "itemsTransferred": { + "message": "Items transferred" + }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "maxFileSizeSansPunctuation": { "message": "Maximum file size is 500 MB" }, @@ -908,6 +920,12 @@ "unexpectedError": { "message": "An unexpected error has occurred." }, + "unexpectedErrorShort": { + "message": "Unexpected error" + }, + "closeThisBitwardenWindow": { + "message": "Close this Bitwarden window and try again." + }, "itemInformation": { "message": "Item information" }, @@ -1180,8 +1198,8 @@ "followUs": { "message": "Follow us" }, - "syncVault": { - "message": "Sync vault" + "syncNow": { + "message": "Sync now" }, "changeMasterPass": { "message": "Change master password" @@ -1757,8 +1775,11 @@ "exportFrom": { "message": "Export from" }, - "exportVault": { - "message": "Export vault" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "File format" @@ -2619,9 +2640,6 @@ "removedMasterPassword": { "message": "Master password removed" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "organizationName": { "message": "Organization name" }, @@ -3477,10 +3495,6 @@ "aliasDomain": { "message": "Alias domain" }, - "importData": { - "message": "Import data", - "description": "Used for the desktop menu item and the header of the import dialog" - }, "importError": { "message": "Import error" }, @@ -3886,6 +3900,75 @@ "fileSavedToDevice": { "message": "File saved to device. Manage from your device downloads." }, + "importantNotice": { + "message": "Important notice" + }, + "setupTwoStepLogin": { + "message": "Set up two-step login" + }, + "newDeviceVerificationNoticeContentPage1": { + "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." + }, + "newDeviceVerificationNoticeContentPage2": { + "message": "You can set up two-step login as an alternative way to protect your account or change your email to one you can access." + }, + "remindMeLater": { + "message": "Remind me later" + }, + "newDeviceVerificationNoticePageOneFormContent": { + "message": "Do you have reliable access to your email, $EMAIL$?", + "placeholders": { + "email": { + "content": "$1", + "example": "your_name@email.com" + } + } + }, + "newDeviceVerificationNoticePageOneEmailAccessNo": { + "message": "No, I do not" + }, + "newDeviceVerificationNoticePageOneEmailAccessYes": { + "message": "Yes, I can reliably access my email" + }, + "turnOnTwoStepLogin": { + "message": "Turn on two-step login" + }, + "changeAcctEmail": { + "message": "Change account email" + }, + "passkeyLogin": { + "message": "Log in with passkey?" + }, + "savePasskeyQuestion": { + "message": "Save passkey?" + }, + "saveNewPasskey": { + "message": "Save as new login" + }, + "savePasskeyNewLogin": { + "message": "Save passkey as new login" + }, + "noMatchingLoginsForSite": { + "message": "No matching logins for this site" + }, + "overwritePasskey": { + "message": "Overwrite passkey?" + }, + "unableToSavePasskey": { + "message": "Unable to save passkey" + }, + "alreadyContainsPasskey": { + "message": "This item already contains a passkey. Are you sure you want to overwrite the current passkey?" + }, + "passkeyAlreadyExists": { + "message": "A passkey already exists for this application." + }, + "applicationDoesNotSupportDuplicates": { + "message": "This application does not support duplicates." + }, + "closeThisWindow": { + "message": "Close this window" + }, "allowScreenshots": { "message": "Allow screen capture" }, @@ -4244,16 +4327,147 @@ "andMoreFeatures": { "message": "And more!" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "upgradeToPremium": { "message": "Upgrade to Premium" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { "message": "Timeout action" }, "sessionTimeoutHeader": { "message": "Session timeout" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Your organization has set the default session timeout to On system lock." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On restart." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On restart" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/desktop/src/locales/hr/messages.json b/apps/desktop/src/locales/hr/messages.json index 07effb638b8..4c7ceb732e7 100644 --- a/apps/desktop/src/locales/hr/messages.json +++ b/apps/desktop/src/locales/hr/messages.json @@ -708,6 +708,18 @@ "addAttachment": { "message": "Dodaj privitak" }, + "itemsTransferred": { + "message": "Items transferred" + }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "maxFileSizeSansPunctuation": { "message": "Najveća veličina datoteke je 500 MB" }, @@ -908,6 +920,12 @@ "unexpectedError": { "message": "Došlo je do neočekivane pogreške." }, + "unexpectedErrorShort": { + "message": "Unexpected error" + }, + "closeThisBitwardenWindow": { + "message": "Close this Bitwarden window and try again." + }, "itemInformation": { "message": "Informacije o stavci" }, @@ -1180,8 +1198,8 @@ "followUs": { "message": "Prati nas" }, - "syncVault": { - "message": "Sinkronizraj trezor" + "syncNow": { + "message": "Sync now" }, "changeMasterPass": { "message": "Promjeni glavnu lozinku" @@ -1757,8 +1775,11 @@ "exportFrom": { "message": "Izvezi iz" }, - "exportVault": { - "message": "Izvezi trezor" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "Format datoteke" @@ -2619,9 +2640,6 @@ "removedMasterPassword": { "message": "Glavna lozinka uklonjena." }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "Glavna lozinka više nije obavezna za članove ove organizacije. Provjeri prikazanu domenu sa svojim administratorom." - }, "organizationName": { "message": "Naziv Organizacije" }, @@ -3477,10 +3495,6 @@ "aliasDomain": { "message": "Alias domene" }, - "importData": { - "message": "Uvezi podatke", - "description": "Used for the desktop menu item and the header of the import dialog" - }, "importError": { "message": "Greška prilikom uvoza" }, @@ -3886,6 +3900,75 @@ "fileSavedToDevice": { "message": "Datoteka spremljena na uređaj. Upravljaj u preuzimanjima svog uređaja." }, + "importantNotice": { + "message": "Important notice" + }, + "setupTwoStepLogin": { + "message": "Set up two-step login" + }, + "newDeviceVerificationNoticeContentPage1": { + "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." + }, + "newDeviceVerificationNoticeContentPage2": { + "message": "You can set up two-step login as an alternative way to protect your account or change your email to one you can access." + }, + "remindMeLater": { + "message": "Remind me later" + }, + "newDeviceVerificationNoticePageOneFormContent": { + "message": "Do you have reliable access to your email, $EMAIL$?", + "placeholders": { + "email": { + "content": "$1", + "example": "your_name@email.com" + } + } + }, + "newDeviceVerificationNoticePageOneEmailAccessNo": { + "message": "No, I do not" + }, + "newDeviceVerificationNoticePageOneEmailAccessYes": { + "message": "Yes, I can reliably access my email" + }, + "turnOnTwoStepLogin": { + "message": "Turn on two-step login" + }, + "changeAcctEmail": { + "message": "Change account email" + }, + "passkeyLogin": { + "message": "Log in with passkey?" + }, + "savePasskeyQuestion": { + "message": "Save passkey?" + }, + "saveNewPasskey": { + "message": "Save as new login" + }, + "savePasskeyNewLogin": { + "message": "Save passkey as new login" + }, + "noMatchingLoginsForSite": { + "message": "No matching logins for this site" + }, + "overwritePasskey": { + "message": "Overwrite passkey?" + }, + "unableToSavePasskey": { + "message": "Unable to save passkey" + }, + "alreadyContainsPasskey": { + "message": "This item already contains a passkey. Are you sure you want to overwrite the current passkey?" + }, + "passkeyAlreadyExists": { + "message": "A passkey already exists for this application." + }, + "applicationDoesNotSupportDuplicates": { + "message": "This application does not support duplicates." + }, + "closeThisWindow": { + "message": "Close this window" + }, "allowScreenshots": { "message": "Dozvoli snimanje zaslona" }, @@ -4244,16 +4327,147 @@ "andMoreFeatures": { "message": "I više!" }, - "planDescPremium": { - "message": "Dovrši online sigurnost" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "upgradeToPremium": { "message": " Nadogradi na Premium" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { "message": "Radnja nakon isteka" }, "sessionTimeoutHeader": { "message": "Istek sesije" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Your organization has set the default session timeout to On system lock." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On restart." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On restart" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/desktop/src/locales/hu/messages.json b/apps/desktop/src/locales/hu/messages.json index 583d5b86a59..ca03a7d11a9 100644 --- a/apps/desktop/src/locales/hu/messages.json +++ b/apps/desktop/src/locales/hu/messages.json @@ -708,6 +708,18 @@ "addAttachment": { "message": "Melléklet hozzáadása" }, + "itemsTransferred": { + "message": "Az elemek átvitelre kerültek." + }, + "fixEncryption": { + "message": "Titkosítás javítása" + }, + "fixEncryptionTooltip": { + "message": "Ez a fájl elavult titkosítási módszert használ." + }, + "attachmentUpdated": { + "message": "A melléklet frissítésre került." + }, "maxFileSizeSansPunctuation": { "message": "A maximális fájlméret 500 MB." }, @@ -908,6 +920,12 @@ "unexpectedError": { "message": "Váratlan hiba történt." }, + "unexpectedErrorShort": { + "message": "Váratlan hiba" + }, + "closeThisBitwardenWindow": { + "message": "Zárjuk be ezt a Bitwarden ablakot és próbáljuk újra." + }, "itemInformation": { "message": "Elem információ" }, @@ -1180,8 +1198,8 @@ "followUs": { "message": "Követés" }, - "syncVault": { - "message": "Széf szinkronizálása" + "syncNow": { + "message": "Szinkronizálás most" }, "changeMasterPass": { "message": "Mesterjelszó módosítása" @@ -1757,8 +1775,11 @@ "exportFrom": { "message": "Exportálás innen:" }, - "exportVault": { - "message": "Széf exportálása" + "export": { + "message": "Exportálás" + }, + "import": { + "message": "Importálás" }, "fileFormat": { "message": "Fájlformátum" @@ -2619,9 +2640,6 @@ "removedMasterPassword": { "message": "A mesterjelszó eltávolításra került." }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A következő szervezet tagjai számára már nincs szükség mesterjelszóra. Erősítsük meg az alábbi tartományt a szervezet adminisztrátorával." - }, "organizationName": { "message": "Szervezet neve" }, @@ -3477,10 +3495,6 @@ "aliasDomain": { "message": "Áldomain" }, - "importData": { - "message": "Adatok importálása", - "description": "Used for the desktop menu item and the header of the import dialog" - }, "importError": { "message": "Importálási hiba" }, @@ -3886,6 +3900,75 @@ "fileSavedToDevice": { "message": "A fájl mentésre került az eszközre. Kezeljük az eszközről a letöltéseket." }, + "importantNotice": { + "message": "Fontos megjegyzés" + }, + "setupTwoStepLogin": { + "message": "Kétlépéses bejelentkezés beüzemelése" + }, + "newDeviceVerificationNoticeContentPage1": { + "message": "A Bitwarden 2025 februárjától kódot küld a fiókhoz tartozó email címre, amellyel ellenőrizhetők az új eszközökről történő bejelentkezések." + }, + "newDeviceVerificationNoticeContentPage2": { + "message": "A fiók védelmének alternatív módjaként beállíthatunk kétlépéses bejelentkezést vagy módosíthatjuk az email címet egy elérhetőre." + }, + "remindMeLater": { + "message": "Emlékeztetés később" + }, + "newDeviceVerificationNoticePageOneFormContent": { + "message": "Megbízható a hozzáférés $EMAIL$ email címhez?", + "placeholders": { + "email": { + "content": "$1", + "example": "your_name@email.com" + } + } + }, + "newDeviceVerificationNoticePageOneEmailAccessNo": { + "message": "Nem, nem érem el" + }, + "newDeviceVerificationNoticePageOneEmailAccessYes": { + "message": "Igen, megbízhatóan hozzáférek az email címhez" + }, + "turnOnTwoStepLogin": { + "message": "Kétlépéses bejelentkezés bekapcsolása" + }, + "changeAcctEmail": { + "message": "Fiók email cím megváltoztatása" + }, + "passkeyLogin": { + "message": "Bejelentkezés belépőkulccsal?" + }, + "savePasskeyQuestion": { + "message": "Belépőkulcs mentése?" + }, + "saveNewPasskey": { + "message": "Mentés új bejelentkezésként" + }, + "savePasskeyNewLogin": { + "message": "Belépőkulcs mentése új bejelentkezésként" + }, + "noMatchingLoginsForSite": { + "message": "Nincsenek egyező bejelentkezések ehhez a webhelyhez." + }, + "overwritePasskey": { + "message": "Belépőkulcs felülírása?" + }, + "unableToSavePasskey": { + "message": "Nem lehet menteni a belépőkulcsot." + }, + "alreadyContainsPasskey": { + "message": "Ez az elem már tartalmaz egy belépőkulcsot. Biztosan felülírásra kerüljön az aktuális belépőkulcs?" + }, + "passkeyAlreadyExists": { + "message": "Ehhez az alkalmazáshoz már létezik belépőkulcs." + }, + "applicationDoesNotSupportDuplicates": { + "message": "Ez az alkalmazás nem támogatja a másolatokat." + }, + "closeThisWindow": { + "message": "Ezen ablak bezárása" + }, "allowScreenshots": { "message": "Képernyőrögzítés engedélyezése" }, @@ -4244,16 +4327,147 @@ "andMoreFeatures": { "message": "És még több!" }, - "planDescPremium": { - "message": "Teljes körű online biztonság" + "advancedOnlineSecurity": { + "message": "Bővített online biztonság" }, "upgradeToPremium": { "message": "Áttérés Prémium csomagra" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "A szervezet már nem használ mesterjelszavakat a Bitwardenbe bejelentkezéshez. A folytatáshoz ellenőrizzük a szervezetet és a tartományt." + }, + "continueWithLogIn": { + "message": "Folytatás bejelentkezéssel" + }, + "doNotContinue": { + "message": "Nincs folytatás" + }, + "domain": { + "message": "Tartomány" + }, + "keyConnectorDomainTooltip": { + "message": "Ez a tartomány tárolja a fiók titkosítási kulcsait, ezért győződjünk meg róla, hogy megbízunk-e benne. Ha nem vagyunk biztos benne, érdeklődjünk adminisztrátornál." + }, + "verifyYourOrganization": { + "message": "Szervezet ellenőrzése a bejelentkezéshez" + }, + "organizationVerified": { + "message": "A szervezet ellenőrzésre került." + }, + "domainVerified": { + "message": "A tartomány ellenőrzésre került." + }, + "leaveOrganizationContent": { + "message": "Ha nem ellenőrizzük a szervezetet, a szervezethez hozzáférés visszavonásra kerül." + }, + "leaveNow": { + "message": "Elhagyás most" + }, + "verifyYourDomainToLogin": { + "message": "Tartomány ellenőrzése a bejelentkezéshez" + }, + "verifyYourDomainDescription": { + "message": "A bejelentkezés folytatásához ellenőrizzük ezt a tartományt." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "A bejelentkezés folytatásához ellenőrizzük a szervezetet és a tartományt." + }, "sessionTimeoutSettingsAction": { "message": "Időkifutási művelet" }, "sessionTimeoutHeader": { "message": "Munkamenet időkifutás" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "Ezt a beállítást a szervezet lezeli." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "A szervezet a munkamenet maximális munkamenet időkifutását $HOURS$ órára és $MINUTES$ percre állította be.", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "A szervezet az alapértelmezett munkamenet időkifutástr Rendszerzár be értékre állította." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "A szervezet a maximális munkamenet időkifutást Újraindításkor értékre állította." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "A maximális időtúllépés nem haladhatja meg a $HOURS$ óra és $MINUTES$ perc értéket.", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "Újraindításkor" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Állítsunk be egy feloldási módot a széf időkifutási műveletének módosításához." + }, + "upgrade": { + "message": "Áttérés" + }, + "leaveConfirmationDialogTitle": { + "message": "Biztosan szeretnénk kilépni?" + }, + "leaveConfirmationDialogContentOne": { + "message": "Az elutasítással a személyes elemek a fiókban maradnak, de elveszítjük hozzáférést a megosztott elemekhez és a szervezeti funkciókhoz." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Lépjünk kapcsolatba az adminisztrátorral a hozzáférés visszaszerzéséért." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "$ORGANIZATION$ elhagyása", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "Hogyan kezeljem a széfet?" + }, + "transferItemsToOrganizationTitle": { + "message": "Elemek átvitele $ORGANIZATION$ szervezethez", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ megköveteli, hogy minden elem a szervezet tulajdonában legyen a biztonság és a megfelelőség érdekében. Kattintás az elfogadásra az elemek tulajdonjogának átruházásához.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Átvitel elfogadása" + }, + "declineAndLeave": { + "message": "Elutasítás és kilépés" + }, + "whyAmISeeingThis": { + "message": "Miért látható ez?" } } diff --git a/apps/desktop/src/locales/id/messages.json b/apps/desktop/src/locales/id/messages.json index fbbb1440990..9da92c35d11 100644 --- a/apps/desktop/src/locales/id/messages.json +++ b/apps/desktop/src/locales/id/messages.json @@ -708,6 +708,18 @@ "addAttachment": { "message": "Add attachment" }, + "itemsTransferred": { + "message": "Items transferred" + }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "maxFileSizeSansPunctuation": { "message": "Maximum file size is 500 MB" }, @@ -908,6 +920,12 @@ "unexpectedError": { "message": "Terjadi kesalahan yang tak diduga." }, + "unexpectedErrorShort": { + "message": "Unexpected error" + }, + "closeThisBitwardenWindow": { + "message": "Close this Bitwarden window and try again." + }, "itemInformation": { "message": "Informasi Item" }, @@ -1180,8 +1198,8 @@ "followUs": { "message": "Ikuti Kami" }, - "syncVault": { - "message": "Sinkronisasi Brankas" + "syncNow": { + "message": "Sync now" }, "changeMasterPass": { "message": "Ubah Kata Sandi Utama" @@ -1757,8 +1775,11 @@ "exportFrom": { "message": "Export from" }, - "exportVault": { - "message": "Ekspor Brankas" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "File Format" @@ -2619,9 +2640,6 @@ "removedMasterPassword": { "message": "Sandi utama dihapus" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "organizationName": { "message": "Organization name" }, @@ -3477,10 +3495,6 @@ "aliasDomain": { "message": "Alias domain" }, - "importData": { - "message": "Import data", - "description": "Used for the desktop menu item and the header of the import dialog" - }, "importError": { "message": "Import error" }, @@ -3886,6 +3900,75 @@ "fileSavedToDevice": { "message": "File saved to device. Manage from your device downloads." }, + "importantNotice": { + "message": "Important notice" + }, + "setupTwoStepLogin": { + "message": "Set up two-step login" + }, + "newDeviceVerificationNoticeContentPage1": { + "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." + }, + "newDeviceVerificationNoticeContentPage2": { + "message": "You can set up two-step login as an alternative way to protect your account or change your email to one you can access." + }, + "remindMeLater": { + "message": "Remind me later" + }, + "newDeviceVerificationNoticePageOneFormContent": { + "message": "Do you have reliable access to your email, $EMAIL$?", + "placeholders": { + "email": { + "content": "$1", + "example": "your_name@email.com" + } + } + }, + "newDeviceVerificationNoticePageOneEmailAccessNo": { + "message": "No, I do not" + }, + "newDeviceVerificationNoticePageOneEmailAccessYes": { + "message": "Yes, I can reliably access my email" + }, + "turnOnTwoStepLogin": { + "message": "Turn on two-step login" + }, + "changeAcctEmail": { + "message": "Change account email" + }, + "passkeyLogin": { + "message": "Log in with passkey?" + }, + "savePasskeyQuestion": { + "message": "Save passkey?" + }, + "saveNewPasskey": { + "message": "Save as new login" + }, + "savePasskeyNewLogin": { + "message": "Save passkey as new login" + }, + "noMatchingLoginsForSite": { + "message": "No matching logins for this site" + }, + "overwritePasskey": { + "message": "Overwrite passkey?" + }, + "unableToSavePasskey": { + "message": "Unable to save passkey" + }, + "alreadyContainsPasskey": { + "message": "This item already contains a passkey. Are you sure you want to overwrite the current passkey?" + }, + "passkeyAlreadyExists": { + "message": "A passkey already exists for this application." + }, + "applicationDoesNotSupportDuplicates": { + "message": "This application does not support duplicates." + }, + "closeThisWindow": { + "message": "Close this window" + }, "allowScreenshots": { "message": "Allow screen capture" }, @@ -4244,16 +4327,147 @@ "andMoreFeatures": { "message": "And more!" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "upgradeToPremium": { "message": "Upgrade to Premium" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { "message": "Timeout action" }, "sessionTimeoutHeader": { "message": "Session timeout" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Your organization has set the default session timeout to On system lock." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On restart." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On restart" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/desktop/src/locales/it/messages.json b/apps/desktop/src/locales/it/messages.json index 64e31e4136f..ed46bd2763a 100644 --- a/apps/desktop/src/locales/it/messages.json +++ b/apps/desktop/src/locales/it/messages.json @@ -708,6 +708,18 @@ "addAttachment": { "message": "Aggiungi allegato" }, + "itemsTransferred": { + "message": "Elementi trasferiti" + }, + "fixEncryption": { + "message": "Correggi la crittografia" + }, + "fixEncryptionTooltip": { + "message": "Questo file usa un metodo di crittografia obsoleto." + }, + "attachmentUpdated": { + "message": "Allegato aggiornato" + }, "maxFileSizeSansPunctuation": { "message": "La dimensione massima consentita è 500 MB." }, @@ -908,6 +920,12 @@ "unexpectedError": { "message": "Si è verificato un errore imprevisto." }, + "unexpectedErrorShort": { + "message": "Errore inaspettato" + }, + "closeThisBitwardenWindow": { + "message": "Chiudi questa finestra di Bitwarden e riprova." + }, "itemInformation": { "message": "Informazioni sull'elemento" }, @@ -1180,8 +1198,8 @@ "followUs": { "message": "Seguici" }, - "syncVault": { - "message": "Sincronizza cassaforte" + "syncNow": { + "message": "Sync now" }, "changeMasterPass": { "message": "Cambia password principale" @@ -1757,8 +1775,11 @@ "exportFrom": { "message": "Esporta da" }, - "exportVault": { - "message": "Esporta cassaforte" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "Formato file" @@ -2619,9 +2640,6 @@ "removedMasterPassword": { "message": "Password principale rimossa" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "La password principale non è più richiesta per i membri dell'organizzazione. Per favore, conferma il dominio qui sotto con l'amministratore." - }, "organizationName": { "message": "Nome dell'organizzazione" }, @@ -3477,10 +3495,6 @@ "aliasDomain": { "message": "Dominio alias" }, - "importData": { - "message": "Importa dati", - "description": "Used for the desktop menu item and the header of the import dialog" - }, "importError": { "message": "Errore di importazione" }, @@ -3886,6 +3900,75 @@ "fileSavedToDevice": { "message": "File salvato sul dispositivo. Gestisci dai download del dispositivo." }, + "importantNotice": { + "message": "Avviso importante" + }, + "setupTwoStepLogin": { + "message": "Imposta l'accesso in due passaggi" + }, + "newDeviceVerificationNoticeContentPage1": { + "message": "Bitwarden invierà un codice all'email del tuo account per verificare gli accessi da nuovi dispositivi." + }, + "newDeviceVerificationNoticeContentPage2": { + "message": "Puoi impostare l'accesso in due passaggi per proteggere il tuo account, oppure scegliere una email alla quale hai accesso." + }, + "remindMeLater": { + "message": "Ricordamelo in seguito" + }, + "newDeviceVerificationNoticePageOneFormContent": { + "message": "Confermi di poter accedere all'email $EMAIL$?", + "placeholders": { + "email": { + "content": "$1", + "example": "your_name@email.com" + } + } + }, + "newDeviceVerificationNoticePageOneEmailAccessNo": { + "message": "No" + }, + "newDeviceVerificationNoticePageOneEmailAccessYes": { + "message": "Sì, ho accesso all'email" + }, + "turnOnTwoStepLogin": { + "message": "Attiva l'accesso in due passaggi" + }, + "changeAcctEmail": { + "message": "Cambia l'email dell'account" + }, + "passkeyLogin": { + "message": "Vuoi accedere con la passkey?" + }, + "savePasskeyQuestion": { + "message": "Vuoi salvare la passkey?" + }, + "saveNewPasskey": { + "message": "Salva come nuovo login" + }, + "savePasskeyNewLogin": { + "message": "Salva la passkey come nuovo elemento" + }, + "noMatchingLoginsForSite": { + "message": "Nessun login salvato per questa pagina" + }, + "overwritePasskey": { + "message": "Vuoi sovrascrivere la passkey?" + }, + "unableToSavePasskey": { + "message": "Impossibile salvare la passkey" + }, + "alreadyContainsPasskey": { + "message": "Questo elemento contiene già una passkey. Vuoi sovrascriverla?" + }, + "passkeyAlreadyExists": { + "message": "Esiste già una passkey per questa applicazione." + }, + "applicationDoesNotSupportDuplicates": { + "message": "Questa applicazione non supporta duplicati." + }, + "closeThisWindow": { + "message": "Chiudi questa finestra" + }, "allowScreenshots": { "message": "Permetti cattura dello schermo" }, @@ -4244,16 +4327,147 @@ "andMoreFeatures": { "message": "E molto altro!" }, - "planDescPremium": { - "message": "Sicurezza online completa" + "advancedOnlineSecurity": { + "message": "Sicurezza online avanzata" }, "upgradeToPremium": { "message": "Aggiorna a Premium" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { "message": "Azione al timeout" }, "sessionTimeoutHeader": { "message": "Timeout della sessione" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "Questa impostazione è gestita dalla tua organizzazione." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "La tua organizzazione ha impostato $HOURS$ ora/e e $MINUTES$ minuto/i come durata massima della sessione.", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "In base alle impostazioni della tua organizzazione, la sessione terminerà al blocco del dispositivo." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "In base alle impostazioni della tua organizzazione, la sessione terminerà al riavvio dell'applicazione." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "La durata della sessione non può superare $HOURS$ ora/e e $MINUTES$ minuto/i", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "Al riavvio" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Imposta un metodo di sblocco per modificare l'azione al timeout" + }, + "upgrade": { + "message": "Aggiorna" + }, + "leaveConfirmationDialogTitle": { + "message": "Vuoi davvero abbandonare?" + }, + "leaveConfirmationDialogContentOne": { + "message": "Se rifiuti, tutti gli elementi esistenti resteranno nel tuo account, ma perderai l'accesso agli oggetti condivisi e alle funzioni organizzative." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contatta il tuo amministratore per recuperare l'accesso." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Abbandona $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "Come si gestisce la cassaforte?" + }, + "transferItemsToOrganizationTitle": { + "message": "Trasferisci gli elementi in $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ richiede che tutti gli elementi siano di proprietà dell'organizzazione per motivi di conformità e sicurezza. Clicca su 'Accetta' per trasferire la proprietà.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accetta il trasferimento" + }, + "declineAndLeave": { + "message": "Rifiuta e abbandona" + }, + "whyAmISeeingThis": { + "message": "Perché vedo questo avviso?" } } diff --git a/apps/desktop/src/locales/ja/messages.json b/apps/desktop/src/locales/ja/messages.json index 5accef2b5ee..d930bd22b9c 100644 --- a/apps/desktop/src/locales/ja/messages.json +++ b/apps/desktop/src/locales/ja/messages.json @@ -708,6 +708,18 @@ "addAttachment": { "message": "Add attachment" }, + "itemsTransferred": { + "message": "Items transferred" + }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "maxFileSizeSansPunctuation": { "message": "Maximum file size is 500 MB" }, @@ -908,6 +920,12 @@ "unexpectedError": { "message": "予期せぬエラーが発生しました。" }, + "unexpectedErrorShort": { + "message": "Unexpected error" + }, + "closeThisBitwardenWindow": { + "message": "Close this Bitwarden window and try again." + }, "itemInformation": { "message": "アイテム情報" }, @@ -1180,8 +1198,8 @@ "followUs": { "message": "フォロー" }, - "syncVault": { - "message": "保管庫の同期" + "syncNow": { + "message": "Sync now" }, "changeMasterPass": { "message": "マスターパスワードの変更" @@ -1757,8 +1775,11 @@ "exportFrom": { "message": "エクスポート元" }, - "exportVault": { - "message": "保管庫のエクスポート" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "ファイル形式" @@ -2619,9 +2640,6 @@ "removedMasterPassword": { "message": "マスターパスワードを削除しました。" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "organizationName": { "message": "Organization name" }, @@ -3477,10 +3495,6 @@ "aliasDomain": { "message": "エイリアスドメイン" }, - "importData": { - "message": "データのインポート", - "description": "Used for the desktop menu item and the header of the import dialog" - }, "importError": { "message": "インポート エラー" }, @@ -3886,6 +3900,75 @@ "fileSavedToDevice": { "message": "ファイルをデバイスに保存しました。デバイスのダウンロードで管理できます。" }, + "importantNotice": { + "message": "Important notice" + }, + "setupTwoStepLogin": { + "message": "Set up two-step login" + }, + "newDeviceVerificationNoticeContentPage1": { + "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." + }, + "newDeviceVerificationNoticeContentPage2": { + "message": "You can set up two-step login as an alternative way to protect your account or change your email to one you can access." + }, + "remindMeLater": { + "message": "Remind me later" + }, + "newDeviceVerificationNoticePageOneFormContent": { + "message": "Do you have reliable access to your email, $EMAIL$?", + "placeholders": { + "email": { + "content": "$1", + "example": "your_name@email.com" + } + } + }, + "newDeviceVerificationNoticePageOneEmailAccessNo": { + "message": "No, I do not" + }, + "newDeviceVerificationNoticePageOneEmailAccessYes": { + "message": "Yes, I can reliably access my email" + }, + "turnOnTwoStepLogin": { + "message": "Turn on two-step login" + }, + "changeAcctEmail": { + "message": "Change account email" + }, + "passkeyLogin": { + "message": "Log in with passkey?" + }, + "savePasskeyQuestion": { + "message": "Save passkey?" + }, + "saveNewPasskey": { + "message": "Save as new login" + }, + "savePasskeyNewLogin": { + "message": "Save passkey as new login" + }, + "noMatchingLoginsForSite": { + "message": "No matching logins for this site" + }, + "overwritePasskey": { + "message": "Overwrite passkey?" + }, + "unableToSavePasskey": { + "message": "Unable to save passkey" + }, + "alreadyContainsPasskey": { + "message": "This item already contains a passkey. Are you sure you want to overwrite the current passkey?" + }, + "passkeyAlreadyExists": { + "message": "A passkey already exists for this application." + }, + "applicationDoesNotSupportDuplicates": { + "message": "This application does not support duplicates." + }, + "closeThisWindow": { + "message": "Close this window" + }, "allowScreenshots": { "message": "スクリーンショットを許可" }, @@ -4244,16 +4327,147 @@ "andMoreFeatures": { "message": "And more!" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "upgradeToPremium": { "message": "Upgrade to Premium" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { "message": "Timeout action" }, "sessionTimeoutHeader": { "message": "Session timeout" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Your organization has set the default session timeout to On system lock." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On restart." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On restart" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/desktop/src/locales/ka/messages.json b/apps/desktop/src/locales/ka/messages.json index cca3ab548cf..d4fae58fca4 100644 --- a/apps/desktop/src/locales/ka/messages.json +++ b/apps/desktop/src/locales/ka/messages.json @@ -708,6 +708,18 @@ "addAttachment": { "message": "Add attachment" }, + "itemsTransferred": { + "message": "Items transferred" + }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "maxFileSizeSansPunctuation": { "message": "Maximum file size is 500 MB" }, @@ -908,6 +920,12 @@ "unexpectedError": { "message": "An unexpected error has occurred." }, + "unexpectedErrorShort": { + "message": "Unexpected error" + }, + "closeThisBitwardenWindow": { + "message": "Close this Bitwarden window and try again." + }, "itemInformation": { "message": "Item information" }, @@ -1180,8 +1198,8 @@ "followUs": { "message": "Follow us" }, - "syncVault": { - "message": "Sync vault" + "syncNow": { + "message": "Sync now" }, "changeMasterPass": { "message": "Change master password" @@ -1757,8 +1775,11 @@ "exportFrom": { "message": "Export from" }, - "exportVault": { - "message": "Export vault" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "File format" @@ -2619,9 +2640,6 @@ "removedMasterPassword": { "message": "Master password removed" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "organizationName": { "message": "Organization name" }, @@ -3477,10 +3495,6 @@ "aliasDomain": { "message": "Alias domain" }, - "importData": { - "message": "მონაცემების შემოტანა", - "description": "Used for the desktop menu item and the header of the import dialog" - }, "importError": { "message": "შემოტანის შეცდომა" }, @@ -3886,6 +3900,75 @@ "fileSavedToDevice": { "message": "File saved to device. Manage from your device downloads." }, + "importantNotice": { + "message": "Important notice" + }, + "setupTwoStepLogin": { + "message": "Set up two-step login" + }, + "newDeviceVerificationNoticeContentPage1": { + "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." + }, + "newDeviceVerificationNoticeContentPage2": { + "message": "You can set up two-step login as an alternative way to protect your account or change your email to one you can access." + }, + "remindMeLater": { + "message": "Remind me later" + }, + "newDeviceVerificationNoticePageOneFormContent": { + "message": "Do you have reliable access to your email, $EMAIL$?", + "placeholders": { + "email": { + "content": "$1", + "example": "your_name@email.com" + } + } + }, + "newDeviceVerificationNoticePageOneEmailAccessNo": { + "message": "No, I do not" + }, + "newDeviceVerificationNoticePageOneEmailAccessYes": { + "message": "Yes, I can reliably access my email" + }, + "turnOnTwoStepLogin": { + "message": "Turn on two-step login" + }, + "changeAcctEmail": { + "message": "Change account email" + }, + "passkeyLogin": { + "message": "Log in with passkey?" + }, + "savePasskeyQuestion": { + "message": "Save passkey?" + }, + "saveNewPasskey": { + "message": "Save as new login" + }, + "savePasskeyNewLogin": { + "message": "Save passkey as new login" + }, + "noMatchingLoginsForSite": { + "message": "No matching logins for this site" + }, + "overwritePasskey": { + "message": "Overwrite passkey?" + }, + "unableToSavePasskey": { + "message": "Unable to save passkey" + }, + "alreadyContainsPasskey": { + "message": "This item already contains a passkey. Are you sure you want to overwrite the current passkey?" + }, + "passkeyAlreadyExists": { + "message": "A passkey already exists for this application." + }, + "applicationDoesNotSupportDuplicates": { + "message": "This application does not support duplicates." + }, + "closeThisWindow": { + "message": "Close this window" + }, "allowScreenshots": { "message": "Allow screen capture" }, @@ -4244,16 +4327,147 @@ "andMoreFeatures": { "message": "And more!" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "upgradeToPremium": { "message": "Upgrade to Premium" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { "message": "Timeout action" }, "sessionTimeoutHeader": { "message": "Session timeout" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Your organization has set the default session timeout to On system lock." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On restart." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On restart" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/desktop/src/locales/km/messages.json b/apps/desktop/src/locales/km/messages.json index 0211550b08d..315272ae464 100644 --- a/apps/desktop/src/locales/km/messages.json +++ b/apps/desktop/src/locales/km/messages.json @@ -708,6 +708,18 @@ "addAttachment": { "message": "Add attachment" }, + "itemsTransferred": { + "message": "Items transferred" + }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "maxFileSizeSansPunctuation": { "message": "Maximum file size is 500 MB" }, @@ -908,6 +920,12 @@ "unexpectedError": { "message": "An unexpected error has occurred." }, + "unexpectedErrorShort": { + "message": "Unexpected error" + }, + "closeThisBitwardenWindow": { + "message": "Close this Bitwarden window and try again." + }, "itemInformation": { "message": "Item information" }, @@ -1180,8 +1198,8 @@ "followUs": { "message": "Follow us" }, - "syncVault": { - "message": "Sync vault" + "syncNow": { + "message": "Sync now" }, "changeMasterPass": { "message": "Change master password" @@ -1757,8 +1775,11 @@ "exportFrom": { "message": "Export from" }, - "exportVault": { - "message": "Export vault" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "File format" @@ -2619,9 +2640,6 @@ "removedMasterPassword": { "message": "Master password removed" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "organizationName": { "message": "Organization name" }, @@ -3477,10 +3495,6 @@ "aliasDomain": { "message": "Alias domain" }, - "importData": { - "message": "Import data", - "description": "Used for the desktop menu item and the header of the import dialog" - }, "importError": { "message": "Import error" }, @@ -3886,6 +3900,75 @@ "fileSavedToDevice": { "message": "File saved to device. Manage from your device downloads." }, + "importantNotice": { + "message": "Important notice" + }, + "setupTwoStepLogin": { + "message": "Set up two-step login" + }, + "newDeviceVerificationNoticeContentPage1": { + "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." + }, + "newDeviceVerificationNoticeContentPage2": { + "message": "You can set up two-step login as an alternative way to protect your account or change your email to one you can access." + }, + "remindMeLater": { + "message": "Remind me later" + }, + "newDeviceVerificationNoticePageOneFormContent": { + "message": "Do you have reliable access to your email, $EMAIL$?", + "placeholders": { + "email": { + "content": "$1", + "example": "your_name@email.com" + } + } + }, + "newDeviceVerificationNoticePageOneEmailAccessNo": { + "message": "No, I do not" + }, + "newDeviceVerificationNoticePageOneEmailAccessYes": { + "message": "Yes, I can reliably access my email" + }, + "turnOnTwoStepLogin": { + "message": "Turn on two-step login" + }, + "changeAcctEmail": { + "message": "Change account email" + }, + "passkeyLogin": { + "message": "Log in with passkey?" + }, + "savePasskeyQuestion": { + "message": "Save passkey?" + }, + "saveNewPasskey": { + "message": "Save as new login" + }, + "savePasskeyNewLogin": { + "message": "Save passkey as new login" + }, + "noMatchingLoginsForSite": { + "message": "No matching logins for this site" + }, + "overwritePasskey": { + "message": "Overwrite passkey?" + }, + "unableToSavePasskey": { + "message": "Unable to save passkey" + }, + "alreadyContainsPasskey": { + "message": "This item already contains a passkey. Are you sure you want to overwrite the current passkey?" + }, + "passkeyAlreadyExists": { + "message": "A passkey already exists for this application." + }, + "applicationDoesNotSupportDuplicates": { + "message": "This application does not support duplicates." + }, + "closeThisWindow": { + "message": "Close this window" + }, "allowScreenshots": { "message": "Allow screen capture" }, @@ -4244,16 +4327,147 @@ "andMoreFeatures": { "message": "And more!" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "upgradeToPremium": { "message": "Upgrade to Premium" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { "message": "Timeout action" }, "sessionTimeoutHeader": { "message": "Session timeout" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Your organization has set the default session timeout to On system lock." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On restart." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On restart" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/desktop/src/locales/kn/messages.json b/apps/desktop/src/locales/kn/messages.json index 4ef1f0edd05..0f880c88f1f 100644 --- a/apps/desktop/src/locales/kn/messages.json +++ b/apps/desktop/src/locales/kn/messages.json @@ -708,6 +708,18 @@ "addAttachment": { "message": "Add attachment" }, + "itemsTransferred": { + "message": "Items transferred" + }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "maxFileSizeSansPunctuation": { "message": "Maximum file size is 500 MB" }, @@ -908,6 +920,12 @@ "unexpectedError": { "message": "ಅನಿರೀಕ್ಷಿತ ದೋಷ ಸಂಭವಿಸಿದೆ." }, + "unexpectedErrorShort": { + "message": "Unexpected error" + }, + "closeThisBitwardenWindow": { + "message": "Close this Bitwarden window and try again." + }, "itemInformation": { "message": "ಐಟಂ ಮಾಹಿತಿ" }, @@ -1180,8 +1198,8 @@ "followUs": { "message": "ನಮ್ಮನ್ನು ಅನುಸರಿಸಿ" }, - "syncVault": { - "message": "ಸಿಂಕ್ ವಾಲ್ಟ್" + "syncNow": { + "message": "Sync now" }, "changeMasterPass": { "message": "ಮಾಸ್ಟರ್ ಪಾಸ್ವರ್ಡ್ ಬದಲಾಯಿಸಿ" @@ -1757,8 +1775,11 @@ "exportFrom": { "message": "Export from" }, - "exportVault": { - "message": "ರಫ್ತು ವಾಲ್ಟ್" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "ಕಡತದ ಮಾದರಿ" @@ -2619,9 +2640,6 @@ "removedMasterPassword": { "message": "Master password removed" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "organizationName": { "message": "Organization name" }, @@ -3477,10 +3495,6 @@ "aliasDomain": { "message": "Alias domain" }, - "importData": { - "message": "Import data", - "description": "Used for the desktop menu item and the header of the import dialog" - }, "importError": { "message": "Import error" }, @@ -3886,6 +3900,75 @@ "fileSavedToDevice": { "message": "File saved to device. Manage from your device downloads." }, + "importantNotice": { + "message": "Important notice" + }, + "setupTwoStepLogin": { + "message": "Set up two-step login" + }, + "newDeviceVerificationNoticeContentPage1": { + "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." + }, + "newDeviceVerificationNoticeContentPage2": { + "message": "You can set up two-step login as an alternative way to protect your account or change your email to one you can access." + }, + "remindMeLater": { + "message": "Remind me later" + }, + "newDeviceVerificationNoticePageOneFormContent": { + "message": "Do you have reliable access to your email, $EMAIL$?", + "placeholders": { + "email": { + "content": "$1", + "example": "your_name@email.com" + } + } + }, + "newDeviceVerificationNoticePageOneEmailAccessNo": { + "message": "No, I do not" + }, + "newDeviceVerificationNoticePageOneEmailAccessYes": { + "message": "Yes, I can reliably access my email" + }, + "turnOnTwoStepLogin": { + "message": "Turn on two-step login" + }, + "changeAcctEmail": { + "message": "Change account email" + }, + "passkeyLogin": { + "message": "Log in with passkey?" + }, + "savePasskeyQuestion": { + "message": "Save passkey?" + }, + "saveNewPasskey": { + "message": "Save as new login" + }, + "savePasskeyNewLogin": { + "message": "Save passkey as new login" + }, + "noMatchingLoginsForSite": { + "message": "No matching logins for this site" + }, + "overwritePasskey": { + "message": "Overwrite passkey?" + }, + "unableToSavePasskey": { + "message": "Unable to save passkey" + }, + "alreadyContainsPasskey": { + "message": "This item already contains a passkey. Are you sure you want to overwrite the current passkey?" + }, + "passkeyAlreadyExists": { + "message": "A passkey already exists for this application." + }, + "applicationDoesNotSupportDuplicates": { + "message": "This application does not support duplicates." + }, + "closeThisWindow": { + "message": "Close this window" + }, "allowScreenshots": { "message": "Allow screen capture" }, @@ -4244,16 +4327,147 @@ "andMoreFeatures": { "message": "And more!" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "upgradeToPremium": { "message": "Upgrade to Premium" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { "message": "Timeout action" }, "sessionTimeoutHeader": { "message": "Session timeout" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Your organization has set the default session timeout to On system lock." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On restart." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On restart" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/desktop/src/locales/ko/messages.json b/apps/desktop/src/locales/ko/messages.json index 9f6153cb314..2d3c0bba871 100644 --- a/apps/desktop/src/locales/ko/messages.json +++ b/apps/desktop/src/locales/ko/messages.json @@ -708,6 +708,18 @@ "addAttachment": { "message": "Add attachment" }, + "itemsTransferred": { + "message": "Items transferred" + }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "maxFileSizeSansPunctuation": { "message": "Maximum file size is 500 MB" }, @@ -908,6 +920,12 @@ "unexpectedError": { "message": "예기치 못한 오류가 발생했습니다." }, + "unexpectedErrorShort": { + "message": "Unexpected error" + }, + "closeThisBitwardenWindow": { + "message": "Close this Bitwarden window and try again." + }, "itemInformation": { "message": "항목 정보" }, @@ -1180,8 +1198,8 @@ "followUs": { "message": "팔로우하기" }, - "syncVault": { - "message": "보관함 동기화" + "syncNow": { + "message": "Sync now" }, "changeMasterPass": { "message": "마스터 비밀번호 변경" @@ -1757,8 +1775,11 @@ "exportFrom": { "message": "Export from" }, - "exportVault": { - "message": "보관함 내보내기" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "파일 형식" @@ -2619,9 +2640,6 @@ "removedMasterPassword": { "message": "마스터 비밀번호가 제거되었습니다." }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "organizationName": { "message": "Organization name" }, @@ -3477,10 +3495,6 @@ "aliasDomain": { "message": "Alias domain" }, - "importData": { - "message": "데이터 가져오기", - "description": "Used for the desktop menu item and the header of the import dialog" - }, "importError": { "message": "Import error" }, @@ -3886,6 +3900,75 @@ "fileSavedToDevice": { "message": "File saved to device. Manage from your device downloads." }, + "importantNotice": { + "message": "Important notice" + }, + "setupTwoStepLogin": { + "message": "Set up two-step login" + }, + "newDeviceVerificationNoticeContentPage1": { + "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." + }, + "newDeviceVerificationNoticeContentPage2": { + "message": "You can set up two-step login as an alternative way to protect your account or change your email to one you can access." + }, + "remindMeLater": { + "message": "Remind me later" + }, + "newDeviceVerificationNoticePageOneFormContent": { + "message": "Do you have reliable access to your email, $EMAIL$?", + "placeholders": { + "email": { + "content": "$1", + "example": "your_name@email.com" + } + } + }, + "newDeviceVerificationNoticePageOneEmailAccessNo": { + "message": "No, I do not" + }, + "newDeviceVerificationNoticePageOneEmailAccessYes": { + "message": "Yes, I can reliably access my email" + }, + "turnOnTwoStepLogin": { + "message": "Turn on two-step login" + }, + "changeAcctEmail": { + "message": "Change account email" + }, + "passkeyLogin": { + "message": "Log in with passkey?" + }, + "savePasskeyQuestion": { + "message": "Save passkey?" + }, + "saveNewPasskey": { + "message": "Save as new login" + }, + "savePasskeyNewLogin": { + "message": "Save passkey as new login" + }, + "noMatchingLoginsForSite": { + "message": "No matching logins for this site" + }, + "overwritePasskey": { + "message": "Overwrite passkey?" + }, + "unableToSavePasskey": { + "message": "Unable to save passkey" + }, + "alreadyContainsPasskey": { + "message": "This item already contains a passkey. Are you sure you want to overwrite the current passkey?" + }, + "passkeyAlreadyExists": { + "message": "A passkey already exists for this application." + }, + "applicationDoesNotSupportDuplicates": { + "message": "This application does not support duplicates." + }, + "closeThisWindow": { + "message": "Close this window" + }, "allowScreenshots": { "message": "Allow screen capture" }, @@ -4244,16 +4327,147 @@ "andMoreFeatures": { "message": "And more!" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "upgradeToPremium": { "message": "Upgrade to Premium" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { "message": "Timeout action" }, "sessionTimeoutHeader": { "message": "Session timeout" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Your organization has set the default session timeout to On system lock." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On restart." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On restart" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/desktop/src/locales/lt/messages.json b/apps/desktop/src/locales/lt/messages.json index d7612765f1a..cce5cd5d223 100644 --- a/apps/desktop/src/locales/lt/messages.json +++ b/apps/desktop/src/locales/lt/messages.json @@ -708,6 +708,18 @@ "addAttachment": { "message": "Add attachment" }, + "itemsTransferred": { + "message": "Items transferred" + }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "maxFileSizeSansPunctuation": { "message": "Maximum file size is 500 MB" }, @@ -908,6 +920,12 @@ "unexpectedError": { "message": "Įvyko netikėta klaida." }, + "unexpectedErrorShort": { + "message": "Unexpected error" + }, + "closeThisBitwardenWindow": { + "message": "Close this Bitwarden window and try again." + }, "itemInformation": { "message": "Elemento informacija" }, @@ -1180,8 +1198,8 @@ "followUs": { "message": "Sekite mus" }, - "syncVault": { - "message": "Sinchronizuoti saugyklą" + "syncNow": { + "message": "Sync now" }, "changeMasterPass": { "message": "Keisti pagrindinį slaptažodį" @@ -1757,8 +1775,11 @@ "exportFrom": { "message": "Export from" }, - "exportVault": { - "message": "Eksportuoti saugyklą" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "Failo formatas" @@ -2619,9 +2640,6 @@ "removedMasterPassword": { "message": "Pagrindinis slaptažodis pašalintas" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "organizationName": { "message": "Organization name" }, @@ -3477,10 +3495,6 @@ "aliasDomain": { "message": "Alias domenas" }, - "importData": { - "message": "Importuoti duomenis", - "description": "Used for the desktop menu item and the header of the import dialog" - }, "importError": { "message": "Importavimo klaida" }, @@ -3886,6 +3900,75 @@ "fileSavedToDevice": { "message": "File saved to device. Manage from your device downloads." }, + "importantNotice": { + "message": "Important notice" + }, + "setupTwoStepLogin": { + "message": "Set up two-step login" + }, + "newDeviceVerificationNoticeContentPage1": { + "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." + }, + "newDeviceVerificationNoticeContentPage2": { + "message": "You can set up two-step login as an alternative way to protect your account or change your email to one you can access." + }, + "remindMeLater": { + "message": "Remind me later" + }, + "newDeviceVerificationNoticePageOneFormContent": { + "message": "Do you have reliable access to your email, $EMAIL$?", + "placeholders": { + "email": { + "content": "$1", + "example": "your_name@email.com" + } + } + }, + "newDeviceVerificationNoticePageOneEmailAccessNo": { + "message": "No, I do not" + }, + "newDeviceVerificationNoticePageOneEmailAccessYes": { + "message": "Yes, I can reliably access my email" + }, + "turnOnTwoStepLogin": { + "message": "Turn on two-step login" + }, + "changeAcctEmail": { + "message": "Change account email" + }, + "passkeyLogin": { + "message": "Log in with passkey?" + }, + "savePasskeyQuestion": { + "message": "Save passkey?" + }, + "saveNewPasskey": { + "message": "Save as new login" + }, + "savePasskeyNewLogin": { + "message": "Save passkey as new login" + }, + "noMatchingLoginsForSite": { + "message": "No matching logins for this site" + }, + "overwritePasskey": { + "message": "Overwrite passkey?" + }, + "unableToSavePasskey": { + "message": "Unable to save passkey" + }, + "alreadyContainsPasskey": { + "message": "This item already contains a passkey. Are you sure you want to overwrite the current passkey?" + }, + "passkeyAlreadyExists": { + "message": "A passkey already exists for this application." + }, + "applicationDoesNotSupportDuplicates": { + "message": "This application does not support duplicates." + }, + "closeThisWindow": { + "message": "Close this window" + }, "allowScreenshots": { "message": "Allow screen capture" }, @@ -4244,16 +4327,147 @@ "andMoreFeatures": { "message": "And more!" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "upgradeToPremium": { "message": "Upgrade to Premium" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { "message": "Timeout action" }, "sessionTimeoutHeader": { "message": "Session timeout" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Your organization has set the default session timeout to On system lock." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On restart." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On restart" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/desktop/src/locales/lv/messages.json b/apps/desktop/src/locales/lv/messages.json index 5624f89f5db..edfab735fbd 100644 --- a/apps/desktop/src/locales/lv/messages.json +++ b/apps/desktop/src/locales/lv/messages.json @@ -708,6 +708,18 @@ "addAttachment": { "message": "Pievienot pielikumu" }, + "itemsTransferred": { + "message": "Vienumi pārcelti" + }, + "fixEncryption": { + "message": "Salabot šifrēšanu" + }, + "fixEncryptionTooltip": { + "message": "Šī datne izmanto novecojušu šifrēšanas veidu." + }, + "attachmentUpdated": { + "message": "Pielikums atjaunināts" + }, "maxFileSizeSansPunctuation": { "message": "Lielākais pieļaujamais datnes izmērs ir 500 MB" }, @@ -908,6 +920,12 @@ "unexpectedError": { "message": "Atgadījās neparedzēta kļūda." }, + "unexpectedErrorShort": { + "message": "Neparedzēta kļūda" + }, + "closeThisBitwardenWindow": { + "message": "Šis Bitwarden logs jāaizver un jāmēģina vēlreiz." + }, "itemInformation": { "message": "Vienuma informācija" }, @@ -1180,8 +1198,8 @@ "followUs": { "message": "Sekot mums" }, - "syncVault": { - "message": "Sinhronizēt glabātavu" + "syncNow": { + "message": "Sinhronizēt tūlīt" }, "changeMasterPass": { "message": "Mainīt galveno paroli" @@ -1757,8 +1775,11 @@ "exportFrom": { "message": "Izgūt no" }, - "exportVault": { - "message": "Izgūt glabātavas saturu" + "export": { + "message": "Izgūt" + }, + "import": { + "message": "Ievietot" }, "fileFormat": { "message": "Datnes veids" @@ -2619,9 +2640,6 @@ "removedMasterPassword": { "message": "Galvenā parole tika noņemta." }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "Galvenā parole vairs nav nepieciešama turpmāk minētās apvienības dalībniekiem. Lūgums saskaņot zemāk esošo domēnu ar savas apvienības pārvaldītāju." - }, "organizationName": { "message": "Apvienības nosaukums" }, @@ -3477,10 +3495,6 @@ "aliasDomain": { "message": "Aizstājdomēns" }, - "importData": { - "message": "Ievietot datus", - "description": "Used for the desktop menu item and the header of the import dialog" - }, "importError": { "message": "Ievietošanas kļūda" }, @@ -3886,6 +3900,75 @@ "fileSavedToDevice": { "message": "Datne saglabāta ierīcē. Tā ir atrodama ierīces lejupielāžu mapē." }, + "importantNotice": { + "message": "Svarīgs paziņojums" + }, + "setupTwoStepLogin": { + "message": "Iestatīt divpakāpju pieteikšanos" + }, + "newDeviceVerificationNoticeContentPage1": { + "message": "Bitwarden, sākot ar 2025. gada februāri, nosūtīs kodu uz konta e-pasta adresi, lai apliecinātu pieteikšanos jaunās ierīcēs." + }, + "newDeviceVerificationNoticeContentPage2": { + "message": "Var iestatīt divpakāpju pieteikšanos kā citu veidu, kā aizsargāt savu kontu, vai iestatīt savu e-pasta adresi uz tādu, kurai ir piekļuve." + }, + "remindMeLater": { + "message": "Atgādināt man vēlāk" + }, + "newDeviceVerificationNoticePageOneFormContent": { + "message": "Vai ir uzticama piekļuve savai e-pasta adresei $EMAIL$?", + "placeholders": { + "email": { + "content": "$1", + "example": "your_name@email.com" + } + } + }, + "newDeviceVerificationNoticePageOneEmailAccessNo": { + "message": "Nē, nav" + }, + "newDeviceVerificationNoticePageOneEmailAccessYes": { + "message": "Jā, varu uzticami piekļūt savam e-pastam" + }, + "turnOnTwoStepLogin": { + "message": "Ieslēgt divpakāpju pieteikšanos" + }, + "changeAcctEmail": { + "message": "Mainīt konta e-pasta adresi" + }, + "passkeyLogin": { + "message": "Pieteikties ar piekļuves atslēgu?" + }, + "savePasskeyQuestion": { + "message": "Saglabāt piekļuves atslēgu?" + }, + "saveNewPasskey": { + "message": "Saglabāt kā jaunu pieteikšanās vienumu" + }, + "savePasskeyNewLogin": { + "message": "Saglabāt piekļuves atslēgu kā jaunu pieteikšanās vienumu" + }, + "noMatchingLoginsForSite": { + "message": "Šai vietnei nav atbilstošu pieteikšanās vietnumu" + }, + "overwritePasskey": { + "message": "Pārrakstīt piekļuves atslēgu?" + }, + "unableToSavePasskey": { + "message": "Nevar saglabāt piekļuves atslēgu" + }, + "alreadyContainsPasskey": { + "message": "Šis vienums jau satur piekļuves atslēgu. Vai tiešām pārrakstīt pašreizējo piekļuves atslēgu?" + }, + "passkeyAlreadyExists": { + "message": "Šai lietotnei jau pastāv piekļuves atslēga." + }, + "applicationDoesNotSupportDuplicates": { + "message": "This application does not support duplicates." + }, + "closeThisWindow": { + "message": "Aizvērt šo logu" + }, "allowScreenshots": { "message": "Atļaut ekrāna tveršanu" }, @@ -4244,16 +4327,147 @@ "andMoreFeatures": { "message": "Un vēl!" }, - "planDescPremium": { - "message": "Pilnīga drošība tiešsaistē" + "advancedOnlineSecurity": { + "message": "Izvērsta tiešsaistes drošība" }, "upgradeToPremium": { "message": "Uzlabot uz Premium" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Apvienība vairs neizmanto galvenās paroles, lai pieteiktos Bitwarden. Lai turpinātu, jāapliecina apvienība un domēns." + }, + "continueWithLogIn": { + "message": "Turpināt ar pieteikšanos" + }, + "doNotContinue": { + "message": "Neturpināt" + }, + "domain": { + "message": "Domēns" + }, + "keyConnectorDomainTooltip": { + "message": "Šajā domēnā tiks glabātas konta šifrēšanas atslēgas, tādēļ jāpārliecinās par uzticamību. Ja nav pārliecības, jāsazinās ar savu pārvaldītāju." + }, + "verifyYourOrganization": { + "message": "Jāapliecina apvienība, lai pieteiktos" + }, + "organizationVerified": { + "message": "Apvienība apliecināta" + }, + "domainVerified": { + "message": "Domēns ir apliecināts" + }, + "leaveOrganizationContent": { + "message": "Ja neapliecināsi apvienību, tiks atsaukta piekļuve tai." + }, + "leaveNow": { + "message": "Pamest tagad" + }, + "verifyYourDomainToLogin": { + "message": "Jāapliecina domēns, lai pieteiktos" + }, + "verifyYourDomainDescription": { + "message": "Lai turpinātu pieteikšanos, jāapliecina šis domēns." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "Lai turpinātu pieteikšanos, jāapliecina apvienība un domēns." + }, "sessionTimeoutSettingsAction": { "message": "Noildzes darbība" }, "sessionTimeoutHeader": { "message": "Sesijas noildze" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "Šo iestatījumu pārvalda apvienība." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Your organization has set the default session timeout to On system lock." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On restart." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On restart" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "upgrade": { + "message": "Uzlabot" + }, + "leaveConfirmationDialogTitle": { + "message": "Vai tiešām pamest?" + }, + "leaveConfirmationDialogContentOne": { + "message": "Pēc noraidīšanas personīgie vienumi paliks Tavā kontā, bet Tu zaudēsi piekļvuvi kopīgotajiem vienumiem un apvienību iespējām." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Jāsazinās ar savu pārvaldītāju, lai atgūtu piekļuvi." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Pamest $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "Kā es varu pārvaldīt savu glabātavu?" + }, + "transferItemsToOrganizationTitle": { + "message": "Pārcelt vienumus uz $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/desktop/src/locales/me/messages.json b/apps/desktop/src/locales/me/messages.json index 684c4682aa0..f2e8df3449b 100644 --- a/apps/desktop/src/locales/me/messages.json +++ b/apps/desktop/src/locales/me/messages.json @@ -708,6 +708,18 @@ "addAttachment": { "message": "Add attachment" }, + "itemsTransferred": { + "message": "Items transferred" + }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "maxFileSizeSansPunctuation": { "message": "Maximum file size is 500 MB" }, @@ -908,6 +920,12 @@ "unexpectedError": { "message": "Došlo je do neočekivane greške." }, + "unexpectedErrorShort": { + "message": "Unexpected error" + }, + "closeThisBitwardenWindow": { + "message": "Close this Bitwarden window and try again." + }, "itemInformation": { "message": "Informacija o stavki" }, @@ -1180,8 +1198,8 @@ "followUs": { "message": "Prati nas" }, - "syncVault": { - "message": "Sinhronizacija trezora" + "syncNow": { + "message": "Sync now" }, "changeMasterPass": { "message": "Promjena glavne lozinke" @@ -1757,8 +1775,11 @@ "exportFrom": { "message": "Export from" }, - "exportVault": { - "message": "Izvezi trezor" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "Format datoteke" @@ -2619,9 +2640,6 @@ "removedMasterPassword": { "message": "Master password removed" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "organizationName": { "message": "Organization name" }, @@ -3477,10 +3495,6 @@ "aliasDomain": { "message": "Alias domain" }, - "importData": { - "message": "Import data", - "description": "Used for the desktop menu item and the header of the import dialog" - }, "importError": { "message": "Import error" }, @@ -3886,6 +3900,75 @@ "fileSavedToDevice": { "message": "File saved to device. Manage from your device downloads." }, + "importantNotice": { + "message": "Important notice" + }, + "setupTwoStepLogin": { + "message": "Set up two-step login" + }, + "newDeviceVerificationNoticeContentPage1": { + "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." + }, + "newDeviceVerificationNoticeContentPage2": { + "message": "You can set up two-step login as an alternative way to protect your account or change your email to one you can access." + }, + "remindMeLater": { + "message": "Remind me later" + }, + "newDeviceVerificationNoticePageOneFormContent": { + "message": "Do you have reliable access to your email, $EMAIL$?", + "placeholders": { + "email": { + "content": "$1", + "example": "your_name@email.com" + } + } + }, + "newDeviceVerificationNoticePageOneEmailAccessNo": { + "message": "No, I do not" + }, + "newDeviceVerificationNoticePageOneEmailAccessYes": { + "message": "Yes, I can reliably access my email" + }, + "turnOnTwoStepLogin": { + "message": "Turn on two-step login" + }, + "changeAcctEmail": { + "message": "Change account email" + }, + "passkeyLogin": { + "message": "Log in with passkey?" + }, + "savePasskeyQuestion": { + "message": "Save passkey?" + }, + "saveNewPasskey": { + "message": "Save as new login" + }, + "savePasskeyNewLogin": { + "message": "Save passkey as new login" + }, + "noMatchingLoginsForSite": { + "message": "No matching logins for this site" + }, + "overwritePasskey": { + "message": "Overwrite passkey?" + }, + "unableToSavePasskey": { + "message": "Unable to save passkey" + }, + "alreadyContainsPasskey": { + "message": "This item already contains a passkey. Are you sure you want to overwrite the current passkey?" + }, + "passkeyAlreadyExists": { + "message": "A passkey already exists for this application." + }, + "applicationDoesNotSupportDuplicates": { + "message": "This application does not support duplicates." + }, + "closeThisWindow": { + "message": "Close this window" + }, "allowScreenshots": { "message": "Allow screen capture" }, @@ -4244,16 +4327,147 @@ "andMoreFeatures": { "message": "And more!" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "upgradeToPremium": { "message": "Upgrade to Premium" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { "message": "Timeout action" }, "sessionTimeoutHeader": { "message": "Session timeout" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Your organization has set the default session timeout to On system lock." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On restart." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On restart" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/desktop/src/locales/ml/messages.json b/apps/desktop/src/locales/ml/messages.json index e91ca21a686..97294b878fd 100644 --- a/apps/desktop/src/locales/ml/messages.json +++ b/apps/desktop/src/locales/ml/messages.json @@ -708,6 +708,18 @@ "addAttachment": { "message": "Add attachment" }, + "itemsTransferred": { + "message": "Items transferred" + }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "maxFileSizeSansPunctuation": { "message": "Maximum file size is 500 MB" }, @@ -908,6 +920,12 @@ "unexpectedError": { "message": "ഒരു അപ്രതീക്ഷിത പിശക് സംഭവിച്ചു." }, + "unexpectedErrorShort": { + "message": "Unexpected error" + }, + "closeThisBitwardenWindow": { + "message": "Close this Bitwarden window and try again." + }, "itemInformation": { "message": "വിവരം" }, @@ -1180,8 +1198,8 @@ "followUs": { "message": "ഞങ്ങളെ പിന്തുടരുക" }, - "syncVault": { - "message": "വാൾട് സമന്വയിപ്പിക്കുക" + "syncNow": { + "message": "Sync now" }, "changeMasterPass": { "message": "പ്രാഥമിക പാസ്‌വേഡ് മാറ്റുക" @@ -1757,8 +1775,11 @@ "exportFrom": { "message": "Export from" }, - "exportVault": { - "message": "വാൾട് എക്സ്പോർട്" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "ഫയൽ ഫോർമാറ്റ്" @@ -2619,9 +2640,6 @@ "removedMasterPassword": { "message": "Master password removed" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "organizationName": { "message": "Organization name" }, @@ -3477,10 +3495,6 @@ "aliasDomain": { "message": "Alias domain" }, - "importData": { - "message": "Import data", - "description": "Used for the desktop menu item and the header of the import dialog" - }, "importError": { "message": "Import error" }, @@ -3886,6 +3900,75 @@ "fileSavedToDevice": { "message": "File saved to device. Manage from your device downloads." }, + "importantNotice": { + "message": "Important notice" + }, + "setupTwoStepLogin": { + "message": "Set up two-step login" + }, + "newDeviceVerificationNoticeContentPage1": { + "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." + }, + "newDeviceVerificationNoticeContentPage2": { + "message": "You can set up two-step login as an alternative way to protect your account or change your email to one you can access." + }, + "remindMeLater": { + "message": "Remind me later" + }, + "newDeviceVerificationNoticePageOneFormContent": { + "message": "Do you have reliable access to your email, $EMAIL$?", + "placeholders": { + "email": { + "content": "$1", + "example": "your_name@email.com" + } + } + }, + "newDeviceVerificationNoticePageOneEmailAccessNo": { + "message": "No, I do not" + }, + "newDeviceVerificationNoticePageOneEmailAccessYes": { + "message": "Yes, I can reliably access my email" + }, + "turnOnTwoStepLogin": { + "message": "Turn on two-step login" + }, + "changeAcctEmail": { + "message": "Change account email" + }, + "passkeyLogin": { + "message": "Log in with passkey?" + }, + "savePasskeyQuestion": { + "message": "Save passkey?" + }, + "saveNewPasskey": { + "message": "Save as new login" + }, + "savePasskeyNewLogin": { + "message": "Save passkey as new login" + }, + "noMatchingLoginsForSite": { + "message": "No matching logins for this site" + }, + "overwritePasskey": { + "message": "Overwrite passkey?" + }, + "unableToSavePasskey": { + "message": "Unable to save passkey" + }, + "alreadyContainsPasskey": { + "message": "This item already contains a passkey. Are you sure you want to overwrite the current passkey?" + }, + "passkeyAlreadyExists": { + "message": "A passkey already exists for this application." + }, + "applicationDoesNotSupportDuplicates": { + "message": "This application does not support duplicates." + }, + "closeThisWindow": { + "message": "Close this window" + }, "allowScreenshots": { "message": "Allow screen capture" }, @@ -4244,16 +4327,147 @@ "andMoreFeatures": { "message": "And more!" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "upgradeToPremium": { "message": "Upgrade to Premium" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { "message": "Timeout action" }, "sessionTimeoutHeader": { "message": "Session timeout" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Your organization has set the default session timeout to On system lock." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On restart." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On restart" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/desktop/src/locales/mr/messages.json b/apps/desktop/src/locales/mr/messages.json index 0211550b08d..315272ae464 100644 --- a/apps/desktop/src/locales/mr/messages.json +++ b/apps/desktop/src/locales/mr/messages.json @@ -708,6 +708,18 @@ "addAttachment": { "message": "Add attachment" }, + "itemsTransferred": { + "message": "Items transferred" + }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "maxFileSizeSansPunctuation": { "message": "Maximum file size is 500 MB" }, @@ -908,6 +920,12 @@ "unexpectedError": { "message": "An unexpected error has occurred." }, + "unexpectedErrorShort": { + "message": "Unexpected error" + }, + "closeThisBitwardenWindow": { + "message": "Close this Bitwarden window and try again." + }, "itemInformation": { "message": "Item information" }, @@ -1180,8 +1198,8 @@ "followUs": { "message": "Follow us" }, - "syncVault": { - "message": "Sync vault" + "syncNow": { + "message": "Sync now" }, "changeMasterPass": { "message": "Change master password" @@ -1757,8 +1775,11 @@ "exportFrom": { "message": "Export from" }, - "exportVault": { - "message": "Export vault" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "File format" @@ -2619,9 +2640,6 @@ "removedMasterPassword": { "message": "Master password removed" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "organizationName": { "message": "Organization name" }, @@ -3477,10 +3495,6 @@ "aliasDomain": { "message": "Alias domain" }, - "importData": { - "message": "Import data", - "description": "Used for the desktop menu item and the header of the import dialog" - }, "importError": { "message": "Import error" }, @@ -3886,6 +3900,75 @@ "fileSavedToDevice": { "message": "File saved to device. Manage from your device downloads." }, + "importantNotice": { + "message": "Important notice" + }, + "setupTwoStepLogin": { + "message": "Set up two-step login" + }, + "newDeviceVerificationNoticeContentPage1": { + "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." + }, + "newDeviceVerificationNoticeContentPage2": { + "message": "You can set up two-step login as an alternative way to protect your account or change your email to one you can access." + }, + "remindMeLater": { + "message": "Remind me later" + }, + "newDeviceVerificationNoticePageOneFormContent": { + "message": "Do you have reliable access to your email, $EMAIL$?", + "placeholders": { + "email": { + "content": "$1", + "example": "your_name@email.com" + } + } + }, + "newDeviceVerificationNoticePageOneEmailAccessNo": { + "message": "No, I do not" + }, + "newDeviceVerificationNoticePageOneEmailAccessYes": { + "message": "Yes, I can reliably access my email" + }, + "turnOnTwoStepLogin": { + "message": "Turn on two-step login" + }, + "changeAcctEmail": { + "message": "Change account email" + }, + "passkeyLogin": { + "message": "Log in with passkey?" + }, + "savePasskeyQuestion": { + "message": "Save passkey?" + }, + "saveNewPasskey": { + "message": "Save as new login" + }, + "savePasskeyNewLogin": { + "message": "Save passkey as new login" + }, + "noMatchingLoginsForSite": { + "message": "No matching logins for this site" + }, + "overwritePasskey": { + "message": "Overwrite passkey?" + }, + "unableToSavePasskey": { + "message": "Unable to save passkey" + }, + "alreadyContainsPasskey": { + "message": "This item already contains a passkey. Are you sure you want to overwrite the current passkey?" + }, + "passkeyAlreadyExists": { + "message": "A passkey already exists for this application." + }, + "applicationDoesNotSupportDuplicates": { + "message": "This application does not support duplicates." + }, + "closeThisWindow": { + "message": "Close this window" + }, "allowScreenshots": { "message": "Allow screen capture" }, @@ -4244,16 +4327,147 @@ "andMoreFeatures": { "message": "And more!" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "upgradeToPremium": { "message": "Upgrade to Premium" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { "message": "Timeout action" }, "sessionTimeoutHeader": { "message": "Session timeout" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Your organization has set the default session timeout to On system lock." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On restart." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On restart" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/desktop/src/locales/my/messages.json b/apps/desktop/src/locales/my/messages.json index 969e67d3560..78cce9590cb 100644 --- a/apps/desktop/src/locales/my/messages.json +++ b/apps/desktop/src/locales/my/messages.json @@ -708,6 +708,18 @@ "addAttachment": { "message": "Add attachment" }, + "itemsTransferred": { + "message": "Items transferred" + }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "maxFileSizeSansPunctuation": { "message": "Maximum file size is 500 MB" }, @@ -908,6 +920,12 @@ "unexpectedError": { "message": "An unexpected error has occurred." }, + "unexpectedErrorShort": { + "message": "Unexpected error" + }, + "closeThisBitwardenWindow": { + "message": "Close this Bitwarden window and try again." + }, "itemInformation": { "message": "Item information" }, @@ -1180,8 +1198,8 @@ "followUs": { "message": "Follow us" }, - "syncVault": { - "message": "Sync vault" + "syncNow": { + "message": "Sync now" }, "changeMasterPass": { "message": "Change master password" @@ -1757,8 +1775,11 @@ "exportFrom": { "message": "Export from" }, - "exportVault": { - "message": "Export vault" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "File format" @@ -2619,9 +2640,6 @@ "removedMasterPassword": { "message": "Master password removed" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "organizationName": { "message": "Organization name" }, @@ -3477,10 +3495,6 @@ "aliasDomain": { "message": "Alias domain" }, - "importData": { - "message": "Import data", - "description": "Used for the desktop menu item and the header of the import dialog" - }, "importError": { "message": "Import error" }, @@ -3886,6 +3900,75 @@ "fileSavedToDevice": { "message": "File saved to device. Manage from your device downloads." }, + "importantNotice": { + "message": "Important notice" + }, + "setupTwoStepLogin": { + "message": "Set up two-step login" + }, + "newDeviceVerificationNoticeContentPage1": { + "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." + }, + "newDeviceVerificationNoticeContentPage2": { + "message": "You can set up two-step login as an alternative way to protect your account or change your email to one you can access." + }, + "remindMeLater": { + "message": "Remind me later" + }, + "newDeviceVerificationNoticePageOneFormContent": { + "message": "Do you have reliable access to your email, $EMAIL$?", + "placeholders": { + "email": { + "content": "$1", + "example": "your_name@email.com" + } + } + }, + "newDeviceVerificationNoticePageOneEmailAccessNo": { + "message": "No, I do not" + }, + "newDeviceVerificationNoticePageOneEmailAccessYes": { + "message": "Yes, I can reliably access my email" + }, + "turnOnTwoStepLogin": { + "message": "Turn on two-step login" + }, + "changeAcctEmail": { + "message": "Change account email" + }, + "passkeyLogin": { + "message": "Log in with passkey?" + }, + "savePasskeyQuestion": { + "message": "Save passkey?" + }, + "saveNewPasskey": { + "message": "Save as new login" + }, + "savePasskeyNewLogin": { + "message": "Save passkey as new login" + }, + "noMatchingLoginsForSite": { + "message": "No matching logins for this site" + }, + "overwritePasskey": { + "message": "Overwrite passkey?" + }, + "unableToSavePasskey": { + "message": "Unable to save passkey" + }, + "alreadyContainsPasskey": { + "message": "This item already contains a passkey. Are you sure you want to overwrite the current passkey?" + }, + "passkeyAlreadyExists": { + "message": "A passkey already exists for this application." + }, + "applicationDoesNotSupportDuplicates": { + "message": "This application does not support duplicates." + }, + "closeThisWindow": { + "message": "Close this window" + }, "allowScreenshots": { "message": "Allow screen capture" }, @@ -4244,16 +4327,147 @@ "andMoreFeatures": { "message": "And more!" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "upgradeToPremium": { "message": "Upgrade to Premium" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { "message": "Timeout action" }, "sessionTimeoutHeader": { "message": "Session timeout" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Your organization has set the default session timeout to On system lock." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On restart." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On restart" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/desktop/src/locales/nb/messages.json b/apps/desktop/src/locales/nb/messages.json index 35bd7750481..01bab1584af 100644 --- a/apps/desktop/src/locales/nb/messages.json +++ b/apps/desktop/src/locales/nb/messages.json @@ -708,6 +708,18 @@ "addAttachment": { "message": "Add attachment" }, + "itemsTransferred": { + "message": "Items transferred" + }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "maxFileSizeSansPunctuation": { "message": "Maximum file size is 500 MB" }, @@ -908,6 +920,12 @@ "unexpectedError": { "message": "En uventet feil har oppstått." }, + "unexpectedErrorShort": { + "message": "Unexpected error" + }, + "closeThisBitwardenWindow": { + "message": "Close this Bitwarden window and try again." + }, "itemInformation": { "message": "Objektsinformasjon" }, @@ -1180,8 +1198,8 @@ "followUs": { "message": "Følg oss" }, - "syncVault": { - "message": "Synkroniser hvelvet" + "syncNow": { + "message": "Sync now" }, "changeMasterPass": { "message": "Endre hovedpassordet" @@ -1757,8 +1775,11 @@ "exportFrom": { "message": "Eksporter fra" }, - "exportVault": { - "message": "Eksporter hvelvet" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "Filformat" @@ -2619,9 +2640,6 @@ "removedMasterPassword": { "message": "Hovedpassordet er fjernet." }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "organizationName": { "message": "Organisasjonens navn" }, @@ -3477,10 +3495,6 @@ "aliasDomain": { "message": "Alias-domene" }, - "importData": { - "message": "Importer data", - "description": "Used for the desktop menu item and the header of the import dialog" - }, "importError": { "message": "Importeringsfeil" }, @@ -3886,6 +3900,75 @@ "fileSavedToDevice": { "message": "File saved to device. Manage from your device downloads." }, + "importantNotice": { + "message": "Important notice" + }, + "setupTwoStepLogin": { + "message": "Set up two-step login" + }, + "newDeviceVerificationNoticeContentPage1": { + "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." + }, + "newDeviceVerificationNoticeContentPage2": { + "message": "You can set up two-step login as an alternative way to protect your account or change your email to one you can access." + }, + "remindMeLater": { + "message": "Remind me later" + }, + "newDeviceVerificationNoticePageOneFormContent": { + "message": "Do you have reliable access to your email, $EMAIL$?", + "placeholders": { + "email": { + "content": "$1", + "example": "your_name@email.com" + } + } + }, + "newDeviceVerificationNoticePageOneEmailAccessNo": { + "message": "No, I do not" + }, + "newDeviceVerificationNoticePageOneEmailAccessYes": { + "message": "Yes, I can reliably access my email" + }, + "turnOnTwoStepLogin": { + "message": "Turn on two-step login" + }, + "changeAcctEmail": { + "message": "Change account email" + }, + "passkeyLogin": { + "message": "Log in with passkey?" + }, + "savePasskeyQuestion": { + "message": "Save passkey?" + }, + "saveNewPasskey": { + "message": "Save as new login" + }, + "savePasskeyNewLogin": { + "message": "Save passkey as new login" + }, + "noMatchingLoginsForSite": { + "message": "No matching logins for this site" + }, + "overwritePasskey": { + "message": "Overwrite passkey?" + }, + "unableToSavePasskey": { + "message": "Unable to save passkey" + }, + "alreadyContainsPasskey": { + "message": "This item already contains a passkey. Are you sure you want to overwrite the current passkey?" + }, + "passkeyAlreadyExists": { + "message": "A passkey already exists for this application." + }, + "applicationDoesNotSupportDuplicates": { + "message": "This application does not support duplicates." + }, + "closeThisWindow": { + "message": "Close this window" + }, "allowScreenshots": { "message": "Tillat skjermklipp/-opptak" }, @@ -4244,16 +4327,147 @@ "andMoreFeatures": { "message": "And more!" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "upgradeToPremium": { "message": "Upgrade to Premium" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { "message": "Timeout action" }, "sessionTimeoutHeader": { "message": "Session timeout" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Your organization has set the default session timeout to On system lock." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On restart." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On restart" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/desktop/src/locales/ne/messages.json b/apps/desktop/src/locales/ne/messages.json index b8038093f90..a3d42ed7f57 100644 --- a/apps/desktop/src/locales/ne/messages.json +++ b/apps/desktop/src/locales/ne/messages.json @@ -708,6 +708,18 @@ "addAttachment": { "message": "Add attachment" }, + "itemsTransferred": { + "message": "Items transferred" + }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "maxFileSizeSansPunctuation": { "message": "Maximum file size is 500 MB" }, @@ -908,6 +920,12 @@ "unexpectedError": { "message": "An unexpected error has occurred." }, + "unexpectedErrorShort": { + "message": "Unexpected error" + }, + "closeThisBitwardenWindow": { + "message": "Close this Bitwarden window and try again." + }, "itemInformation": { "message": "Item information" }, @@ -1180,8 +1198,8 @@ "followUs": { "message": "Follow us" }, - "syncVault": { - "message": "Sync vault" + "syncNow": { + "message": "Sync now" }, "changeMasterPass": { "message": "Change master password" @@ -1757,8 +1775,11 @@ "exportFrom": { "message": "Export from" }, - "exportVault": { - "message": "Export vault" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "File format" @@ -2619,9 +2640,6 @@ "removedMasterPassword": { "message": "Master password removed" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "organizationName": { "message": "Organization name" }, @@ -3477,10 +3495,6 @@ "aliasDomain": { "message": "Alias domain" }, - "importData": { - "message": "Import data", - "description": "Used for the desktop menu item and the header of the import dialog" - }, "importError": { "message": "Import error" }, @@ -3886,6 +3900,75 @@ "fileSavedToDevice": { "message": "File saved to device. Manage from your device downloads." }, + "importantNotice": { + "message": "Important notice" + }, + "setupTwoStepLogin": { + "message": "Set up two-step login" + }, + "newDeviceVerificationNoticeContentPage1": { + "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." + }, + "newDeviceVerificationNoticeContentPage2": { + "message": "You can set up two-step login as an alternative way to protect your account or change your email to one you can access." + }, + "remindMeLater": { + "message": "Remind me later" + }, + "newDeviceVerificationNoticePageOneFormContent": { + "message": "Do you have reliable access to your email, $EMAIL$?", + "placeholders": { + "email": { + "content": "$1", + "example": "your_name@email.com" + } + } + }, + "newDeviceVerificationNoticePageOneEmailAccessNo": { + "message": "No, I do not" + }, + "newDeviceVerificationNoticePageOneEmailAccessYes": { + "message": "Yes, I can reliably access my email" + }, + "turnOnTwoStepLogin": { + "message": "Turn on two-step login" + }, + "changeAcctEmail": { + "message": "Change account email" + }, + "passkeyLogin": { + "message": "Log in with passkey?" + }, + "savePasskeyQuestion": { + "message": "Save passkey?" + }, + "saveNewPasskey": { + "message": "Save as new login" + }, + "savePasskeyNewLogin": { + "message": "Save passkey as new login" + }, + "noMatchingLoginsForSite": { + "message": "No matching logins for this site" + }, + "overwritePasskey": { + "message": "Overwrite passkey?" + }, + "unableToSavePasskey": { + "message": "Unable to save passkey" + }, + "alreadyContainsPasskey": { + "message": "This item already contains a passkey. Are you sure you want to overwrite the current passkey?" + }, + "passkeyAlreadyExists": { + "message": "A passkey already exists for this application." + }, + "applicationDoesNotSupportDuplicates": { + "message": "This application does not support duplicates." + }, + "closeThisWindow": { + "message": "Close this window" + }, "allowScreenshots": { "message": "Allow screen capture" }, @@ -4244,16 +4327,147 @@ "andMoreFeatures": { "message": "And more!" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "upgradeToPremium": { "message": "Upgrade to Premium" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { "message": "Timeout action" }, "sessionTimeoutHeader": { "message": "Session timeout" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Your organization has set the default session timeout to On system lock." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On restart." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On restart" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/desktop/src/locales/nl/messages.json b/apps/desktop/src/locales/nl/messages.json index 8833f59489a..0e8b96d747d 100644 --- a/apps/desktop/src/locales/nl/messages.json +++ b/apps/desktop/src/locales/nl/messages.json @@ -708,6 +708,18 @@ "addAttachment": { "message": "Bijlage toevoegen" }, + "itemsTransferred": { + "message": "Items overgedragen" + }, + "fixEncryption": { + "message": "Versleuteling repareren" + }, + "fixEncryptionTooltip": { + "message": "Dit bestand gebruikt een verouderde versleutelingsmethode." + }, + "attachmentUpdated": { + "message": "Bijlagen bijgewerkt" + }, "maxFileSizeSansPunctuation": { "message": "Maximale bestandsgrootte is 500 MB" }, @@ -908,6 +920,12 @@ "unexpectedError": { "message": "Er is een onverwachte fout opgetreden." }, + "unexpectedErrorShort": { + "message": "Onverwachte fout" + }, + "closeThisBitwardenWindow": { + "message": "Sluit dit Bitwarden-venster en probeer het opnieuw." + }, "itemInformation": { "message": "Item-informatie" }, @@ -1180,8 +1198,8 @@ "followUs": { "message": "Volg ons" }, - "syncVault": { - "message": "Kluis synchroniseren" + "syncNow": { + "message": "Nu synchroniseren" }, "changeMasterPass": { "message": "Hoofdwachtwoord wijzigen" @@ -1757,8 +1775,11 @@ "exportFrom": { "message": "Exporteren vanuit" }, - "exportVault": { - "message": "Kluis exporteren" + "export": { + "message": "Exporteren" + }, + "import": { + "message": "Importeren" }, "fileFormat": { "message": "Bestandsindeling" @@ -2619,9 +2640,6 @@ "removedMasterPassword": { "message": "Hoofdwachtwoord verwijderd." }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "Voor leden van de volgende organisatie is een hoofdwachtwoord niet langer nodig. Bevestig het domein hieronder met de beheerder van je organisatie." - }, "organizationName": { "message": "Organisatienaam" }, @@ -3477,10 +3495,6 @@ "aliasDomain": { "message": "Aliasdomein" }, - "importData": { - "message": "Data importeren", - "description": "Used for the desktop menu item and the header of the import dialog" - }, "importError": { "message": "Fout bij importeren" }, @@ -3886,6 +3900,75 @@ "fileSavedToDevice": { "message": "Bestand op apparaat opgeslagen. Beheer vanaf de downloads op je apparaat." }, + "importantNotice": { + "message": "Belangrijke melding" + }, + "setupTwoStepLogin": { + "message": "Tweestapsaanmelding instellen" + }, + "newDeviceVerificationNoticeContentPage1": { + "message": "Vanaf februari 2025 stuurt Bitwarden een code naar het e-mailadres van je account om inloggen op nieuwe apparaten te verifiëren." + }, + "newDeviceVerificationNoticeContentPage2": { + "message": "Je kunt tweestapsaanmelding instellen als een alternatieve manier om je account te beschermen of je e-mailadres te veranderen naar een waar je toegang toe hebt." + }, + "remindMeLater": { + "message": "Herinner me later" + }, + "newDeviceVerificationNoticePageOneFormContent": { + "message": "Heb je betrouwbare toegang tot je e-mail, $EMAIL$?", + "placeholders": { + "email": { + "content": "$1", + "example": "your_name@email.com" + } + } + }, + "newDeviceVerificationNoticePageOneEmailAccessNo": { + "message": "Nee, dat heb ik niet" + }, + "newDeviceVerificationNoticePageOneEmailAccessYes": { + "message": "Ja, ik heb betrouwbare toegang tot mijn e-mail" + }, + "turnOnTwoStepLogin": { + "message": "Tweestapsaanmelding inschakelen" + }, + "changeAcctEmail": { + "message": "E-mailadres van het account veranderen" + }, + "passkeyLogin": { + "message": "Inloggen met passkey?" + }, + "savePasskeyQuestion": { + "message": "Passkey opslaan?" + }, + "saveNewPasskey": { + "message": "Opslaan als nieuwe login" + }, + "savePasskeyNewLogin": { + "message": "Passkey als nieuwe login opslaan" + }, + "noMatchingLoginsForSite": { + "message": "Geen overeenkomende logins voor deze site" + }, + "overwritePasskey": { + "message": "Passkey overschrijven?" + }, + "unableToSavePasskey": { + "message": "Kon passkey niet opslaan" + }, + "alreadyContainsPasskey": { + "message": "Dit item heeft al een passkey. Weet je zeker dat je de huidige passkey wilt overschrijven?" + }, + "passkeyAlreadyExists": { + "message": "Er bestaat al een passkey voor deze applicatie." + }, + "applicationDoesNotSupportDuplicates": { + "message": "Deze applicatie ondersteunt geen duplicaten." + }, + "closeThisWindow": { + "message": "Sluit dit venster" + }, "allowScreenshots": { "message": "Schermopname toestaan" }, @@ -4244,16 +4327,147 @@ "andMoreFeatures": { "message": "En meer!" }, - "planDescPremium": { - "message": "Online beveiliging voltooien" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "upgradeToPremium": { "message": "Opwaarderen naar Premium" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Je organisatie maakt niet langer gebruik van hoofdwachtwoorden om in te loggen op Bitwarden. Controleer de organisatie en het domein om door te gaan." + }, + "continueWithLogIn": { + "message": "Doorgaan met inloggen" + }, + "doNotContinue": { + "message": "Niet verder gaan" + }, + "domain": { + "message": "Domein" + }, + "keyConnectorDomainTooltip": { + "message": "Dit domein zal de encryptiesleutels van je account opslaan, dus zorg ervoor dat je het vertrouwt. Als je het niet zeker weet, controleer dan bij je beheerder." + }, + "verifyYourOrganization": { + "message": "Verifieer je organisatie om in te loggen" + }, + "organizationVerified": { + "message": "Organisatie geverifieerd" + }, + "domainVerified": { + "message": "Domein geverifieerd" + }, + "leaveOrganizationContent": { + "message": "Als je je organisatie niet verifieert, wordt je toegang tot de organisatie ingetrokken." + }, + "leaveNow": { + "message": "Nu verlaten" + }, + "verifyYourDomainToLogin": { + "message": "Verifieer je domein om in te loggen" + }, + "verifyYourDomainDescription": { + "message": "Bevestig dit domein om verder te gaan met inloggen." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "Bevestig organisatie en domein om verder te gaan met inloggen." + }, "sessionTimeoutSettingsAction": { "message": "Time-out actie" }, "sessionTimeoutHeader": { "message": "Sessietime-out" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "Deze instelling wordt beheerd door je organisatie." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Je organisatie heeft de maximale sessietime-out ingesteld op $HOURS$ uur en $MINUTES$ minuten.", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Je organisatie heeft de standaard sessietime-out ingesteld op Systeem vergrendelen." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Je organisatie heeft de standaard sessietime-out ingesteld op Herstarten applicatie." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximale time-out kan niet langer zijn dan $HOURS$ uur en $MINUTES$ minu(u)t(en)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "Herstarten" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Stel een ontgrendelingsmethode in om je kluis time-out actie te wijzigen" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Weet je zeker dat je wilt verlaten?" + }, + "leaveConfirmationDialogContentOne": { + "message": "Door te weigeren, blijven je persoonlijke items in je account, maar verlies je toegang tot gedeelde items en organisatiefunctionaliteit." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Neem contact op met je beheerder om weer toegang te krijgen." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "$ORGANIZATION$ verlaten", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "Hoe beheer ik mijn kluis?" + }, + "transferItemsToOrganizationTitle": { + "message": "Items overdragen aan $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ vereist dat alle items eigendom zijn van de organisatie voor veiligheid en naleving. Klik op accepteren voor het overdragen van eigendom van je items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Overdacht accepteren" + }, + "declineAndLeave": { + "message": "Weigeren en verlaten" + }, + "whyAmISeeingThis": { + "message": "Waarom zie ik dit?" } } diff --git a/apps/desktop/src/locales/nn/messages.json b/apps/desktop/src/locales/nn/messages.json index 5264ce8c561..5354db9004f 100644 --- a/apps/desktop/src/locales/nn/messages.json +++ b/apps/desktop/src/locales/nn/messages.json @@ -708,6 +708,18 @@ "addAttachment": { "message": "Add attachment" }, + "itemsTransferred": { + "message": "Items transferred" + }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "maxFileSizeSansPunctuation": { "message": "Maximum file size is 500 MB" }, @@ -908,6 +920,12 @@ "unexpectedError": { "message": "Det har oppstått ein feil." }, + "unexpectedErrorShort": { + "message": "Unexpected error" + }, + "closeThisBitwardenWindow": { + "message": "Close this Bitwarden window and try again." + }, "itemInformation": { "message": "Elementinformasjon" }, @@ -1180,8 +1198,8 @@ "followUs": { "message": "Følg oss" }, - "syncVault": { - "message": "Synkroniser kvelven" + "syncNow": { + "message": "Sync now" }, "changeMasterPass": { "message": "Endre hovudpassord" @@ -1757,8 +1775,11 @@ "exportFrom": { "message": "Export from" }, - "exportVault": { - "message": "Export vault" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "Filformat" @@ -2619,9 +2640,6 @@ "removedMasterPassword": { "message": "Master password removed" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "organizationName": { "message": "Organization name" }, @@ -3477,10 +3495,6 @@ "aliasDomain": { "message": "Alias domain" }, - "importData": { - "message": "Import data", - "description": "Used for the desktop menu item and the header of the import dialog" - }, "importError": { "message": "Import error" }, @@ -3886,6 +3900,75 @@ "fileSavedToDevice": { "message": "File saved to device. Manage from your device downloads." }, + "importantNotice": { + "message": "Important notice" + }, + "setupTwoStepLogin": { + "message": "Set up two-step login" + }, + "newDeviceVerificationNoticeContentPage1": { + "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." + }, + "newDeviceVerificationNoticeContentPage2": { + "message": "You can set up two-step login as an alternative way to protect your account or change your email to one you can access." + }, + "remindMeLater": { + "message": "Remind me later" + }, + "newDeviceVerificationNoticePageOneFormContent": { + "message": "Do you have reliable access to your email, $EMAIL$?", + "placeholders": { + "email": { + "content": "$1", + "example": "your_name@email.com" + } + } + }, + "newDeviceVerificationNoticePageOneEmailAccessNo": { + "message": "No, I do not" + }, + "newDeviceVerificationNoticePageOneEmailAccessYes": { + "message": "Yes, I can reliably access my email" + }, + "turnOnTwoStepLogin": { + "message": "Turn on two-step login" + }, + "changeAcctEmail": { + "message": "Change account email" + }, + "passkeyLogin": { + "message": "Log in with passkey?" + }, + "savePasskeyQuestion": { + "message": "Save passkey?" + }, + "saveNewPasskey": { + "message": "Save as new login" + }, + "savePasskeyNewLogin": { + "message": "Save passkey as new login" + }, + "noMatchingLoginsForSite": { + "message": "No matching logins for this site" + }, + "overwritePasskey": { + "message": "Overwrite passkey?" + }, + "unableToSavePasskey": { + "message": "Unable to save passkey" + }, + "alreadyContainsPasskey": { + "message": "This item already contains a passkey. Are you sure you want to overwrite the current passkey?" + }, + "passkeyAlreadyExists": { + "message": "A passkey already exists for this application." + }, + "applicationDoesNotSupportDuplicates": { + "message": "This application does not support duplicates." + }, + "closeThisWindow": { + "message": "Close this window" + }, "allowScreenshots": { "message": "Allow screen capture" }, @@ -4244,16 +4327,147 @@ "andMoreFeatures": { "message": "And more!" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "upgradeToPremium": { "message": "Upgrade to Premium" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { "message": "Timeout action" }, "sessionTimeoutHeader": { "message": "Session timeout" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Your organization has set the default session timeout to On system lock." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On restart." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On restart" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/desktop/src/locales/or/messages.json b/apps/desktop/src/locales/or/messages.json index b145306e14d..2447b568d49 100644 --- a/apps/desktop/src/locales/or/messages.json +++ b/apps/desktop/src/locales/or/messages.json @@ -708,6 +708,18 @@ "addAttachment": { "message": "Add attachment" }, + "itemsTransferred": { + "message": "Items transferred" + }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "maxFileSizeSansPunctuation": { "message": "Maximum file size is 500 MB" }, @@ -908,6 +920,12 @@ "unexpectedError": { "message": "An unexpected error has occurred." }, + "unexpectedErrorShort": { + "message": "Unexpected error" + }, + "closeThisBitwardenWindow": { + "message": "Close this Bitwarden window and try again." + }, "itemInformation": { "message": "Item information" }, @@ -1180,8 +1198,8 @@ "followUs": { "message": "Follow us" }, - "syncVault": { - "message": "Sync vault" + "syncNow": { + "message": "Sync now" }, "changeMasterPass": { "message": "Change master password" @@ -1757,8 +1775,11 @@ "exportFrom": { "message": "Export from" }, - "exportVault": { - "message": "Export vault" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "File format" @@ -2619,9 +2640,6 @@ "removedMasterPassword": { "message": "Master password removed" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "organizationName": { "message": "Organization name" }, @@ -3477,10 +3495,6 @@ "aliasDomain": { "message": "Alias domain" }, - "importData": { - "message": "Import data", - "description": "Used for the desktop menu item and the header of the import dialog" - }, "importError": { "message": "Import error" }, @@ -3886,6 +3900,75 @@ "fileSavedToDevice": { "message": "File saved to device. Manage from your device downloads." }, + "importantNotice": { + "message": "Important notice" + }, + "setupTwoStepLogin": { + "message": "Set up two-step login" + }, + "newDeviceVerificationNoticeContentPage1": { + "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." + }, + "newDeviceVerificationNoticeContentPage2": { + "message": "You can set up two-step login as an alternative way to protect your account or change your email to one you can access." + }, + "remindMeLater": { + "message": "Remind me later" + }, + "newDeviceVerificationNoticePageOneFormContent": { + "message": "Do you have reliable access to your email, $EMAIL$?", + "placeholders": { + "email": { + "content": "$1", + "example": "your_name@email.com" + } + } + }, + "newDeviceVerificationNoticePageOneEmailAccessNo": { + "message": "No, I do not" + }, + "newDeviceVerificationNoticePageOneEmailAccessYes": { + "message": "Yes, I can reliably access my email" + }, + "turnOnTwoStepLogin": { + "message": "Turn on two-step login" + }, + "changeAcctEmail": { + "message": "Change account email" + }, + "passkeyLogin": { + "message": "Log in with passkey?" + }, + "savePasskeyQuestion": { + "message": "Save passkey?" + }, + "saveNewPasskey": { + "message": "Save as new login" + }, + "savePasskeyNewLogin": { + "message": "Save passkey as new login" + }, + "noMatchingLoginsForSite": { + "message": "No matching logins for this site" + }, + "overwritePasskey": { + "message": "Overwrite passkey?" + }, + "unableToSavePasskey": { + "message": "Unable to save passkey" + }, + "alreadyContainsPasskey": { + "message": "This item already contains a passkey. Are you sure you want to overwrite the current passkey?" + }, + "passkeyAlreadyExists": { + "message": "A passkey already exists for this application." + }, + "applicationDoesNotSupportDuplicates": { + "message": "This application does not support duplicates." + }, + "closeThisWindow": { + "message": "Close this window" + }, "allowScreenshots": { "message": "Allow screen capture" }, @@ -4244,16 +4327,147 @@ "andMoreFeatures": { "message": "And more!" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "upgradeToPremium": { "message": "Upgrade to Premium" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { "message": "Timeout action" }, "sessionTimeoutHeader": { "message": "Session timeout" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Your organization has set the default session timeout to On system lock." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On restart." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On restart" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/desktop/src/locales/pl/messages.json b/apps/desktop/src/locales/pl/messages.json index 4cb6650e2c7..ad0610aad5c 100644 --- a/apps/desktop/src/locales/pl/messages.json +++ b/apps/desktop/src/locales/pl/messages.json @@ -708,6 +708,18 @@ "addAttachment": { "message": "Dodaj załącznik" }, + "itemsTransferred": { + "message": "Items transferred" + }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "maxFileSizeSansPunctuation": { "message": "Maksymalny rozmiar pliku to 500 MB" }, @@ -908,6 +920,12 @@ "unexpectedError": { "message": "Wystąpił nieoczekiwany błąd." }, + "unexpectedErrorShort": { + "message": "Unexpected error" + }, + "closeThisBitwardenWindow": { + "message": "Close this Bitwarden window and try again." + }, "itemInformation": { "message": "Informacje o elemencie" }, @@ -1180,8 +1198,8 @@ "followUs": { "message": "Obserwuj nas" }, - "syncVault": { - "message": "Synchronizuj sejf" + "syncNow": { + "message": "Sync now" }, "changeMasterPass": { "message": "Zmień hasło główne" @@ -1757,8 +1775,11 @@ "exportFrom": { "message": "Eksportuj z" }, - "exportVault": { - "message": "Eksportuj sejf" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "Format pliku" @@ -2619,9 +2640,6 @@ "removedMasterPassword": { "message": "Hasło główne zostało usunięte" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "Hasło główne nie jest już wymagane dla członków następującej organizacji. Potwierdź poniższą domenę z administratorem organizacji." - }, "organizationName": { "message": "Nazwa organizacji" }, @@ -3477,10 +3495,6 @@ "aliasDomain": { "message": "Domena aliasu" }, - "importData": { - "message": "Importuj dane", - "description": "Used for the desktop menu item and the header of the import dialog" - }, "importError": { "message": "Błąd importowania" }, @@ -3886,6 +3900,75 @@ "fileSavedToDevice": { "message": "Plik zapisany na urządzeniu. Zarządzaj plikiem na swoim urządzeniu." }, + "importantNotice": { + "message": "Important notice" + }, + "setupTwoStepLogin": { + "message": "Set up two-step login" + }, + "newDeviceVerificationNoticeContentPage1": { + "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." + }, + "newDeviceVerificationNoticeContentPage2": { + "message": "You can set up two-step login as an alternative way to protect your account or change your email to one you can access." + }, + "remindMeLater": { + "message": "Remind me later" + }, + "newDeviceVerificationNoticePageOneFormContent": { + "message": "Do you have reliable access to your email, $EMAIL$?", + "placeholders": { + "email": { + "content": "$1", + "example": "your_name@email.com" + } + } + }, + "newDeviceVerificationNoticePageOneEmailAccessNo": { + "message": "No, I do not" + }, + "newDeviceVerificationNoticePageOneEmailAccessYes": { + "message": "Yes, I can reliably access my email" + }, + "turnOnTwoStepLogin": { + "message": "Turn on two-step login" + }, + "changeAcctEmail": { + "message": "Change account email" + }, + "passkeyLogin": { + "message": "Log in with passkey?" + }, + "savePasskeyQuestion": { + "message": "Save passkey?" + }, + "saveNewPasskey": { + "message": "Save as new login" + }, + "savePasskeyNewLogin": { + "message": "Save passkey as new login" + }, + "noMatchingLoginsForSite": { + "message": "No matching logins for this site" + }, + "overwritePasskey": { + "message": "Overwrite passkey?" + }, + "unableToSavePasskey": { + "message": "Unable to save passkey" + }, + "alreadyContainsPasskey": { + "message": "This item already contains a passkey. Are you sure you want to overwrite the current passkey?" + }, + "passkeyAlreadyExists": { + "message": "A passkey already exists for this application." + }, + "applicationDoesNotSupportDuplicates": { + "message": "This application does not support duplicates." + }, + "closeThisWindow": { + "message": "Close this window" + }, "allowScreenshots": { "message": "Zezwalaj na wykonywanie zrzutów ekranu" }, @@ -4244,16 +4327,147 @@ "andMoreFeatures": { "message": "And more!" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "upgradeToPremium": { "message": "Upgrade to Premium" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { "message": "Timeout action" }, "sessionTimeoutHeader": { "message": "Session timeout" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Your organization has set the default session timeout to On system lock." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On restart." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On restart" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/desktop/src/locales/pt_BR/messages.json b/apps/desktop/src/locales/pt_BR/messages.json index dc64f1b701b..8350392709c 100644 --- a/apps/desktop/src/locales/pt_BR/messages.json +++ b/apps/desktop/src/locales/pt_BR/messages.json @@ -708,6 +708,18 @@ "addAttachment": { "message": "Adicionar anexo" }, + "itemsTransferred": { + "message": "Itens transferidos" + }, + "fixEncryption": { + "message": "Corrigir criptografia" + }, + "fixEncryptionTooltip": { + "message": "Este arquivo está usando um método de criptografia desatualizado." + }, + "attachmentUpdated": { + "message": "Anexo atualizado" + }, "maxFileSizeSansPunctuation": { "message": "O tamanho máximo do arquivo é de 500 MB" }, @@ -908,6 +920,12 @@ "unexpectedError": { "message": "Ocorreu um erro inesperado." }, + "unexpectedErrorShort": { + "message": "Erro inesperado" + }, + "closeThisBitwardenWindow": { + "message": "Feche esta janela do Bitwarden e tente novamente." + }, "itemInformation": { "message": "Informações do item" }, @@ -1180,8 +1198,8 @@ "followUs": { "message": "Siga-nos" }, - "syncVault": { - "message": "Sincronizar cofre" + "syncNow": { + "message": "Sincronizar agora" }, "changeMasterPass": { "message": "Alterar senha principal" @@ -1265,19 +1283,19 @@ "message": "Autenticação em duas etapas" }, "vaultTimeoutHeader": { - "message": "Tempo limite do cofre" + "message": "Limite de tempo do cofre" }, "vaultTimeout": { - "message": "Tempo limite do cofre" + "message": "Limite de tempo do cofre" }, "vaultTimeout1": { - "message": "Tempo limite" + "message": "Limite de tempo" }, "vaultTimeoutAction1": { - "message": "Ação do tempo limite" + "message": "Ação do limite de tempo" }, "vaultTimeoutDesc": { - "message": "Escolha quando o seu cofre executará a ação do tempo limite do cofre." + "message": "Escolha quando o seu cofre executará a ação do limite de tempo do cofre." }, "immediately": { "message": "Imediatamente" @@ -1521,7 +1539,7 @@ "message": "Opções proprietárias de autenticação em duas etapas como YubiKey e Duo." }, "premiumSignUpReports": { - "message": "Higiene de senha, saúde da conta, e relatórios de brechas de dados para manter o seu cofre seguro." + "message": "Relatórios de higiene de senha, saúde da conta, e vazamentos de dados para manter o seu cofre seguro." }, "premiumSignUpTotp": { "message": "Gerador de códigos de verificação TOTP (2FA) para credenciais no seu cofre." @@ -1677,7 +1695,7 @@ "message": "Confira se a senha foi exposta." }, "passwordExposed": { - "message": "Esta senha foi exposta $VALUE$ vez(es) em brechas de dados. Você deve alterá-la.", + "message": "Esta senha foi exposta $VALUE$ vez(es) em vazamentos de dados. Você deve alterá-la.", "placeholders": { "value": { "content": "$1", @@ -1686,7 +1704,7 @@ } }, "passwordSafe": { - "message": "Esta senha não foi encontrada em brechas de dados conhecidas. Deve ser segura de usar." + "message": "Esta senha não foi encontrada em vazamentos de dados conhecidos. Deve ser segura de usar." }, "baseDomain": { "message": "Domínio de base", @@ -1757,8 +1775,11 @@ "exportFrom": { "message": "Exportar de" }, - "exportVault": { - "message": "Exportar cofre" + "export": { + "message": "Exportar" + }, + "import": { + "message": "Importar" }, "fileFormat": { "message": "Formato do arquivo" @@ -1807,7 +1828,7 @@ "message": "Confirmar exportação do cofre" }, "exportWarningDesc": { - "message": "Esta exportação contém os dados do seu cofre em um formato não criptografado. Você não deve armazenar ou enviar o arquivo exportado por canais inseguros (como e-mail). Apague o arquivo imediatamente após terminar de usá-lo." + "message": "Esta exportação contém os dados do seu cofre em um formato sem criptografia. Você não deve armazenar ou enviar o arquivo exportado por canais inseguros (como e-mail). Apague o arquivo imediatamente após terminar de usá-lo." }, "encExportKeyWarningDesc": { "message": "Esta exportação criptografa seus dados usando a chave de criptografia da sua conta. Se você rotacionar a chave de criptografia da sua conta, você deve exportar novamente, já que você não será capaz de descriptografar este arquivo de exportação." @@ -1962,7 +1983,7 @@ "message": "Uma ou mais políticas da organização estão afetando as configurações do seu gerador." }, "vaultTimeoutAction": { - "message": "Ação do tempo limite do cofre" + "message": "Ação do limite de tempo do cofre" }, "vaultTimeoutActionLockDesc": { "message": "A senha principal ou outro método de desbloqueio é necessário para acessar seu cofre novamente." @@ -1971,7 +1992,7 @@ "message": "Reautenticação é necessária para acessar seu cofre novamente." }, "unlockMethodNeededToChangeTimeoutActionDesc": { - "message": "Configure um método de desbloqueio para alterar a ação do tempo limite do cofre." + "message": "Configure um método de desbloqueio para alterar a ação do limite de tempo do cofre." }, "lock": { "message": "Bloquear", @@ -2000,10 +2021,10 @@ "message": "Apagar para sempre" }, "vaultTimeoutLogOutConfirmation": { - "message": "Ao desconectar-se, todo o seu acesso ao cofre será removido e será necessário autenticação on-line após o período do tempo limite. Tem certeza que quer usar esta configuração?" + "message": "Ao desconectar-se, todo o seu acesso ao cofre será removido e será necessário autenticação on-line após o período do limite de tempo. Tem certeza que quer usar esta configuração?" }, "vaultTimeoutLogOutConfirmationTitle": { - "message": "Confirmação de ação do tempo limite" + "message": "Confirmação de ação do limite de tempo" }, "enterpriseSingleSignOn": { "message": "Autenticação única empresarial" @@ -2535,7 +2556,7 @@ } }, "vaultTimeoutPolicyWithActionInEffect": { - "message": "As políticas da sua organização estão afetando o tempo limite do seu cofre. O máximo permitido do tempo limite do cofre é $HOURS$ hora(s) e $MINUTES$ minuto(s). A ação de tempo limite do seu cofre está configurada para $ACTION$.", + "message": "As políticas da sua organização estão afetando o limite de tempo do seu cofre. O máximo permitido do limite de tempo do cofre é $HOURS$ hora(s) e $MINUTES$ minuto(s). A ação de limite de tempo do seu cofre está configurada para $ACTION$.", "placeholders": { "hours": { "content": "$1", @@ -2552,7 +2573,7 @@ } }, "vaultTimeoutActionPolicyInEffect": { - "message": "As políticas da sua organização configuraram a ação do tempo limite do seu cofre para $ACTION$.", + "message": "As políticas da sua organização configuraram a ação do limite de tempo do seu cofre para $ACTION$.", "placeholders": { "action": { "content": "$1", @@ -2561,13 +2582,13 @@ } }, "vaultTimeoutTooLarge": { - "message": "O tempo limite do seu cofre excede as restrições definidas por sua organização." + "message": "O limite de tempo do seu cofre excede as restrições definidas por sua organização." }, "vaultTimeoutPolicyAffectingOptions": { - "message": "Os requisitos das políticas corporativas foram aplicados às suas opções de tempo limite" + "message": "Os requisitos das políticas corporativas foram aplicados às suas opções de limite de tempo" }, "vaultTimeoutPolicyInEffect": { - "message": "As políticas da sua organização configuraram o seu máximo permitido do tempo limite do cofre para $HOURS$ hora(s) e $MINUTES$ minuto(s).", + "message": "As políticas da sua organização configuraram o seu máximo permitido do limite de tempo do cofre para $HOURS$ hora(s) e $MINUTES$ minuto(s).", "placeholders": { "hours": { "content": "$1", @@ -2580,7 +2601,7 @@ } }, "vaultTimeoutPolicyMaximumError": { - "message": "O tempo limite excede a restrição definida pela sua organização: máximo de $HOURS$ hora(s) e $MINUTES$ minuto(s)", + "message": "O limite de tempo excede a restrição definida pela sua organização: máximo de $HOURS$ hora(s) e $MINUTES$ minuto(s)", "placeholders": { "hours": { "content": "$1", @@ -2593,7 +2614,7 @@ } }, "vaultCustomTimeoutMinimum": { - "message": "O mínimo do tempo limite personalizado é de 1 minuto." + "message": "O mínimo do limite de tempo personalizado é de 1 minuto." }, "inviteAccepted": { "message": "Convite aceito" @@ -2619,9 +2640,6 @@ "removedMasterPassword": { "message": "Senha principal removida" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "Uma senha principal não é mais necessária para membros da seguinte organização. Confirme o domínio abaixo com o administrador da sua organização." - }, "organizationName": { "message": "Nome da organização" }, @@ -3105,7 +3123,7 @@ } }, "loginRequestApprovedForEmailOnDevice": { - "message": "Solicitação de autenticação aprovada para $EMAIL$ em $DEVICE$", + "message": "Solicitação de acesso aprovada para $EMAIL$ em $DEVICE$", "placeholders": { "email": { "content": "$1", @@ -3118,7 +3136,7 @@ } }, "youDeniedLoginAttemptFromAnotherDevice": { - "message": "Você negou uma tentativa de autenticação por outro dispositivo. Se foi você, tente conectar-se com o dispositivo novamente." + "message": "Você negou uma tentativa de acesso de outro dispositivo. Se era você, tente se conectar com o dispositivo novamente." }, "webApp": { "message": "Aplicativo web" @@ -3146,7 +3164,7 @@ "message": "Servidor" }, "loginRequest": { - "message": "Solicitação de autenticação" + "message": "Solicitação de acesso" }, "deviceType": { "message": "Tipo do dispositivo" @@ -3230,7 +3248,7 @@ "message": "Senha fraca identificada e encontrada em um vazamento de dados. Use uma senha forte e única para proteger a sua conta. Tem certeza de que deseja usar essa senha?" }, "checkForBreaches": { - "message": "Conferir vazamentos de dados conhecidos por esta senha" + "message": "Conferir se esta senha vazou ao público" }, "loggedInExclamation": { "message": "Conectado!" @@ -3324,7 +3342,7 @@ "message": "Problemas para acessar?" }, "loginApproved": { - "message": "Autenticação aprovada" + "message": "Acesso aprovado" }, "userEmailMissing": { "message": "E-mail do usuário ausente" @@ -3477,10 +3495,6 @@ "aliasDomain": { "message": "Domínio de alias" }, - "importData": { - "message": "Importar dados", - "description": "Used for the desktop menu item and the header of the import dialog" - }, "importError": { "message": "Erro ao importar" }, @@ -3886,6 +3900,75 @@ "fileSavedToDevice": { "message": "Arquivo salvo no dispositivo. Gerencie a partir das transferências do seu dispositivo." }, + "importantNotice": { + "message": "Aviso importante" + }, + "setupTwoStepLogin": { + "message": "Configurar autenticação em duas etapas" + }, + "newDeviceVerificationNoticeContentPage1": { + "message": "O Bitwarden enviará um código no e-mail da sua conta para verificar o acesso de novos dispositivos começando em fevereiro de 2025." + }, + "newDeviceVerificationNoticeContentPage2": { + "message": "Você pode configurar a autenticação em duas etapas como um método alternativo de proteção da sua conta, ou você pode alterar o seu e-mail para um que possa acessar." + }, + "remindMeLater": { + "message": "Lembre-me depois" + }, + "newDeviceVerificationNoticePageOneFormContent": { + "message": "Você tem acesso adequado ao seu e-mail, $EMAIL$?", + "placeholders": { + "email": { + "content": "$1", + "example": "your_name@email.com" + } + } + }, + "newDeviceVerificationNoticePageOneEmailAccessNo": { + "message": "Não tenho" + }, + "newDeviceVerificationNoticePageOneEmailAccessYes": { + "message": "Sim, consigo acessar meu e-mail adequadamente" + }, + "turnOnTwoStepLogin": { + "message": "Ativar autenticação em duas etapas" + }, + "changeAcctEmail": { + "message": "Alterar e-mail da conta" + }, + "passkeyLogin": { + "message": "Conectar-se com chave de acesso?" + }, + "savePasskeyQuestion": { + "message": "Salvar chave de acesso?" + }, + "saveNewPasskey": { + "message": "Salvar como nova credencial" + }, + "savePasskeyNewLogin": { + "message": "Salvar chave de acesso como nova credencial" + }, + "noMatchingLoginsForSite": { + "message": "Nenhuma credencial correspondente para este site" + }, + "overwritePasskey": { + "message": "Substituir chave de acesso?" + }, + "unableToSavePasskey": { + "message": "Não é possível salvar a chave de acesso" + }, + "alreadyContainsPasskey": { + "message": "Este item já contém uma chave de acesso. Tem certeza que deseja substituir a atual?" + }, + "passkeyAlreadyExists": { + "message": "Uma chave de acesso já existe para este aplicativo." + }, + "applicationDoesNotSupportDuplicates": { + "message": "Este aplicativo não suporta duplicatas." + }, + "closeThisWindow": { + "message": "Fechar esta janela" + }, "allowScreenshots": { "message": "Permitir captura de tela" }, @@ -4244,16 +4327,147 @@ "andMoreFeatures": { "message": "E mais!" }, - "planDescPremium": { - "message": "Segurança on-line completa" + "advancedOnlineSecurity": { + "message": "Segurança on-line avançada" }, "upgradeToPremium": { "message": "Faça upgrade para o Premium" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "A sua organização não está mais usando senhas principais para se conectar ao Bitwarden. Para continuar, verifique a organização e o domínio." + }, + "continueWithLogIn": { + "message": "Continuar acessando" + }, + "doNotContinue": { + "message": "Não continuar" + }, + "domain": { + "message": "Domínio" + }, + "keyConnectorDomainTooltip": { + "message": "Este domínio armazenará as chaves de criptografia da sua conta, então certifique-se que confia nele. Se não tiver certeza, verifique com o seu administrador." + }, + "verifyYourOrganization": { + "message": "Verifique sua organização para se conectar" + }, + "organizationVerified": { + "message": "Organização verificada" + }, + "domainVerified": { + "message": "Domínio verificado" + }, + "leaveOrganizationContent": { + "message": "Se você não verificar a sua organização, o seu acesso à organização será revogado." + }, + "leaveNow": { + "message": "Sair agora" + }, + "verifyYourDomainToLogin": { + "message": "Verifique seu domínio para se conectar" + }, + "verifyYourDomainDescription": { + "message": "Para continuar se conectando, verifique este domínio." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "Para continuar se conectando, verifique a organização e o domínio." + }, "sessionTimeoutSettingsAction": { - "message": "Ação do tempo limite" + "message": "Ação do limite de tempo" }, "sessionTimeoutHeader": { - "message": "Tempo limite da sessão" + "message": "Limite de tempo da sessão" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "Esta configuração é gerenciada pela sua organização." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "A sua organização configurou o limite de tempo máximo da sessão para $HOURS$ hora(s) e $MINUTES$ minuto(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "A sua organização configurou o limite de tempo padrão da sessão para ser no bloqueio do sistema." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "A sua organização configurou o limite de tempo padrão da sessão para ser no reinício." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "O limite de tempo máximo não pode exceder $HOURS$ hora(s) e $MINUTES$ minuto(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "No reinício" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Configure um método de desbloqueio para alterar a ação do limite de tempo" + }, + "upgrade": { + "message": "Fazer upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Tem certeza de que quer sair?" + }, + "leaveConfirmationDialogContentOne": { + "message": "Se recusar, seus itens pessoais continuarão na sua conta, mas você perderá o acesso aos itens compartilhados e os recursos de organização." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Entre em contato com o seu administrador para recuperar o acesso." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Sair de $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "Como gerencio meu cofre?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transferir itens para $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ exige que todos os itens sejam propriedade da organização por segurança e conformidade. Clique em aceitar para transferir a propriedade dos seus itens.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Aceitar transferência" + }, + "declineAndLeave": { + "message": "Recusar e sair" + }, + "whyAmISeeingThis": { + "message": "Por que estou vendo isso?" } } diff --git a/apps/desktop/src/locales/pt_PT/messages.json b/apps/desktop/src/locales/pt_PT/messages.json index c0e396c63c4..f8d329480e0 100644 --- a/apps/desktop/src/locales/pt_PT/messages.json +++ b/apps/desktop/src/locales/pt_PT/messages.json @@ -708,6 +708,18 @@ "addAttachment": { "message": "Adicionar anexo" }, + "itemsTransferred": { + "message": "Itens transferidos" + }, + "fixEncryption": { + "message": "Corrigir encriptação" + }, + "fixEncryptionTooltip": { + "message": "Este ficheiro está a utilizar um método de encriptação desatualizado." + }, + "attachmentUpdated": { + "message": "Anexo atualizado" + }, "maxFileSizeSansPunctuation": { "message": "O tamanho máximo do ficheiro é de 500 MB" }, @@ -908,6 +920,12 @@ "unexpectedError": { "message": "Ocorreu um erro inesperado." }, + "unexpectedErrorShort": { + "message": "Erro inesperado" + }, + "closeThisBitwardenWindow": { + "message": "Feche esta janela do Bitwarden e tente novamente." + }, "itemInformation": { "message": "Informações do item" }, @@ -1180,8 +1198,8 @@ "followUs": { "message": "Siga-nos" }, - "syncVault": { - "message": "Sincronizar cofre" + "syncNow": { + "message": "Sincronizar agora" }, "changeMasterPass": { "message": "Alterar palavra-passe mestra" @@ -1757,8 +1775,11 @@ "exportFrom": { "message": "Exportar de" }, - "exportVault": { - "message": "Exportar cofre" + "export": { + "message": "Exportar" + }, + "import": { + "message": "Importar" }, "fileFormat": { "message": "Formato do ficheiro" @@ -2619,9 +2640,6 @@ "removedMasterPassword": { "message": "Palavra-passe mestra removida" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "Já não é necessária uma palavra-passe mestra para os membros da seguinte organização. Por favor, confirme o domínio abaixo com o administrador da sua organização." - }, "organizationName": { "message": "Nome da organização" }, @@ -3477,10 +3495,6 @@ "aliasDomain": { "message": "Alias de domínio" }, - "importData": { - "message": "Importar dados", - "description": "Used for the desktop menu item and the header of the import dialog" - }, "importError": { "message": "Erro de importação" }, @@ -3886,6 +3900,75 @@ "fileSavedToDevice": { "message": "Ficheiro guardado no dispositivo. Faça a gestão a partir das transferências do seu dispositivo." }, + "importantNotice": { + "message": "Aviso importante" + }, + "setupTwoStepLogin": { + "message": "Definir a verificação de dois passos" + }, + "newDeviceVerificationNoticeContentPage1": { + "message": "O Bitwarden enviará um código para o e-mail da sua conta para verificar as credenciais de novos dispositivos a partir de fevereiro de 2025." + }, + "newDeviceVerificationNoticeContentPage2": { + "message": "Pode configurar a verificação de dois passos como forma alternativa de proteger a sua conta ou alterar o seu e-mail para um a que possa aceder." + }, + "remindMeLater": { + "message": "Lembrar-me mais tarde" + }, + "newDeviceVerificationNoticePageOneFormContent": { + "message": "Tem um acesso fiável ao seu e-mail, $EMAIL$?", + "placeholders": { + "email": { + "content": "$1", + "example": "your_name@email.com" + } + } + }, + "newDeviceVerificationNoticePageOneEmailAccessNo": { + "message": "Não, não tenho" + }, + "newDeviceVerificationNoticePageOneEmailAccessYes": { + "message": "Sim, consigo aceder de forma fiável ao meu e-mail" + }, + "turnOnTwoStepLogin": { + "message": "Ativar a verificação de dois passos" + }, + "changeAcctEmail": { + "message": "Alterar o e-mail da conta" + }, + "passkeyLogin": { + "message": "Iniciar sessão com a chave de acesso?" + }, + "savePasskeyQuestion": { + "message": "Guardar a chave de acesso?" + }, + "saveNewPasskey": { + "message": "Guardar como nova credencial" + }, + "savePasskeyNewLogin": { + "message": "Guarde a chave de acesso como uma nova credencial" + }, + "noMatchingLoginsForSite": { + "message": "Sem credenciais correspondentes para este site" + }, + "overwritePasskey": { + "message": "Substituir chave de acesso?" + }, + "unableToSavePasskey": { + "message": "Não é possível guardar a chave de acesso" + }, + "alreadyContainsPasskey": { + "message": "Este item já contém uma chave de acesso. Tem a certeza de que pretende substituir a chave de acesso atual?" + }, + "passkeyAlreadyExists": { + "message": "Já existe uma chave de acesso para esta aplicação." + }, + "applicationDoesNotSupportDuplicates": { + "message": "Esta aplicação não suporta duplicados." + }, + "closeThisWindow": { + "message": "Fechar esta janela" + }, "allowScreenshots": { "message": "Permitir a captura de ecrã" }, @@ -4244,16 +4327,147 @@ "andMoreFeatures": { "message": "E muito mais!" }, - "planDescPremium": { - "message": "Segurança total online" + "advancedOnlineSecurity": { + "message": "Segurança online avançada" }, "upgradeToPremium": { "message": "Atualizar para o Premium" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "A sua organização já não utiliza palavras-passe mestras para iniciar sessão no Bitwarden. Para continuar, verifique a organização e o domínio." + }, + "continueWithLogIn": { + "message": "Continuar com o início de sessão" + }, + "doNotContinue": { + "message": "Não continuar" + }, + "domain": { + "message": "Domínio" + }, + "keyConnectorDomainTooltip": { + "message": "Este domínio armazenará as chaves de encriptação da sua conta, portanto certifique-se de que confia nele. Se não tiver a certeza, verifique com o seu administrador." + }, + "verifyYourOrganization": { + "message": "Verifique a sua organização para iniciar sessão" + }, + "organizationVerified": { + "message": "Organização verificada" + }, + "domainVerified": { + "message": "Domínio verificado" + }, + "leaveOrganizationContent": { + "message": "Se não verificar a sua organização, o seu acesso à organização será revogado." + }, + "leaveNow": { + "message": "Sair agora" + }, + "verifyYourDomainToLogin": { + "message": "Verifique o seu domínio para iniciar sessão" + }, + "verifyYourDomainDescription": { + "message": "Para continuar com o início de sessão, verifique este domínio." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "Para continuar com o início de sessão, verifique a organização e o domínio." + }, "sessionTimeoutSettingsAction": { "message": "Ação de tempo limite" }, "sessionTimeoutHeader": { "message": "Tempo limite da sessão" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "Esta configuração é gerida pela sua organização." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "A sua organização definiu o tempo limite máximo da sessão para $HOURS$ hora(s) e $MINUTES$ minuto(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "A sua organização definiu o tempo limite de sessão predefinido para Ao bloquear o sistema." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "A sua organização definiu o tempo limite predefinido da sessão para Ao reiniciar a app." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "O tempo limite máximo não pode ser superior a $HOURS$ hora(s) e $MINUTES$ minuto(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "Ao reiniciar" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Configure um método de desbloqueio para alterar a sua ação de tempo limite" + }, + "upgrade": { + "message": "Atualizar" + }, + "leaveConfirmationDialogTitle": { + "message": "Tem a certeza de que pretende sair?" + }, + "leaveConfirmationDialogContentOne": { + "message": "Ao recusar, os seus itens pessoais permanecerão na sua conta, mas perderá o acesso aos itens partilhados e às funcionalidades da organização." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Entre em contacto com o seu administrador para recuperar o acesso." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Sair de $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "Como posso gerir o meu cofre?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transferir itens para $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ exige que todos os itens sejam propriedade da organização por motivos de segurança e conformidade. Clique em Aceitar para transferir a propriedade dos seus itens.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Aceitar transferência" + }, + "declineAndLeave": { + "message": "Recusar e sair" + }, + "whyAmISeeingThis": { + "message": "Porque é que estou a ver isto?" } } diff --git a/apps/desktop/src/locales/ro/messages.json b/apps/desktop/src/locales/ro/messages.json index d2e589836e0..f68fb7fc86f 100644 --- a/apps/desktop/src/locales/ro/messages.json +++ b/apps/desktop/src/locales/ro/messages.json @@ -708,6 +708,18 @@ "addAttachment": { "message": "Add attachment" }, + "itemsTransferred": { + "message": "Items transferred" + }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "maxFileSizeSansPunctuation": { "message": "Maximum file size is 500 MB" }, @@ -908,6 +920,12 @@ "unexpectedError": { "message": "A survenit o eroare neașteptată." }, + "unexpectedErrorShort": { + "message": "Unexpected error" + }, + "closeThisBitwardenWindow": { + "message": "Close this Bitwarden window and try again." + }, "itemInformation": { "message": "Informații despre articol" }, @@ -1180,8 +1198,8 @@ "followUs": { "message": "Urmăriți-ne" }, - "syncVault": { - "message": "Sincronizare seif" + "syncNow": { + "message": "Sync now" }, "changeMasterPass": { "message": "Schimbare parolă principală" @@ -1757,8 +1775,11 @@ "exportFrom": { "message": "Export from" }, - "exportVault": { - "message": "Export de seif" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "Format de fișier" @@ -2619,9 +2640,6 @@ "removedMasterPassword": { "message": "Parola principală înlăturată" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "organizationName": { "message": "Organization name" }, @@ -3477,10 +3495,6 @@ "aliasDomain": { "message": "Alias domain" }, - "importData": { - "message": "Import data", - "description": "Used for the desktop menu item and the header of the import dialog" - }, "importError": { "message": "Import error" }, @@ -3886,6 +3900,75 @@ "fileSavedToDevice": { "message": "File saved to device. Manage from your device downloads." }, + "importantNotice": { + "message": "Important notice" + }, + "setupTwoStepLogin": { + "message": "Set up two-step login" + }, + "newDeviceVerificationNoticeContentPage1": { + "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." + }, + "newDeviceVerificationNoticeContentPage2": { + "message": "You can set up two-step login as an alternative way to protect your account or change your email to one you can access." + }, + "remindMeLater": { + "message": "Remind me later" + }, + "newDeviceVerificationNoticePageOneFormContent": { + "message": "Do you have reliable access to your email, $EMAIL$?", + "placeholders": { + "email": { + "content": "$1", + "example": "your_name@email.com" + } + } + }, + "newDeviceVerificationNoticePageOneEmailAccessNo": { + "message": "No, I do not" + }, + "newDeviceVerificationNoticePageOneEmailAccessYes": { + "message": "Yes, I can reliably access my email" + }, + "turnOnTwoStepLogin": { + "message": "Turn on two-step login" + }, + "changeAcctEmail": { + "message": "Change account email" + }, + "passkeyLogin": { + "message": "Log in with passkey?" + }, + "savePasskeyQuestion": { + "message": "Save passkey?" + }, + "saveNewPasskey": { + "message": "Save as new login" + }, + "savePasskeyNewLogin": { + "message": "Save passkey as new login" + }, + "noMatchingLoginsForSite": { + "message": "No matching logins for this site" + }, + "overwritePasskey": { + "message": "Overwrite passkey?" + }, + "unableToSavePasskey": { + "message": "Unable to save passkey" + }, + "alreadyContainsPasskey": { + "message": "This item already contains a passkey. Are you sure you want to overwrite the current passkey?" + }, + "passkeyAlreadyExists": { + "message": "A passkey already exists for this application." + }, + "applicationDoesNotSupportDuplicates": { + "message": "This application does not support duplicates." + }, + "closeThisWindow": { + "message": "Close this window" + }, "allowScreenshots": { "message": "Allow screen capture" }, @@ -4244,16 +4327,147 @@ "andMoreFeatures": { "message": "And more!" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "upgradeToPremium": { "message": "Upgrade to Premium" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { "message": "Timeout action" }, "sessionTimeoutHeader": { "message": "Session timeout" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Your organization has set the default session timeout to On system lock." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On restart." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On restart" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/desktop/src/locales/ru/messages.json b/apps/desktop/src/locales/ru/messages.json index a59ae2282d5..2c8a5052988 100644 --- a/apps/desktop/src/locales/ru/messages.json +++ b/apps/desktop/src/locales/ru/messages.json @@ -708,6 +708,18 @@ "addAttachment": { "message": "Добавить вложение" }, + "itemsTransferred": { + "message": "Элементы переданы" + }, + "fixEncryption": { + "message": "Исправить шифрование" + }, + "fixEncryptionTooltip": { + "message": "Этот файл использует устаревший метод шифрования." + }, + "attachmentUpdated": { + "message": "Вложение обновлено" + }, "maxFileSizeSansPunctuation": { "message": "Максимальный размер файла 500 МБ" }, @@ -775,7 +787,7 @@ "message": "Использовать единый вход" }, "yourOrganizationRequiresSingleSignOn": { - "message": "Your organization requires single sign-on." + "message": "Ваша организация требует единого входа." }, "submit": { "message": "Отправить" @@ -908,6 +920,12 @@ "unexpectedError": { "message": "Произошла непредвиденная ошибка." }, + "unexpectedErrorShort": { + "message": "Неожиданная ошибка" + }, + "closeThisBitwardenWindow": { + "message": "Закройте это окно Bitwarden и повторите попытку." + }, "itemInformation": { "message": "Информация об элементе" }, @@ -1051,7 +1069,7 @@ "message": "Тайм-аут аутентификации" }, "authenticationSessionTimedOut": { - "message": "Сеанс аутентификации завершился по времени. Пожалуйста, перезапустите процесс авторизации." + "message": "Сессия аутентификации завершилась по времени. Пожалуйста, перезапустите процесс авторизации." }, "selfHostBaseUrl": { "message": "URL собственного сервера", @@ -1121,7 +1139,7 @@ "message": "Вы вышли из своего аккаунта." }, "loginExpired": { - "message": "Истек срок действия вашего сеанса." + "message": "Истек срок действия вашей сессии." }, "restartRegistration": { "message": "Перезапустить регистрацию" @@ -1180,8 +1198,8 @@ "followUs": { "message": "Подписывайтесь на нас" }, - "syncVault": { - "message": "Синхронизировать хранилище" + "syncNow": { + "message": "Синхронизировать" }, "changeMasterPass": { "message": "Изменить мастер-пароль" @@ -1757,8 +1775,11 @@ "exportFrom": { "message": "Экспорт из" }, - "exportVault": { - "message": "Экспорт хранилища" + "export": { + "message": "Экспорт" + }, + "import": { + "message": "Импорт" }, "fileFormat": { "message": "Формат файла" @@ -2462,10 +2483,10 @@ "message": "Обновить мастер-пароль" }, "updateMasterPasswordWarning": { - "message": "Мастер-пароль недавно был изменен администратором вашей организации. Чтобы получить доступ к хранилищу, вы должны обновить его сейчас. В результате текущий сеанс будет завершен, потребуется повторный вход. Сеансы на других устройствах могут оставаться активными в течение одного часа." + "message": "Мастер-пароль недавно был изменен администратором вашей организации. Чтобы получить доступ к хранилищу, вы должны обновить его сейчас. В результате текущая сессия будет завершена, потребуется повторный вход. Сессии на других устройствах могут оставаться активными в течение одного часа." }, "updateWeakMasterPasswordWarning": { - "message": "Ваш мастер-пароль не соответствует требованиям политики вашей организации. Для доступа к хранилищу вы должны обновить свой мастер-пароль прямо сейчас. При этом текущий сеанс будет завершен и потребуется повторная авторизация. Сеансы на других устройствах могут оставаться активными в течение часа." + "message": "Ваш мастер-пароль не соответствует требованиям политики вашей организации. Для доступа к хранилищу вы должны обновить свой мастер-пароль прямо сейчас. При этом текущая сессия будет завершена и потребуется повторная авторизация. Сессии на других устройствах могут оставаться активными в течение часа." }, "changePasswordWarning": { "message": "После смены пароля потребуется авторизоваться с новым паролем. Активные сессии на других устройствах будут завершены в течение одного часа." @@ -2619,9 +2640,6 @@ "removedMasterPassword": { "message": "Мастер-пароль удален." }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "Мастер-пароль больше не требуется для членов следующей организации. Пожалуйста, подтвердите указанный ниже домен у администратора вашей организации." - }, "organizationName": { "message": "Название организации" }, @@ -2674,7 +2692,7 @@ "message": "Опции" }, "sessionTimeout": { - "message": "Время вашего сеанса истекло. Пожалуйста, вернитесь и попробуйте войти снова." + "message": "Время вашей сессии истекло. Пожалуйста, вернитесь и попробуйте войти снова." }, "exportingPersonalVaultTitle": { "message": "Экспорт личного хранилища" @@ -3477,10 +3495,6 @@ "aliasDomain": { "message": "Псевдоним домена" }, - "importData": { - "message": "Импорт данных", - "description": "Used for the desktop menu item and the header of the import dialog" - }, "importError": { "message": "Ошибка импорта" }, @@ -3886,11 +3900,80 @@ "fileSavedToDevice": { "message": "Файл сохранен на устройстве. Управляйте им из загрузок устройства." }, + "importantNotice": { + "message": "Важное уведомление" + }, + "setupTwoStepLogin": { + "message": "Настроить двухэтапную аутентификацию" + }, + "newDeviceVerificationNoticeContentPage1": { + "message": "Начиная с февраля 2025 года Bitwarden будет отправлять код на электронную почту вашего аккаунта для подтверждения авторизации с новых устройств." + }, + "newDeviceVerificationNoticeContentPage2": { + "message": "В качестве альтернативного способа защиты учетной записи вы можете настроить двухэтапную аутентификацию или сменить электронную почту на ту, к которой вы можете получить доступ." + }, + "remindMeLater": { + "message": "Напомнить позже" + }, + "newDeviceVerificationNoticePageOneFormContent": { + "message": "Есть ли у вас надежный доступ к электронной почте $EMAIL$?", + "placeholders": { + "email": { + "content": "$1", + "example": "your_name@email.com" + } + } + }, + "newDeviceVerificationNoticePageOneEmailAccessNo": { + "message": "Нет, не знаю" + }, + "newDeviceVerificationNoticePageOneEmailAccessYes": { + "message": "Да, я имею надежный доступ к своей электронной почте" + }, + "turnOnTwoStepLogin": { + "message": "Включить двухэтапную аутентификацию" + }, + "changeAcctEmail": { + "message": "Изменить email аккаунта" + }, + "passkeyLogin": { + "message": "Войти с passkey?" + }, + "savePasskeyQuestion": { + "message": "Сохранить passkey?" + }, + "saveNewPasskey": { + "message": "Сохранить как новый логин" + }, + "savePasskeyNewLogin": { + "message": "Сохранить passkey как новый логин" + }, + "noMatchingLoginsForSite": { + "message": "Нет подходящих логинов для этого сайта" + }, + "overwritePasskey": { + "message": "Перезаписать passkey?" + }, + "unableToSavePasskey": { + "message": "Не удалось сохранить passkey" + }, + "alreadyContainsPasskey": { + "message": "Этот элемент уже содержит passkey. Вы уверены, что хотите перезаписать текущий passkey?" + }, + "passkeyAlreadyExists": { + "message": "Для данного приложения уже существует passkey." + }, + "applicationDoesNotSupportDuplicates": { + "message": "Это приложение не поддерживает дубликаты." + }, + "closeThisWindow": { + "message": "Закрыть это окно" + }, "allowScreenshots": { "message": "Разрешить захват экрана" }, "allowScreenshotsDesc": { - "message": "Разрешить приложению Bitwarden захват экрана для скриншотов и просмотра в сеансах удаленного рабочего стола. Отключение параметра запретит доступ на некоторых внешних дисплеях." + "message": "Разрешить приложению Bitwarden захват экрана для скриншотов и просмотра в сессиях удаленного рабочего стола. Отключение параметра запретит доступ на некоторых внешних дисплеях." }, "confirmWindowStillVisibleTitle": { "message": "Окно подтверждения остается видимым" @@ -4244,16 +4327,147 @@ "andMoreFeatures": { "message": "И многое другое!" }, - "planDescPremium": { - "message": "Полная онлайн-защищенность" + "advancedOnlineSecurity": { + "message": "Расширенная онлайн-безопасность" }, "upgradeToPremium": { "message": "Обновить до Премиум" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Ваша организация больше не использует мастер-пароли для входа в Bitwarden. Чтобы продолжить, подтвердите организацию и домен." + }, + "continueWithLogIn": { + "message": "Продолжить с логином" + }, + "doNotContinue": { + "message": "Не продолжать" + }, + "domain": { + "message": "Домен" + }, + "keyConnectorDomainTooltip": { + "message": "В этом домене будут храниться ключи шифрования вашего аккаунта, поэтому убедитесь, что вы ему доверяете. Если вы не уверены, обратитесь к своему администратору." + }, + "verifyYourOrganization": { + "message": "Подтвердите свою организацию для входа" + }, + "organizationVerified": { + "message": "Организация подтверждена" + }, + "domainVerified": { + "message": "Домен верифицирован" + }, + "leaveOrganizationContent": { + "message": "Если вы не подтвердите свою организацию, ваш доступ к ней будет аннулирован." + }, + "leaveNow": { + "message": "Покинуть" + }, + "verifyYourDomainToLogin": { + "message": "Подтвердите свой домен для входа" + }, + "verifyYourDomainDescription": { + "message": "Чтобы продолжить с логином, подтвердите этот домен." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "Чтобы продолжить с логином, подтвердите организацию и домен." + }, "sessionTimeoutSettingsAction": { "message": "Тайм-аут действия" }, "sessionTimeoutHeader": { - "message": "Тайм-аут сеанса" + "message": "Тайм-аут сессии" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "Эта настройка управляется вашей организацией." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "В вашей организации максимальный тайм-аут сессии установлен равным $HOURS$ час. и $MINUTES$ мин.", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Ваша организация установила тайм-аут сессии по умолчанию на При блокировке системы." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Ваша организация установила тайм-аут сессии по умолчанию на При перезапуске." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Максимальный тайм-аут не может превышать $HOURS$ час. и $MINUTES$ мин.", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "При перезапуске" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Установите способ разблокировки для изменения действия при истечении тайм-аута" + }, + "upgrade": { + "message": "Перейти" + }, + "leaveConfirmationDialogTitle": { + "message": "Вы уверены, что хотите покинуть?" + }, + "leaveConfirmationDialogContentOne": { + "message": "В случае отказа ваши личные данные останутся в вашем аккаунте, но вы потеряете доступ к общим элементам и возможностям организации." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Свяжитесь с вашим администратором для восстановления доступа." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Покинуть $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "Как я могу управлять своим хранилищем?" + }, + "transferItemsToOrganizationTitle": { + "message": "Перенести элементы в $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ требует, чтобы все элементы принадлежали организации для обеспечения безопасности и соответствия требованиям. Нажмите Принять, чтобы передать собственность на ваши элементы.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Принять передачу" + }, + "declineAndLeave": { + "message": "Отклонить и покинуть" + }, + "whyAmISeeingThis": { + "message": "Почему я это вижу?" } } diff --git a/apps/desktop/src/locales/si/messages.json b/apps/desktop/src/locales/si/messages.json index 51333386f8a..0c3b61c35cf 100644 --- a/apps/desktop/src/locales/si/messages.json +++ b/apps/desktop/src/locales/si/messages.json @@ -708,6 +708,18 @@ "addAttachment": { "message": "Add attachment" }, + "itemsTransferred": { + "message": "Items transferred" + }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "maxFileSizeSansPunctuation": { "message": "Maximum file size is 500 MB" }, @@ -908,6 +920,12 @@ "unexpectedError": { "message": "An unexpected error has occurred." }, + "unexpectedErrorShort": { + "message": "Unexpected error" + }, + "closeThisBitwardenWindow": { + "message": "Close this Bitwarden window and try again." + }, "itemInformation": { "message": "Item information" }, @@ -1180,8 +1198,8 @@ "followUs": { "message": "Follow us" }, - "syncVault": { - "message": "Sync vault" + "syncNow": { + "message": "Sync now" }, "changeMasterPass": { "message": "Change master password" @@ -1757,8 +1775,11 @@ "exportFrom": { "message": "Export from" }, - "exportVault": { - "message": "Export vault" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "File format" @@ -2619,9 +2640,6 @@ "removedMasterPassword": { "message": "Master password removed" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "organizationName": { "message": "Organization name" }, @@ -3477,10 +3495,6 @@ "aliasDomain": { "message": "Alias domain" }, - "importData": { - "message": "Import data", - "description": "Used for the desktop menu item and the header of the import dialog" - }, "importError": { "message": "Import error" }, @@ -3886,6 +3900,75 @@ "fileSavedToDevice": { "message": "File saved to device. Manage from your device downloads." }, + "importantNotice": { + "message": "Important notice" + }, + "setupTwoStepLogin": { + "message": "Set up two-step login" + }, + "newDeviceVerificationNoticeContentPage1": { + "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." + }, + "newDeviceVerificationNoticeContentPage2": { + "message": "You can set up two-step login as an alternative way to protect your account or change your email to one you can access." + }, + "remindMeLater": { + "message": "Remind me later" + }, + "newDeviceVerificationNoticePageOneFormContent": { + "message": "Do you have reliable access to your email, $EMAIL$?", + "placeholders": { + "email": { + "content": "$1", + "example": "your_name@email.com" + } + } + }, + "newDeviceVerificationNoticePageOneEmailAccessNo": { + "message": "No, I do not" + }, + "newDeviceVerificationNoticePageOneEmailAccessYes": { + "message": "Yes, I can reliably access my email" + }, + "turnOnTwoStepLogin": { + "message": "Turn on two-step login" + }, + "changeAcctEmail": { + "message": "Change account email" + }, + "passkeyLogin": { + "message": "Log in with passkey?" + }, + "savePasskeyQuestion": { + "message": "Save passkey?" + }, + "saveNewPasskey": { + "message": "Save as new login" + }, + "savePasskeyNewLogin": { + "message": "Save passkey as new login" + }, + "noMatchingLoginsForSite": { + "message": "No matching logins for this site" + }, + "overwritePasskey": { + "message": "Overwrite passkey?" + }, + "unableToSavePasskey": { + "message": "Unable to save passkey" + }, + "alreadyContainsPasskey": { + "message": "This item already contains a passkey. Are you sure you want to overwrite the current passkey?" + }, + "passkeyAlreadyExists": { + "message": "A passkey already exists for this application." + }, + "applicationDoesNotSupportDuplicates": { + "message": "This application does not support duplicates." + }, + "closeThisWindow": { + "message": "Close this window" + }, "allowScreenshots": { "message": "Allow screen capture" }, @@ -4244,16 +4327,147 @@ "andMoreFeatures": { "message": "And more!" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "upgradeToPremium": { "message": "Upgrade to Premium" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { "message": "Timeout action" }, "sessionTimeoutHeader": { "message": "Session timeout" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Your organization has set the default session timeout to On system lock." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On restart." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On restart" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/desktop/src/locales/sk/messages.json b/apps/desktop/src/locales/sk/messages.json index 0e62a8ee8b0..d01591e1734 100644 --- a/apps/desktop/src/locales/sk/messages.json +++ b/apps/desktop/src/locales/sk/messages.json @@ -708,6 +708,18 @@ "addAttachment": { "message": "Priložiť prílohu" }, + "itemsTransferred": { + "message": "Items transferred" + }, + "fixEncryption": { + "message": "Opraviť šifrovanie" + }, + "fixEncryptionTooltip": { + "message": "Tento súbor používa zastaranú metódu šifrovania." + }, + "attachmentUpdated": { + "message": "Príloha bola aktualizovaná" + }, "maxFileSizeSansPunctuation": { "message": "Maximálna veľkosť súboru je 500 MB" }, @@ -908,6 +920,12 @@ "unexpectedError": { "message": "Vyskytla sa neočakávaná chyba." }, + "unexpectedErrorShort": { + "message": "Neočakávaná chyba" + }, + "closeThisBitwardenWindow": { + "message": "Zatvorte toto okno Bitwardenu a skúste to znova." + }, "itemInformation": { "message": "Informácie o položke" }, @@ -1180,8 +1198,8 @@ "followUs": { "message": "Sledujte nás" }, - "syncVault": { - "message": "Synchronizovať trezor" + "syncNow": { + "message": "Synchronizovať teraz" }, "changeMasterPass": { "message": "Zmeniť hlavné heslo" @@ -1319,7 +1337,7 @@ "message": "Keď je systém v režime spánku" }, "onLocked": { - "message": "Keď je systém uzamknutý" + "message": "Pri uzamknutí systému" }, "onRestart": { "message": "Pri reštarte" @@ -1757,8 +1775,11 @@ "exportFrom": { "message": "Exportovať z" }, - "exportVault": { - "message": "Export trezoru" + "export": { + "message": "Exportovať" + }, + "import": { + "message": "Importovať" }, "fileFormat": { "message": "Formát súboru" @@ -2619,9 +2640,6 @@ "removedMasterPassword": { "message": "Hlavné heslo bolo odstránené." }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "Hlavné heslo sa už nevyžaduje pre členov tejto organizácie. Nižšie uvedenú doménu potvrďte u správcu organizácie." - }, "organizationName": { "message": "Názov organizácie" }, @@ -3477,10 +3495,6 @@ "aliasDomain": { "message": "Alias doména" }, - "importData": { - "message": "Import údajov", - "description": "Used for the desktop menu item and the header of the import dialog" - }, "importError": { "message": "Chyba importu" }, @@ -3886,6 +3900,75 @@ "fileSavedToDevice": { "message": "Súbor sa uložil do zariadenia. Spravujte stiahnuté súbory zo zariadenia." }, + "importantNotice": { + "message": "Dôležité upozornenie" + }, + "setupTwoStepLogin": { + "message": "Nastaviť dvojstupňové prihlásenie" + }, + "newDeviceVerificationNoticeContentPage1": { + "message": "Bitwarden vám od februára 2025 pošle na e-mail vášho účtu kód na overenie prihlásenia z nových zariadení." + }, + "newDeviceVerificationNoticeContentPage2": { + "message": "Ako alternatívny spôsob ochrany svojho účtu môžete nastaviť dvojstupňové prihlásenie alebo zmeniť e-mail na taký, ku ktorému máte prístup." + }, + "remindMeLater": { + "message": "Pripomenúť neskôr" + }, + "newDeviceVerificationNoticePageOneFormContent": { + "message": "Máte zaručený prístup k e-mailu $EMAIL$?", + "placeholders": { + "email": { + "content": "$1", + "example": "your_name@email.com" + } + } + }, + "newDeviceVerificationNoticePageOneEmailAccessNo": { + "message": "Nie, nemám" + }, + "newDeviceVerificationNoticePageOneEmailAccessYes": { + "message": "Áno, mám zaručený prístup k e-mailu" + }, + "turnOnTwoStepLogin": { + "message": "Zapnúť dvojstupňové prihlásenie" + }, + "changeAcctEmail": { + "message": "Zmeniť e-mail účtu" + }, + "passkeyLogin": { + "message": "Prihlásiť sa s prístupovým kľúčom?" + }, + "savePasskeyQuestion": { + "message": "Uložiť prístupový kľúč?" + }, + "saveNewPasskey": { + "message": "Uložiť ako nové prihlasovacie údaje" + }, + "savePasskeyNewLogin": { + "message": "Uložiť prístupový kľúč ako nové prihlásenie" + }, + "noMatchingLoginsForSite": { + "message": "Pre túto stránku sa nenašli prihlasovacie údaje" + }, + "overwritePasskey": { + "message": "Prepísať prístupový kľúč?" + }, + "unableToSavePasskey": { + "message": "Prístupový kľúč sa nepodarilo uložiť" + }, + "alreadyContainsPasskey": { + "message": "Táto položka už obsahuje prístupový kľúč. Naozaj chcete prepísať aktuálny prístupový kľúč?" + }, + "passkeyAlreadyExists": { + "message": "Pre túto aplikáciu už existuje prístupový kľúč." + }, + "applicationDoesNotSupportDuplicates": { + "message": "Táto aplikácia nepodporuje duplikáty." + }, + "closeThisWindow": { + "message": "Zatvoriť toto okno" + }, "allowScreenshots": { "message": "Povoliť snímanie obrazovky" }, @@ -4244,16 +4327,147 @@ "andMoreFeatures": { "message": "A ešte viac!" }, - "planDescPremium": { - "message": "Úplné online zabezpečenie" + "advancedOnlineSecurity": { + "message": "Pokročilá online ochrana" }, "upgradeToPremium": { "message": "Upgradovať na Prémium" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Vaša organizácia už nepoužíva hlavné heslá na prihlásenie do Bitwardenu. Ak chcete pokračovať, overte organizáciu a doménu." + }, + "continueWithLogIn": { + "message": "Pokračujte prihlásením" + }, + "doNotContinue": { + "message": "Nepokračovať" + }, + "domain": { + "message": "Doména" + }, + "keyConnectorDomainTooltip": { + "message": "Táto doména bude ukladať šifrovacie kľúče vášho účtu, takže sa uistite, že jej dôverujete. Ak si nie ste istí, overte si to u správcu." + }, + "verifyYourOrganization": { + "message": "Na prihlásenie overte organizáciu" + }, + "organizationVerified": { + "message": "Organizácia je overená" + }, + "domainVerified": { + "message": "Doména je overená" + }, + "leaveOrganizationContent": { + "message": "Ak organizáciu neoveríte, váš prístup k nej bude zrušený." + }, + "leaveNow": { + "message": "Opustiť teraz" + }, + "verifyYourDomainToLogin": { + "message": "Na prihlásenie overte doménu" + }, + "verifyYourDomainDescription": { + "message": "Na pokračovanie prihlásením, overte túto doménu." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "Na pokračovanie prihlásením, overte organizáciu a doménu." + }, "sessionTimeoutSettingsAction": { "message": "Akcia pri vypršaní časového limitu" }, "sessionTimeoutHeader": { "message": "Časový limit relácie" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "Toto nastavenie spravuje vaša organizácia." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Vaša organizácia nastavila maximálny časový limit relácie na $HOURS$ hod. a $MINUTES$ min.", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Vaša organizácia nastavila predvolený časový limit relácie na Pri uzamknutí systému." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Vaša organizácia nastavila predvolený časový limit relácie na Pri reštarte prehliadača." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximálny časový limit nesmie prekročiť $HOURS$ hod. a $MINUTES$ min.", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "Pri reštarte" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Nastavte metódu odomknutia, aby ste zmenili akciu pri vypršaní časového limitu" + }, + "upgrade": { + "message": "Upgradovať" + }, + "leaveConfirmationDialogTitle": { + "message": "Naozaj chcete odísť?" + }, + "leaveConfirmationDialogContentOne": { + "message": "Ak odmietnete, vaše osobné položky zostanú vo vašom účte, ale stratíte prístup k zdieľaným položkám a funkciám organizácie." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Ak chcete obnoviť prístup, obráťte sa na správcu." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Opustiť $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "Ako môžem spravovať svoj trezor?" + }, + "transferItemsToOrganizationTitle": { + "message": "Prenos položiek do $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ vyžaduje, aby všetky položky boli vo vlastníctve organizácie z dôvodu bezpečnosti a dodržiavania predpisov. Ak chcete previesť vlastníctvo položiek, kliknite na tlačidlo Prijať.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Prijať prevody" + }, + "declineAndLeave": { + "message": "Zamietnuť a odísť" + }, + "whyAmISeeingThis": { + "message": "Prečo to vidím?" } } diff --git a/apps/desktop/src/locales/sl/messages.json b/apps/desktop/src/locales/sl/messages.json index c640320ab1a..f336c8c1261 100644 --- a/apps/desktop/src/locales/sl/messages.json +++ b/apps/desktop/src/locales/sl/messages.json @@ -708,6 +708,18 @@ "addAttachment": { "message": "Add attachment" }, + "itemsTransferred": { + "message": "Items transferred" + }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "maxFileSizeSansPunctuation": { "message": "Maximum file size is 500 MB" }, @@ -908,6 +920,12 @@ "unexpectedError": { "message": "An unexpected error has occurred." }, + "unexpectedErrorShort": { + "message": "Unexpected error" + }, + "closeThisBitwardenWindow": { + "message": "Close this Bitwarden window and try again." + }, "itemInformation": { "message": "Informacije o elementu" }, @@ -1180,8 +1198,8 @@ "followUs": { "message": "Sledite nam" }, - "syncVault": { - "message": "Sinhroniziraj trezor" + "syncNow": { + "message": "Sync now" }, "changeMasterPass": { "message": "Spremeni glavno geslo" @@ -1757,8 +1775,11 @@ "exportFrom": { "message": "Export from" }, - "exportVault": { - "message": "Izvoz trezorja" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "Format datoteke" @@ -2619,9 +2640,6 @@ "removedMasterPassword": { "message": "Glavno geslo je bilo odstranjeno." }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "organizationName": { "message": "Organization name" }, @@ -3477,10 +3495,6 @@ "aliasDomain": { "message": "Alias domain" }, - "importData": { - "message": "Import data", - "description": "Used for the desktop menu item and the header of the import dialog" - }, "importError": { "message": "Import error" }, @@ -3886,6 +3900,75 @@ "fileSavedToDevice": { "message": "File saved to device. Manage from your device downloads." }, + "importantNotice": { + "message": "Important notice" + }, + "setupTwoStepLogin": { + "message": "Set up two-step login" + }, + "newDeviceVerificationNoticeContentPage1": { + "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." + }, + "newDeviceVerificationNoticeContentPage2": { + "message": "You can set up two-step login as an alternative way to protect your account or change your email to one you can access." + }, + "remindMeLater": { + "message": "Remind me later" + }, + "newDeviceVerificationNoticePageOneFormContent": { + "message": "Do you have reliable access to your email, $EMAIL$?", + "placeholders": { + "email": { + "content": "$1", + "example": "your_name@email.com" + } + } + }, + "newDeviceVerificationNoticePageOneEmailAccessNo": { + "message": "No, I do not" + }, + "newDeviceVerificationNoticePageOneEmailAccessYes": { + "message": "Yes, I can reliably access my email" + }, + "turnOnTwoStepLogin": { + "message": "Turn on two-step login" + }, + "changeAcctEmail": { + "message": "Change account email" + }, + "passkeyLogin": { + "message": "Log in with passkey?" + }, + "savePasskeyQuestion": { + "message": "Save passkey?" + }, + "saveNewPasskey": { + "message": "Save as new login" + }, + "savePasskeyNewLogin": { + "message": "Save passkey as new login" + }, + "noMatchingLoginsForSite": { + "message": "No matching logins for this site" + }, + "overwritePasskey": { + "message": "Overwrite passkey?" + }, + "unableToSavePasskey": { + "message": "Unable to save passkey" + }, + "alreadyContainsPasskey": { + "message": "This item already contains a passkey. Are you sure you want to overwrite the current passkey?" + }, + "passkeyAlreadyExists": { + "message": "A passkey already exists for this application." + }, + "applicationDoesNotSupportDuplicates": { + "message": "This application does not support duplicates." + }, + "closeThisWindow": { + "message": "Close this window" + }, "allowScreenshots": { "message": "Allow screen capture" }, @@ -4244,16 +4327,147 @@ "andMoreFeatures": { "message": "And more!" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "upgradeToPremium": { "message": "Upgrade to Premium" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { "message": "Timeout action" }, "sessionTimeoutHeader": { "message": "Session timeout" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Your organization has set the default session timeout to On system lock." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On restart." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On restart" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/desktop/src/locales/sr/messages.json b/apps/desktop/src/locales/sr/messages.json index 4e69efe726f..e5b6a9e2762 100644 --- a/apps/desktop/src/locales/sr/messages.json +++ b/apps/desktop/src/locales/sr/messages.json @@ -708,6 +708,18 @@ "addAttachment": { "message": "Додај прилог" }, + "itemsTransferred": { + "message": "Пренете ставке" + }, + "fixEncryption": { + "message": "Поправи шифровање" + }, + "fixEncryptionTooltip": { + "message": "Ова датотека користи застарели метод шифровања." + }, + "attachmentUpdated": { + "message": "Прилог је ажуриран" + }, "maxFileSizeSansPunctuation": { "message": "Максимална величина је 500МБ" }, @@ -908,6 +920,12 @@ "unexpectedError": { "message": "Дошло је до неочекиване грешке." }, + "unexpectedErrorShort": { + "message": "Неочекивана грешка" + }, + "closeThisBitwardenWindow": { + "message": "Затворите овај Bitwarden прозор и покушајте поново." + }, "itemInformation": { "message": "Инфо о ставци" }, @@ -1094,22 +1112,22 @@ "message": "Сазнај више" }, "migrationsFailed": { - "message": "An error occurred updating the encryption settings." + "message": "Дошло је до грешке при ажурирању подешавања шифровања." }, "updateEncryptionSettingsTitle": { - "message": "Update your encryption settings" + "message": "Ажурирај своје поставке за шифровање" }, "updateEncryptionSettingsDesc": { - "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + "message": "Нова препоручена подешавања шифрирања побољшаће вашу сигурност налога. Унесите своју главну лозинку за ажурирање." }, "confirmIdentityToContinue": { - "message": "Confirm your identity to continue" + "message": "Да бисте наставили потврдите ваш идентитет" }, "enterYourMasterPassword": { - "message": "Enter your master password" + "message": "Унети вашу главну лозинку" }, "updateSettings": { - "message": "Update settings" + "message": "Ажурирај подешавања" }, "featureUnavailable": { "message": "Функција је недоступна" @@ -1180,8 +1198,8 @@ "followUs": { "message": "Пратите нас" }, - "syncVault": { - "message": "Синхронизуј сеф" + "syncNow": { + "message": "Синхронизуј сада" }, "changeMasterPass": { "message": "Промени главну лозинку" @@ -1509,7 +1527,7 @@ "message": "1ГБ шифровано складиште за прилоге." }, "premiumSignUpStorageV2": { - "message": "$SIZE$ encrypted storage for file attachments.", + "message": "$SIZE$ шифровано складиште за прилоге.", "placeholders": { "size": { "content": "$1", @@ -1757,8 +1775,11 @@ "exportFrom": { "message": "Извоз од" }, - "exportVault": { - "message": "Извоз сефа" + "export": { + "message": "Извези" + }, + "import": { + "message": "Увоз" }, "fileFormat": { "message": "Формат датотеке" @@ -2619,9 +2640,6 @@ "removedMasterPassword": { "message": "Главна лозинка уклоњена" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "Главна лозинка више није потребна за чланове следеће организације. Молимо потврдите домен са администратором организације." - }, "organizationName": { "message": "Назив организације" }, @@ -3477,10 +3495,6 @@ "aliasDomain": { "message": "Домен алијаса" }, - "importData": { - "message": "Увези податке", - "description": "Used for the desktop menu item and the header of the import dialog" - }, "importError": { "message": "Грешка при увозу" }, @@ -3886,6 +3900,75 @@ "fileSavedToDevice": { "message": "Датотека је сачувана на уређају. Управљајте преузимањима са свог уређаја." }, + "importantNotice": { + "message": "Важно обавештење" + }, + "setupTwoStepLogin": { + "message": "Поставити дво-степенску пријаву" + }, + "newDeviceVerificationNoticeContentPage1": { + "message": "Bitwarden ће послати кôд на имејл вашег налога за верификовање пријављивања са нових уређаја почевши од фебруара 2025." + }, + "newDeviceVerificationNoticeContentPage2": { + "message": "Можете да подесите пријаву у два корака као алтернативни начин да заштитите свој налог или да промените свој имејл у један који можете да приступите." + }, + "remindMeLater": { + "message": "Подсети ме касније" + }, + "newDeviceVerificationNoticePageOneFormContent": { + "message": "Да ли имате поуздан приступ својим имејлом, $EMAIL$?", + "placeholders": { + "email": { + "content": "$1", + "example": "your_name@email.com" + } + } + }, + "newDeviceVerificationNoticePageOneEmailAccessNo": { + "message": "Не, ненам" + }, + "newDeviceVerificationNoticePageOneEmailAccessYes": { + "message": "Да, могу поуздано да приступим овим имејлом" + }, + "turnOnTwoStepLogin": { + "message": "Упалити дво-степенску пријаву" + }, + "changeAcctEmail": { + "message": "Променити имејл налога" + }, + "passkeyLogin": { + "message": "Пријавите се са приступним кључем?" + }, + "savePasskeyQuestion": { + "message": "Сачувати приступни кључ?" + }, + "saveNewPasskey": { + "message": "Сачувати као нову пријаву" + }, + "savePasskeyNewLogin": { + "message": "Сачувати приступни кључ као нову пријаву" + }, + "noMatchingLoginsForSite": { + "message": "Нема одговарајућих пријава за овај сајт" + }, + "overwritePasskey": { + "message": "Заменити приступни кључ?" + }, + "unableToSavePasskey": { + "message": "Није могуће сачувати приступни кључ" + }, + "alreadyContainsPasskey": { + "message": "Ова ставка већ садржи приступни кључ. Да ли сте сигурни да желите да замените тренутни приступни кључ?" + }, + "passkeyAlreadyExists": { + "message": "За ову апликацију већ постоји приступни кључ." + }, + "applicationDoesNotSupportDuplicates": { + "message": "Ова апликација не подржава дупликате." + }, + "closeThisWindow": { + "message": "Затвори овај прозор" + }, "allowScreenshots": { "message": "Дозволи снимање екрана" }, @@ -4244,16 +4327,147 @@ "andMoreFeatures": { "message": "И још више!" }, - "planDescPremium": { - "message": "Потпуна онлајн безбедност" + "advancedOnlineSecurity": { + "message": "Напредна онлајн безбедност" }, "upgradeToPremium": { "message": "Надоградите на Premium" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Ваша организација више не користи главне лозинке за пријаву на Bitwarden. Да бисте наставили, верификујте организацију и домен." + }, + "continueWithLogIn": { + "message": "Наставити са пријавом" + }, + "doNotContinue": { + "message": "Не настави" + }, + "domain": { + "message": "Домен" + }, + "keyConnectorDomainTooltip": { + "message": "Овај домен ће чувати кључеве за шифровање вашег налога, па се уверите да му верујете. Ако нисте сигурни, проверите код свог администратора." + }, + "verifyYourOrganization": { + "message": "Верификујте своју организацију да бисте се пријавили" + }, + "organizationVerified": { + "message": "Организација верификована" + }, + "domainVerified": { + "message": "Домен верификован" + }, + "leaveOrganizationContent": { + "message": "Ако не верификујете своју организацију, ваш приступ организацији ће бити опозван." + }, + "leaveNow": { + "message": "Напусти сада" + }, + "verifyYourDomainToLogin": { + "message": "Верификујте домен да бисте се пријавили" + }, + "verifyYourDomainDescription": { + "message": "Да бисте наставили са пријављивањем, верификујте овај домен." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "Да бисте наставили са пријављивањем, верификујте организацију и домен." + }, "sessionTimeoutSettingsAction": { - "message": "Timeout action" + "message": "Акција тајмаута" }, "sessionTimeoutHeader": { - "message": "Session timeout" + "message": "Истек сесије" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "Овим подешавањем управља ваша организација." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Ваша организација је подесила максимално временско ограничење сесије на $HOURS$ сати и $MINUTES$ минута.", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Ваша организација је поставила подразумевано временско ограничење сесије на Блокирање система." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Ваша организација је поставила подразумевано временско ограничење сесије на При рестартовању." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Максимално временско ограничење не може да пређе $HOURS$ сат(а) и $MINUTES$ минут(а)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "На поновно покретање" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Подесите метод откључавања да бисте променили радњу временског ограничења" + }, + "upgrade": { + "message": "Надогради" + }, + "leaveConfirmationDialogTitle": { + "message": "Да ли сте сигурни да желите да напустите?" + }, + "leaveConfirmationDialogContentOne": { + "message": "Ако одбијете, ваши лични предмети ће остати на вашем налогу, али ћете изгубити приступ дељеним ставкама и организационим функцијама." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Контактирајте свог администратора да бисте поново добили приступ." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Напустити $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "Како да управљам својим сефом?" + }, + "transferItemsToOrganizationTitle": { + "message": "Премести ставке у $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ захтева да све ставке буду у власништву организације ради безбедности и усклађености. Кликните на прихвати да бисте пренели власништво над својим ставкама.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Прихвати трансфер" + }, + "declineAndLeave": { + "message": "Одбиј и напусти" + }, + "whyAmISeeingThis": { + "message": "Зашто видите ово?" } } diff --git a/apps/desktop/src/locales/sv/messages.json b/apps/desktop/src/locales/sv/messages.json index 6f3e68c8959..34b55de166b 100644 --- a/apps/desktop/src/locales/sv/messages.json +++ b/apps/desktop/src/locales/sv/messages.json @@ -708,6 +708,18 @@ "addAttachment": { "message": "Lägg till bilaga" }, + "itemsTransferred": { + "message": "Objekt överförda" + }, + "fixEncryption": { + "message": "Fixa kryptering" + }, + "fixEncryptionTooltip": { + "message": "Denna fil använder en föråldrad krypteringsmetod." + }, + "attachmentUpdated": { + "message": "Bilaga uppdaterad" + }, "maxFileSizeSansPunctuation": { "message": "Maximal filstorlek är 500 MB" }, @@ -908,6 +920,12 @@ "unexpectedError": { "message": "Ett oväntat fel har inträffat." }, + "unexpectedErrorShort": { + "message": "Oväntat fel" + }, + "closeThisBitwardenWindow": { + "message": "Stäng detta Bitwarden-fönster och försök igen." + }, "itemInformation": { "message": "Objektinformation" }, @@ -1180,8 +1198,8 @@ "followUs": { "message": "Följ oss" }, - "syncVault": { - "message": "Synkronisera valv" + "syncNow": { + "message": "Synkronisera nu" }, "changeMasterPass": { "message": "Ändra huvudlösenord" @@ -1757,8 +1775,11 @@ "exportFrom": { "message": "Exportera från" }, - "exportVault": { - "message": "Exportera valv" + "export": { + "message": "Exportera" + }, + "import": { + "message": "Importera" }, "fileFormat": { "message": "Filformat" @@ -2619,9 +2640,6 @@ "removedMasterPassword": { "message": "Huvudlösenord togs bort" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "Ett huvudlösenord krävs inte längre för medlemmar i följande organisation. Vänligen bekräfta domänen nedan med din organisationsadministratör." - }, "organizationName": { "message": "Organisationsnamn" }, @@ -3477,10 +3495,6 @@ "aliasDomain": { "message": "Aliasdomän" }, - "importData": { - "message": "Importera data", - "description": "Used for the desktop menu item and the header of the import dialog" - }, "importError": { "message": "Fel vid import" }, @@ -3886,6 +3900,75 @@ "fileSavedToDevice": { "message": "Fil sparad till enhet. Hantera nedladdningar från din enhet." }, + "importantNotice": { + "message": "Viktigt meddelande" + }, + "setupTwoStepLogin": { + "message": "Ställ in tvåstegsverifiering" + }, + "newDeviceVerificationNoticeContentPage1": { + "message": "Bitwarden kommer att skicka en kod till din e-postadress för ditt konto för att verifiera inloggningar från nya enheter med start i februari 2025." + }, + "newDeviceVerificationNoticeContentPage2": { + "message": "Du kan ställa in tvåstegsverifiering som ett alternativt sätt att skydda ditt konto eller ändra din e-post till en som du kan komma åt." + }, + "remindMeLater": { + "message": "Påminn mig senare" + }, + "newDeviceVerificationNoticePageOneFormContent": { + "message": "Har du tillförlitlig åtkomst till din e-post, $EMAIL$?", + "placeholders": { + "email": { + "content": "$1", + "example": "your_name@email.com" + } + } + }, + "newDeviceVerificationNoticePageOneEmailAccessNo": { + "message": "Nej, det har jag inte" + }, + "newDeviceVerificationNoticePageOneEmailAccessYes": { + "message": "Ja, jag har tillförlitlig åtkomst till min e-post" + }, + "turnOnTwoStepLogin": { + "message": "Aktivera tvåstegsverifiering" + }, + "changeAcctEmail": { + "message": "Byt e-postadress för konto" + }, + "passkeyLogin": { + "message": "Logga in med inloggningsnyckel?" + }, + "savePasskeyQuestion": { + "message": "Spara inloggningsnyckel?" + }, + "saveNewPasskey": { + "message": "Spara som ny inloggning" + }, + "savePasskeyNewLogin": { + "message": "Spara inloggningsnyckel som ny inloggning" + }, + "noMatchingLoginsForSite": { + "message": "Inga matchande inloggningar för denna webbplats" + }, + "overwritePasskey": { + "message": "Skriv över inloggningsnyckel?" + }, + "unableToSavePasskey": { + "message": "Kunde inte spara inloggningsnyckel" + }, + "alreadyContainsPasskey": { + "message": "Detta objekt innehåller redan en inloggningsnyckel. Är du säker på att du vill skriva över den aktuella inloggningsnyckeln?" + }, + "passkeyAlreadyExists": { + "message": "En inloggningsnyckel finns redan för detta program." + }, + "applicationDoesNotSupportDuplicates": { + "message": "Denna applikation har inte stöd för dubbletter." + }, + "closeThisWindow": { + "message": "Stäng detta fönster" + }, "allowScreenshots": { "message": "Tillåt skärmdump" }, @@ -4244,16 +4327,147 @@ "andMoreFeatures": { "message": "och mer!" }, - "planDescPremium": { - "message": "Komplett säkerhet online" + "advancedOnlineSecurity": { + "message": "Avancerad säkerhet online" }, "upgradeToPremium": { "message": "Uppgradera till Premium" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Din organisation använder inte längre huvudlösenord för att logga in på Bitwarden. För att fortsätta, verifiera organisationen och domänen." + }, + "continueWithLogIn": { + "message": "Fortsätt med inloggning" + }, + "doNotContinue": { + "message": "Fortsätt inte" + }, + "domain": { + "message": "Domän" + }, + "keyConnectorDomainTooltip": { + "message": "Denna domän kommer att lagra dina krypteringsnycklar, så se till att du litar på den. Om du inte är säker, kontrollera med din administratör." + }, + "verifyYourOrganization": { + "message": "Verifiera din organisation för att logga in" + }, + "organizationVerified": { + "message": "Organisation verifierad" + }, + "domainVerified": { + "message": "Domän verifierad" + }, + "leaveOrganizationContent": { + "message": "Om du inte verifierar din organisation kommer din åtkomst till organisationen att återkallas." + }, + "leaveNow": { + "message": "Lämna nu" + }, + "verifyYourDomainToLogin": { + "message": "Verifiera din domän för att logga in" + }, + "verifyYourDomainDescription": { + "message": "För att fortsätta med inloggning, verifiera denna domän." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "För att fortsätta logga in, verifiera organisationen och domänen." + }, "sessionTimeoutSettingsAction": { "message": "Tidsgränsåtgärd" }, "sessionTimeoutHeader": { "message": "Sessionstidsgräns" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "Den här inställningen hanteras av din organisation." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Din organisation har ställt in maximal sessionstidsgräns till $HOURS$ timmar och $MINUTES$ minut(er).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Din organisation har ställt in tidsgräns för standardsession till Vid systemlåsning." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Din organisation har ställt in tidsgräns för standardsession till Vid omstart." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximal tidsgräns får inte överstiga $HOURS$ timmar och $MINUTES$ minut(er)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "Vid omstart" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Ställ in en upplåsningsmetod för att ändra din tidsgränsåtgärd" + }, + "upgrade": { + "message": "Uppgradera" + }, + "leaveConfirmationDialogTitle": { + "message": "Är du säker på att du vill lämna?" + }, + "leaveConfirmationDialogContentOne": { + "message": "Genom att avböja kommer dina personliga objekt att stanna på ditt konto, men du kommer att förlora åtkomst till delade objekt och organisationsfunktioner." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Kontakta administratören för att återfå åtkomst." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Lämna $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "Hur hanterar jag mitt valv?" + }, + "transferItemsToOrganizationTitle": { + "message": "Överför objekt till $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ kräver att alla objekt ägs av organisationen för säkerhet och efterlevnad. Klicka på godkänn för att överföra ägarskapet för dina objekt.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Godkänn överföring" + }, + "declineAndLeave": { + "message": "Avböj och lämna" + }, + "whyAmISeeingThis": { + "message": "Varför ser jag det här?" } } diff --git a/apps/desktop/src/locales/ta/messages.json b/apps/desktop/src/locales/ta/messages.json index a83867a9eff..3abb4eb17c1 100644 --- a/apps/desktop/src/locales/ta/messages.json +++ b/apps/desktop/src/locales/ta/messages.json @@ -708,6 +708,18 @@ "addAttachment": { "message": "இணைப்பைச் சேர்" }, + "itemsTransferred": { + "message": "Items transferred" + }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "maxFileSizeSansPunctuation": { "message": "அதிகபட்ச கோப்பு அளவு 500 MB" }, @@ -908,6 +920,12 @@ "unexpectedError": { "message": "ஒரு எதிர்பாராத பிழை ஏற்பட்டது." }, + "unexpectedErrorShort": { + "message": "Unexpected error" + }, + "closeThisBitwardenWindow": { + "message": "Close this Bitwarden window and try again." + }, "itemInformation": { "message": "உருப்படி தகவல்" }, @@ -1180,8 +1198,8 @@ "followUs": { "message": "எங்களைப் பின்தொடரவும்" }, - "syncVault": { - "message": "பெட்டகத்தை ஒத்திசைக்கவும்" + "syncNow": { + "message": "Sync now" }, "changeMasterPass": { "message": "முதன்மை கடவுச்சொல்லை மாற்றவும்" @@ -1757,8 +1775,11 @@ "exportFrom": { "message": "இருந்து ஏற்றுமதி" }, - "exportVault": { - "message": "பெட்டகத்தை ஏற்றுமதி செய்" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "கோப்பு வடிவம்" @@ -2619,9 +2640,6 @@ "removedMasterPassword": { "message": "முதன்மை கடவுச்சொல் நீக்கப்பட்டது" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "பின்வரும் அமைப்பின் உறுப்பினர்களுக்கு ஒரு முதன்மை கடவுச்சொல் இனி தேவையில்லை. கீழே உள்ள டொமைனை உங்கள் நிறுவன நிர்வாகியுடன் உறுதிப்படுத்தவும்." - }, "organizationName": { "message": "நிறுவனத்தின் பெயர்" }, @@ -3477,10 +3495,6 @@ "aliasDomain": { "message": "புனைப்பெயர் டொமைன்" }, - "importData": { - "message": "தரவை இறக்குமதி செய்", - "description": "Used for the desktop menu item and the header of the import dialog" - }, "importError": { "message": "இறக்குமதி பிழை" }, @@ -3886,6 +3900,75 @@ "fileSavedToDevice": { "message": "கோப்பு சாதனத்தில் சேமிக்கப்பட்டது. உங்கள் சாதன பதிவிறக்கங்களிலிருந்து நிர்வகி." }, + "importantNotice": { + "message": "Important notice" + }, + "setupTwoStepLogin": { + "message": "Set up two-step login" + }, + "newDeviceVerificationNoticeContentPage1": { + "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." + }, + "newDeviceVerificationNoticeContentPage2": { + "message": "You can set up two-step login as an alternative way to protect your account or change your email to one you can access." + }, + "remindMeLater": { + "message": "Remind me later" + }, + "newDeviceVerificationNoticePageOneFormContent": { + "message": "Do you have reliable access to your email, $EMAIL$?", + "placeholders": { + "email": { + "content": "$1", + "example": "your_name@email.com" + } + } + }, + "newDeviceVerificationNoticePageOneEmailAccessNo": { + "message": "No, I do not" + }, + "newDeviceVerificationNoticePageOneEmailAccessYes": { + "message": "Yes, I can reliably access my email" + }, + "turnOnTwoStepLogin": { + "message": "Turn on two-step login" + }, + "changeAcctEmail": { + "message": "Change account email" + }, + "passkeyLogin": { + "message": "Log in with passkey?" + }, + "savePasskeyQuestion": { + "message": "Save passkey?" + }, + "saveNewPasskey": { + "message": "Save as new login" + }, + "savePasskeyNewLogin": { + "message": "Save passkey as new login" + }, + "noMatchingLoginsForSite": { + "message": "No matching logins for this site" + }, + "overwritePasskey": { + "message": "Overwrite passkey?" + }, + "unableToSavePasskey": { + "message": "Unable to save passkey" + }, + "alreadyContainsPasskey": { + "message": "This item already contains a passkey. Are you sure you want to overwrite the current passkey?" + }, + "passkeyAlreadyExists": { + "message": "A passkey already exists for this application." + }, + "applicationDoesNotSupportDuplicates": { + "message": "This application does not support duplicates." + }, + "closeThisWindow": { + "message": "Close this window" + }, "allowScreenshots": { "message": "திரை பிடிப்பை அனுமதி" }, @@ -4244,16 +4327,147 @@ "andMoreFeatures": { "message": "And more!" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "upgradeToPremium": { "message": "Upgrade to Premium" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { "message": "Timeout action" }, "sessionTimeoutHeader": { "message": "Session timeout" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Your organization has set the default session timeout to On system lock." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On restart." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On restart" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/desktop/src/locales/te/messages.json b/apps/desktop/src/locales/te/messages.json index 0211550b08d..315272ae464 100644 --- a/apps/desktop/src/locales/te/messages.json +++ b/apps/desktop/src/locales/te/messages.json @@ -708,6 +708,18 @@ "addAttachment": { "message": "Add attachment" }, + "itemsTransferred": { + "message": "Items transferred" + }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "maxFileSizeSansPunctuation": { "message": "Maximum file size is 500 MB" }, @@ -908,6 +920,12 @@ "unexpectedError": { "message": "An unexpected error has occurred." }, + "unexpectedErrorShort": { + "message": "Unexpected error" + }, + "closeThisBitwardenWindow": { + "message": "Close this Bitwarden window and try again." + }, "itemInformation": { "message": "Item information" }, @@ -1180,8 +1198,8 @@ "followUs": { "message": "Follow us" }, - "syncVault": { - "message": "Sync vault" + "syncNow": { + "message": "Sync now" }, "changeMasterPass": { "message": "Change master password" @@ -1757,8 +1775,11 @@ "exportFrom": { "message": "Export from" }, - "exportVault": { - "message": "Export vault" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "File format" @@ -2619,9 +2640,6 @@ "removedMasterPassword": { "message": "Master password removed" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "organizationName": { "message": "Organization name" }, @@ -3477,10 +3495,6 @@ "aliasDomain": { "message": "Alias domain" }, - "importData": { - "message": "Import data", - "description": "Used for the desktop menu item and the header of the import dialog" - }, "importError": { "message": "Import error" }, @@ -3886,6 +3900,75 @@ "fileSavedToDevice": { "message": "File saved to device. Manage from your device downloads." }, + "importantNotice": { + "message": "Important notice" + }, + "setupTwoStepLogin": { + "message": "Set up two-step login" + }, + "newDeviceVerificationNoticeContentPage1": { + "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." + }, + "newDeviceVerificationNoticeContentPage2": { + "message": "You can set up two-step login as an alternative way to protect your account or change your email to one you can access." + }, + "remindMeLater": { + "message": "Remind me later" + }, + "newDeviceVerificationNoticePageOneFormContent": { + "message": "Do you have reliable access to your email, $EMAIL$?", + "placeholders": { + "email": { + "content": "$1", + "example": "your_name@email.com" + } + } + }, + "newDeviceVerificationNoticePageOneEmailAccessNo": { + "message": "No, I do not" + }, + "newDeviceVerificationNoticePageOneEmailAccessYes": { + "message": "Yes, I can reliably access my email" + }, + "turnOnTwoStepLogin": { + "message": "Turn on two-step login" + }, + "changeAcctEmail": { + "message": "Change account email" + }, + "passkeyLogin": { + "message": "Log in with passkey?" + }, + "savePasskeyQuestion": { + "message": "Save passkey?" + }, + "saveNewPasskey": { + "message": "Save as new login" + }, + "savePasskeyNewLogin": { + "message": "Save passkey as new login" + }, + "noMatchingLoginsForSite": { + "message": "No matching logins for this site" + }, + "overwritePasskey": { + "message": "Overwrite passkey?" + }, + "unableToSavePasskey": { + "message": "Unable to save passkey" + }, + "alreadyContainsPasskey": { + "message": "This item already contains a passkey. Are you sure you want to overwrite the current passkey?" + }, + "passkeyAlreadyExists": { + "message": "A passkey already exists for this application." + }, + "applicationDoesNotSupportDuplicates": { + "message": "This application does not support duplicates." + }, + "closeThisWindow": { + "message": "Close this window" + }, "allowScreenshots": { "message": "Allow screen capture" }, @@ -4244,16 +4327,147 @@ "andMoreFeatures": { "message": "And more!" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "upgradeToPremium": { "message": "Upgrade to Premium" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { "message": "Timeout action" }, "sessionTimeoutHeader": { "message": "Session timeout" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Your organization has set the default session timeout to On system lock." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On restart." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On restart" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/desktop/src/locales/th/messages.json b/apps/desktop/src/locales/th/messages.json index f5bbde79a86..1eac91a6c79 100644 --- a/apps/desktop/src/locales/th/messages.json +++ b/apps/desktop/src/locales/th/messages.json @@ -708,6 +708,18 @@ "addAttachment": { "message": "Add attachment" }, + "itemsTransferred": { + "message": "Items transferred" + }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "maxFileSizeSansPunctuation": { "message": "Maximum file size is 500 MB" }, @@ -908,6 +920,12 @@ "unexpectedError": { "message": "ข้อผิดพลาดที่ไม่คาดคิดได้เกิดขึ้น." }, + "unexpectedErrorShort": { + "message": "Unexpected error" + }, + "closeThisBitwardenWindow": { + "message": "Close this Bitwarden window and try again." + }, "itemInformation": { "message": "ข้อมูลรายการ" }, @@ -1180,8 +1198,8 @@ "followUs": { "message": "Follow Us" }, - "syncVault": { - "message": "Sync Vault" + "syncNow": { + "message": "Sync now" }, "changeMasterPass": { "message": "เปลี่ยนรหัสผ่านหลัก" @@ -1757,8 +1775,11 @@ "exportFrom": { "message": "Export from" }, - "exportVault": { - "message": "Export Vault" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "File Format" @@ -2619,9 +2640,6 @@ "removedMasterPassword": { "message": "Master password removed" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "organizationName": { "message": "Organization name" }, @@ -3477,10 +3495,6 @@ "aliasDomain": { "message": "Alias domain" }, - "importData": { - "message": "Import data", - "description": "Used for the desktop menu item and the header of the import dialog" - }, "importError": { "message": "Import error" }, @@ -3886,6 +3900,75 @@ "fileSavedToDevice": { "message": "File saved to device. Manage from your device downloads." }, + "importantNotice": { + "message": "Important notice" + }, + "setupTwoStepLogin": { + "message": "Set up two-step login" + }, + "newDeviceVerificationNoticeContentPage1": { + "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." + }, + "newDeviceVerificationNoticeContentPage2": { + "message": "You can set up two-step login as an alternative way to protect your account or change your email to one you can access." + }, + "remindMeLater": { + "message": "Remind me later" + }, + "newDeviceVerificationNoticePageOneFormContent": { + "message": "Do you have reliable access to your email, $EMAIL$?", + "placeholders": { + "email": { + "content": "$1", + "example": "your_name@email.com" + } + } + }, + "newDeviceVerificationNoticePageOneEmailAccessNo": { + "message": "No, I do not" + }, + "newDeviceVerificationNoticePageOneEmailAccessYes": { + "message": "Yes, I can reliably access my email" + }, + "turnOnTwoStepLogin": { + "message": "Turn on two-step login" + }, + "changeAcctEmail": { + "message": "Change account email" + }, + "passkeyLogin": { + "message": "Log in with passkey?" + }, + "savePasskeyQuestion": { + "message": "Save passkey?" + }, + "saveNewPasskey": { + "message": "Save as new login" + }, + "savePasskeyNewLogin": { + "message": "Save passkey as new login" + }, + "noMatchingLoginsForSite": { + "message": "No matching logins for this site" + }, + "overwritePasskey": { + "message": "Overwrite passkey?" + }, + "unableToSavePasskey": { + "message": "Unable to save passkey" + }, + "alreadyContainsPasskey": { + "message": "This item already contains a passkey. Are you sure you want to overwrite the current passkey?" + }, + "passkeyAlreadyExists": { + "message": "A passkey already exists for this application." + }, + "applicationDoesNotSupportDuplicates": { + "message": "This application does not support duplicates." + }, + "closeThisWindow": { + "message": "Close this window" + }, "allowScreenshots": { "message": "Allow screen capture" }, @@ -4244,16 +4327,147 @@ "andMoreFeatures": { "message": "And more!" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "upgradeToPremium": { "message": "Upgrade to Premium" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { "message": "Timeout action" }, "sessionTimeoutHeader": { "message": "Session timeout" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Your organization has set the default session timeout to On system lock." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On restart." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On restart" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/desktop/src/locales/tr/messages.json b/apps/desktop/src/locales/tr/messages.json index bb0b6f2fd51..a7ed829ce32 100644 --- a/apps/desktop/src/locales/tr/messages.json +++ b/apps/desktop/src/locales/tr/messages.json @@ -708,6 +708,18 @@ "addAttachment": { "message": "Dosya ekle" }, + "itemsTransferred": { + "message": "Kayıtlar aktarıldı" + }, + "fixEncryption": { + "message": "Şifrelemeyi düzelt" + }, + "fixEncryptionTooltip": { + "message": "Bu dosya eski bir şifreleme yöntemi kullanıyor." + }, + "attachmentUpdated": { + "message": "Ek güncellendi" + }, "maxFileSizeSansPunctuation": { "message": "Maksimum dosya boyutu 500 MB'dir" }, @@ -908,6 +920,12 @@ "unexpectedError": { "message": "Beklenmedik bir hata oluştu." }, + "unexpectedErrorShort": { + "message": "Beklenmeyen hata" + }, + "closeThisBitwardenWindow": { + "message": "Bu Bitwarden penceresini kapatıp yeniden deneyin." + }, "itemInformation": { "message": "Kayıt bilgileri" }, @@ -1094,22 +1112,22 @@ "message": "Daha fazla bilgi al" }, "migrationsFailed": { - "message": "An error occurred updating the encryption settings." + "message": "Şifreleme ayarları güncellenirken bir hata oluştu." }, "updateEncryptionSettingsTitle": { - "message": "Update your encryption settings" + "message": "Şifreleme ayarlarınızı güncelleyin" }, "updateEncryptionSettingsDesc": { - "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + "message": "Önerilen yeni şifreleme ayarları hesap güvenliğinizi artıracaktır. Şimdi güncellemek için ana parolanızı girin." }, "confirmIdentityToContinue": { - "message": "Confirm your identity to continue" + "message": "Devam etmek için kimliğinizi doğrulayın" }, "enterYourMasterPassword": { - "message": "Enter your master password" + "message": "Ana parolanızı girin" }, "updateSettings": { - "message": "Update settings" + "message": "Ayarları güncelle" }, "featureUnavailable": { "message": "Özellik kullanılamıyor" @@ -1180,8 +1198,8 @@ "followUs": { "message": "Bizi takip edin" }, - "syncVault": { - "message": "Kasayı eşitle" + "syncNow": { + "message": "Şimdi eşitle" }, "changeMasterPass": { "message": "Ana parolayı değiştir" @@ -1757,8 +1775,11 @@ "exportFrom": { "message": "Dışa aktarılacak konum" }, - "exportVault": { - "message": "Kasayı dışa aktar" + "export": { + "message": "Dışa aktar" + }, + "import": { + "message": "İçe aktar" }, "fileFormat": { "message": "Dosya biçimi" @@ -1788,7 +1809,7 @@ "message": "Hesap kısıtlı" }, "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." @@ -1868,7 +1889,7 @@ "message": "Kilidi Windows Hello ile aç" }, "unlockWithPolkit": { - "message": "Sistem kimlik doğrulaması ile kilit açma" + "message": "Kilidi sistem kimlik doğrulamasıyla aç" }, "windowsHelloConsentMessage": { "message": "Bitwarden için doğrulayın." @@ -2619,9 +2640,6 @@ "removedMasterPassword": { "message": "Ana parola kaldırıldı" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "Aşağıdaki organizasyonun üyeleri için artık ana parola gerekmemektedir. Lütfen alan adını organizasyon yöneticinizle doğrulayın." - }, "organizationName": { "message": "Kuruluş adı" }, @@ -2710,7 +2728,7 @@ } }, "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { - "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "message": "Yalnızca $ORGANIZATION$ ile ilişkili kuruluş kasası dışa aktarılacaktır.", "placeholders": { "organization": { "content": "$1", @@ -2719,7 +2737,7 @@ } }, "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { - "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "message": "Yalnızca $ORGANIZATION$ ile ilişkilendirilmiş kuruluş kasası dışa aktarılacaktır. Kayıtlarım koleksiyonları dahil edilmeyecek.", "placeholders": { "organization": { "content": "$1", @@ -3477,10 +3495,6 @@ "aliasDomain": { "message": "Alias alan adı" }, - "importData": { - "message": "Verileri içe aktar", - "description": "Used for the desktop menu item and the header of the import dialog" - }, "importError": { "message": "İçe aktarma hatası" }, @@ -3851,22 +3865,22 @@ "message": "Uyarı: Aracı Yönlendirme" }, "agentForwardingWarningText": { - "message": "Bu istek, oturum açtığınız uzak bir cihazdan gelir" + "message": "Bu istek, giriş yaptığınız uzak bir cihazdan geliyor" }, "sshkeyApprovalMessageInfix": { - "message": "erişim istiyor" + "message": "buraya erişmek istiyor:" }, "sshkeyApprovalMessageSuffix": { - "message": "amacıyla" + "message": "amaç:" }, "sshActionLogin": { - "message": "bir sunucuya kimlik doğrulamak" + "message": "sunucuda kimlik doğrulamak" }, "sshActionSign": { - "message": "bir iletiyi imzala" + "message": "ileti imzalamak" }, "sshActionGitSign": { - "message": "bir git commit'i imzala" + "message": "git commit'i imzalamak" }, "unknownApplication": { "message": "Bir uygulama" @@ -3886,6 +3900,75 @@ "fileSavedToDevice": { "message": "Dosya cihaza kaydedildi. Cihazınızın indirilenler klasöründen yönetebilirsiniz." }, + "importantNotice": { + "message": "Önemli uyarı" + }, + "setupTwoStepLogin": { + "message": "İki adımlı girişi ayarlayın" + }, + "newDeviceVerificationNoticeContentPage1": { + "message": "Şubat 2025 itibarıyla Bitwarden, yeni cihazlardan yeni girişleri doğrulamanız için e-posta adresinize bir kod gönderecektir." + }, + "newDeviceVerificationNoticeContentPage2": { + "message": "Hesabınızı korumanın alternatif bir yolu olarak iki adımlı girişi etkinleştirebilirsiniz. Aksi halde e-posta adresinizin doğru olduğundan emin olmalısınız." + }, + "remindMeLater": { + "message": "Daha sonra hatırlat" + }, + "newDeviceVerificationNoticePageOneFormContent": { + "message": "$EMAIL$ adresinize sağlıklı bir şekilde erişebiliyor musunuz?", + "placeholders": { + "email": { + "content": "$1", + "example": "your_name@email.com" + } + } + }, + "newDeviceVerificationNoticePageOneEmailAccessNo": { + "message": "Hayır, erişemiyorum" + }, + "newDeviceVerificationNoticePageOneEmailAccessYes": { + "message": "Evet, e-postalarıma sağlıklı bir şekilde erişebiliyorum" + }, + "turnOnTwoStepLogin": { + "message": "İki adımlı girişi etkinleştir" + }, + "changeAcctEmail": { + "message": "Hesap e-postasını değiştir" + }, + "passkeyLogin": { + "message": "Geçiş anahtarı ile giriş yapılsın mı?" + }, + "savePasskeyQuestion": { + "message": "Geçiş anahtarı kaydedilsin mi?" + }, + "saveNewPasskey": { + "message": "Yeni hesap olarak kaydet" + }, + "savePasskeyNewLogin": { + "message": "Geçiş anahtarını yeni hesap olarak kaydet" + }, + "noMatchingLoginsForSite": { + "message": "Bu siteyle eşleşen hesap bulunamadı" + }, + "overwritePasskey": { + "message": "Geçiş anahtarının üzerine yazılsın mı?" + }, + "unableToSavePasskey": { + "message": "Geçiş anahtarı kaydedilemedi" + }, + "alreadyContainsPasskey": { + "message": "Bu kayıt zaten bir geçiş anahtarı içeriyor. Mevcut geçiş anahtarının üzerine yazmak istediğinizden emin misiniz?" + }, + "passkeyAlreadyExists": { + "message": "Bu uygulama için bir geçiş anahtarı zaten mevcut." + }, + "applicationDoesNotSupportDuplicates": { + "message": "Bu uygulama yinelenen kayıtları desteklemiyor." + }, + "closeThisWindow": { + "message": "Bu pencereyi kapat" + }, "allowScreenshots": { "message": "Ekran kaydına izin ver" }, @@ -3893,7 +3976,7 @@ "message": "Bitwarden masaüstü uygulamasının ekran görüntülerinde yakalanmasına ve uzak masaüstü oturumlarında görüntülenmesine izin verin. Bunun devre dışı bırakılması bazı harici ekranlarda erişimi engelleyecektir." }, "confirmWindowStillVisibleTitle": { - "message": "Pencerenin hala görünür durumda olduğunu onayla" + "message": "Pencerenin hâlâ görünür durumda olduğunu onayla" }, "confirmWindowStillVisibleContent": { "message": "Lütfen pencerenin hala görünür olduğunu onaylayın." @@ -4012,7 +4095,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, hangi simgenin veya parola değiştirme URL'sinin kullanılacağını belirlemek için kaydedilmiş hesap URI'larını kullanacaktır; bu da deneyiminizi iyileştirmeye yardımcı olur. Bu hizmeti kullandığınızda herhangi bir bilgi toplanmaz veya kaydedilmez." }, "assignToCollections": { "message": "Koleksiyonlara ata" @@ -4161,7 +4244,7 @@ "message": "Kısayolu yazın" }, "editAutotypeShortcutDescription": { - "message": "Include one or two of the following modifiers: Ctrl, Alt, Win, or Shift, and a letter." + "message": "Aşağıdaki değiştirici tuşlardan birini veya ikisini (Ctrl, Alt, Win ya da Shift) ve bir harf kullanın." }, "invalidShortcut": { "message": "Geçersiz kısayol" @@ -4180,7 +4263,7 @@ "message": "Onayla" }, "enableAutotypeShortcutPreview": { - "message": "Enable autotype shortcut (Feature Preview)" + "message": "Otomatik yazma kısayolunu etkinleştir (Özellik Önizlemesi)" }, "enableAutotypeShortcutDescription": { "message": "Verilerin yanlış yere doldurulmasını önlemek için kısayolu kullanmadan önce doğru alanda olduğunuzdan emin olun." @@ -4200,13 +4283,13 @@ "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ı ile otomatik doldurma önerilerinden hariç tutulacaktır." }, "itemWasSentToArchive": { "message": "Kayıt arşive gönderildi" @@ -4215,10 +4298,10 @@ "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ğinizden emin misiniz?" }, "zipPostalCodeLabel": { "message": "ZIP / posta kodu" @@ -4244,16 +4327,147 @@ "andMoreFeatures": { "message": "Ve daha fazlası!" }, - "planDescPremium": { - "message": "Eksiksiz çevrimiçi güvenlik" + "advancedOnlineSecurity": { + "message": "Gelişmiş çevrimiçi güvenlik" }, "upgradeToPremium": { "message": "Premium'a yükselt" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { "message": "Zaman aşımı eylemi" }, "sessionTimeoutHeader": { "message": "Oturum zaman aşımı" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "Bu ayar kuruluşunuz tarafından yönetiliyor." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Kuruluşunuz maksimum kasa zaman aşımını $HOURS$ saat $MINUTES$ dakika olarak belirlemiş.", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Kuruluşunuz varsayılan oturum zaman aşımını “Sistem kilitlenince” olarak ayarlamış." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Kuruluşunuz varsayılan oturum zaman aşımını “Yeniden başlatılınca” olarak ayarlamış." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maksimum zaman aşımı en fazla $HOURS$ saat $MINUTES$ dakika olabilir", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "Yeniden başlatılınca" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Zaman aşımı eyleminizi değiştirmek için kilit açma yönteminizi ayarlayın" + }, + "upgrade": { + "message": "Yükselt" + }, + "leaveConfirmationDialogTitle": { + "message": "Ayrılmak istediğinizden emin misiniz?" + }, + "leaveConfirmationDialogContentOne": { + "message": "Reddetmeniz halinde kişisel kayıtlarınız hesabınızda kalmaya devam edecek, ancak paylaşılan kayıtlara ve kuruluş özelliklerine erişiminizi kaybedeceksiniz." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Erişiminizi yeniden kazanmak için yöneticinizle iletişime geçin." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "$ORGANIZATION$ kuruluşundan ayrıl", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "Kasamı nasıl yönetebilirim?" + }, + "transferItemsToOrganizationTitle": { + "message": "Kayıtları $ORGANIZATION$ kuruluşuna aktar", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$, güvenlik ve mevzuata uyum amacıyla tüm kayıtların kuruluşa ait olmasını zorunlu kılıyor. Kayıtlarınızın sahipliğini devretmek için \"Kabul et\"e tıklayın.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Aktarımı kabul et" + }, + "declineAndLeave": { + "message": "Reddet ve ayrıl" + }, + "whyAmISeeingThis": { + "message": "Bunu neden görüyorum?" } } diff --git a/apps/desktop/src/locales/uk/messages.json b/apps/desktop/src/locales/uk/messages.json index bed09352f03..9e4cc948e37 100644 --- a/apps/desktop/src/locales/uk/messages.json +++ b/apps/desktop/src/locales/uk/messages.json @@ -708,6 +708,18 @@ "addAttachment": { "message": "Додати вкладення" }, + "itemsTransferred": { + "message": "Items transferred" + }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "maxFileSizeSansPunctuation": { "message": "Максимальний розмір файлу – 500 МБ" }, @@ -908,6 +920,12 @@ "unexpectedError": { "message": "Сталася неочікувана помилка." }, + "unexpectedErrorShort": { + "message": "Unexpected error" + }, + "closeThisBitwardenWindow": { + "message": "Close this Bitwarden window and try again." + }, "itemInformation": { "message": "Інформація про запис" }, @@ -1180,8 +1198,8 @@ "followUs": { "message": "Стежте за нами" }, - "syncVault": { - "message": "Синхронізувати сховище" + "syncNow": { + "message": "Sync now" }, "changeMasterPass": { "message": "Змінити головний пароль" @@ -1757,8 +1775,11 @@ "exportFrom": { "message": "Експортувати з" }, - "exportVault": { - "message": "Експортувати сховище" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "Формат файлу" @@ -2619,9 +2640,6 @@ "removedMasterPassword": { "message": "Головний пароль вилучено" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "Головний пароль більше не є обов'язковим для учасників зазначеної організації. Підтвердьте вказаний нижче домен з адміністратором вашої організації." - }, "organizationName": { "message": "Назва організації" }, @@ -3477,10 +3495,6 @@ "aliasDomain": { "message": "Псевдонім домену" }, - "importData": { - "message": "Імпортувати дані", - "description": "Used for the desktop menu item and the header of the import dialog" - }, "importError": { "message": "Помилка імпорту" }, @@ -3886,6 +3900,75 @@ "fileSavedToDevice": { "message": "Файл збережено на пристрої. Ви можете його знайти у теці завантажень." }, + "importantNotice": { + "message": "Important notice" + }, + "setupTwoStepLogin": { + "message": "Set up two-step login" + }, + "newDeviceVerificationNoticeContentPage1": { + "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." + }, + "newDeviceVerificationNoticeContentPage2": { + "message": "You can set up two-step login as an alternative way to protect your account or change your email to one you can access." + }, + "remindMeLater": { + "message": "Remind me later" + }, + "newDeviceVerificationNoticePageOneFormContent": { + "message": "Do you have reliable access to your email, $EMAIL$?", + "placeholders": { + "email": { + "content": "$1", + "example": "your_name@email.com" + } + } + }, + "newDeviceVerificationNoticePageOneEmailAccessNo": { + "message": "No, I do not" + }, + "newDeviceVerificationNoticePageOneEmailAccessYes": { + "message": "Yes, I can reliably access my email" + }, + "turnOnTwoStepLogin": { + "message": "Turn on two-step login" + }, + "changeAcctEmail": { + "message": "Change account email" + }, + "passkeyLogin": { + "message": "Log in with passkey?" + }, + "savePasskeyQuestion": { + "message": "Save passkey?" + }, + "saveNewPasskey": { + "message": "Save as new login" + }, + "savePasskeyNewLogin": { + "message": "Save passkey as new login" + }, + "noMatchingLoginsForSite": { + "message": "No matching logins for this site" + }, + "overwritePasskey": { + "message": "Overwrite passkey?" + }, + "unableToSavePasskey": { + "message": "Unable to save passkey" + }, + "alreadyContainsPasskey": { + "message": "This item already contains a passkey. Are you sure you want to overwrite the current passkey?" + }, + "passkeyAlreadyExists": { + "message": "A passkey already exists for this application." + }, + "applicationDoesNotSupportDuplicates": { + "message": "This application does not support duplicates." + }, + "closeThisWindow": { + "message": "Close this window" + }, "allowScreenshots": { "message": "Дозволити захоплення екрана" }, @@ -4244,16 +4327,147 @@ "andMoreFeatures": { "message": "Інші можливості!" }, - "planDescPremium": { - "message": "Повна онлайн-безпека" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "upgradeToPremium": { "message": "Покращити до Premium" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { "message": "Timeout action" }, "sessionTimeoutHeader": { "message": "Session timeout" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Your organization has set the default session timeout to On system lock." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On restart." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On restart" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/desktop/src/locales/vi/messages.json b/apps/desktop/src/locales/vi/messages.json index 30038f046db..7591b92f8ee 100644 --- a/apps/desktop/src/locales/vi/messages.json +++ b/apps/desktop/src/locales/vi/messages.json @@ -70,7 +70,7 @@ } }, "noEditPermissions": { - "message": "You don't have permission to edit this item" + "message": "Bạn không có quyền chỉnh sửa mục này" }, "welcomeBack": { "message": "Chào mừng bạn trở lại" @@ -708,6 +708,18 @@ "addAttachment": { "message": "Thêm tệp đính kèm" }, + "itemsTransferred": { + "message": "Các mục đã chuyển" + }, + "fixEncryption": { + "message": "Sửa mã hóa" + }, + "fixEncryptionTooltip": { + "message": "Tệp này đang sử dụng phương pháp mã hóa lỗi thời." + }, + "attachmentUpdated": { + "message": "Tệp đính kèm đã được cập nhật" + }, "maxFileSizeSansPunctuation": { "message": "Kích thước tối đa của tập tin là 500MB" }, @@ -908,6 +920,12 @@ "unexpectedError": { "message": "Đã xảy ra lỗi không mong muốn." }, + "unexpectedErrorShort": { + "message": "Lỗi bất thường" + }, + "closeThisBitwardenWindow": { + "message": "Đóng cửa sổ Bitwarden này rồi thử lại." + }, "itemInformation": { "message": "Thông tin mục" }, @@ -1094,22 +1112,22 @@ "message": "Tìm hiểu thêm" }, "migrationsFailed": { - "message": "An error occurred updating the encryption settings." + "message": "Đã xảy ra lỗi khi cập nhật cài đặt mã hóa." }, "updateEncryptionSettingsTitle": { - "message": "Update your encryption settings" + "message": "Cập nhật cài đặt mã hóa của bạn" }, "updateEncryptionSettingsDesc": { - "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + "message": "Cài đặt mã hóa được khuyến nghị sẽ cải thiện bảo mật cho tài khoản của bạn. Nhập mật khẩu chính để cập nhật ngay." }, "confirmIdentityToContinue": { - "message": "Confirm your identity to continue" + "message": "Xác minh danh tính để tiếp tục" }, "enterYourMasterPassword": { - "message": "Enter your master password" + "message": "Nhập mật khẩu chính của bạn" }, "updateSettings": { - "message": "Update settings" + "message": "Cập nhật cài đặt" }, "featureUnavailable": { "message": "Tính năng không có sẵn" @@ -1180,8 +1198,8 @@ "followUs": { "message": "Theo dõi chúng tôi" }, - "syncVault": { - "message": "Đồng bộ kho" + "syncNow": { + "message": "Sync now" }, "changeMasterPass": { "message": "Thay đổi mật khẩu chính" @@ -1313,7 +1331,7 @@ "message": "4 giờ" }, "onIdle": { - "message": "Khi hệ thống không hoạt động (rảnh rỗi)" + "message": "Khi hệ thống nhàn rỗi" }, "onSleep": { "message": "Khi hệ thống ngủ" @@ -1509,7 +1527,7 @@ "message": "1GB bộ nhớ lưu trữ được mã hóa cho các tệp đính kèm." }, "premiumSignUpStorageV2": { - "message": "$SIZE$ encrypted storage for file attachments.", + "message": "$SIZE$ bộ nhớ lưu trữ được mã hóa cho các tệp đính kèm.", "placeholders": { "size": { "content": "$1", @@ -1757,8 +1775,11 @@ "exportFrom": { "message": "Xuất từ" }, - "exportVault": { - "message": "Xuất kho" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "Định dạng tập tin" @@ -2619,9 +2640,6 @@ "removedMasterPassword": { "message": "Đã xóa mật khẩu chính" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "Mật khẩu chính không còn được yêu cầu đối với các thành viên của tổ chức sau đây. Vui lòng xác nhận tên miền bên dưới với quản trị viên của tổ chức." - }, "organizationName": { "message": "Tên tổ chức" }, @@ -3477,10 +3495,6 @@ "aliasDomain": { "message": "Tên miền thay thế" }, - "importData": { - "message": "Nhập dữ liệu", - "description": "Used for the desktop menu item and the header of the import dialog" - }, "importError": { "message": "Lỗi khi nhập" }, @@ -3886,6 +3900,75 @@ "fileSavedToDevice": { "message": "Tệp đã được lưu vào thiết bị. Quản lý từ phần Tải về trên thiết bị của bạn." }, + "importantNotice": { + "message": "Thông báo quan trọng" + }, + "setupTwoStepLogin": { + "message": "Thiết lập đăng nhập hai bước" + }, + "newDeviceVerificationNoticeContentPage1": { + "message": "Bitwarden sẽ gửi mã đến email tài khoản của bạn để xác minh thông tin đăng nhập từ thiết bị mới bắt đầu từ tháng 2 năm 2025." + }, + "newDeviceVerificationNoticeContentPage2": { + "message": "Bạn có thể thiết lập đăng nhập hai bước như một cách thay thế để bảo vệ tài khoản của mình hoặc thay đổi email thành email mà bạn có thể truy cập." + }, + "remindMeLater": { + "message": "Nhắc tôi sau" + }, + "newDeviceVerificationNoticePageOneFormContent": { + "message": "Bạn có thể truy cập vào email $EMAIL$ không?", + "placeholders": { + "email": { + "content": "$1", + "example": "your_name@email.com" + } + } + }, + "newDeviceVerificationNoticePageOneEmailAccessNo": { + "message": "Không, tôi không có" + }, + "newDeviceVerificationNoticePageOneEmailAccessYes": { + "message": "Có, tôi có quyền truy cập email này" + }, + "turnOnTwoStepLogin": { + "message": "Bật đăng nhập hai bước" + }, + "changeAcctEmail": { + "message": "Đổi email tài khoản" + }, + "passkeyLogin": { + "message": "Đăng nhập bằng mã khóa?" + }, + "savePasskeyQuestion": { + "message": "Lưu mã khóa?" + }, + "saveNewPasskey": { + "message": "Lưu như đăng nhập mới" + }, + "savePasskeyNewLogin": { + "message": "Lưu mã khoá như đăng nhập mới" + }, + "noMatchingLoginsForSite": { + "message": "Không có đăng nhập khớp với trang web này" + }, + "overwritePasskey": { + "message": "Ghi đè mã khoá?" + }, + "unableToSavePasskey": { + "message": "Không thể lưu mã khóa" + }, + "alreadyContainsPasskey": { + "message": "Mục này đã chứa mã khóa. Bạn có chắc muốn ghi đè mã khóa hiện tại không?" + }, + "passkeyAlreadyExists": { + "message": "Ứng dụng này đã có mã khoá." + }, + "applicationDoesNotSupportDuplicates": { + "message": "Ứng dụng này không hỗ trợ các mục trùng lặp." + }, + "closeThisWindow": { + "message": "Đóng cửa sổ này" + }, "allowScreenshots": { "message": "Cho phép chụp màn hình" }, @@ -4244,16 +4327,147 @@ "andMoreFeatures": { "message": "Và nhiều hơn nữa!" }, - "planDescPremium": { - "message": "Bảo mật trực tuyến toàn diện" + "advancedOnlineSecurity": { + "message": "Bảo mật trực tuyến nâng cao" }, "upgradeToPremium": { "message": "Nâng cấp lên gói Cao cấp" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { - "message": "Timeout action" + "message": "Hành động sau khi đóng kho" }, "sessionTimeoutHeader": { - "message": "Session timeout" + "message": "Thời gian hết phiên" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "Cài đặt này do tổ chức của bạn quản lý." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Tổ chức của bạn đã đặt thời gian chờ phiên tối đa là $HOURS$ giờ và $MINUTES$ phút.", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Tổ chức của bạn đã đặt thời gian chờ phiên mặc định là Mỗi khi khóa máy." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Tổ chức của bạn đã đặt thời gian chờ phiên mặc định là Khi khởi động lại." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Thời gian chờ tối đa không thể vượt quá $HOURS$ giờ và $MINUTES$ phút", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "Khi khởi động lại máy" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Đặt phương thức mở khóa để thay đổi hành động khi hết thời gian chờ" + }, + "upgrade": { + "message": "Nâng cấp" + }, + "leaveConfirmationDialogTitle": { + "message": "Bạn có chắc chắn muốn rời đi không?" + }, + "leaveConfirmationDialogContentOne": { + "message": "Bằng việc từ chối, các mục cá nhân sẽ vẫn nằm trong tài khoản của bạn, nhưng bạn sẽ mất quyền truy cập vào các mục được chia sẻ và tính năng tổ chức." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Liên hệ quản trị viên của bạn để lấy lại quyền truy cập." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Rời khỏi $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "Tôi quản lý kho của mình như thế nào?" + }, + "transferItemsToOrganizationTitle": { + "message": "Chuyển các mục đến $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ yêu cầu tất cả các mục phải thuộc sở hữu của tổ chức để đảm bảo an ninh và tuân thủ. Nhấp chấp nhận để chuyển quyền sở hữu các mục của bạn.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Chấp nhận chuyển" + }, + "declineAndLeave": { + "message": "Từ chối và rời đi" + }, + "whyAmISeeingThis": { + "message": "Tại sao tôi thấy điều này?" } } diff --git a/apps/desktop/src/locales/zh_CN/messages.json b/apps/desktop/src/locales/zh_CN/messages.json index 80965415475..507be48bf04 100644 --- a/apps/desktop/src/locales/zh_CN/messages.json +++ b/apps/desktop/src/locales/zh_CN/messages.json @@ -708,6 +708,18 @@ "addAttachment": { "message": "添加附件" }, + "itemsTransferred": { + "message": "项目已传输" + }, + "fixEncryption": { + "message": "修复加密" + }, + "fixEncryptionTooltip": { + "message": "此文件正在使用过时的加密方式。" + }, + "attachmentUpdated": { + "message": "附件已更新" + }, "maxFileSizeSansPunctuation": { "message": "文件最大为 500 MB" }, @@ -908,6 +920,12 @@ "unexpectedError": { "message": "发生意外错误。" }, + "unexpectedErrorShort": { + "message": "意外错误" + }, + "closeThisBitwardenWindow": { + "message": "关闭此 Bitwarden 窗口,然后重试。" + }, "itemInformation": { "message": "项目信息" }, @@ -927,7 +945,7 @@ "message": "验证码" }, "confirmIdentity": { - "message": "确认后继续。" + "message": "确认您的身份以继续。" }, "verificationCodeRequired": { "message": "必须填写验证码。" @@ -1094,22 +1112,22 @@ "message": "进一步了解" }, "migrationsFailed": { - "message": "An error occurred updating the encryption settings." + "message": "更新加密设置时发生错误。" }, "updateEncryptionSettingsTitle": { - "message": "Update your encryption settings" + "message": "更新您的加密设置" }, "updateEncryptionSettingsDesc": { - "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + "message": "新推荐的加密设置将提高您的账户安全性。输入您的主密码以立即更新。" }, "confirmIdentityToContinue": { - "message": "Confirm your identity to continue" + "message": "确认您的身份以继续" }, "enterYourMasterPassword": { - "message": "Enter your master password" + "message": "输入您的主密码" }, "updateSettings": { - "message": "Update settings" + "message": "更新设置" }, "featureUnavailable": { "message": "功能不可用" @@ -1180,8 +1198,8 @@ "followUs": { "message": "关注我们" }, - "syncVault": { - "message": "同步密码库" + "syncNow": { + "message": "立即同步" }, "changeMasterPass": { "message": "修改主密码" @@ -1259,7 +1277,7 @@ } }, "twoStepLoginConfirmation": { - "message": "两步登录要求您从其他设备(例如安全密钥、验证器 App、短信、电话或者电子邮件)来验证您的登录,这能使您的账户更加安全。两步登录需要在 bitwarden.com 网页版密码库中设置。现在访问此网站吗?" + "message": "两步登录要求您从其他设备(例如安全密钥、验证器 App、短信、电话或者电子邮件)来验证您的登录,这能使您的账户更加安全。两步登录需要在 bitwarden.com 网页版密码库中设置。现在要访问此网站吗?" }, "twoStepLogin": { "message": "两步登录" @@ -1453,7 +1471,7 @@ "message": "有可用的更新" }, "updateAvailableDesc": { - "message": "发现更新。是否立即下载?" + "message": "发现更新。要立即下载吗?" }, "restart": { "message": "重启" @@ -1494,7 +1512,7 @@ "message": "管理会员资格" }, "premiumManageAlert": { - "message": "您可以在 bitwarden.com 网页版密码库管理您的会员资格。现在要访问吗?" + "message": "您可以在 bitwarden.com 网页版密码库管理您的会员资格。现在要访问此网站吗?" }, "premiumRefresh": { "message": "刷新会员资格" @@ -1757,8 +1775,11 @@ "exportFrom": { "message": "导出自" }, - "exportVault": { - "message": "导出密码库" + "export": { + "message": "导出" + }, + "import": { + "message": "导入" }, "fileFormat": { "message": "文件格式" @@ -2175,7 +2196,7 @@ "message": "启用浏览器集成时出错" }, "browserIntegrationErrorDesc": { - "message": "启用浏览器集成时出错。" + "message": "启用浏览器集成时发生错误。" }, "browserIntegrationWindowsStoreDesc": { "message": "很遗憾,Microsoft Store 版本目前不支持浏览器集成。" @@ -2619,9 +2640,6 @@ "removedMasterPassword": { "message": "主密码已移除" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "以下组织的成员不再需要主密码。请与您的组织管理员确认下面的域名。" - }, "organizationName": { "message": "组织名称" }, @@ -3080,7 +3098,7 @@ "message": "请求获得批准后,您将收到通知" }, "needAnotherOption": { - "message": "必须在 Bitwarden App 的设置中启用设备登录。需要其他登录选项吗?" + "message": "必须在 Bitwarden App 的设置中启用设备登录。需要其他选项吗?" }, "viewAllLogInOptions": { "message": "查看所有登录选项" @@ -3203,10 +3221,10 @@ "message": "检查您的电子邮箱" }, "followTheLinkInTheEmailSentTo": { - "message": "点击发送到电子邮件中的链接" + "message": "点击发送到" }, "andContinueCreatingYourAccount": { - "message": "然后继续创建您的账户。" + "message": "的电子邮件中的链接,然后继续创建您的账户。" }, "noEmail": { "message": "没有收到电子邮件吗?" @@ -3477,10 +3495,6 @@ "aliasDomain": { "message": "别名域" }, - "importData": { - "message": "导入数据", - "description": "Used for the desktop menu item and the header of the import dialog" - }, "importError": { "message": "导入出错" }, @@ -3886,6 +3900,75 @@ "fileSavedToDevice": { "message": "文件已保存到设备。可以在设备下载中进行管理。" }, + "importantNotice": { + "message": "重要通知" + }, + "setupTwoStepLogin": { + "message": "设置两步登录" + }, + "newDeviceVerificationNoticeContentPage1": { + "message": "从 2025 年 02 月起,Bitwarden 将向您的账户电子邮箱发送验证码,以验证来自新设备的登录。" + }, + "newDeviceVerificationNoticeContentPage2": { + "message": "您可以设置两步登录作为保护账户的替代方法,或将您的电子邮箱更改为您可以访问的电子邮箱。" + }, + "remindMeLater": { + "message": "稍后提醒我" + }, + "newDeviceVerificationNoticePageOneFormContent": { + "message": "您可以正常访问您的电子邮箱 $EMAIL$ 吗?", + "placeholders": { + "email": { + "content": "$1", + "example": "your_name@email.com" + } + } + }, + "newDeviceVerificationNoticePageOneEmailAccessNo": { + "message": "不,我不能" + }, + "newDeviceVerificationNoticePageOneEmailAccessYes": { + "message": "是的,我可以正常访问我的电子邮箱" + }, + "turnOnTwoStepLogin": { + "message": "启用两步登录" + }, + "changeAcctEmail": { + "message": "更改账户电子邮箱" + }, + "passkeyLogin": { + "message": "使用通行密钥登录吗?" + }, + "savePasskeyQuestion": { + "message": "保存通行密钥吗?" + }, + "saveNewPasskey": { + "message": "保存为新的登录" + }, + "savePasskeyNewLogin": { + "message": "将通行密钥保存为新的登录" + }, + "noMatchingLoginsForSite": { + "message": "此站点没有匹配的登录" + }, + "overwritePasskey": { + "message": "覆盖通行密钥吗?" + }, + "unableToSavePasskey": { + "message": "无法保存通行密钥" + }, + "alreadyContainsPasskey": { + "message": "此项目已包含一个通行密钥。确定要覆盖当前的通行密钥吗?" + }, + "passkeyAlreadyExists": { + "message": "此应用程序已存在一个通行密钥。" + }, + "applicationDoesNotSupportDuplicates": { + "message": "此应用程序不支持重复项。" + }, + "closeThisWindow": { + "message": "关闭此窗口" + }, "allowScreenshots": { "message": "允许屏幕截图" }, @@ -4244,16 +4327,147 @@ "andMoreFeatures": { "message": "以及更多!" }, - "planDescPremium": { - "message": "全面的在线安全防护" + "advancedOnlineSecurity": { + "message": "高级在线安全防护" }, "upgradeToPremium": { "message": "升级为高级版" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "您的组织已不再使用主密码登录 Bitwarden。要继续,请验证组织和域名。" + }, + "continueWithLogIn": { + "message": "继续登录" + }, + "doNotContinue": { + "message": "不要继续" + }, + "domain": { + "message": "域名" + }, + "keyConnectorDomainTooltip": { + "message": "此域名将存储您的账户加密密钥,所以请确保您信任它。如果您不确定,请与您的管理员联系。" + }, + "verifyYourOrganization": { + "message": "验证您的组织以登录" + }, + "organizationVerified": { + "message": "组织已验证" + }, + "domainVerified": { + "message": "域名已验证" + }, + "leaveOrganizationContent": { + "message": "如果不验证您的组织,您对组织的访问权限将被撤销。" + }, + "leaveNow": { + "message": "立即退出" + }, + "verifyYourDomainToLogin": { + "message": "验证您的域名以登录" + }, + "verifyYourDomainDescription": { + "message": "要继续登录,请验证此域名。" + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "要继续登录,请验证组织和域名。" + }, "sessionTimeoutSettingsAction": { "message": "超时动作" }, "sessionTimeoutHeader": { "message": "会话超时" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "此设置由您的组织管理。" + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "您的组织已将最大会话超时设置为 $HOURS$ 小时 $MINUTES$ 分钟。", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "您的组织已将默认会话超时设置为「系统锁定时」。" + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "您的组织已将默认会话超时设置为「重启时」。" + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "最大超时不能超过 $HOURS$ 小时 $MINUTES$ 分钟", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "重启时" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "设置一个解锁方式以更改您的超时动作" + }, + "upgrade": { + "message": "升级" + }, + "leaveConfirmationDialogTitle": { + "message": "确定要退出吗?" + }, + "leaveConfirmationDialogContentOne": { + "message": "拒绝后,您的个人项目将保留在您的账户中,但您将失去对共享项目和组织功能的访问权限。" + }, + "leaveConfirmationDialogContentTwo": { + "message": "联系您的管理员以重新获取访问权限。" + }, + "leaveConfirmationDialogConfirmButton": { + "message": "退出 $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "我该如何管理我的密码库?" + }, + "transferItemsToOrganizationTitle": { + "message": "传输项目到 $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "出于安全和合规考虑,$ORGANIZATION$ 要求所有项目归组织所有。点击「接受」以传输您的项目的所有权。", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "接受传输" + }, + "declineAndLeave": { + "message": "拒绝并退出" + }, + "whyAmISeeingThis": { + "message": "为什么我会看到这个?" } } diff --git a/apps/desktop/src/locales/zh_TW/messages.json b/apps/desktop/src/locales/zh_TW/messages.json index e412adf9e5b..3e00280b364 100644 --- a/apps/desktop/src/locales/zh_TW/messages.json +++ b/apps/desktop/src/locales/zh_TW/messages.json @@ -708,6 +708,18 @@ "addAttachment": { "message": "新增附件" }, + "itemsTransferred": { + "message": "Items transferred" + }, + "fixEncryption": { + "message": "修正加密" + }, + "fixEncryptionTooltip": { + "message": "此檔案使用了過時的加密方式。" + }, + "attachmentUpdated": { + "message": "附件已更新" + }, "maxFileSizeSansPunctuation": { "message": "最大檔案大小為 500MB" }, @@ -908,6 +920,12 @@ "unexpectedError": { "message": "發生了未預期的錯誤。" }, + "unexpectedErrorShort": { + "message": "未預期的錯誤" + }, + "closeThisBitwardenWindow": { + "message": "關閉此 Bitwarden 視窗後再試一次。" + }, "itemInformation": { "message": "項目資訊" }, @@ -1094,22 +1112,22 @@ "message": "了解更多" }, "migrationsFailed": { - "message": "An error occurred updating the encryption settings." + "message": "更新加密設定時發生錯誤。" }, "updateEncryptionSettingsTitle": { - "message": "Update your encryption settings" + "message": "更新您的加密設定" }, "updateEncryptionSettingsDesc": { - "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + "message": "新的建議加密設定將提升您的帳戶安全性。請輸入主密碼以立即更新。" }, "confirmIdentityToContinue": { - "message": "Confirm your identity to continue" + "message": "請先確認身分後再繼續" }, "enterYourMasterPassword": { - "message": "Enter your master password" + "message": "輸入您的主密碼" }, "updateSettings": { - "message": "Update settings" + "message": "更新設定" }, "featureUnavailable": { "message": "功能不可用" @@ -1180,8 +1198,8 @@ "followUs": { "message": "關注我們" }, - "syncVault": { - "message": "同步密碼庫" + "syncNow": { + "message": "Sync now" }, "changeMasterPass": { "message": "變更主密碼" @@ -1509,7 +1527,7 @@ "message": "用於檔案附件的 1 GB 的加密檔案儲存空間。" }, "premiumSignUpStorageV2": { - "message": "$SIZE$ encrypted storage for file attachments.", + "message": "用於檔案附件的 $SIZE$ 加密儲存空間。", "placeholders": { "size": { "content": "$1", @@ -1757,8 +1775,11 @@ "exportFrom": { "message": "匯出自" }, - "exportVault": { - "message": "匯出密碼庫" + "export": { + "message": "Export" + }, + "import": { + "message": "Import" }, "fileFormat": { "message": "檔案格式" @@ -2619,9 +2640,6 @@ "removedMasterPassword": { "message": "主密碼已移除" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "以下組織的成員已不再需要主密碼。請與你的組織管理員確認下方的網域。" - }, "organizationName": { "message": "機構名稱" }, @@ -3477,10 +3495,6 @@ "aliasDomain": { "message": "別名網域" }, - "importData": { - "message": "匯入資料", - "description": "Used for the desktop menu item and the header of the import dialog" - }, "importError": { "message": "匯入時發生錯誤" }, @@ -3886,6 +3900,75 @@ "fileSavedToDevice": { "message": "檔案已儲存到裝置。在您的裝置上管理下載檔案。" }, + "importantNotice": { + "message": "重要通知" + }, + "setupTwoStepLogin": { + "message": "啟動兩階段登入" + }, + "newDeviceVerificationNoticeContentPage1": { + "message": "從 2025 年 2 月開始,Bitwarden 會傳送代碼到您的帳號電子郵件中來驗證新裝置的登入。" + }, + "newDeviceVerificationNoticeContentPage2": { + "message": "您可以啟動兩階段認證來保護您的帳號或更改您可以存取的電子郵件位址。" + }, + "remindMeLater": { + "message": "稍後再提醒我" + }, + "newDeviceVerificationNoticePageOneFormContent": { + "message": "您可以存取您的電子郵件位址 $EMAIL$ 嗎?", + "placeholders": { + "email": { + "content": "$1", + "example": "your_name@email.com" + } + } + }, + "newDeviceVerificationNoticePageOneEmailAccessNo": { + "message": "不,我不行" + }, + "newDeviceVerificationNoticePageOneEmailAccessYes": { + "message": "是,我可以存取我的電子郵件位址" + }, + "turnOnTwoStepLogin": { + "message": "啟動兩階段登入" + }, + "changeAcctEmail": { + "message": "更改帳號電子郵件位址" + }, + "passkeyLogin": { + "message": "使用密碼金鑰登入?" + }, + "savePasskeyQuestion": { + "message": "儲存密碼金鑰?" + }, + "saveNewPasskey": { + "message": "儲存為新的登入資訊" + }, + "savePasskeyNewLogin": { + "message": "將密碼金鑰儲存為新的登入資訊" + }, + "noMatchingLoginsForSite": { + "message": "未找到此網站的登入資訊" + }, + "overwritePasskey": { + "message": "要覆寫密碼金鑰嗎?" + }, + "unableToSavePasskey": { + "message": "無法儲存通行金鑰" + }, + "alreadyContainsPasskey": { + "message": "該項目已包含一個密碼金鑰。您確定要覆寫目前的密碼金鑰嗎?" + }, + "passkeyAlreadyExists": { + "message": "用於這個應用程式的密碼金鑰已經存在。" + }, + "applicationDoesNotSupportDuplicates": { + "message": "此應用程式不支援重複項目。" + }, + "closeThisWindow": { + "message": "關閉此視窗" + }, "allowScreenshots": { "message": "允許螢幕擷取" }, @@ -4244,16 +4327,147 @@ "andMoreFeatures": { "message": "以及其他功能功能!" }, - "planDescPremium": { - "message": "完整的線上安全" + "advancedOnlineSecurity": { + "message": "進階線上安全防護" }, "upgradeToPremium": { "message": "升級到 Premium" }, + "removeMasterPasswordForOrgUserKeyConnector": { + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified": { + "message": "Organization verified" + }, + "domainVerified": { + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { "message": "逾時後動作" }, "sessionTimeoutHeader": { "message": "工作階段逾時" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "此設定由您的組織管理。" + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "您的組織已將最長工作階段逾時設為 $HOURS$ 小時與 $MINUTES$ 分鐘。", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "您的組織已將預設工作階段逾時設定為「在系統鎖定時」。" + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "您的組織已將預設工作階段逾時設定為「在重新啟動時」。" + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "最長逾時時間不可超過 $HOURS$ 小時 $MINUTES$ 分鐘", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "重新啟動時" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "設定一個解鎖方式來變更您的密碼庫逾時動作。" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } From b63e1cb26c08bf43969c3e6de8df18325860ea40 Mon Sep 17 00:00:00 2001 From: Mike Amirault <mamirault@bitwarden.com> Date: Tue, 16 Dec 2025 13:34:31 -0500 Subject: [PATCH 087/188] [PM-28181] Open send dialog in drawer instead of popup in refreshed UI (#17666) * [PM-28181] Open send dialog in drawer instead of popup in refreshed UI * Fix types * [PM-28181] Use drawer to edit sends with refreshed UI * [PM-28181] Address bug where multiple Sends could not be navigated between --- .../new-send-dropdown.component.spec.ts | 94 +++++++++++++++++++ .../new-send/new-send-dropdown.component.ts | 10 +- apps/web/src/app/tools/send/send.component.ts | 20 +++- libs/common/src/enums/feature-flag.enum.ts | 2 + .../send-add-edit-dialog.component.ts | 15 +++ 5 files changed, 135 insertions(+), 6 deletions(-) create mode 100644 apps/web/src/app/tools/send/new-send/new-send-dropdown.component.spec.ts diff --git a/apps/web/src/app/tools/send/new-send/new-send-dropdown.component.spec.ts b/apps/web/src/app/tools/send/new-send/new-send-dropdown.component.spec.ts new file mode 100644 index 00000000000..4f5dda1745e --- /dev/null +++ b/apps/web/src/app/tools/send/new-send/new-send-dropdown.component.spec.ts @@ -0,0 +1,94 @@ +import { ComponentFixture, TestBed } from "@angular/core/testing"; +import { mock } from "jest-mock-extended"; +import { of } from "rxjs"; + +import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; +import { Account, 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 { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { SendType } from "@bitwarden/common/tools/send/enums/send-type"; +import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction"; +import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction"; +import { PremiumUpgradePromptService } from "@bitwarden/common/vault/abstractions/premium-upgrade-prompt.service"; +import { SendAddEditDialogComponent } from "@bitwarden/send-ui"; + +import { NewSendDropdownComponent } from "./new-send-dropdown.component"; + +describe("NewSendDropdownComponent", () => { + let component: NewSendDropdownComponent; + let fixture: ComponentFixture<NewSendDropdownComponent>; + const mockBillingAccountProfileStateService = mock<BillingAccountProfileStateService>(); + const mockAccountService = mock<AccountService>(); + const mockConfigService = mock<ConfigService>(); + const mockI18nService = mock<I18nService>(); + const mockPolicyService = mock<PolicyService>(); + const mockSendService = mock<SendService>(); + const mockPremiumUpgradePromptService = mock<PremiumUpgradePromptService>(); + const mockSendApiService = mock<SendApiService>(); + + beforeAll(() => { + mockBillingAccountProfileStateService.hasPremiumFromAnySource$.mockImplementation(() => + of(true), + ); + mockAccountService.activeAccount$ = of({ id: "myTestAccount" } as Account); + mockPolicyService.policyAppliesToUser$.mockImplementation(() => of(false)); + mockPremiumUpgradePromptService.promptForPremium.mockImplementation(async () => {}); + }); + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [NewSendDropdownComponent], + declarations: [], + providers: [ + { + provide: BillingAccountProfileStateService, + useValue: mockBillingAccountProfileStateService, + }, + { provide: AccountService, useValue: mockAccountService }, + { provide: ConfigService, useValue: mockConfigService }, + { provide: I18nService, useValue: mockI18nService }, + { provide: PolicyService, useValue: mockPolicyService }, + { provide: SendService, useValue: mockSendService }, + { provide: PremiumUpgradePromptService, useValue: mockPremiumUpgradePromptService }, + { provide: SendApiService, useValue: mockSendApiService }, + ], + }).compileComponents(); + fixture = TestBed.createComponent(NewSendDropdownComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + it("should create", () => { + expect(component).toBeTruthy(); + }); + + it("should open send dialog in a popup without feature flag", async () => { + const openSpy = jest.spyOn(SendAddEditDialogComponent, "open"); + const openDrawerSpy = jest.spyOn(SendAddEditDialogComponent, "openDrawer"); + mockConfigService.getFeatureFlag.mockResolvedValue(false); + + await component.createSend(SendType.Text); + + expect(openSpy).toHaveBeenCalled(); + expect(openDrawerSpy).not.toHaveBeenCalled(); + }); + + it("should open send dialog in drawer with feature flag", async () => { + const openSpy = jest.spyOn(SendAddEditDialogComponent, "open"); + const openDrawerSpy = jest.spyOn(SendAddEditDialogComponent, "openDrawer"); + mockConfigService.getFeatureFlag.mockImplementation(async (key) => + key === FeatureFlag.SendUIRefresh ? true : false, + ); + + await component.createSend(SendType.Text); + + expect(openSpy).not.toHaveBeenCalled(); + expect(openDrawerSpy).toHaveBeenCalled(); + }); +}); diff --git a/apps/web/src/app/tools/send/new-send/new-send-dropdown.component.ts b/apps/web/src/app/tools/send/new-send/new-send-dropdown.component.ts index 80d1d0e1e12..22f07e4fe92 100644 --- a/apps/web/src/app/tools/send/new-send/new-send-dropdown.component.ts +++ b/apps/web/src/app/tools/send/new-send/new-send-dropdown.component.ts @@ -6,6 +6,8 @@ import { PremiumBadgeComponent } from "@bitwarden/angular/billing/components/pre import { JslibModule } from "@bitwarden/angular/jslib.module"; 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 { SendType } from "@bitwarden/common/tools/send/enums/send-type"; import { ButtonModule, DialogService, MenuModule } from "@bitwarden/components"; import { DefaultSendFormConfigService, SendAddEditDialogComponent } from "@bitwarden/send-ui"; @@ -38,6 +40,7 @@ export class NewSendDropdownComponent { private accountService: AccountService, private dialogService: DialogService, private addEditFormConfigService: DefaultSendFormConfigService, + private configService: ConfigService, ) { this.canAccessPremium$ = this.accountService.activeAccount$.pipe( switchMap((account) => @@ -60,6 +63,11 @@ export class NewSendDropdownComponent { const formConfig = await this.addEditFormConfigService.buildConfig("add", undefined, type); - SendAddEditDialogComponent.open(this.dialogService, { formConfig }); + const useRefresh = await this.configService.getFeatureFlag(FeatureFlag.SendUIRefresh); + if (useRefresh) { + SendAddEditDialogComponent.openDrawer(this.dialogService, { formConfig }); + } else { + SendAddEditDialogComponent.open(this.dialogService, { formConfig }); + } } } diff --git a/apps/web/src/app/tools/send/send.component.ts b/apps/web/src/app/tools/send/send.component.ts index e9ad7aee1f1..11559976fbf 100644 --- a/apps/web/src/app/tools/send/send.component.ts +++ b/apps/web/src/app/tools/send/send.component.ts @@ -7,7 +7,9 @@ import { SendComponent as BaseSendComponent } from "@bitwarden/angular/tools/sen import { NoSendsIcon } from "@bitwarden/assets/svg"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +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 { 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"; @@ -77,6 +79,7 @@ export class SendComponent extends BaseSendComponent implements OnInit, OnDestro toastService: ToastService, private addEditFormConfigService: DefaultSendFormConfigService, accountService: AccountService, + private configService: ConfigService, ) { super( sendService, @@ -144,14 +147,21 @@ export class SendComponent extends BaseSendComponent implements OnInit, OnDestro * @param formConfig The form configuration. * */ async openSendItemDialog(formConfig: SendFormConfig) { - // Prevent multiple dialogs from being opened. - if (this.sendItemDialogRef) { + const useRefresh = await this.configService.getFeatureFlag(FeatureFlag.SendUIRefresh); + // Prevent multiple dialogs from being opened but allow drawers since they will prevent multiple being open themselves + if (this.sendItemDialogRef && !useRefresh) { return; } - this.sendItemDialogRef = SendAddEditDialogComponent.open(this.dialogService, { - formConfig, - }); + if (useRefresh) { + this.sendItemDialogRef = SendAddEditDialogComponent.openDrawer(this.dialogService, { + formConfig, + }); + } else { + this.sendItemDialogRef = SendAddEditDialogComponent.open(this.dialogService, { + formConfig, + }); + } const result = await lastValueFrom(this.sendItemDialogRef.closed); this.sendItemDialogRef = undefined; diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts index fb8edd8aa7d..f2037f1a89f 100644 --- a/libs/common/src/enums/feature-flag.enum.ts +++ b/libs/common/src/enums/feature-flag.enum.ts @@ -50,6 +50,7 @@ export enum FeatureFlag { DesktopSendUIRefresh = "desktop-send-ui-refresh", UseSdkPasswordGenerators = "pm-19976-use-sdk-password-generators", ChromiumImporterWithABE = "pm-25855-chromium-importer-abe", + SendUIRefresh = "pm-28175-send-ui-refresh", /* DIRT */ EventManagementForDataDogAndCrowdStrike = "event-management-for-datadog-and-crowdstrike", @@ -110,6 +111,7 @@ export const DefaultFeatureFlagValue = { [FeatureFlag.DesktopSendUIRefresh]: FALSE, [FeatureFlag.UseSdkPasswordGenerators]: FALSE, [FeatureFlag.ChromiumImporterWithABE]: FALSE, + [FeatureFlag.SendUIRefresh]: FALSE, /* DIRT */ [FeatureFlag.EventManagementForDataDogAndCrowdStrike]: FALSE, diff --git a/libs/tools/send/send-ui/src/add-edit/send-add-edit-dialog.component.ts b/libs/tools/send/send-ui/src/add-edit/send-add-edit-dialog.component.ts index 6f49c0ecce5..38257df603a 100644 --- a/libs/tools/send/send-ui/src/add-edit/send-add-edit-dialog.component.ts +++ b/libs/tools/send/send-ui/src/add-edit/send-add-edit-dialog.component.ts @@ -174,4 +174,19 @@ export class SendAddEditDialogComponent { }, ); } + + /** + * Opens the send add/edit dialog in a drawer + * @param dialogService Instance of the DialogService. + * @param params The parameters for the drawer. + * @returns The drawer result. + */ + static openDrawer(dialogService: DialogService, params: SendItemDialogParams) { + return dialogService.openDrawer<SendItemDialogResult, SendItemDialogParams>( + SendAddEditDialogComponent, + { + data: params, + }, + ); + } } From ac0a0fd2198d833c2bc047dbb0104beb02dac28e Mon Sep 17 00:00:00 2001 From: neuronull <9162534+neuronull@users.noreply.github.com> Date: Tue, 16 Dec 2025 13:00:56 -0700 Subject: [PATCH 088/188] Desktop Autotype toggle on vault lock/unlock (#17062) * Desktop Autotype toggle on vault lock/unlock * lint * add back disable on will-quit signal * improve IPC message args * claude: takeUntilDestroyed * claude: try/catch * claude: multiple listeners * claude: === * claude: concatMap * claude: IPC Handler Registration in Constructor * claude: helper function * claude: Type Safety for IPC Messages * fix claude suggestion? * bit by commit hook file write again * remove the type qualifier * add log svc dep * move the initialized ipcs back to constructor * frageele? * try disable premium check * replace takeUntilDestroy with takeUntil(destroy) * add import * create separate observable for premium check * clean up and remove distinctUntilChanged * re-add distinctUntilChanged * ipc handlers in init * check double initialization * Revert "check double initialization" This reverts commit 8488b8a6130e69a31497c7c0148dde256f2c9667. * Revert "ipc handlers in init" This reverts commit a23999edcfda396eb0c0910d8a318b084a3c4120. * ipc out of constructor * claude suggestion does not compile, awesome * add a dispose method for cleanup of ipc handlers * claude: remove of(false) on observable initializing * claude: remove the init/init'd * claude: remove takeUntil on isPremiumAccount * Revert "claude: remove takeUntil on isPremiumAccount" This reverts commit 9fc32c5fcf47964df63ed4198d96223e26d9ae1f. * align models file name with interface name * rename ipc listeners function * improve debug log message * improve debug log message * remove reference to not present observable in unit test * add function comment * make `autotypeKeyboardShortcut` private --- .../app/accounts/settings.component.spec.ts | 1 - .../src/app/services/services.module.ts | 1 + .../main/main-desktop-autotype.service.ts | 103 +++++++--- .../src/autofill/models/autotype-config.ts | 3 + .../src/autofill/models/ipc-channels.ts | 9 + apps/desktop/src/autofill/preload.ts | 17 +- .../services/desktop-autotype.service.ts | 184 ++++++++++++------ apps/desktop/src/main.ts | 11 +- 8 files changed, 220 insertions(+), 109 deletions(-) create mode 100644 apps/desktop/src/autofill/models/autotype-config.ts create mode 100644 apps/desktop/src/autofill/models/ipc-channels.ts diff --git a/apps/desktop/src/app/accounts/settings.component.spec.ts b/apps/desktop/src/app/accounts/settings.component.spec.ts index a424f230778..d518ac29aa4 100644 --- a/apps/desktop/src/app/accounts/settings.component.spec.ts +++ b/apps/desktop/src/app/accounts/settings.component.spec.ts @@ -187,7 +187,6 @@ describe("SettingsComponent", () => { i18nService.userSetLocale$ = of("en"); pinServiceAbstraction.isPinSet.mockResolvedValue(false); policyService.policiesByType$.mockReturnValue(of([null])); - desktopAutotypeService.resolvedAutotypeEnabled$ = of(false); desktopAutotypeService.autotypeEnabledUserSetting$ = of(false); desktopAutotypeService.autotypeKeyboardShortcut$ = of(["Control", "Shift", "B"]); billingAccountProfileStateService.hasPremiumFromAnySource$.mockReturnValue(of(false)); diff --git a/apps/desktop/src/app/services/services.module.ts b/apps/desktop/src/app/services/services.module.ts index 59021a556e4..874a4d851da 100644 --- a/apps/desktop/src/app/services/services.module.ts +++ b/apps/desktop/src/app/services/services.module.ts @@ -489,6 +489,7 @@ const safeProviders: SafeProvider[] = [ PlatformUtilsServiceAbstraction, BillingAccountProfileStateService, DesktopAutotypeDefaultSettingPolicy, + LogService, ], }), safeProvider({ diff --git a/apps/desktop/src/autofill/main/main-desktop-autotype.service.ts b/apps/desktop/src/autofill/main/main-desktop-autotype.service.ts index 4dcf05a4220..ea2bdd1fe12 100644 --- a/apps/desktop/src/autofill/main/main-desktop-autotype.service.ts +++ b/apps/desktop/src/autofill/main/main-desktop-autotype.service.ts @@ -5,51 +5,46 @@ import { LogService } from "@bitwarden/logging"; import { WindowMain } from "../../main/window.main"; import { stringIsNotUndefinedNullAndEmpty } from "../../utils"; +import { AutotypeConfig } from "../models/autotype-config"; import { AutotypeMatchError } from "../models/autotype-errors"; import { AutotypeVaultData } from "../models/autotype-vault-data"; +import { AUTOTYPE_IPC_CHANNELS } from "../models/ipc-channels"; import { AutotypeKeyboardShortcut } from "../models/main-autotype-keyboard-shortcut"; export class MainDesktopAutotypeService { - autotypeKeyboardShortcut: AutotypeKeyboardShortcut; + private autotypeKeyboardShortcut: AutotypeKeyboardShortcut; constructor( private logService: LogService, private windowMain: WindowMain, ) { this.autotypeKeyboardShortcut = new AutotypeKeyboardShortcut(); + + this.registerIpcListeners(); } - init() { - ipcMain.on("autofill.configureAutotype", (event, data) => { - if (data.enabled) { - const newKeyboardShortcut = new AutotypeKeyboardShortcut(); - const newKeyboardShortcutIsValid = newKeyboardShortcut.set(data.keyboardShortcut); - - if (newKeyboardShortcutIsValid) { - this.disableAutotype(); - this.autotypeKeyboardShortcut = newKeyboardShortcut; - this.enableAutotype(); - } else { - this.logService.error( - "Attempting to configure autotype but the shortcut given is invalid.", - ); - } + registerIpcListeners() { + ipcMain.on(AUTOTYPE_IPC_CHANNELS.TOGGLE, (_event, enable: boolean) => { + if (enable) { + this.enableAutotype(); } else { this.disableAutotype(); - - // Deregister the incoming keyboard shortcut if needed - const setCorrectly = this.autotypeKeyboardShortcut.set(data.keyboardShortcut); - if ( - setCorrectly && - globalShortcut.isRegistered(this.autotypeKeyboardShortcut.getElectronFormat()) - ) { - globalShortcut.unregister(this.autotypeKeyboardShortcut.getElectronFormat()); - this.logService.info("Autotype disabled."); - } } }); - ipcMain.on("autofill.completeAutotypeRequest", (_event, vaultData: AutotypeVaultData) => { + ipcMain.on(AUTOTYPE_IPC_CHANNELS.CONFIGURE, (_event, config: AutotypeConfig) => { + const newKeyboardShortcut = new AutotypeKeyboardShortcut(); + const newKeyboardShortcutIsValid = newKeyboardShortcut.set(config.keyboardShortcut); + + if (!newKeyboardShortcutIsValid) { + this.logService.error("Configure autotype failed: the keyboard shortcut is invalid."); + return; + } + + this.setKeyboardShortcut(newKeyboardShortcut); + }); + + ipcMain.on(AUTOTYPE_IPC_CHANNELS.EXECUTE, (_event, vaultData: AutotypeVaultData) => { if ( stringIsNotUndefinedNullAndEmpty(vaultData.username) && stringIsNotUndefinedNullAndEmpty(vaultData.password) @@ -67,30 +62,74 @@ export class MainDesktopAutotypeService { }); } + // Deregister the keyboard shortcut if registered. disableAutotype() { - // Deregister the current keyboard shortcut if needed const formattedKeyboardShortcut = this.autotypeKeyboardShortcut.getElectronFormat(); + if (globalShortcut.isRegistered(formattedKeyboardShortcut)) { globalShortcut.unregister(formattedKeyboardShortcut); - this.logService.info("Autotype disabled."); + this.logService.debug("Autotype disabled."); + } else { + this.logService.debug("Autotype is not registered, implicitly disabled."); } } + dispose() { + ipcMain.removeAllListeners(AUTOTYPE_IPC_CHANNELS.TOGGLE); + ipcMain.removeAllListeners(AUTOTYPE_IPC_CHANNELS.CONFIGURE); + ipcMain.removeAllListeners(AUTOTYPE_IPC_CHANNELS.EXECUTE); + + // Also unregister the global shortcut + this.disableAutotype(); + } + + // Register the current keyboard shortcut if not already registered. private enableAutotype() { + const formattedKeyboardShortcut = this.autotypeKeyboardShortcut.getElectronFormat(); + if (globalShortcut.isRegistered(formattedKeyboardShortcut)) { + this.logService.debug( + "Autotype is already enabled with this keyboard shortcut: " + formattedKeyboardShortcut, + ); + return; + } + const result = globalShortcut.register( this.autotypeKeyboardShortcut.getElectronFormat(), () => { const windowTitle = autotype.getForegroundWindowTitle(); - this.windowMain.win.webContents.send("autofill.listenAutotypeRequest", { + this.windowMain.win.webContents.send(AUTOTYPE_IPC_CHANNELS.LISTEN, { windowTitle, }); }, ); result - ? this.logService.info("Autotype enabled.") - : this.logService.info("Enabling autotype failed."); + ? this.logService.debug("Autotype enabled.") + : this.logService.error("Failed to enable Autotype."); + } + + // Set the keyboard shortcut if it differs from the present one. If + // the keyboard shortcut is set, de-register the old shortcut first. + private setKeyboardShortcut(keyboardShortcut: AutotypeKeyboardShortcut) { + if ( + keyboardShortcut.getElectronFormat() !== this.autotypeKeyboardShortcut.getElectronFormat() + ) { + const registered = globalShortcut.isRegistered( + this.autotypeKeyboardShortcut.getElectronFormat(), + ); + if (registered) { + this.disableAutotype(); + } + this.autotypeKeyboardShortcut = keyboardShortcut; + if (registered) { + this.enableAutotype(); + } + } else { + this.logService.debug( + "setKeyboardShortcut() called but shortcut is not different from current.", + ); + } } private doAutotype(vaultData: AutotypeVaultData, keyboardShortcut: string[]) { diff --git a/apps/desktop/src/autofill/models/autotype-config.ts b/apps/desktop/src/autofill/models/autotype-config.ts new file mode 100644 index 00000000000..dda39023c8c --- /dev/null +++ b/apps/desktop/src/autofill/models/autotype-config.ts @@ -0,0 +1,3 @@ +export interface AutotypeConfig { + keyboardShortcut: string[]; +} diff --git a/apps/desktop/src/autofill/models/ipc-channels.ts b/apps/desktop/src/autofill/models/ipc-channels.ts new file mode 100644 index 00000000000..5fea2daf0cf --- /dev/null +++ b/apps/desktop/src/autofill/models/ipc-channels.ts @@ -0,0 +1,9 @@ +export const AUTOTYPE_IPC_CHANNELS = { + INIT: "autofill.initAutotype", + INITIALIZED: "autofill.autotypeIsInitialized", + TOGGLE: "autofill.toggleAutotype", + CONFIGURE: "autofill.configureAutotype", + LISTEN: "autofill.listenAutotypeRequest", + EXECUTION_ERROR: "autofill.autotypeExecutionError", + EXECUTE: "autofill.executeAutotype", +} as const; diff --git a/apps/desktop/src/autofill/preload.ts b/apps/desktop/src/autofill/preload.ts index 6a7a8459ea9..f4f5552944c 100644 --- a/apps/desktop/src/autofill/preload.ts +++ b/apps/desktop/src/autofill/preload.ts @@ -5,8 +5,10 @@ import type { autofill } from "@bitwarden/desktop-napi"; import { Command } from "../platform/main/autofill/command"; import { RunCommandParams, RunCommandResult } from "../platform/main/autofill/native-autofill.main"; +import { AutotypeConfig } from "./models/autotype-config"; import { AutotypeMatchError } from "./models/autotype-errors"; import { AutotypeVaultData } from "./models/autotype-vault-data"; +import { AUTOTYPE_IPC_CHANNELS } from "./models/ipc-channels"; export default { runCommand: <C extends Command>(params: RunCommandParams<C>): Promise<RunCommandResult<C>> => @@ -132,7 +134,6 @@ export default { }, ); }, - listenNativeStatus: ( fn: (clientId: number, sequenceNumber: number, status: { key: string; value: string }) => void, ) => { @@ -151,8 +152,11 @@ export default { }, ); }, - configureAutotype: (enabled: boolean, keyboardShortcut: string[]) => { - ipcRenderer.send("autofill.configureAutotype", { enabled, keyboardShortcut }); + configureAutotype: (config: AutotypeConfig) => { + ipcRenderer.send(AUTOTYPE_IPC_CHANNELS.CONFIGURE, config); + }, + toggleAutotype: (enable: boolean) => { + ipcRenderer.send(AUTOTYPE_IPC_CHANNELS.TOGGLE, enable); }, listenAutotypeRequest: ( fn: ( @@ -161,7 +165,7 @@ export default { ) => void, ) => { ipcRenderer.on( - "autofill.listenAutotypeRequest", + AUTOTYPE_IPC_CHANNELS.LISTEN, ( _event, data: { @@ -176,11 +180,12 @@ export default { windowTitle, errorMessage: error.message, }; - ipcRenderer.send("autofill.completeAutotypeError", matchError); + ipcRenderer.send(AUTOTYPE_IPC_CHANNELS.EXECUTION_ERROR, matchError); return; } + if (vaultData !== null) { - ipcRenderer.send("autofill.completeAutotypeRequest", vaultData); + ipcRenderer.send(AUTOTYPE_IPC_CHANNELS.EXECUTE, vaultData); } }); }, diff --git a/apps/desktop/src/autofill/services/desktop-autotype.service.ts b/apps/desktop/src/autofill/services/desktop-autotype.service.ts index 7ee889e7b81..46fec662d7a 100644 --- a/apps/desktop/src/autofill/services/desktop-autotype.service.ts +++ b/apps/desktop/src/autofill/services/desktop-autotype.service.ts @@ -1,4 +1,17 @@ -import { combineLatest, filter, firstValueFrom, map, Observable, of, switchMap } from "rxjs"; +import { Injectable, OnDestroy } from "@angular/core"; +import { + combineLatest, + concatMap, + distinctUntilChanged, + filter, + firstValueFrom, + map, + Observable, + of, + Subject, + switchMap, + takeUntil, +} from "rxjs"; import { Account, AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; @@ -15,8 +28,10 @@ import { } from "@bitwarden/common/platform/state"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; +import { LogService } from "@bitwarden/logging"; import { UserId } from "@bitwarden/user-core"; +import { AutotypeConfig } from "../models/autotype-config"; import { AutotypeVaultData } from "../models/autotype-vault-data"; import { DesktopAutotypeDefaultSettingPolicy } from "./desktop-autotype-policy.service"; @@ -44,16 +59,26 @@ export const AUTOTYPE_KEYBOARD_SHORTCUT = new KeyDefinition<string[]>( { deserializer: (b) => b }, ); -export class DesktopAutotypeService { +@Injectable({ + providedIn: "root", +}) +export class DesktopAutotypeService implements OnDestroy { private readonly autotypeEnabledState = this.globalStateProvider.get(AUTOTYPE_ENABLED); private readonly autotypeKeyboardShortcut = this.globalStateProvider.get( AUTOTYPE_KEYBOARD_SHORTCUT, ); - autotypeEnabledUserSetting$: Observable<boolean> = of(false); - resolvedAutotypeEnabled$: Observable<boolean> = of(false); + // if the user's account is Premium + private readonly isPremiumAccount$: Observable<boolean>; + + // The enabled/disabled state from the user settings menu + autotypeEnabledUserSetting$: Observable<boolean>; + + // The keyboard shortcut from the user settings menu autotypeKeyboardShortcut$: Observable<string[]> = of(defaultWindowsAutotypeKeyboardShortcut); + private destroy$ = new Subject<void>(); + constructor( private accountService: AccountService, private authService: AuthService, @@ -63,76 +88,110 @@ export class DesktopAutotypeService { private platformUtilsService: PlatformUtilsService, private billingAccountProfileStateService: BillingAccountProfileStateService, private desktopAutotypePolicy: DesktopAutotypeDefaultSettingPolicy, + private logService: LogService, ) { + this.autotypeEnabledUserSetting$ = this.autotypeEnabledState.state$.pipe( + map((enabled) => enabled ?? false), + distinctUntilChanged(), // Only emit when the boolean result changes + takeUntil(this.destroy$), + ); + + this.isPremiumAccount$ = this.accountService.activeAccount$.pipe( + filter((account): account is Account => !!account), + switchMap((account) => + this.billingAccountProfileStateService.hasPremiumFromAnySource$(account.id), + ), + distinctUntilChanged(), // Only emit when the boolean result changes + takeUntil(this.destroy$), + ); + + this.autotypeKeyboardShortcut$ = this.autotypeKeyboardShortcut.state$.pipe( + map((shortcut) => shortcut ?? defaultWindowsAutotypeKeyboardShortcut), + takeUntil(this.destroy$), + ); + } + + async init() { + // Currently Autotype is only supported for Windows + if (this.platformUtilsService.getDevice() !== DeviceType.WindowsDesktop) { + return; + } + ipc.autofill.listenAutotypeRequest(async (windowTitle, callback) => { const possibleCiphers = await this.matchCiphersToWindowTitle(windowTitle); const firstCipher = possibleCiphers?.at(0); const [error, vaultData] = getAutotypeVaultData(firstCipher); callback(error, vaultData); }); - } - async init() { - this.autotypeEnabledUserSetting$ = this.autotypeEnabledState.state$; - this.autotypeKeyboardShortcut$ = this.autotypeKeyboardShortcut.state$.pipe( - map((shortcut) => shortcut ?? defaultWindowsAutotypeKeyboardShortcut), - ); - - // Currently Autotype is only supported for Windows - if (this.platformUtilsService.getDevice() === DeviceType.WindowsDesktop) { - // If `autotypeDefaultPolicy` is `true` for a user's organization, and the - // user has never changed their local autotype setting (`autotypeEnabledState`), - // we set their local setting to `true` (once the local user setting is changed - // by this policy or the user themselves, the default policy should - // never change the user setting again). - combineLatest([ - this.autotypeEnabledState.state$, - this.desktopAutotypePolicy.autotypeDefaultSetting$, - ]) - .pipe( - map(async ([autotypeEnabledState, autotypeDefaultPolicy]) => { + // If `autotypeDefaultPolicy` is `true` for a user's organization, and the + // user has never changed their local autotype setting (`autotypeEnabledState`), + // we set their local setting to `true` (once the local user setting is changed + // by this policy or the user themselves, the default policy should + // never change the user setting again). + combineLatest([ + this.autotypeEnabledState.state$, + this.desktopAutotypePolicy.autotypeDefaultSetting$, + ]) + .pipe( + concatMap(async ([autotypeEnabledState, autotypeDefaultPolicy]) => { + try { if (autotypeDefaultPolicy === true && autotypeEnabledState === null) { await this.setAutotypeEnabledState(true); } - }), - ) - .subscribe(); + } catch { + this.logService.error("Failed to set Autotype enabled state."); + } + }), + takeUntil(this.destroy$), + ) + .subscribe(); - // autotypeEnabledUserSetting$ publicly represents the value the - // user has set for autotyeEnabled in their local settings. - this.autotypeEnabledUserSetting$ = this.autotypeEnabledState.state$; + // listen for changes in keyboard shortcut settings + this.autotypeKeyboardShortcut$ + .pipe( + concatMap(async (keyboardShortcut) => { + const config: AutotypeConfig = { + keyboardShortcut, + }; + ipc.autofill.configureAutotype(config); + }), + takeUntil(this.destroy$), + ) + .subscribe(); - // resolvedAutotypeEnabled$ represents the final determination if the Autotype - // feature should be on or off. - this.resolvedAutotypeEnabled$ = combineLatest([ - this.autotypeEnabledState.state$, - this.configService.getFeatureFlag$(FeatureFlag.WindowsDesktopAutotype), - this.accountService.activeAccount$.pipe( - map((activeAccount) => activeAccount?.id), - switchMap((userId) => this.authService.authStatusFor$(userId)), - ), - this.accountService.activeAccount$.pipe( - filter((account): account is Account => !!account), - switchMap((account) => - this.billingAccountProfileStateService.hasPremiumFromAnySource$(account.id), - ), - ), - ]).pipe( - map( - ([autotypeEnabled, windowsDesktopAutotypeFeatureFlag, authStatus, hasPremium]) => - autotypeEnabled && - windowsDesktopAutotypeFeatureFlag && - authStatus == AuthenticationStatus.Unlocked && - hasPremium, - ), - ); + this.autotypeFeatureEnabled$ + .pipe( + concatMap(async (enabled) => { + ipc.autofill.toggleAutotype(enabled); + }), + takeUntil(this.destroy$), + ) + .subscribe(); + } - combineLatest([this.resolvedAutotypeEnabled$, this.autotypeKeyboardShortcut$]).subscribe( - ([resolvedAutotypeEnabled, autotypeKeyboardShortcut]) => { - ipc.autofill.configureAutotype(resolvedAutotypeEnabled, autotypeKeyboardShortcut); - }, - ); - } + // Returns an observable that represents whether autotype is enabled for the current user. + private get autotypeFeatureEnabled$(): Observable<boolean> { + return combineLatest([ + // if the user has enabled the setting + this.autotypeEnabledUserSetting$, + // if the feature flag is set + this.configService.getFeatureFlag$(FeatureFlag.WindowsDesktopAutotype), + // if there is an active account with an unlocked vault + this.authService.activeAccountStatus$, + // if the active user's account is Premium + this.isPremiumAccount$, + ]).pipe( + map( + ([settingsEnabled, ffEnabled, authStatus, isPremiumAcct]) => + settingsEnabled && + ffEnabled && + authStatus === AuthenticationStatus.Unlocked && + isPremiumAcct, + ), + distinctUntilChanged(), // Only emit when the boolean result changes + takeUntil(this.destroy$), + ); } async setAutotypeEnabledState(enabled: boolean): Promise<void> { @@ -176,6 +235,11 @@ export class DesktopAutotypeService { return possibleCiphers; } + + ngOnDestroy() { + this.destroy$.next(); + this.destroy$.complete(); + } } /** diff --git a/apps/desktop/src/main.ts b/apps/desktop/src/main.ts index fbb83a1bf56..4734288f3c1 100644 --- a/apps/desktop/src/main.ts +++ b/apps/desktop/src/main.ts @@ -311,17 +311,8 @@ export class Main { this.windowMain, ); - app - .whenReady() - .then(() => { - this.mainDesktopAutotypeService.init(); - }) - .catch((reason) => { - this.logService.error("Error initializing Autotype.", reason); - }); - app.on("will-quit", () => { - this.mainDesktopAutotypeService.disableAutotype(); + this.mainDesktopAutotypeService.dispose(); }); } From 1c44e2a4c63f54081385b22eb848f3a430998a8c Mon Sep 17 00:00:00 2001 From: Todd Martin <106564991+trmartin4@users.noreply.github.com> Date: Tue, 16 Dec 2025 16:07:16 -0500 Subject: [PATCH 089/188] chore(deps): Improve Platform dependency inflow (#17981) * Send Platform minor updates to dashboard. * Assign Platform as lock file owners. --- .github/renovate.json5 | 277 ++++++++++++++++++++++++++++------------- 1 file changed, 189 insertions(+), 88 deletions(-) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index ca57ccf4f86..acd181310d6 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -3,6 +3,7 @@ extends: ["github>bitwarden/renovate-config"], // Extends our default configuration for pinned dependencies enabledManagers: ["cargo", "github-actions", "npm"], packageRules: [ + // ==================== Repo-Wide Update Behavior Rules ==================== { // Group all Github Action minor updates together to reduce PR noise. groupName: "Minor github-actions updates", @@ -16,13 +17,6 @@ matchDepNames: ["rust"], commitMessageTopic: "Rust", }, - { - // By default, we send patch updates to the Dependency Dashboard and do not generate a PR. - // We want to generate PRs for a select number of dependencies to ensure we stay up to date on these. - matchPackageNames: ["browserslist", "electron", "rxjs", "typescript", "webpack", "zone.js"], - matchUpdateTypes: ["patch"], - dependencyDashboardApproval: false, - }, { // Disable major and minor updates for TypeScript and Zone.js because they are managed by Angular. matchPackageNames: ["typescript", "zone.js"], @@ -44,6 +38,8 @@ description: "Manually updated using ng update", enabled: false, }, + + // ==================== Team Ownership Rules ==================== { matchPackageNames: ["buffer", "bufferutil", "core-js", "process", "url", "util"], description: "Admin Console owned dependencies", @@ -79,28 +75,6 @@ commitMessagePrefix: "[deps] Architecture:", reviewers: ["team:dept-architecture"], }, - { - matchPackageNames: [ - "@angular-eslint/schematics", - "@eslint/compat", - "@typescript-eslint/rule-tester", - "@typescript-eslint/utils", - "angular-eslint", - "eslint-config-prettier", - "eslint-import-resolver-typescript", - "eslint-plugin-import", - "eslint-plugin-rxjs-angular", - "eslint-plugin-rxjs", - "eslint-plugin-storybook", - "eslint-plugin-tailwindcss", - "eslint", - "husky", - "lint-staged", - "typescript-eslint", - ], - groupName: "Minor and patch linting updates", - matchUpdateTypes: ["minor", "patch"], - }, { matchPackageNames: [ "@emotion/css", @@ -241,60 +215,10 @@ reviewers: ["team:team-platform-dev"], }, { - // We need to group all napi-related packages together to avoid build errors caused by version incompatibilities. - groupName: "napi", - matchPackageNames: ["napi", "napi-build", "napi-derive"], - }, - { - // We need to group all macOS/iOS binding-related packages together to avoid build errors caused by version incompatibilities. - groupName: "macOS/iOS bindings", - matchPackageNames: ["core-foundation", "security-framework", "security-framework-sys"], - }, - { - // We need to group all zbus-related packages together to avoid build errors caused by version incompatibilities. - groupName: "zbus", - matchPackageNames: ["zbus", "zbus_polkit"], - }, - { - // We need to group all windows-related packages together to avoid build errors caused by version incompatibilities. - groupName: "windows", - matchPackageNames: ["windows", "windows-core", "windows-future", "windows-registry"], - }, - { - // We need to group all tokio-related packages together to avoid build errors caused by version incompatibilities. - groupName: "tokio", - matchPackageNames: ["bytes", "tokio", "tokio-util"], - }, - { - // We group all webpack build-related minor and patch updates together to reduce PR noise. - // We include patch updates here because we want PRs for webpack patch updates and it's in this group. - matchPackageNames: [ - "@babel/core", - "@babel/preset-env", - "babel-loader", - "base64-loader", - "browserslist", - "copy-webpack-plugin", - "css-loader", - "html-loader", - "html-webpack-injector", - "html-webpack-plugin", - "mini-css-extract-plugin", - "postcss-loader", - "postcss", - "sass-loader", - "sass", - "style-loader", - "ts-loader", - "tsconfig-paths-webpack-plugin", - "webpack-cli", - "webpack-dev-server", - "webpack-node-externals", - "webpack", - ], - description: "webpack-related build dependencies", - groupName: "Minor and patch webpack updates", - matchUpdateTypes: ["minor", "patch"], + matchUpdateTypes: ["lockFileMaintenance"], + description: "Platform owns lock file maintenance", + commitMessagePrefix: "[deps] Platform:", + reviewers: ["team:team-platform-dev"], }, { matchPackageNames: [ @@ -353,11 +277,6 @@ commitMessagePrefix: "[deps] SM:", reviewers: ["team:team-secrets-manager-dev"], }, - { - // We need to update several Jest-related packages together, for version compatibility. - groupName: "jest", - matchPackageNames: ["@types/jest", "jest", "ts-jest", "jest-preset-angular"], - }, { matchPackageNames: [ "@microsoft/signalr-protocol-msgpack", @@ -428,6 +347,188 @@ commitMessagePrefix: "[deps] KM:", reviewers: ["team:team-key-management-dev"], }, + + // ==================== Grouping Rules ==================== + // These come after any specific team assignment rules to ensure + // that grouping is not overridden by subsequent rule definitions. + { + matchPackageNames: [ + "@angular-eslint/schematics", + "@eslint/compat", + "@typescript-eslint/rule-tester", + "@typescript-eslint/utils", + "angular-eslint", + "eslint-config-prettier", + "eslint-import-resolver-typescript", + "eslint-plugin-import", + "eslint-plugin-rxjs-angular", + "eslint-plugin-rxjs", + "eslint-plugin-storybook", + "eslint-plugin-tailwindcss", + "eslint", + "husky", + "lint-staged", + "typescript-eslint", + ], + groupName: "Minor and patch linting updates", + matchUpdateTypes: ["minor", "patch"], + }, + { + // We need to group all napi-related packages together to avoid build errors caused by version incompatibilities. + groupName: "napi", + matchPackageNames: ["napi", "napi-build", "napi-derive"], + }, + { + // We need to group all macOS/iOS binding-related packages together to avoid build errors caused by version incompatibilities. + groupName: "macOS/iOS bindings", + matchPackageNames: ["core-foundation", "security-framework", "security-framework-sys"], + }, + { + // We need to group all zbus-related packages together to avoid build errors caused by version incompatibilities. + groupName: "zbus", + matchPackageNames: ["zbus", "zbus_polkit"], + }, + { + // We need to group all windows-related packages together to avoid build errors caused by version incompatibilities. + groupName: "windows", + matchPackageNames: ["windows", "windows-core", "windows-future", "windows-registry"], + }, + { + // We need to group all tokio-related packages together to avoid build errors caused by version incompatibilities. + groupName: "tokio", + matchPackageNames: ["bytes", "tokio", "tokio-util"], + }, + { + // We group all webpack build-related minor and patch updates together to reduce PR noise. + // We include patch updates here because we want PRs for webpack patch updates and it's in this group. + matchPackageNames: [ + "@babel/core", + "@babel/preset-env", + "babel-loader", + "base64-loader", + "browserslist", + "copy-webpack-plugin", + "css-loader", + "html-loader", + "html-webpack-injector", + "html-webpack-plugin", + "mini-css-extract-plugin", + "postcss-loader", + "postcss", + "sass-loader", + "sass", + "style-loader", + "ts-loader", + "tsconfig-paths-webpack-plugin", + "webpack-cli", + "webpack-dev-server", + "webpack-node-externals", + "webpack", + ], + description: "webpack-related build dependencies", + groupName: "Minor and patch webpack updates", + matchUpdateTypes: ["minor", "patch"], + }, + { + // We need to update several Jest-related packages together, for version compatibility. + groupName: "jest", + matchPackageNames: ["@types/jest", "jest", "ts-jest", "jest-preset-angular"], + }, + + // ==================== Dashboard Rules ==================== + { + // For the packages below, we have decided we will only be creating PRs + // for major updates, and sending minor (as well as patch) to the dashboard. + // This rule comes AFTER grouping rules so that groups are respected while still + // sending minor/patch updates to the dependency dashboard for approval. + matchPackageNames: [ + "anyhow", + "arboard", + "babel-loader", + "base64-loader", + "base64", + "bindgen", + "byteorder", + "bytes", + "core-foundation", + "copy-webpack-plugin", + "css-loader", + "dirs", + "electron-builder", + "electron-log", + "electron-reload", + "electron-store", + "electron-updater", + "embed_plist", + "futures", + "hex", + "homedir", + "html-loader", + "html-webpack-injector", + "html-webpack-plugin", + "interprocess", + "json5", + "keytar", + "libc", + "lowdb", + "mini-css-extract-plugin", + "napi", + "napi-build", + "napi-derive", + "node-ipc", + "nx", + "oo7", + "oslog", + "pin-project", + "pkg", + "postcss", + "postcss-loader", + "rand", + "sass", + "sass-loader", + "scopeguard", + "security-framework", + "security-framework-sys", + "semver", + "serde", + "serde_json", + "simplelog", + "style-loader", + "sysinfo", + "tokio", + "tokio-util", + "tracing", + "tracing-subscriber", + "ts-node", + "ts-loader", + "tsconfig-paths-webpack-plugin", + "type-fest", + "typenum", + "typescript-strict-plugin", + "uniffi", + "webpack-cli", + "webpack-dev-server", + "webpack-node-externals", + "widestring", + "windows", + "windows-core", + "windows-future", + "windows-registry", + "zbus", + "zbus_polkit", + ], + matchUpdateTypes: ["minor", "patch"], + dependencyDashboardApproval: true, + }, + { + // By default, we send patch updates to the Dependency Dashboard and do not generate a PR. + // We want to generate PRs for a select number of dependencies to ensure we stay up to date on these. + matchPackageNames: ["browserslist", "electron", "rxjs", "typescript", "webpack", "zone.js"], + matchUpdateTypes: ["patch"], + dependencyDashboardApproval: false, + }, + + // ==================== Special Version Constraints ==================== { // Any versions of lowdb above 1.0.0 are not compatible with CommonJS. matchPackageNames: ["lowdb"], From 3049cfad7dfa1f6c7975ff47d7f4d08822eadf18 Mon Sep 17 00:00:00 2001 From: Nick Krantz <125900171+nick-livefront@users.noreply.github.com> Date: Tue, 16 Dec 2025 15:47:58 -0600 Subject: [PATCH 090/188] [PM-29103] Premium Prompt for Archive (#17800) * prompt for premium using badge workflow rather than premium page * update test * address claude feedback --- .../settings/vault-settings-v2.component.html | 9 +- .../vault-settings-v2.component.spec.ts | 199 ++++++++++++++++++ .../settings/vault-settings-v2.component.ts | 18 +- 3 files changed, 223 insertions(+), 3 deletions(-) create mode 100644 apps/browser/src/vault/popup/settings/vault-settings-v2.component.spec.ts 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 c042af8cbac..407015d3a06 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,14 +37,19 @@ @if (showArchiveItem()) { @if (userCanArchive()) { <bit-item> - <a bit-item-content routerLink="/archive"> + <a data-test-id="archive-link" bit-item-content routerLink="/archive"> {{ "archiveNoun" | i18n }} <i slot="end" class="bwi bwi-angle-right" aria-hidden="true"></i> </a> </bit-item> } @else { <bit-item> - <a bit-item-content [routerLink]="userHasArchivedItems() ? '/archive' : '/premium'"> + <a + data-test-id="premium-archive-link" + bit-item-content + href="#" + (click)="conditionallyRouteToArchive($event)" + > <span class="tw-flex tw-items-center tw-gap-2"> {{ "archiveNoun" | i18n }} @if (!userHasArchivedItems()) { diff --git a/apps/browser/src/vault/popup/settings/vault-settings-v2.component.spec.ts b/apps/browser/src/vault/popup/settings/vault-settings-v2.component.spec.ts new file mode 100644 index 00000000000..fc30a3f8899 --- /dev/null +++ b/apps/browser/src/vault/popup/settings/vault-settings-v2.component.spec.ts @@ -0,0 +1,199 @@ +import { ChangeDetectionStrategy, Component, DebugElement, input } from "@angular/core"; +import { ComponentFixture, TestBed } from "@angular/core/testing"; +import { By } from "@angular/platform-browser"; +import { provideRouter, Router } from "@angular/router"; +import { mock } from "jest-mock-extended"; +import { BehaviorSubject } from "rxjs"; + +import { NudgesService } from "@bitwarden/angular/vault"; +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 { CipherArchiveService } from "@bitwarden/common/vault/abstractions/cipher-archive.service"; +import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; +import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; +import { DialogService, ToastService } from "@bitwarden/components"; + +import { PopOutComponent } from "../../../platform/popup/components/pop-out.component"; +import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-header.component"; +import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.component"; + +import { VaultSettingsV2Component } from "./vault-settings-v2.component"; + +@Component({ + selector: "popup-header", + template: `<ng-content></ng-content>`, + changeDetection: ChangeDetectionStrategy.OnPush, +}) +class MockPopupHeaderComponent { + readonly pageTitle = input<string>(); + readonly showBackButton = input<boolean>(); +} + +@Component({ + selector: "popup-page", + template: `<ng-content></ng-content>`, + changeDetection: ChangeDetectionStrategy.OnPush, +}) +class MockPopupPageComponent {} + +@Component({ + selector: "app-pop-out", + template: ``, + changeDetection: ChangeDetectionStrategy.OnPush, +}) +class MockPopOutComponent { + readonly show = input(true); +} + +describe("VaultSettingsV2Component", () => { + let component: VaultSettingsV2Component; + let fixture: ComponentFixture<VaultSettingsV2Component>; + let router: Router; + let mockCipherArchiveService: jest.Mocked<CipherArchiveService>; + + const mockActiveAccount$ = new BehaviorSubject<{ id: string }>({ + id: "user-id", + }); + const mockUserCanArchive$ = new BehaviorSubject<boolean>(false); + const mockHasArchiveFlagEnabled$ = new BehaviorSubject<boolean>(true); + const mockArchivedCiphers$ = new BehaviorSubject<CipherView[]>([]); + const mockShowNudgeBadge$ = new BehaviorSubject<boolean>(false); + + const queryByTestId = (testId: string): DebugElement | null => { + return fixture.debugElement.query(By.css(`[data-test-id="${testId}"]`)); + }; + + const setArchiveState = ( + canArchive: boolean, + archivedItems: CipherView[] = [], + flagEnabled = true, + ) => { + mockUserCanArchive$.next(canArchive); + mockArchivedCiphers$.next(archivedItems); + mockHasArchiveFlagEnabled$.next(flagEnabled); + fixture.detectChanges(); + }; + + beforeEach(async () => { + mockCipherArchiveService = mock<CipherArchiveService>({ + userCanArchive$: jest.fn().mockReturnValue(mockUserCanArchive$), + hasArchiveFlagEnabled$: jest.fn().mockReturnValue(mockHasArchiveFlagEnabled$), + archivedCiphers$: jest.fn().mockReturnValue(mockArchivedCiphers$), + }); + + await TestBed.configureTestingModule({ + imports: [VaultSettingsV2Component], + providers: [ + provideRouter([ + { path: "archive", component: VaultSettingsV2Component }, + { path: "premium", component: VaultSettingsV2Component }, + ]), + { provide: SyncService, useValue: mock<SyncService>() }, + { provide: ToastService, useValue: mock<ToastService>() }, + { provide: ConfigService, useValue: mock<ConfigService>() }, + { provide: DialogService, useValue: mock<DialogService>() }, + { provide: I18nService, useValue: { t: (key: string) => key } }, + { provide: CipherArchiveService, useValue: mockCipherArchiveService }, + { + provide: NudgesService, + useValue: { showNudgeBadge$: jest.fn().mockReturnValue(mockShowNudgeBadge$) }, + }, + + { + provide: BillingAccountProfileStateService, + useValue: mock<BillingAccountProfileStateService>(), + }, + { + provide: AccountService, + useValue: { activeAccount$: mockActiveAccount$ }, + }, + ], + }) + .overrideComponent(VaultSettingsV2Component, { + remove: { + imports: [PopupHeaderComponent, PopupPageComponent, PopOutComponent], + }, + add: { + imports: [MockPopupHeaderComponent, MockPopupPageComponent, MockPopOutComponent], + }, + }) + .compileComponents(); + + fixture = TestBed.createComponent(VaultSettingsV2Component); + component = fixture.componentInstance; + router = TestBed.inject(Router); + jest.spyOn(router, "navigate"); + }); + + describe("archive link", () => { + it("shows direct archive link when user can archive", () => { + setArchiveState(true); + + const archiveLink = queryByTestId("archive-link"); + + expect(archiveLink.nativeElement.getAttribute("routerLink")).toBe("/archive"); + }); + + it("routes to archive when user has archived items but cannot archive", async () => { + setArchiveState(false, [{ id: "cipher1" } as CipherView]); + + const premiumArchiveLink = queryByTestId("premium-archive-link"); + + premiumArchiveLink.nativeElement.click(); + await fixture.whenStable(); + + expect(router.navigate).toHaveBeenCalledWith(["/archive"]); + }); + + it("prompts for premium when user cannot archive and has no archived items", async () => { + setArchiveState(false, []); + const badge = component["premiumBadgeComponent"](); + jest.spyOn(badge, "promptForPremium"); + + const premiumArchiveLink = queryByTestId("premium-archive-link"); + + premiumArchiveLink.nativeElement.click(); + await fixture.whenStable(); + + expect(badge.promptForPremium).toHaveBeenCalled(); + }); + }); + + describe("archive visibility", () => { + it("displays archive link when user can archive", () => { + setArchiveState(true); + + const archiveLink = queryByTestId("archive-link"); + + expect(archiveLink).toBeTruthy(); + expect(component["userCanArchive"]()).toBe(true); + }); + + it("hides archive link when feature flag is disabled", () => { + setArchiveState(false, [], false); + + const archiveLink = queryByTestId("archive-link"); + const premiumArchiveLink = queryByTestId("premium-archive-link"); + + expect(archiveLink).toBeNull(); + expect(premiumArchiveLink).toBeNull(); + expect(component["showArchiveItem"]()).toBe(false); + }); + + it("shows premium badge when user has no archived items and cannot archive", () => { + setArchiveState(false, []); + + expect(component["premiumBadgeComponent"]()).toBeTruthy(); + expect(component["userHasArchivedItems"]()).toBe(false); + }); + + it("hides premium badge when user has archived items", () => { + setArchiveState(false, [{ id: "cipher1" } as CipherView]); + + expect(component["premiumBadgeComponent"]()).toBeUndefined(); + expect(component["userHasArchivedItems"]()).toBe(true); + }); + }); +}); 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 e085cb21c2d..c1d90d678cb 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 @@ -1,5 +1,5 @@ import { CommonModule } from "@angular/common"; -import { Component, OnDestroy, OnInit } from "@angular/core"; +import { Component, OnDestroy, OnInit, viewChild } from "@angular/core"; import { toSignal } from "@angular/core/rxjs-interop"; import { Router, RouterModule } from "@angular/router"; import { firstValueFrom, map, switchMap } from "rxjs"; @@ -42,6 +42,8 @@ import { BrowserPremiumUpgradePromptService } from "../services/browser-premium- ], }) export class VaultSettingsV2Component implements OnInit, OnDestroy { + private readonly premiumBadgeComponent = viewChild(PremiumBadgeComponent); + lastSync = "--"; private userId$ = this.accountService.activeAccount$.pipe(getUserId); @@ -117,4 +119,18 @@ export class VaultSettingsV2Component implements OnInit, OnDestroy { this.lastSync = this.i18nService.t("never"); } } + + /** + * When a user can archive or has previously archived items, route them to + * the archive page. Otherwise, prompt them to upgrade to premium. + */ + async conditionallyRouteToArchive(event: Event) { + event.preventDefault(); + const premiumBadge = this.premiumBadgeComponent(); + if (this.userCanArchive() || this.userHasArchivedItems()) { + await this.router.navigate(["/archive"]); + } else if (premiumBadge) { + await premiumBadge.promptForPremium(event); + } + } } From 06d15e96811f709f285299978424ae537abe4f6a Mon Sep 17 00:00:00 2001 From: Shane Melton <smelton@bitwarden.com> Date: Tue, 16 Dec 2025 15:03:48 -0800 Subject: [PATCH 091/188] [PM-27675] Browser item transfer integration (#17918) * [PM-27675] Integrate dialogs into VaultItemTransferService * [PM-27675] Update tests for new dialogs * [PM-27675] Center dialogs and prevent closing with escape or pointer events * [PM-27675] Add transferInProgress$ observable to VaultItemsTransferService * [PM-27675] Hook vault item transfer service into browser vault component * [PM-27675] Move defaultUserCollection$ to collection service * [PM-27675] Cleanup dialog styles * [PM-27675] Introduce readySubject to popup vault component to keep prevent flashing content while item transfer is in progress * [PM-27675] Fix vault-v2 tests --- .../vault-v2/vault-v2.component.html | 2 +- .../vault-v2/vault-v2.component.spec.ts | 18 +- .../components/vault-v2/vault-v2.component.ts | 39 +- .../abstractions/collection.service.ts | 8 + .../default-collection.service.spec.ts | 80 +++- .../services/default-collection.service.ts | 11 + .../vault-items-transfer.service.ts | 5 + .../leave-confirmation-dialog.component.html | 4 +- .../leave-confirmation-dialog.component.ts | 3 + .../transfer-items-dialog.component.html | 2 +- .../transfer-items-dialog.component.ts | 3 + ...fault-vault-items-transfer.service.spec.ts | 342 +++++++++++++----- .../default-vault-items-transfer.service.ts | 84 +++-- 13 files changed, 464 insertions(+), 137 deletions(-) diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.html b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.html index 347c5fe6286..6382b5fee0e 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.html +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.html @@ -108,7 +108,7 @@ </div> <ng-template #vaultContentTemplate> - <ng-container *ngIf="vaultState === null"> + <ng-container *ngIf="vaultState === null && !(loading$ | async)"> <app-autofill-vault-list-items></app-autofill-vault-list-items> <app-vault-list-items-container [title]="'favorites' | i18n" diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.spec.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.spec.ts index 4b992d9f1ee..883d17b61c3 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.spec.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.spec.ts @@ -28,7 +28,11 @@ import { RestrictedItemTypesService } from "@bitwarden/common/vault/services/res import { TaskService } from "@bitwarden/common/vault/tasks"; import { DialogService } from "@bitwarden/components"; import { StateProvider } from "@bitwarden/state"; -import { DecryptionFailureDialogComponent } from "@bitwarden/vault"; +import { + DecryptionFailureDialogComponent, + VaultItemsTransferService, + DefaultVaultItemsTransferService, +} from "@bitwarden/vault"; import { BrowserApi } from "../../../../platform/browser/browser-api"; import BrowserPopupUtils from "../../../../platform/browser/browser-popup-utils"; @@ -193,6 +197,11 @@ describe("VaultV2Component", () => { stop: jest.fn(), } as Partial<VaultPopupScrollPositionService>; + const vaultItemsTransferSvc = { + transferInProgress$: new BehaviorSubject<boolean>(false), + enforceOrganizationDataOwnership: jest.fn().mockResolvedValue(undefined), + } as Partial<VaultItemsTransferService>; + function getObs<T = unknown>(cmp: any, key: string): Observable<T> { return cmp[key] as Observable<T>; } @@ -283,6 +292,9 @@ describe("VaultV2Component", () => { AutofillVaultListItemsComponent, VaultListItemsContainerComponent, ], + providers: [ + { provide: VaultItemsTransferService, useValue: DefaultVaultItemsTransferService }, + ], }, add: { imports: [ @@ -296,6 +308,7 @@ describe("VaultV2Component", () => { AutofillVaultListItemsStubComponent, VaultListItemsContainerStubComponent, ], + providers: [{ provide: VaultItemsTransferService, useValue: vaultItemsTransferSvc }], }, }); @@ -344,6 +357,7 @@ describe("VaultV2Component", () => { it("loading$ is true when items loading or filters missing; false when both ready", () => { const itemsLoading$ = itemsSvc.loading$ as unknown as BehaviorSubject<boolean>; const allFilters$ = filtersSvc.allFilters$ as unknown as Subject<any>; + const readySubject$ = component["readySubject"] as unknown as BehaviorSubject<boolean>; const values: boolean[] = []; getObs<boolean>(component, "loading$").subscribe((v) => values.push(!!v)); @@ -354,6 +368,8 @@ describe("VaultV2Component", () => { itemsLoading$.next(false); + readySubject$.next(true); + expect(values[values.length - 1]).toBe(false); }); diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts index 63d971081df..30d1d21abfb 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts @@ -16,6 +16,7 @@ import { switchMap, take, tap, + BehaviorSubject, } from "rxjs"; import { PremiumUpgradeDialogComponent } from "@bitwarden/angular/billing/components"; @@ -42,7 +43,11 @@ import { NoItemsModule, TypographyModule, } from "@bitwarden/components"; -import { DecryptionFailureDialogComponent } from "@bitwarden/vault"; +import { + DecryptionFailureDialogComponent, + VaultItemsTransferService, + DefaultVaultItemsTransferService, +} from "@bitwarden/vault"; import { CurrentAccountComponent } from "../../../../auth/popup/account-switching/current-account.component"; import { BrowserApi } from "../../../../platform/browser/browser-api"; @@ -105,6 +110,7 @@ type VaultState = UnionOfValues<typeof VaultState>; VaultFadeInOutSkeletonComponent, VaultFadeInOutComponent, ], + providers: [{ provide: VaultItemsTransferService, useClass: DefaultVaultItemsTransferService }], }) export class VaultV2Component implements OnInit, AfterViewInit, OnDestroy { // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals @@ -125,7 +131,22 @@ export class VaultV2Component implements OnInit, AfterViewInit, OnDestroy { activeUserId: UserId | null = null; - private loading$ = this.vaultPopupLoadingService.loading$.pipe( + /** + * Subject that indicates whether the vault is ready to render + * and that all initialization tasks have been completed (ngOnInit). + * @private + */ + private readySubject = new BehaviorSubject(false); + + /** + * Indicates whether the vault is loading and not yet ready to be displayed. + * @protected + */ + protected loading$ = combineLatest([ + this.vaultPopupLoadingService.loading$, + this.readySubject.asObservable(), + ]).pipe( + map(([loading, ready]) => loading || !ready), distinctUntilChanged(), tap((loading) => { const key = loading ? "loadingVault" : "vaultLoaded"; @@ -200,14 +221,15 @@ export class VaultV2Component implements OnInit, AfterViewInit, OnDestroy { protected showSkeletonsLoaders$ = combineLatest([ this.loading$, this.searchService.isCipherSearching$, + this.vaultItemsTransferService.transferInProgress$, this.skeletonFeatureFlag$, ]).pipe( - map( - ([loading, cipherSearching, skeletonsEnabled]) => - (loading || cipherSearching) && skeletonsEnabled, - ), + map(([loading, cipherSearching, transferInProgress, skeletonsEnabled]) => { + return (loading || cipherSearching || transferInProgress) && skeletonsEnabled; + }), distinctUntilChanged(), skeletonLoadingDelay(), + shareReplay({ bufferSize: 1, refCount: true }), ); protected newItemItemValues$: Observable<NewItemInitialValues> = @@ -251,6 +273,7 @@ export class VaultV2Component implements OnInit, AfterViewInit, OnDestroy { private i18nService: I18nService, private configService: ConfigService, private searchService: SearchService, + private vaultItemsTransferService: VaultItemsTransferService, ) { combineLatest([ this.vaultPopupItemsService.emptyVault$, @@ -305,6 +328,10 @@ export class VaultV2Component implements OnInit, AfterViewInit, OnDestroy { cipherIds: ciphers.map((c) => c.id as CipherId), }); }); + + await this.vaultItemsTransferService.enforceOrganizationDataOwnership(this.activeUserId); + + this.readySubject.next(true); } ngOnDestroy() { diff --git a/libs/admin-console/src/common/collections/abstractions/collection.service.ts b/libs/admin-console/src/common/collections/abstractions/collection.service.ts index f879831324d..f0f02ee377e 100644 --- a/libs/admin-console/src/common/collections/abstractions/collection.service.ts +++ b/libs/admin-console/src/common/collections/abstractions/collection.service.ts @@ -9,6 +9,14 @@ import { CollectionData, Collection, CollectionView } from "../models"; export abstract class CollectionService { abstract encryptedCollections$(userId: UserId): Observable<Collection[] | null>; abstract decryptedCollections$(userId: UserId): Observable<CollectionView[]>; + + /** + * Gets the default collection for a user in a given organization, if it exists. + */ + abstract defaultUserCollection$( + userId: UserId, + orgId: OrganizationId, + ): Observable<CollectionView | undefined>; abstract upsert(collection: CollectionData, userId: UserId): Promise<any>; abstract replace(collections: { [id: string]: CollectionData }, userId: UserId): Promise<any>; /** diff --git a/libs/admin-console/src/common/collections/services/default-collection.service.spec.ts b/libs/admin-console/src/common/collections/services/default-collection.service.spec.ts index ced3b720e3c..2eaaa48594e 100644 --- a/libs/admin-console/src/common/collections/services/default-collection.service.spec.ts +++ b/libs/admin-console/src/common/collections/services/default-collection.service.spec.ts @@ -15,9 +15,10 @@ import { } from "@bitwarden/common/spec"; import { CollectionId, OrganizationId, UserId } from "@bitwarden/common/types/guid"; import { OrgKey } from "@bitwarden/common/types/key"; +import { newGuid } from "@bitwarden/guid"; import { KeyService } from "@bitwarden/key-management"; -import { CollectionData, CollectionView } from "../models"; +import { CollectionData, CollectionTypes, CollectionView } from "../models"; import { DECRYPTED_COLLECTION_DATA_KEY, ENCRYPTED_COLLECTION_DATA_KEY } from "./collection.state"; import { DefaultCollectionService } from "./default-collection.service"; @@ -389,6 +390,83 @@ describe("DefaultCollectionService", () => { }); }); + describe("defaultUserCollection$", () => { + it("returns the default collection when one exists matching the org", async () => { + const orgId = newGuid() as OrganizationId; + const defaultCollection = collectionViewDataFactory(orgId); + defaultCollection.type = CollectionTypes.DefaultUserCollection; + + const regularCollection = collectionViewDataFactory(orgId); + regularCollection.type = CollectionTypes.SharedCollection; + + await setDecryptedState([defaultCollection, regularCollection]); + + const result = await firstValueFrom(collectionService.defaultUserCollection$(userId, orgId)); + + expect(result).toBeDefined(); + expect(result?.id).toBe(defaultCollection.id); + expect(result?.isDefaultCollection).toBe(true); + }); + + it("returns undefined when no default collection exists", async () => { + const orgId = newGuid() as OrganizationId; + const collection1 = collectionViewDataFactory(orgId); + collection1.type = CollectionTypes.SharedCollection; + + const collection2 = collectionViewDataFactory(orgId); + collection2.type = CollectionTypes.SharedCollection; + + await setDecryptedState([collection1, collection2]); + + const result = await firstValueFrom(collectionService.defaultUserCollection$(userId, orgId)); + + expect(result).toBeUndefined(); + }); + + it("returns undefined when default collection exists but for different org", async () => { + const orgA = newGuid() as OrganizationId; + const orgB = newGuid() as OrganizationId; + + const defaultCollectionForOrgA = collectionViewDataFactory(orgA); + defaultCollectionForOrgA.type = CollectionTypes.DefaultUserCollection; + + await setDecryptedState([defaultCollectionForOrgA]); + + const result = await firstValueFrom(collectionService.defaultUserCollection$(userId, orgB)); + + expect(result).toBeUndefined(); + }); + + it("returns undefined when collections array is empty", async () => { + const orgId = newGuid() as OrganizationId; + + await setDecryptedState([]); + + const result = await firstValueFrom(collectionService.defaultUserCollection$(userId, orgId)); + + expect(result).toBeUndefined(); + }); + + it("returns correct collection when multiple orgs have default collections", async () => { + const orgA = newGuid() as OrganizationId; + const orgB = newGuid() as OrganizationId; + + const defaultCollectionForOrgA = collectionViewDataFactory(orgA); + defaultCollectionForOrgA.type = CollectionTypes.DefaultUserCollection; + + const defaultCollectionForOrgB = collectionViewDataFactory(orgB); + defaultCollectionForOrgB.type = CollectionTypes.DefaultUserCollection; + + await setDecryptedState([defaultCollectionForOrgA, defaultCollectionForOrgB]); + + const result = await firstValueFrom(collectionService.defaultUserCollection$(userId, orgB)); + + expect(result).toBeDefined(); + expect(result?.id).toBe(defaultCollectionForOrgB.id); + expect(result?.organizationId).toBe(orgB); + }); + }); + const setEncryptedState = (collectionData: CollectionData[] | null) => stateProvider.setUserState( ENCRYPTED_COLLECTION_DATA_KEY, diff --git a/libs/admin-console/src/common/collections/services/default-collection.service.ts b/libs/admin-console/src/common/collections/services/default-collection.service.ts index 0511b692b38..ccc2e6f0de5 100644 --- a/libs/admin-console/src/common/collections/services/default-collection.service.ts +++ b/libs/admin-console/src/common/collections/services/default-collection.service.ts @@ -87,6 +87,17 @@ export class DefaultCollectionService implements CollectionService { return result$; } + defaultUserCollection$( + userId: UserId, + orgId: OrganizationId, + ): Observable<CollectionView | undefined> { + return this.decryptedCollections$(userId).pipe( + map((collections) => { + return collections.find((c) => c.isDefaultCollection && c.organizationId === orgId); + }), + ); + } + private initializeDecryptedState(userId: UserId): Observable<CollectionView[]> { return combineLatest([ this.encryptedCollections$(userId), diff --git a/libs/vault/src/abstractions/vault-items-transfer.service.ts b/libs/vault/src/abstractions/vault-items-transfer.service.ts index ced9f71eb83..0fc19cc0e79 100644 --- a/libs/vault/src/abstractions/vault-items-transfer.service.ts +++ b/libs/vault/src/abstractions/vault-items-transfer.service.ts @@ -31,6 +31,11 @@ export type UserMigrationInfo = }; export abstract class VaultItemsTransferService { + /** + * Indicates whether a vault items transfer is currently in progress. + */ + abstract transferInProgress$: Observable<boolean>; + /** * Gets information about whether the given user requires migration of their vault items * from My Vault to a My Items collection, and whether they are capable of performing that migration. diff --git a/libs/vault/src/components/vault-items-transfer/leave-confirmation-dialog.component.html b/libs/vault/src/components/vault-items-transfer/leave-confirmation-dialog.component.html index f0d644fecff..6d1045e1a86 100644 --- a/libs/vault/src/components/vault-items-transfer/leave-confirmation-dialog.component.html +++ b/libs/vault/src/components/vault-items-transfer/leave-confirmation-dialog.component.html @@ -11,7 +11,7 @@ <p bitTypography="body1"> {{ "leaveConfirmationDialogContentOne" | i18n }} </p> - <p bitTypography="body1"> + <p bitTypography="body1" class="tw-mb-0"> {{ "leaveConfirmationDialogContentTwo" | i18n }} </p> </ng-container> @@ -25,7 +25,7 @@ {{ "goBack" | i18n }} </button> - <a bitLink href="#" (click)="openLearnMore($event)" class="tw-w-full tw-text-center"> + <a bitLink href="#" (click)="openLearnMore($event)" class="tw-w-full tw-text-center tw-text-sm"> {{ "howToManageMyVault" | i18n }} <i class="bwi bwi-external-link tw-ml-1" aria-hidden="true"></i> </a> diff --git a/libs/vault/src/components/vault-items-transfer/leave-confirmation-dialog.component.ts b/libs/vault/src/components/vault-items-transfer/leave-confirmation-dialog.component.ts index bd32a1ea6dd..af106376a79 100644 --- a/libs/vault/src/components/vault-items-transfer/leave-confirmation-dialog.component.ts +++ b/libs/vault/src/components/vault-items-transfer/leave-confirmation-dialog.component.ts @@ -12,6 +12,7 @@ import { DialogModule, LinkModule, TypographyModule, + CenterPositionStrategy, } from "@bitwarden/components"; export interface LeaveConfirmationDialogParams { @@ -58,6 +59,8 @@ export class LeaveConfirmationDialogComponent { static open(dialogService: DialogService, config: DialogConfig<LeaveConfirmationDialogParams>) { return dialogService.open<LeaveConfirmationDialogResultType>(LeaveConfirmationDialogComponent, { + positionStrategy: new CenterPositionStrategy(), + disableClose: true, ...config, }); } diff --git a/libs/vault/src/components/vault-items-transfer/transfer-items-dialog.component.html b/libs/vault/src/components/vault-items-transfer/transfer-items-dialog.component.html index 0b77d4ba7d8..3cf626baaf7 100644 --- a/libs/vault/src/components/vault-items-transfer/transfer-items-dialog.component.html +++ b/libs/vault/src/components/vault-items-transfer/transfer-items-dialog.component.html @@ -14,7 +14,7 @@ {{ "declineAndLeave" | i18n }} </button> - <a bitLink href="#" (click)="openLearnMore($event)" class="tw-w-full tw-text-center"> + <a bitLink href="#" (click)="openLearnMore($event)" class="tw-w-full tw-text-center tw-text-sm"> {{ "whyAmISeeingThis" | i18n }} <i class="bwi bwi-external-link tw-ml-1" aria-hidden="true"></i> </a> diff --git a/libs/vault/src/components/vault-items-transfer/transfer-items-dialog.component.ts b/libs/vault/src/components/vault-items-transfer/transfer-items-dialog.component.ts index f28ad2ab3ec..619181f37fc 100644 --- a/libs/vault/src/components/vault-items-transfer/transfer-items-dialog.component.ts +++ b/libs/vault/src/components/vault-items-transfer/transfer-items-dialog.component.ts @@ -12,6 +12,7 @@ import { DialogModule, LinkModule, TypographyModule, + CenterPositionStrategy, } from "@bitwarden/components"; export interface TransferItemsDialogParams { @@ -58,6 +59,8 @@ export class TransferItemsDialogComponent { static open(dialogService: DialogService, config: DialogConfig<TransferItemsDialogParams>) { return dialogService.open<TransferItemsDialogResultType>(TransferItemsDialogComponent, { + positionStrategy: new CenterPositionStrategy(), + disableClose: true, ...config, }); } diff --git a/libs/vault/src/services/default-vault-items-transfer.service.spec.ts b/libs/vault/src/services/default-vault-items-transfer.service.spec.ts index d85fe2ffd43..d78cf95ebf2 100644 --- a/libs/vault/src/services/default-vault-items-transfer.service.spec.ts +++ b/libs/vault/src/services/default-vault-items-transfer.service.spec.ts @@ -1,5 +1,5 @@ import { mock, MockProxy } from "jest-mock-extended"; -import { firstValueFrom, of } from "rxjs"; +import { firstValueFrom, of, Subject } from "rxjs"; // eslint-disable-next-line no-restricted-imports import { CollectionService, CollectionView } from "@bitwarden/admin-console/common"; @@ -14,14 +14,20 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic import { OrganizationId, CollectionId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; -import { DialogService, ToastService } from "@bitwarden/components"; +import { DialogRef, DialogService, ToastService } from "@bitwarden/components"; import { LogService } from "@bitwarden/logging"; import { UserId } from "@bitwarden/user-core"; +import { + LeaveConfirmationDialogResult, + TransferItemsDialogResult, +} from "../components/vault-items-transfer"; + import { DefaultVaultItemsTransferService } from "./default-vault-items-transfer.service"; describe("DefaultVaultItemsTransferService", () => { let service: DefaultVaultItemsTransferService; + let transferInProgressValues: boolean[]; let mockCipherService: MockProxy<CipherService>; let mockPolicyService: MockProxy<PolicyService>; @@ -37,6 +43,25 @@ describe("DefaultVaultItemsTransferService", () => { const organizationId = "org-id" as OrganizationId; const collectionId = "collection-id" as CollectionId; + /** + * Creates a mock DialogRef that emits the provided result when closed + */ + function createMockDialogRef<T>(result: T): DialogRef<T> { + const closed$ = new Subject<T>(); + const dialogRef = { + closed: closed$.asObservable(), + close: jest.fn(), + } as unknown as DialogRef<T>; + + // Emit the result asynchronously to simulate dialog closing + setTimeout(() => { + closed$.next(result); + closed$.complete(); + }, 0); + + return dialogRef; + } + beforeEach(() => { mockCipherService = mock<CipherService>(); mockPolicyService = mock<PolicyService>(); @@ -49,6 +74,7 @@ describe("DefaultVaultItemsTransferService", () => { mockConfigService = mock<ConfigService>(); mockI18nService.t.mockImplementation((key) => key); + transferInProgressValues = []; service = new DefaultVaultItemsTransferService( mockCipherService, @@ -69,12 +95,12 @@ describe("DefaultVaultItemsTransferService", () => { policies?: Policy[]; organizations?: Organization[]; ciphers?: CipherView[]; - collections?: CollectionView[]; + defaultCollection?: CollectionView; }): void { mockPolicyService.policiesByType$.mockReturnValue(of(options.policies ?? [])); mockOrganizationService.organizations$.mockReturnValue(of(options.organizations ?? [])); mockCipherService.cipherViews$.mockReturnValue(of(options.ciphers ?? [])); - mockCollectionService.decryptedCollections$.mockReturnValue(of(options.collections ?? [])); + mockCollectionService.defaultUserCollection$.mockReturnValue(of(options.defaultCollection)); } it("calls policiesByType$ with correct PolicyType", async () => { @@ -151,39 +177,12 @@ describe("DefaultVaultItemsTransferService", () => { }); it("includes defaultCollectionId when a default collection exists", async () => { - mockCollectionService.decryptedCollections$.mockReturnValue( - of([ - { - id: collectionId, - organizationId: organizationId, - isDefaultCollection: true, - } as CollectionView, - ]), - ); - - const result = await firstValueFrom(service.userMigrationInfo$(userId)); - - expect(result).toEqual({ - requiresMigration: true, - enforcingOrganization: organization, - defaultCollectionId: collectionId, - }); - }); - - it("returns default collection only for the enforcing organization", async () => { - mockCollectionService.decryptedCollections$.mockReturnValue( - of([ - { - id: "wrong-collection-id" as CollectionId, - organizationId: "wrong-org-id" as OrganizationId, - isDefaultCollection: true, - } as CollectionView, - { - id: collectionId, - organizationId: organizationId, - isDefaultCollection: true, - } as CollectionView, - ]), + mockCollectionService.defaultUserCollection$.mockReturnValue( + of({ + id: collectionId, + organizationId: organizationId, + isDefaultCollection: true, + } as CollectionView), ); const result = await firstValueFrom(service.userMigrationInfo$(userId)); @@ -542,13 +541,13 @@ describe("DefaultVaultItemsTransferService", () => { policies?: Policy[]; organizations?: Organization[]; ciphers?: CipherView[]; - collections?: CollectionView[]; + defaultCollection?: CollectionView; }): void { mockConfigService.getFeatureFlag.mockResolvedValue(options.featureEnabled ?? true); mockPolicyService.policiesByType$.mockReturnValue(of(options.policies ?? [])); mockOrganizationService.organizations$.mockReturnValue(of(options.organizations ?? [])); mockCipherService.cipherViews$.mockReturnValue(of(options.ciphers ?? [])); - mockCollectionService.decryptedCollections$.mockReturnValue(of(options.collections ?? [])); + mockCollectionService.defaultUserCollection$.mockReturnValue(of(options.defaultCollection)); } it("does nothing when feature flag is disabled", async () => { @@ -557,13 +556,11 @@ describe("DefaultVaultItemsTransferService", () => { policies: [policy], organizations: [organization], ciphers: [{ id: "cipher-1" } as CipherView], - collections: [ - { - id: collectionId, - organizationId: organizationId, - isDefaultCollection: true, - } as CollectionView, - ], + defaultCollection: { + id: collectionId, + organizationId: organizationId, + isDefaultCollection: true, + } as CollectionView, }); await service.enforceOrganizationDataOwnership(userId); @@ -571,7 +568,7 @@ describe("DefaultVaultItemsTransferService", () => { expect(mockConfigService.getFeatureFlag).toHaveBeenCalledWith( FeatureFlag.MigrateMyVaultToMyItems, ); - expect(mockDialogService.openSimpleDialog).not.toHaveBeenCalled(); + expect(mockDialogService.open).not.toHaveBeenCalled(); expect(mockCipherService.shareManyWithServer).not.toHaveBeenCalled(); }); @@ -580,7 +577,7 @@ describe("DefaultVaultItemsTransferService", () => { await service.enforceOrganizationDataOwnership(userId); - expect(mockDialogService.openSimpleDialog).not.toHaveBeenCalled(); + expect(mockDialogService.open).not.toHaveBeenCalled(); expect(mockCipherService.shareManyWithServer).not.toHaveBeenCalled(); }); @@ -593,7 +590,7 @@ describe("DefaultVaultItemsTransferService", () => { await service.enforceOrganizationDataOwnership(userId); - expect(mockDialogService.openSimpleDialog).not.toHaveBeenCalled(); + expect(mockDialogService.open).not.toHaveBeenCalled(); expect(mockCipherService.shareManyWithServer).not.toHaveBeenCalled(); }); @@ -602,7 +599,6 @@ describe("DefaultVaultItemsTransferService", () => { policies: [policy], organizations: [organization], ciphers: [{ id: "cipher-1" } as CipherView], - collections: [], }); await service.enforceOrganizationDataOwnership(userId); @@ -610,69 +606,48 @@ describe("DefaultVaultItemsTransferService", () => { expect(mockLogService.warning).toHaveBeenCalledWith( "Default collection is missing for user during organization data ownership enforcement", ); - expect(mockDialogService.openSimpleDialog).not.toHaveBeenCalled(); + expect(mockDialogService.open).not.toHaveBeenCalled(); expect(mockCipherService.shareManyWithServer).not.toHaveBeenCalled(); }); - it("shows confirmation dialog when migration is required", async () => { + it("does not transfer items when user declines and confirms leaving", async () => { setupMocksForEnforcementScenario({ policies: [policy], organizations: [organization], ciphers: [{ id: "cipher-1" } as CipherView], - collections: [ - { - id: collectionId, - organizationId: organizationId, - isDefaultCollection: true, - } as CollectionView, - ], + defaultCollection: { + id: collectionId, + organizationId: organizationId, + isDefaultCollection: true, + } as CollectionView, }); - mockDialogService.openSimpleDialog.mockResolvedValue(false); - await service.enforceOrganizationDataOwnership(userId); - - expect(mockDialogService.openSimpleDialog).toHaveBeenCalledWith({ - title: "Requires migration", - content: "Your vault requires migration of personal items to your organization.", - type: "warning", - }); - }); - - it("does not transfer items when user declines confirmation", async () => { - setupMocksForEnforcementScenario({ - policies: [policy], - organizations: [organization], - ciphers: [{ id: "cipher-1" } as CipherView], - collections: [ - { - id: collectionId, - organizationId: organizationId, - isDefaultCollection: true, - } as CollectionView, - ], - }); - mockDialogService.openSimpleDialog.mockResolvedValue(false); + // User declines transfer, then confirms leaving + mockDialogService.open + .mockReturnValueOnce(createMockDialogRef(TransferItemsDialogResult.Declined)) + .mockReturnValueOnce(createMockDialogRef(LeaveConfirmationDialogResult.Confirmed)); await service.enforceOrganizationDataOwnership(userId); expect(mockCipherService.shareManyWithServer).not.toHaveBeenCalled(); }); - it("transfers items and shows success toast when user confirms", async () => { + it("transfers items and shows success toast when user accepts transfer", async () => { const personalCiphers = [{ id: "cipher-1" } as CipherView]; setupMocksForEnforcementScenario({ policies: [policy], organizations: [organization], ciphers: personalCiphers, - collections: [ - { - id: collectionId, - organizationId: organizationId, - isDefaultCollection: true, - } as CollectionView, - ], + defaultCollection: { + id: collectionId, + organizationId: organizationId, + isDefaultCollection: true, + } as CollectionView, }); - mockDialogService.openSimpleDialog.mockResolvedValue(true); + + mockDialogService.open.mockReturnValueOnce( + createMockDialogRef(TransferItemsDialogResult.Accepted), + ); mockCipherService.shareManyWithServer.mockResolvedValue(undefined); await service.enforceOrganizationDataOwnership(userId); @@ -695,15 +670,16 @@ describe("DefaultVaultItemsTransferService", () => { policies: [policy], organizations: [organization], ciphers: personalCiphers, - collections: [ - { - id: collectionId, - organizationId: organizationId, - isDefaultCollection: true, - } as CollectionView, - ], + defaultCollection: { + id: collectionId, + organizationId: organizationId, + isDefaultCollection: true, + } as CollectionView, }); - mockDialogService.openSimpleDialog.mockResolvedValue(true); + + mockDialogService.open.mockReturnValueOnce( + createMockDialogRef(TransferItemsDialogResult.Accepted), + ); mockCipherService.shareManyWithServer.mockRejectedValue(new Error("Transfer failed")); await service.enforceOrganizationDataOwnership(userId); @@ -717,5 +693,171 @@ describe("DefaultVaultItemsTransferService", () => { message: "errorOccurred", }); }); + + it("re-shows transfer dialog when user goes back from leave confirmation", async () => { + const personalCiphers = [{ id: "cipher-1" } as CipherView]; + setupMocksForEnforcementScenario({ + policies: [policy], + organizations: [organization], + ciphers: personalCiphers, + defaultCollection: { + id: collectionId, + organizationId: organizationId, + isDefaultCollection: true, + } as CollectionView, + }); + + // User declines, goes back, then accepts + mockDialogService.open + .mockReturnValueOnce(createMockDialogRef(TransferItemsDialogResult.Declined)) + .mockReturnValueOnce(createMockDialogRef(LeaveConfirmationDialogResult.Back)) + .mockReturnValueOnce(createMockDialogRef(TransferItemsDialogResult.Accepted)); + mockCipherService.shareManyWithServer.mockResolvedValue(undefined); + + await service.enforceOrganizationDataOwnership(userId); + + // Dialog should have been opened 3 times: transfer -> leave -> transfer (after going back) + expect(mockDialogService.open).toHaveBeenCalledTimes(3); + expect(mockCipherService.shareManyWithServer).toHaveBeenCalled(); + }); + + it("allows multiple back navigations before accepting transfer", async () => { + const personalCiphers = [{ id: "cipher-1" } as CipherView]; + setupMocksForEnforcementScenario({ + policies: [policy], + organizations: [organization], + ciphers: personalCiphers, + defaultCollection: { + id: collectionId, + organizationId: organizationId, + isDefaultCollection: true, + } as CollectionView, + }); + + // User declines, goes back, declines again, goes back again, then accepts + mockDialogService.open + .mockReturnValueOnce(createMockDialogRef(TransferItemsDialogResult.Declined)) + .mockReturnValueOnce(createMockDialogRef(LeaveConfirmationDialogResult.Back)) + .mockReturnValueOnce(createMockDialogRef(TransferItemsDialogResult.Declined)) + .mockReturnValueOnce(createMockDialogRef(LeaveConfirmationDialogResult.Back)) + .mockReturnValueOnce(createMockDialogRef(TransferItemsDialogResult.Accepted)); + mockCipherService.shareManyWithServer.mockResolvedValue(undefined); + + await service.enforceOrganizationDataOwnership(userId); + + // Dialog should have been opened 5 times + expect(mockDialogService.open).toHaveBeenCalledTimes(5); + expect(mockCipherService.shareManyWithServer).toHaveBeenCalled(); + }); + + it("allows user to go back and then confirm leaving", async () => { + setupMocksForEnforcementScenario({ + policies: [policy], + organizations: [organization], + ciphers: [{ id: "cipher-1" } as CipherView], + defaultCollection: { + id: collectionId, + organizationId: organizationId, + isDefaultCollection: true, + } as CollectionView, + }); + + // User declines, goes back, declines again, then confirms leaving + mockDialogService.open + .mockReturnValueOnce(createMockDialogRef(TransferItemsDialogResult.Declined)) + .mockReturnValueOnce(createMockDialogRef(LeaveConfirmationDialogResult.Back)) + .mockReturnValueOnce(createMockDialogRef(TransferItemsDialogResult.Declined)) + .mockReturnValueOnce(createMockDialogRef(LeaveConfirmationDialogResult.Confirmed)); + + await service.enforceOrganizationDataOwnership(userId); + + expect(mockDialogService.open).toHaveBeenCalledTimes(4); + expect(mockCipherService.shareManyWithServer).not.toHaveBeenCalled(); + }); + }); + + describe("transferInProgress$", () => { + const policy = { + organizationId: organizationId, + revisionDate: new Date("2024-01-01"), + } as Policy; + const organization = { + id: organizationId, + name: "Test Org", + } as Organization; + + function setupMocksForTransferScenario(options: { + featureEnabled?: boolean; + policies?: Policy[]; + organizations?: Organization[]; + ciphers?: CipherView[]; + defaultCollection?: CollectionView; + }): void { + mockConfigService.getFeatureFlag.mockResolvedValue(options.featureEnabled ?? true); + mockPolicyService.policiesByType$.mockReturnValue(of(options.policies ?? [])); + mockOrganizationService.organizations$.mockReturnValue(of(options.organizations ?? [])); + mockCipherService.cipherViews$.mockReturnValue(of(options.ciphers ?? [])); + mockCollectionService.defaultUserCollection$.mockReturnValue(of(options.defaultCollection)); + } + + it("emits false initially", async () => { + const result = await firstValueFrom(service.transferInProgress$); + + expect(result).toBe(false); + }); + + it("emits true during transfer and false after successful completion", async () => { + const personalCiphers = [{ id: "cipher-1" } as CipherView]; + setupMocksForTransferScenario({ + policies: [policy], + organizations: [organization], + ciphers: personalCiphers, + defaultCollection: { + id: collectionId, + organizationId: organizationId, + isDefaultCollection: true, + } as CollectionView, + }); + + mockDialogService.open.mockReturnValueOnce( + createMockDialogRef(TransferItemsDialogResult.Accepted), + ); + mockCipherService.shareManyWithServer.mockResolvedValue(undefined); + + // Subscribe to track all emitted values + service.transferInProgress$.subscribe((value) => transferInProgressValues.push(value)); + + await service.enforceOrganizationDataOwnership(userId); + + // Should have emitted: false (initial), true (transfer started), false (transfer completed) + expect(transferInProgressValues).toEqual([false, true, false]); + }); + + it("emits false after transfer fails with error", async () => { + const personalCiphers = [{ id: "cipher-1" } as CipherView]; + setupMocksForTransferScenario({ + policies: [policy], + organizations: [organization], + ciphers: personalCiphers, + defaultCollection: { + id: collectionId, + organizationId: organizationId, + isDefaultCollection: true, + } as CollectionView, + }); + + mockDialogService.open.mockReturnValueOnce( + createMockDialogRef(TransferItemsDialogResult.Accepted), + ); + mockCipherService.shareManyWithServer.mockRejectedValue(new Error("Transfer failed")); + + // Subscribe to track all emitted values + service.transferInProgress$.subscribe((value) => transferInProgressValues.push(value)); + + await service.enforceOrganizationDataOwnership(userId); + + // Should have emitted: false (initial), true (transfer started), false (transfer failed) + expect(transferInProgressValues).toEqual([false, true, false]); + }); }); }); diff --git a/libs/vault/src/services/default-vault-items-transfer.service.ts b/libs/vault/src/services/default-vault-items-transfer.service.ts index d9c490f870e..d7088873071 100644 --- a/libs/vault/src/services/default-vault-items-transfer.service.ts +++ b/libs/vault/src/services/default-vault-items-transfer.service.ts @@ -1,5 +1,13 @@ import { Injectable } from "@angular/core"; -import { firstValueFrom, switchMap, map, of, Observable, combineLatest } from "rxjs"; +import { + firstValueFrom, + switchMap, + map, + of, + Observable, + combineLatest, + BehaviorSubject, +} from "rxjs"; // eslint-disable-next-line no-restricted-imports import { CollectionService } from "@bitwarden/admin-console/common"; @@ -23,6 +31,12 @@ import { VaultItemsTransferService, UserMigrationInfo, } from "../abstractions/vault-items-transfer.service"; +import { + TransferItemsDialogComponent, + TransferItemsDialogResult, + LeaveConfirmationDialogComponent, + LeaveConfirmationDialogResult, +} from "../components/vault-items-transfer"; @Injectable() export class DefaultVaultItemsTransferService implements VaultItemsTransferService { @@ -38,6 +52,10 @@ export class DefaultVaultItemsTransferService implements VaultItemsTransferServi private configService: ConfigService, ) {} + private _transferInProgressSubject = new BehaviorSubject(false); + + transferInProgress$ = this._transferInProgressSubject.asObservable(); + private enforcingOrganization$(userId: UserId): Observable<Organization | undefined> { return this.policyService.policiesByType$(PolicyType.OrganizationDataOwnership, userId).pipe( map( @@ -60,18 +78,6 @@ export class DefaultVaultItemsTransferService implements VaultItemsTransferServi ); } - private defaultUserCollection$( - userId: UserId, - organizationId: OrganizationId, - ): Observable<CollectionId | undefined> { - return this.collectionService.decryptedCollections$(userId).pipe( - map((collections) => { - return collections.find((c) => c.isDefaultCollection && c.organizationId === organizationId) - ?.id; - }), - ); - } - userMigrationInfo$(userId: UserId): Observable<UserMigrationInfo> { return this.enforcingOrganization$(userId).pipe( switchMap((enforcingOrganization) => { @@ -82,13 +88,13 @@ export class DefaultVaultItemsTransferService implements VaultItemsTransferServi } return combineLatest([ this.personalCiphers$(userId), - this.defaultUserCollection$(userId, enforcingOrganization.id), + this.collectionService.defaultUserCollection$(userId, enforcingOrganization.id), ]).pipe( - map(([personalCiphers, defaultCollectionId]): UserMigrationInfo => { + map(([personalCiphers, defaultCollection]): UserMigrationInfo => { return { requiresMigration: personalCiphers.length > 0, enforcingOrganization, - defaultCollectionId, + defaultCollectionId: defaultCollection?.id, }; }), ); @@ -96,6 +102,35 @@ export class DefaultVaultItemsTransferService implements VaultItemsTransferServi ); } + /** + * Prompts the user to accept or decline the vault items transfer. + * If declined, shows a leave confirmation dialog with option to go back. + * @returns true if user accepts transfer, false if user confirms leaving + */ + private async promptUserForTransfer(organizationName: string): Promise<boolean> { + const confirmDialogRef = TransferItemsDialogComponent.open(this.dialogService, { + data: { organizationName }, + }); + + const confirmResult = await firstValueFrom(confirmDialogRef.closed); + + if (confirmResult === TransferItemsDialogResult.Accepted) { + return true; + } + + const leaveDialogRef = LeaveConfirmationDialogComponent.open(this.dialogService, { + data: { organizationName }, + }); + + const leaveResult = await firstValueFrom(leaveDialogRef.closed); + + if (leaveResult === LeaveConfirmationDialogResult.Back) { + return this.promptUserForTransfer(organizationName); + } + + return false; + } + async enforceOrganizationDataOwnership(userId: UserId): Promise<void> { const featureEnabled = await this.configService.getFeatureFlag( FeatureFlag.MigrateMyVaultToMyItems, @@ -119,30 +154,29 @@ export class DefaultVaultItemsTransferService implements VaultItemsTransferServi return; } - // Temporary confirmation dialog. Full implementation in PM-27663 - const confirmMigration = await this.dialogService.openSimpleDialog({ - title: "Requires migration", - content: "Your vault requires migration of personal items to your organization.", - type: "warning", - }); + const userAcceptedTransfer = await this.promptUserForTransfer( + migrationInfo.enforcingOrganization.name, + ); - if (!confirmMigration) { - // TODO: Show secondary confirmation dialog in PM-27663, for now we just exit - // TODO: Revoke user from organization if they decline migration PM-29465 + if (!userAcceptedTransfer) { + // TODO: Revoke user from organization if they decline migration and show toast PM-29465 return; } try { + this._transferInProgressSubject.next(true); await this.transferPersonalItems( userId, migrationInfo.enforcingOrganization.id, migrationInfo.defaultCollectionId, ); + this._transferInProgressSubject.next(false); this.toastService.showToast({ variant: "success", message: this.i18nService.t("itemsTransferred"), }); } catch (error) { + this._transferInProgressSubject.next(false); this.logService.error("Error transferring personal items to organization", error); this.toastService.showToast({ variant: "error", From ba1c74b9052a79806e1f159547f4e5cc442227e7 Mon Sep 17 00:00:00 2001 From: Jason Ng <jng@bitwarden.com> Date: Tue, 16 Dec 2025 18:04:54 -0500 Subject: [PATCH 092/188] [PM-29286] clear selected items when filter is changed (#17929) * check filter inside vault-items and clear on change --- .../vault-items/vault-items.component.spec.ts | 31 ++++++++++++++++++- .../vault-items/vault-items.component.ts | 28 ++++++++++++++++- .../vault-items/vault-items.stories.ts | 12 +++++++ 3 files changed, 69 insertions(+), 2 deletions(-) diff --git a/apps/web/src/app/vault/components/vault-items/vault-items.component.spec.ts b/apps/web/src/app/vault/components/vault-items/vault-items.component.spec.ts index 1eccb4c49ce..c1c25c625da 100644 --- a/apps/web/src/app/vault/components/vault-items/vault-items.component.spec.ts +++ b/apps/web/src/app/vault/components/vault-items/vault-items.component.spec.ts @@ -1,6 +1,6 @@ import { ScrollingModule } from "@angular/cdk/scrolling"; import { TestBed } from "@angular/core/testing"; -import { of } from "rxjs"; +import { of, Subject } from "rxjs"; import { CollectionView } from "@bitwarden/admin-console/common"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -11,12 +11,15 @@ import { RestrictedItemTypesService } from "@bitwarden/common/vault/services/res import { CipherViewLike } from "@bitwarden/common/vault/utils/cipher-view-like-utils"; import { MenuModule, TableModule } from "@bitwarden/components"; import { I18nPipe } from "@bitwarden/ui-common"; +import { RoutedVaultFilterService } from "@bitwarden/web-vault/app/vault/individual-vault/vault-filter/services/routed-vault-filter.service"; +import { RoutedVaultFilterModel } from "@bitwarden/web-vault/app/vault/individual-vault/vault-filter/shared/models/routed-vault-filter.model"; import { VaultItem } from "./vault-item"; import { VaultItemsComponent } from "./vault-items.component"; describe("VaultItemsComponent", () => { let component: VaultItemsComponent<CipherViewLike>; + let filterSelect: Subject<RoutedVaultFilterModel>; const cipher1: Partial<CipherView> = { id: "cipher-1", @@ -31,6 +34,8 @@ describe("VaultItemsComponent", () => { }; beforeEach(async () => { + filterSelect = new Subject<RoutedVaultFilterModel>(); + await TestBed.configureTestingModule({ declarations: [VaultItemsComponent], imports: [ScrollingModule, TableModule, I18nPipe, MenuModule], @@ -61,6 +66,12 @@ describe("VaultItemsComponent", () => { hasArchiveFlagEnabled$: of(true), }, }, + { + provide: RoutedVaultFilterService, + useValue: { + filter$: filterSelect, + }, + }, ], }); @@ -143,4 +154,22 @@ describe("VaultItemsComponent", () => { expect(component.bulkUnarchiveAllowed).toBe(false); }); }); + + describe("filter change handling", () => { + it("clears selection when routed filter changes", () => { + const items: VaultItem<CipherView>[] = [ + { cipher: cipher1 as CipherView }, + { cipher: cipher2 as CipherView }, + ]; + + component["selection"].select(...items); + expect(component["selection"].selected.length).toBeGreaterThan(0); + + filterSelect.next({ + folderId: "folderId", + }); + + expect(component["selection"].selected.length).toBe(0); + }); + }); }); 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 a935314eb3a..a51009a1e5b 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 @@ -3,7 +3,15 @@ import { SelectionModel } from "@angular/cdk/collections"; import { Component, EventEmitter, Input, Output } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; -import { Observable, combineLatest, map, of, startWith, switchMap } from "rxjs"; +import { + Observable, + combineLatest, + distinctUntilChanged, + map, + of, + startWith, + switchMap, +} from "rxjs"; import { CollectionView, Unassigned, CollectionAdminView } from "@bitwarden/admin-console/common"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; @@ -19,6 +27,7 @@ import { } from "@bitwarden/common/vault/utils/cipher-view-like-utils"; import { SortDirection, TableDataSource } from "@bitwarden/components"; import { OrganizationId } from "@bitwarden/sdk-internal"; +import { RoutedVaultFilterService } from "@bitwarden/web-vault/app/vault/individual-vault/vault-filter/services/routed-vault-filter.service"; import { GroupView } from "../../../admin-console/organizations/core"; @@ -152,6 +161,7 @@ export class VaultItemsComponent<C extends CipherViewLike> { protected cipherAuthorizationService: CipherAuthorizationService, protected restrictedItemTypesService: RestrictedItemTypesService, protected cipherArchiveService: CipherArchiveService, + protected routedVaultFilterService: RoutedVaultFilterService, ) { this.canDeleteSelected$ = this.selection.changed.pipe( startWith(null), @@ -219,6 +229,22 @@ export class VaultItemsComponent<C extends CipherViewLike> { ); }), ); + + this.routedVaultFilterService.filter$ + .pipe( + distinctUntilChanged( + (prev, curr) => + prev.organizationId === curr.organizationId && + prev.collectionId === curr.collectionId && + prev.folderId === curr.folderId && + prev.type === curr.type && + prev.organizationIdParamType === curr.organizationIdParamType, + ), + takeUntilDestroyed(), + ) + .subscribe(() => { + this.clearSelection(); + }); } clearSelection() { diff --git a/apps/web/src/app/vault/components/vault-items/vault-items.stories.ts b/apps/web/src/app/vault/components/vault-items/vault-items.stories.ts index d973fbcbbc7..a71427cf475 100644 --- a/apps/web/src/app/vault/components/vault-items/vault-items.stories.ts +++ b/apps/web/src/app/vault/components/vault-items/vault-items.stories.ts @@ -40,6 +40,7 @@ import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cip import { RestrictedItemTypesService } from "@bitwarden/common/vault/services/restricted-item-types.service"; import { CipherViewLike } from "@bitwarden/common/vault/utils/cipher-view-like-utils"; import { LayoutComponent } from "@bitwarden/components"; +import { RoutedVaultFilterService } from "@bitwarden/web-vault/app/vault/individual-vault/vault-filter/services/routed-vault-filter.service"; import { GroupView } from "../../../admin-console/organizations/core"; import { PreloadedEnglishI18nModule } from "../../../core/tests"; @@ -150,6 +151,17 @@ export default { hasArchiveFlagEnabled$: of(true), }, }, + { + provide: RoutedVaultFilterService, + useValue: { + filter$: of({ + organizationId: null, + collectionId: null, + folderId: null, + type: null, + }), + }, + }, ], }), applicationConfig({ From ced97a4467de3f2eb8d0fb4a902fce9daea1ae33 Mon Sep 17 00:00:00 2001 From: Maciej Zieniuk <167752252+mzieniukbw@users.noreply.github.com> Date: Wed, 17 Dec 2025 10:19:11 +0100 Subject: [PATCH 093/188] cli status command shows locked status when unlocked (#17708) --- apps/cli/src/commands/status.command.ts | 17 ++++++++++++----- apps/cli/src/oss-serve-configurator.ts | 1 + apps/cli/src/program.ts | 1 + 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/apps/cli/src/commands/status.command.ts b/apps/cli/src/commands/status.command.ts index f7fc8541a5f..7ae1e657630 100644 --- a/apps/cli/src/commands/status.command.ts +++ b/apps/cli/src/commands/status.command.ts @@ -1,12 +1,12 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { firstValueFrom, map } from "rxjs"; 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"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; +import { UserAutoUnlockKeyService } from "@bitwarden/common/platform/services/user-auto-unlock-key.service"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; +import { UserId } from "@bitwarden/user-core"; import { Response } from "../models/response"; import { TemplateResponse } from "../models/response/template.response"; @@ -17,16 +17,17 @@ export class StatusCommand { private syncService: SyncService, private accountService: AccountService, private authService: AuthService, + private userAutoUnlockKeyService: UserAutoUnlockKeyService, ) {} async run(): Promise<Response> { try { const baseUrl = await this.baseUrl(); - const status = await this.status(); const lastSync = await this.syncService.getLastSync(); const [userId, email] = await firstValueFrom( this.accountService.activeAccount$.pipe(map((a) => [a?.id, a?.email])), ); + const status = await this.status(userId); return Response.success( new TemplateResponse({ @@ -42,12 +43,18 @@ export class StatusCommand { } } - private async baseUrl(): Promise<string> { + private async baseUrl(): Promise<string | undefined> { const env = await firstValueFrom(this.envService.environment$); return env.getUrls().base; } - private async status(): Promise<"unauthenticated" | "locked" | "unlocked"> { + private async status( + userId: UserId | undefined, + ): Promise<"unauthenticated" | "locked" | "unlocked"> { + if (userId != null) { + await this.userAutoUnlockKeyService.setUserKeyInMemoryIfAutoUserKeySet(userId); + } + const authStatus = await this.authService.getAuthStatus(); if (authStatus === AuthenticationStatus.Unlocked) { return "unlocked"; diff --git a/apps/cli/src/oss-serve-configurator.ts b/apps/cli/src/oss-serve-configurator.ts index dbe17224d07..e8f5e6acd9a 100644 --- a/apps/cli/src/oss-serve-configurator.ts +++ b/apps/cli/src/oss-serve-configurator.ts @@ -122,6 +122,7 @@ export class OssServeConfigurator { this.serviceContainer.syncService, this.serviceContainer.accountService, this.serviceContainer.authService, + this.serviceContainer.userAutoUnlockKeyService, ); this.deleteCommand = new DeleteCommand( this.serviceContainer.cipherService, diff --git a/apps/cli/src/program.ts b/apps/cli/src/program.ts index 3e5b5678629..b0c94b19ae9 100644 --- a/apps/cli/src/program.ts +++ b/apps/cli/src/program.ts @@ -524,6 +524,7 @@ export class Program extends BaseProgram { this.serviceContainer.syncService, this.serviceContainer.accountService, this.serviceContainer.authService, + this.serviceContainer.userAutoUnlockKeyService, ); const response = await command.run(); this.processResponse(response); From 4846d217a93b4b284ee428946a3239381aaf3e83 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann <mail@quexten.com> Date: Wed, 17 Dec 2025 10:57:24 +0100 Subject: [PATCH 094/188] [PM-28901] Fix master key not being set to state after kdf update (#17990) * Fix master key not being set to state after kdf update * Fix cli build * Fix test error * Fix hash purpose * Add test for master key being set * Fix incorrect variable name --- .../service-container/service-container.ts | 7 ++++- .../src/services/jslib-services.module.ts | 4 +-- .../kdf/change-kdf.service.spec.ts | 26 +++++++++++++++++-- .../key-management/kdf/change-kdf.service.ts | 20 +++++++++++++- 4 files changed, 51 insertions(+), 6 deletions(-) diff --git a/apps/cli/src/service-container/service-container.ts b/apps/cli/src/service-container/service-container.ts index 2d4ea7d00b5..f22f7cb6a00 100644 --- a/apps/cli/src/service-container/service-container.ts +++ b/apps/cli/src/service-container/service-container.ts @@ -982,7 +982,12 @@ export class ServiceContainer { this.masterPasswordApiService = new MasterPasswordApiService(this.apiService, this.logService); const changeKdfApiService = new DefaultChangeKdfApiService(this.apiService); - const changeKdfService = new DefaultChangeKdfService(changeKdfApiService, this.sdkService); + const changeKdfService = new DefaultChangeKdfService( + changeKdfApiService, + this.sdkService, + this.keyService, + this.masterPasswordService, + ); this.encryptedMigrator = new DefaultEncryptedMigrator( this.kdfConfigService, changeKdfService, diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index 816e09fd45d..6881862615d 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -528,7 +528,7 @@ const safeProviders: SafeProvider[] = [ safeProvider({ provide: ChangeKdfService, useClass: DefaultChangeKdfService, - deps: [ChangeKdfApiService, SdkService], + deps: [ChangeKdfApiService, SdkService, KeyService, InternalMasterPasswordServiceAbstraction], }), safeProvider({ provide: EncryptedMigrator, @@ -1333,7 +1333,7 @@ const safeProviders: SafeProvider[] = [ safeProvider({ provide: ChangeKdfService, useClass: DefaultChangeKdfService, - deps: [ChangeKdfApiService, SdkService], + deps: [ChangeKdfApiService, SdkService, KeyService, InternalMasterPasswordServiceAbstraction], }), safeProvider({ provide: AuthRequestServiceAbstraction, diff --git a/libs/common/src/key-management/kdf/change-kdf.service.spec.ts b/libs/common/src/key-management/kdf/change-kdf.service.spec.ts index 12096155641..e9f1ddca4e5 100644 --- a/libs/common/src/key-management/kdf/change-kdf.service.spec.ts +++ b/libs/common/src/key-management/kdf/change-kdf.service.spec.ts @@ -2,13 +2,14 @@ import { mock } from "jest-mock-extended"; import { of } from "rxjs"; // eslint-disable-next-line no-restricted-imports -import { PBKDF2KdfConfig } from "@bitwarden/key-management"; +import { KeyService, PBKDF2KdfConfig } from "@bitwarden/key-management"; import { makeEncString } from "../../../spec"; import { KdfRequest } from "../../models/request/kdf.request"; import { SdkService } from "../../platform/abstractions/sdk/sdk.service"; import { UserId } from "../../types/guid"; import { EncString } from "../crypto/models/enc-string"; +import { InternalMasterPasswordServiceAbstraction } from "../master-password/abstractions/master-password.service.abstraction"; import { MasterKeyWrappedUserKey, MasterPasswordAuthenticationHash, @@ -22,6 +23,8 @@ import { DefaultChangeKdfService } from "./change-kdf.service"; describe("ChangeKdfService", () => { const changeKdfApiService = mock<ChangeKdfApiService>(); const sdkService = mock<SdkService>(); + const keyService = mock<KeyService>(); + const masterPasswordService = mock<InternalMasterPasswordServiceAbstraction>(); let sut: DefaultChangeKdfService; @@ -48,7 +51,12 @@ describe("ChangeKdfService", () => { beforeEach(() => { sdkService.userClient$ = jest.fn((userId: UserId) => of(mockSdk)) as any; - sut = new DefaultChangeKdfService(changeKdfApiService, sdkService); + sut = new DefaultChangeKdfService( + changeKdfApiService, + sdkService, + keyService, + masterPasswordService, + ); }); afterEach(() => { @@ -163,6 +171,20 @@ describe("ChangeKdfService", () => { expect(changeKdfApiService.updateUserKdfParams).toHaveBeenCalledWith(expectedRequest); }); + it("should set master key and hash after KDF update", async () => { + const masterPassword = "masterPassword"; + const mockMasterKey = {} as any; + const mockHash = "localHash"; + + keyService.makeMasterKey.mockResolvedValue(mockMasterKey); + keyService.hashMasterKey.mockResolvedValue(mockHash); + + await sut.updateUserKdfParams(masterPassword, mockNewKdfConfig, mockUserId); + + expect(masterPasswordService.setMasterKey).toHaveBeenCalledWith(mockMasterKey, mockUserId); + expect(masterPasswordService.setMasterKeyHash).toHaveBeenCalledWith(mockHash, mockUserId); + }); + it("should properly dispose of SDK resources", async () => { const masterPassword = "masterPassword"; jest.spyOn(mockNewKdfConfig, "toSdkConfig").mockReturnValue({} as any); diff --git a/libs/common/src/key-management/kdf/change-kdf.service.ts b/libs/common/src/key-management/kdf/change-kdf.service.ts index 89d97e6704f..61842e7354b 100644 --- a/libs/common/src/key-management/kdf/change-kdf.service.ts +++ b/libs/common/src/key-management/kdf/change-kdf.service.ts @@ -1,12 +1,14 @@ import { firstValueFrom, map } from "rxjs"; import { assertNonNullish } from "@bitwarden/common/auth/utils"; +import { HashPurpose } from "@bitwarden/common/platform/enums"; import { UserId } from "@bitwarden/common/types/guid"; // eslint-disable-next-line no-restricted-imports -import { KdfConfig } from "@bitwarden/key-management"; +import { KdfConfig, KeyService } from "@bitwarden/key-management"; import { KdfRequest } from "../../models/request/kdf.request"; import { SdkService } from "../../platform/abstractions/sdk/sdk.service"; +import { InternalMasterPasswordServiceAbstraction } from "../master-password/abstractions/master-password.service.abstraction"; import { fromSdkAuthenticationData, MasterPasswordAuthenticationData, @@ -20,6 +22,8 @@ export class DefaultChangeKdfService implements ChangeKdfService { constructor( private changeKdfApiService: ChangeKdfApiService, private sdkService: SdkService, + private keyService: KeyService, + private masterPasswordService: InternalMasterPasswordServiceAbstraction, ) {} async updateUserKdfParams(masterPassword: string, kdf: KdfConfig, userId: UserId): Promise<void> { @@ -56,5 +60,19 @@ export class DefaultChangeKdfService implements ChangeKdfService { const request = new KdfRequest(authenticationData, unlockData); request.authenticateWith(oldAuthenticationData); await this.changeKdfApiService.updateUserKdfParams(request); + + // Update the locally stored master key and hash, so that UV, etc. still works + const masterKey = await this.keyService.makeMasterKey( + masterPassword, + unlockData.salt, + unlockData.kdf, + ); + const localMasterKeyHash = await this.keyService.hashMasterKey( + masterPassword, + masterKey, + HashPurpose.LocalAuthorization, + ); + await this.masterPasswordService.setMasterKeyHash(localMasterKeyHash, userId); + await this.masterPasswordService.setMasterKey(masterKey, userId); } } From 3114b319204ef0c1948dc220c8a14e63cf6de83a Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann <mail@quexten.com> Date: Wed, 17 Dec 2025 11:59:40 +0100 Subject: [PATCH 095/188] Fix slow agent operations (#17867) --- .../desktop_native/core/src/ssh_agent/peerinfo/gather.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/apps/desktop/desktop_native/core/src/ssh_agent/peerinfo/gather.rs b/apps/desktop/desktop_native/core/src/ssh_agent/peerinfo/gather.rs index 699203d613d..bf8e24dd79c 100644 --- a/apps/desktop/desktop_native/core/src/ssh_agent/peerinfo/gather.rs +++ b/apps/desktop/desktop_native/core/src/ssh_agent/peerinfo/gather.rs @@ -3,8 +3,12 @@ use sysinfo::{Pid, System}; use super::models::PeerInfo; pub fn get_peer_info(peer_pid: u32) -> Result<PeerInfo, String> { - let s = System::new_all(); - if let Some(process) = s.process(Pid::from_u32(peer_pid)) { + let mut system = System::new(); + system.refresh_processes( + sysinfo::ProcessesToUpdate::Some(&[Pid::from_u32(peer_pid)]), + true, + ); + if let Some(process) = system.process(Pid::from_u32(peer_pid)) { let peer_process_name = match process.name().to_str() { Some(name) => name.to_string(), None => { From 24dcbb48c61309da382a6a33b96bdee9785e1295 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann <mail@quexten.com> Date: Wed, 17 Dec 2025 12:00:13 +0100 Subject: [PATCH 096/188] [PM-29418] Fix SSH list not working while locked (#17866) * Fix SSH list not working while locked * Add tests * Update private key to SDK test key * Cleanup --- .../desktop_native/core/src/ssh_agent/mod.rs | 86 ++++++++++++++++++- 1 file changed, 85 insertions(+), 1 deletion(-) 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 8ba64618ffa..16cf778b575 100644 --- a/apps/desktop/desktop_native/core/src/ssh_agent/mod.rs +++ b/apps/desktop/desktop_native/core/src/ssh_agent/mod.rs @@ -226,7 +226,7 @@ impl BitwardenDesktopAgent { keystore.0.write().expect("RwLock is not poisoned").clear(); self.needs_unlock - .store(true, std::sync::atomic::Ordering::Relaxed); + .store(false, std::sync::atomic::Ordering::Relaxed); for (key, name, cipher_id) in new_keys.iter() { match parse_key_safe(key) { @@ -307,3 +307,87 @@ fn parse_key_safe(pem: &str) -> Result<ssh_key::private::PrivateKey, anyhow::Err Err(e) => Err(anyhow::Error::msg(format!("Failed to parse key: {e}"))), } } + +#[cfg(test)] +mod tests { + use super::*; + + fn create_test_agent() -> ( + BitwardenDesktopAgent, + tokio::sync::mpsc::Receiver<SshAgentUIRequest>, + tokio::sync::broadcast::Sender<(u32, bool)>, + ) { + let (tx, rx) = tokio::sync::mpsc::channel(10); + let (response_tx, response_rx) = tokio::sync::broadcast::channel(10); + let agent = BitwardenDesktopAgent::new(tx, Arc::new(Mutex::new(response_rx))); + (agent, rx, response_tx) + } + + const TEST_ED25519_KEY: &str = "-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACCWETEIh/JX+ZaK0Xlg5xZ9QIfjiKD2Qs57PjhRY45trwAAAIhqmvSbapr0 +mwAAAAtzc2gtZWQyNTUxOQAAACCWETEIh/JX+ZaK0Xlg5xZ9QIfjiKD2Qs57PjhRY45trw +AAAEAHVflTgR/OEl8mg9UEKcO7SeB0FH4AiaUurhVfBWT4eZYRMQiH8lf5lorReWDnFn1A +h+OIoPZCzns+OFFjjm2vAAAAAAECAwQF +-----END OPENSSH PRIVATE KEY-----"; + + #[tokio::test] + async fn test_needs_unlock_initial_state() { + let (agent, _rx, _response_tx) = create_test_agent(); + + // Initially, needs_unlock should be true + assert!(agent + .needs_unlock + .load(std::sync::atomic::Ordering::Relaxed)); + } + + #[tokio::test] + async fn test_needs_unlock_after_set_keys() { + let (mut agent, _rx, _response_tx) = create_test_agent(); + agent + .is_running + .store(true, std::sync::atomic::Ordering::Relaxed); + + // Set keys should set needs_unlock to false + let keys = vec![( + TEST_ED25519_KEY.to_string(), + "test_key".to_string(), + "cipher_id".to_string(), + )]; + + agent.set_keys(keys).unwrap(); + + assert!(!agent + .needs_unlock + .load(std::sync::atomic::Ordering::Relaxed)); + } + + #[tokio::test] + async fn test_needs_unlock_after_clear_keys() { + let (mut agent, _rx, _response_tx) = create_test_agent(); + agent + .is_running + .store(true, std::sync::atomic::Ordering::Relaxed); + + // Set keys first + let keys = vec![( + TEST_ED25519_KEY.to_string(), + "test_key".to_string(), + "cipher_id".to_string(), + )]; + agent.set_keys(keys).unwrap(); + + // Verify needs_unlock is false + assert!(!agent + .needs_unlock + .load(std::sync::atomic::Ordering::Relaxed)); + + // Clear keys should set needs_unlock back to true + agent.clear_keys().unwrap(); + + // Verify needs_unlock is true + assert!(agent + .needs_unlock + .load(std::sync::atomic::Ordering::Relaxed)); + } +} From e6062ec84e09c713cdac60b428bddba58cb4a9d0 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann <mail@quexten.com> Date: Wed, 17 Dec 2025 15:16:02 +0100 Subject: [PATCH 097/188] Fix agent crashing when account switching (#17868) --- .../autofill/services/ssh-agent.service.ts | 25 +++++-------------- 1 file changed, 6 insertions(+), 19 deletions(-) diff --git a/apps/desktop/src/autofill/services/ssh-agent.service.ts b/apps/desktop/src/autofill/services/ssh-agent.service.ts index d5aed7f3289..a61903a5c82 100644 --- a/apps/desktop/src/autofill/services/ssh-agent.service.ts +++ b/apps/desktop/src/autofill/services/ssh-agent.service.ts @@ -46,8 +46,6 @@ export class SshAgentService implements OnDestroy { private authorizedSshKeys: Record<string, Date> = {}; - private isFeatureFlagEnabled = false; - private destroy$ = new Subject<void>(); constructor( @@ -91,6 +89,7 @@ export class SshAgentService implements OnDestroy { filter(({ enabled }) => enabled), map(({ message }) => message), withLatestFrom(this.authService.activeAccountStatus$, this.accountService.activeAccount$), + filter(([, , account]) => account != null), // This switchMap handles unlocking the vault if it is locked: // - If the vault is locked, we will wait for it to be unlocked. // - If the vault is not unlocked within the timeout, we will abort the flow. @@ -127,7 +126,11 @@ export class SshAgentService implements OnDestroy { throw error; }), - map(() => [message, account.id]), + concatMap(async () => { + // The active account may have switched with account switching during unlock + const updatedAccount = await firstValueFrom(this.accountService.activeAccount$); + return [message, updatedAccount.id] as const; + }), ); } @@ -200,10 +203,6 @@ export class SshAgentService implements OnDestroy { this.accountService.activeAccount$.pipe(skip(1), takeUntil(this.destroy$)).subscribe({ next: (account) => { - if (!this.isFeatureFlagEnabled) { - return; - } - this.authorizedSshKeys = {}; this.logService.info("Active account changed, clearing SSH keys"); ipc.platform.sshAgent @@ -211,20 +210,12 @@ export class SshAgentService implements OnDestroy { .catch((e) => this.logService.error("Failed to clear SSH keys", e)); }, error: (e: unknown) => { - if (!this.isFeatureFlagEnabled) { - return; - } - this.logService.error("Error in active account observable", e); ipc.platform.sshAgent .clearKeys() .catch((e) => this.logService.error("Failed to clear SSH keys", e)); }, complete: () => { - if (!this.isFeatureFlagEnabled) { - return; - } - this.logService.info("Active account observable completed, clearing SSH keys"); this.authorizedSshKeys = {}; ipc.platform.sshAgent @@ -239,10 +230,6 @@ export class SshAgentService implements OnDestroy { ]) .pipe( concatMap(async ([, enabled]) => { - if (!this.isFeatureFlagEnabled) { - return; - } - if (!enabled) { await ipc.platform.sshAgent.clearKeys(); return; From 78f4947d006bc5d0d009ab6eaaba688d7b15a7c8 Mon Sep 17 00:00:00 2001 From: Leslie Tilton <23057410+Banrion@users.noreply.github.com> Date: Wed, 17 Dec 2025 09:23:09 -0600 Subject: [PATCH 098/188] [PM-25884] Disable phishing detection if safari is detected (#17655) * Disable phishing detection if safari is detected * Apply suggestion from @claude[bot] Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com> * Move order of safari vs account checks --------- Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com> --- .../services/phishing-detection.service.spec.ts | 5 +++++ .../services/phishing-detection.service.ts | 13 ++++++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) 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 ceb18bd1573..06a37f12faa 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 @@ -79,4 +79,9 @@ describe("PhishingDetectionService", () => { // phishingDetectionSettingsService, // ); // }); + + // TODO + // it("should not enable phishing detection for safari", () => { + // + // }); }); 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 e04d08559ab..501dfbf7a50 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 @@ -5,6 +5,7 @@ import { filter, map, merge, + of, Subject, switchMap, tap, @@ -111,7 +112,17 @@ export class PhishingDetectionService { .messages$(PHISHING_DETECTION_CANCEL_COMMAND) .pipe(switchMap((message) => BrowserApi.closeTab(message.tabId))); - const phishingDetectionActive$ = phishingDetectionSettingsService.on$; + // Phishing detection is unavailable on Safari due to platform limitations + if (BrowserApi.isSafariApi) { + logService.debug( + "[PhishingDetectionService] Disabling phishing detection service for Safari.", + ); + } + + // Watching for settings changes to enable/disable phishing detection + const phishingDetectionActive$ = BrowserApi.isSafariApi + ? of(false) + : phishingDetectionSettingsService.on$; const initSub = phishingDetectionActive$ .pipe( From b0fcd92f35515144d04da0b8381b3f876c9b386e Mon Sep 17 00:00:00 2001 From: Alex <55413326+AlexRubik@users.noreply.github.com> Date: Wed, 17 Dec 2025 10:23:33 -0500 Subject: [PATCH 099/188] add padding between name and icons in health reports (#18002) Added tw-ml-1 class to shared (bwi-collection-shared) and attachment (bwi-paperclip) icons in report tables to add spacing between the item name and icons. Affected reports: - Weak passwords - Exposed passwords - Reused passwords - Unsecured websites - Inactive two-factor - Emergency access view (PM-29488) --- .../view/emergency-access-view.component.html | 4 ++-- .../reports/pages/exposed-passwords-report.component.html | 4 ++-- .../reports/pages/inactive-two-factor-report.component.html | 4 ++-- .../dirt/reports/pages/reused-passwords-report.component.html | 4 ++-- .../reports/pages/unsecured-websites-report.component.html | 4 ++-- .../dirt/reports/pages/weak-passwords-report.component.html | 4 ++-- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/apps/web/src/app/auth/settings/emergency-access/view/emergency-access-view.component.html b/apps/web/src/app/auth/settings/emergency-access/view/emergency-access-view.component.html index 20cc50c4d59..4aaac6aaa52 100644 --- a/apps/web/src/app/auth/settings/emergency-access/view/emergency-access-view.component.html +++ b/apps/web/src/app/auth/settings/emergency-access/view/emergency-access-view.component.html @@ -19,7 +19,7 @@ > <ng-container *ngIf="currentCipher.organizationId"> <i - class="bwi bwi-collection-shared" + class="bwi bwi-collection-shared tw-ml-1" appStopProp title="{{ 'shared' | i18n }}" aria-hidden="true" @@ -28,7 +28,7 @@ </ng-container> <ng-container *ngIf="currentCipher.hasAttachments"> <i - class="bwi bwi-paperclip" + class="bwi bwi-paperclip tw-ml-1" appStopProp title="{{ 'attachments' | i18n }}" aria-hidden="true" diff --git a/apps/web/src/app/dirt/reports/pages/exposed-passwords-report.component.html b/apps/web/src/app/dirt/reports/pages/exposed-passwords-report.component.html index 8e665936496..eb476090963 100644 --- a/apps/web/src/app/dirt/reports/pages/exposed-passwords-report.component.html +++ b/apps/web/src/app/dirt/reports/pages/exposed-passwords-report.component.html @@ -58,7 +58,7 @@ </ng-template> <ng-container *ngIf="!organization && row.organizationId"> <i - class="bwi bwi-collection-shared" + class="bwi bwi-collection-shared tw-ml-1" appStopProp title="{{ 'shared' | i18n }}" aria-hidden="true" @@ -67,7 +67,7 @@ </ng-container> <ng-container *ngIf="row.hasAttachments"> <i - class="bwi bwi-paperclip" + class="bwi bwi-paperclip tw-ml-1" appStopProp title="{{ 'attachments' | i18n }}" aria-hidden="true" diff --git a/apps/web/src/app/dirt/reports/pages/inactive-two-factor-report.component.html b/apps/web/src/app/dirt/reports/pages/inactive-two-factor-report.component.html index fb19bb382b8..05758a854c2 100644 --- a/apps/web/src/app/dirt/reports/pages/inactive-two-factor-report.component.html +++ b/apps/web/src/app/dirt/reports/pages/inactive-two-factor-report.component.html @@ -62,7 +62,7 @@ </ng-template> <ng-container *ngIf="!organization && r.organizationId"> <i - class="bwi bwi-collection-shared" + class="bwi bwi-collection-shared tw-ml-1" appStopProp title="{{ 'shared' | i18n }}" aria-hidden="true" @@ -71,7 +71,7 @@ </ng-container> <ng-container *ngIf="r.hasAttachments"> <i - class="bwi bwi-paperclip" + class="bwi bwi-paperclip tw-ml-1" appStopProp title="{{ 'attachments' | i18n }}" aria-hidden="true" diff --git a/apps/web/src/app/dirt/reports/pages/reused-passwords-report.component.html b/apps/web/src/app/dirt/reports/pages/reused-passwords-report.component.html index 37c2c2f8a8c..d09dfa81fd4 100644 --- a/apps/web/src/app/dirt/reports/pages/reused-passwords-report.component.html +++ b/apps/web/src/app/dirt/reports/pages/reused-passwords-report.component.html @@ -60,7 +60,7 @@ </ng-template> <ng-container *ngIf="!organization && row.organizationId"> <i - class="bwi bwi-collection-shared" + class="bwi bwi-collection-shared tw-ml-1" appStopProp title="{{ 'shared' | i18n }}" aria-hidden="true" @@ -69,7 +69,7 @@ </ng-container> <ng-container *ngIf="row.hasAttachments"> <i - class="bwi bwi-paperclip" + class="bwi bwi-paperclip tw-ml-1" appStopProp title="{{ 'attachments' | i18n }}" aria-hidden="true" diff --git a/apps/web/src/app/dirt/reports/pages/unsecured-websites-report.component.html b/apps/web/src/app/dirt/reports/pages/unsecured-websites-report.component.html index e28760f7746..5dd11b59999 100644 --- a/apps/web/src/app/dirt/reports/pages/unsecured-websites-report.component.html +++ b/apps/web/src/app/dirt/reports/pages/unsecured-websites-report.component.html @@ -62,7 +62,7 @@ </ng-template> <ng-container *ngIf="!organization && r.organizationId"> <i - class="bwi bwi-collection-shared" + class="bwi bwi-collection-shared tw-ml-1" appStopProp title="{{ 'shared' | i18n }}" aria-hidden="true" @@ -71,7 +71,7 @@ </ng-container> <ng-container *ngIf="r.hasAttachments"> <i - class="bwi bwi-paperclip" + class="bwi bwi-paperclip tw-ml-1" appStopProp title="{{ 'attachments' | i18n }}" aria-hidden="true" diff --git a/apps/web/src/app/dirt/reports/pages/weak-passwords-report.component.html b/apps/web/src/app/dirt/reports/pages/weak-passwords-report.component.html index 807bc751b23..5fa2806d133 100644 --- a/apps/web/src/app/dirt/reports/pages/weak-passwords-report.component.html +++ b/apps/web/src/app/dirt/reports/pages/weak-passwords-report.component.html @@ -62,7 +62,7 @@ </ng-template> <ng-container *ngIf="!organization && row.organizationId"> <i - class="bwi bwi-collection-shared" + class="bwi bwi-collection-shared tw-ml-1" appStopProp title="{{ 'shared' | i18n }}" aria-hidden="true" @@ -71,7 +71,7 @@ </ng-container> <ng-container *ngIf="row.hasAttachments"> <i - class="bwi bwi-paperclip" + class="bwi bwi-paperclip tw-ml-1" appStopProp title="{{ 'attachments' | i18n }}" aria-hidden="true" From d50c55a5df02089130614137ba460a309b98a78f Mon Sep 17 00:00:00 2001 From: Alex <55413326+AlexRubik@users.noreply.github.com> Date: Wed, 17 Dec 2025 10:31:28 -0500 Subject: [PATCH 100/188] [PM-29219] enforce small drawer size for Access Intelligence (#17915) --- .../shared/risk-insights-drawer-dialog.component.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/risk-insights-drawer-dialog.component.html b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/risk-insights-drawer-dialog.component.html index 3fa72358f25..63ff7fd07b6 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/risk-insights-drawer-dialog.component.html +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/risk-insights-drawer-dialog.component.html @@ -1,5 +1,5 @@ @if (isActiveDrawerType(drawerTypes.OrgAtRiskMembers)) { - <bit-dialog dialogSize="large" disablePadding="false"> + <bit-dialog dialogSize="small" disablePadding="false"> <ng-container bitDialogTitle> <span>{{ "atRiskMembersWithCount" | i18n: drawerDetails.atRiskMemberDetails?.length ?? 0 @@ -45,7 +45,7 @@ } @if (isActiveDrawerType(drawerTypes.AppAtRiskMembers)) { - <bit-dialog dialogSize="large" disablePadding="false"> + <bit-dialog dialogSize="small" disablePadding="false"> <ng-container bitDialogTitle> <span>{{ drawerDetails.appAtRiskMembers?.applicationName }}</span> </ng-container> @@ -71,7 +71,7 @@ } @if (isActiveDrawerType(drawerTypes.OrgAtRiskApps)) { - <bit-dialog dialogSize="large" disablePadding="false"> + <bit-dialog dialogSize="small" disablePadding="false"> <ng-container bitDialogTitle> <span>{{ "atRiskApplicationsWithCount" | i18n: drawerDetails.atRiskAppDetails?.length ?? 0 From cbd80d01865509464ec31d947d032b25f538edc3 Mon Sep 17 00:00:00 2001 From: Jared Snider <116684653+JaredSnider-Bitwarden@users.noreply.github.com> Date: Wed, 17 Dec 2025 10:34:42 -0500 Subject: [PATCH 101/188] refactor(IdentityTokenResponse): [Auth/PM-3287] Remove deprecated resetMasterPassword property from IdentityTokenResponse (#17794) * PM-3287 - Remove resetMasterPassword from authResult and identityTokenResponse and replace with userDecryptionOptions where relevant * PM-3287 - (1) Move SSO code to SSO section (2) Update error scenario conditional + log user out upon error. * PM-3287 - Fix comment per PR feedback * PM-3287 - CLI Login with SSO - move MP validation logic back to original location to avoid putting it before 2FA rejection handling. * PM-3287 - Update returns --- apps/cli/src/auth/commands/login.command.ts | 45 ++++++++++++++++--- apps/cli/src/program.ts | 1 + libs/auth/src/angular/sso/sso.component.ts | 2 +- .../two-factor-auth.component.ts | 2 +- .../login-strategies/login.strategy.spec.ts | 4 -- .../common/login-strategies/login.strategy.ts | 2 - .../password-login.strategy.spec.ts | 1 - .../webauthn-login.strategy.spec.ts | 1 - .../login-strategy.service.spec.ts | 4 -- .../src/auth/models/domain/auth-result.ts | 8 ---- .../response/identity-token.response.ts | 2 - 11 files changed, 43 insertions(+), 29 deletions(-) diff --git a/apps/cli/src/auth/commands/login.command.ts b/apps/cli/src/auth/commands/login.command.ts index d8859318b52..89d774b443b 100644 --- a/apps/cli/src/auth/commands/login.command.ts +++ b/apps/cli/src/auth/commands/login.command.ts @@ -13,6 +13,7 @@ import { SsoLoginCredentials, SsoUrlService, UserApiLoginCredentials, + UserDecryptionOptionsServiceAbstraction, } from "@bitwarden/auth/common"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction"; @@ -82,6 +83,7 @@ export class LoginCommand { protected ssoUrlService: SsoUrlService, protected i18nService: I18nService, protected masterPasswordService: MasterPasswordServiceAbstraction, + protected userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction, protected encryptedMigrator: EncryptedMigrator, ) {} @@ -361,11 +363,13 @@ export class LoginCommand { return Response.error("Login failed."); } - if (response.resetMasterPassword) { - return Response.error( - "In order to log in with SSO from the CLI, you must first log in" + - " through the web vault to set your master password.", - ); + // If we are in the SSO flow and we got a successful login response (we are past rejection scenarios + // and should always have a userId here), validate that SSO user in MP encryption org has MP set + // This must be done here b/c we have 2 places we try to login with SSO above and neither has a + // common handleSsoAuthnResult method to consoldiate this logic into (1. the normal SSO flow and + // 2. the requiresSso automatic authentication flow) + if (ssoCode != null && ssoCodeVerifier != null && response.userId) { + await this.validateSsoUserInMpEncryptionOrgHasMp(response.userId); } // Check if Key Connector domain confirmation is required @@ -836,4 +840,35 @@ export class LoginCommand { const checkStateSplit = checkState.split("_identifier="); return stateSplit[0] === checkStateSplit[0]; } + + /** + * Validate that a user logging in with SSO that is in an org using MP encryption + * has a MP set. If not, they cannot set a MP in the CLI and must use another client. + * @param userId + * @returns void + */ + private async validateSsoUserInMpEncryptionOrgHasMp(userId: UserId): Promise<void> { + const userDecryptionOptions = await firstValueFrom( + this.userDecryptionOptionsService.userDecryptionOptionsById$(userId), + ); + + // device trust isn't supported in the CLI as we don't have persistent device key storage. + const notUsingTrustedDeviceEncryption = !userDecryptionOptions.trustedDeviceOption; + const notUsingKeyConnector = !userDecryptionOptions.keyConnectorOption; + + if ( + notUsingTrustedDeviceEncryption && + notUsingKeyConnector && + !userDecryptionOptions.hasMasterPassword + ) { + // If user is in an org that is using MP encryption and they JIT provisioned but + // have not yet set a MP and come to the CLI to login, they won't be able to unlock + // or set a MP in the CLI as it isn't supported. + await this.logoutCallback(); + throw Response.error( + "In order to log in with SSO from the CLI, you must first log in" + + " through the web vault, the desktop, or the extension to set your master password.", + ); + } + } } diff --git a/apps/cli/src/program.ts b/apps/cli/src/program.ts index b0c94b19ae9..870d743095d 100644 --- a/apps/cli/src/program.ts +++ b/apps/cli/src/program.ts @@ -195,6 +195,7 @@ export class Program extends BaseProgram { this.serviceContainer.ssoUrlService, this.serviceContainer.i18nService, this.serviceContainer.masterPasswordService, + this.serviceContainer.userDecryptionOptionsService, this.serviceContainer.encryptedMigrator, ); const response = await command.run(email, password, options); diff --git a/libs/auth/src/angular/sso/sso.component.ts b/libs/auth/src/angular/sso/sso.component.ts index d0cc2bd83e5..f5167cb84cc 100644 --- a/libs/auth/src/angular/sso/sso.component.ts +++ b/libs/auth/src/angular/sso/sso.component.ts @@ -478,7 +478,7 @@ export class SsoComponent implements OnInit { !userDecryptionOpts.hasMasterPassword && userDecryptionOpts.keyConnectorOption === undefined; - if (requireSetPassword || authResult.resetMasterPassword) { + if (requireSetPassword) { // Change implies going no password -> password in this case return await this.handleChangePasswordRequired(orgSsoIdentifier); } diff --git a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts index 4c143cc59f9..91d24532a70 100644 --- a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts +++ b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts @@ -487,7 +487,7 @@ export class TwoFactorAuthComponent implements OnInit, OnDestroy { !userDecryptionOpts.hasMasterPassword && userDecryptionOpts.keyConnectorOption === undefined; // New users without a master password must set a master password before advancing. - if (requireSetPassword || authResult.resetMasterPassword) { + if (requireSetPassword) { // Change implies going no password -> password in this case return await this.handleChangePasswordRequired(this.orgSsoIdentifier); } diff --git a/libs/auth/src/common/login-strategies/login.strategy.spec.ts b/libs/auth/src/common/login-strategies/login.strategy.spec.ts index 82e1183a1d6..4bf72b69e1f 100644 --- a/libs/auth/src/common/login-strategies/login.strategy.spec.ts +++ b/libs/auth/src/common/login-strategies/login.strategy.spec.ts @@ -101,7 +101,6 @@ export function identityTokenResponseFactory( KdfIterations: kdfIterations, Key: encryptedUserKey, PrivateKey: privateKey, - ResetMasterPassword: false, access_token: accessToken, expires_in: 3600, refresh_token: refreshToken, @@ -301,7 +300,6 @@ describe("LoginStrategy", () => { it("builds AuthResult", async () => { const tokenResponse = identityTokenResponseFactory(); tokenResponse.forcePasswordReset = true; - tokenResponse.resetMasterPassword = true; apiService.postIdentityToken.mockResolvedValue(tokenResponse); @@ -310,7 +308,6 @@ describe("LoginStrategy", () => { const expected = new AuthResult(); expected.masterPassword = "password"; expected.userId = userId; - expected.resetMasterPassword = true; expected.twoFactorProviders = null; expect(result).toEqual(expected); }); @@ -326,7 +323,6 @@ describe("LoginStrategy", () => { const expected = new AuthResult(); expected.masterPassword = "password"; expected.userId = userId; - expected.resetMasterPassword = false; expected.twoFactorProviders = null; expect(result).toEqual(expected); diff --git a/libs/auth/src/common/login-strategies/login.strategy.ts b/libs/auth/src/common/login-strategies/login.strategy.ts index acb32969f08..90a36284d3b 100644 --- a/libs/auth/src/common/login-strategies/login.strategy.ts +++ b/libs/auth/src/common/login-strategies/login.strategy.ts @@ -254,8 +254,6 @@ export abstract class LoginStrategy { const userId = await this.saveAccountInformation(response); result.userId = userId; - result.resetMasterPassword = response.resetMasterPassword; - if (response.twoFactorToken != null) { // note: we can read email from access token b/c it was saved in saveAccountInformation const userEmail = await this.tokenService.getEmail(); diff --git a/libs/auth/src/common/login-strategies/password-login.strategy.spec.ts b/libs/auth/src/common/login-strategies/password-login.strategy.spec.ts index 26ae1270f39..2287bfb43e3 100644 --- a/libs/auth/src/common/login-strategies/password-login.strategy.spec.ts +++ b/libs/auth/src/common/login-strategies/password-login.strategy.spec.ts @@ -390,7 +390,6 @@ describe("PasswordLoginStrategy", () => { newDeviceOtp: deviceVerificationOtp, }), ); - expect(result.resetMasterPassword).toBe(false); expect(result.userId).toBe(userId); }); }); 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 53bc1c57905..9d664a7837c 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 @@ -212,7 +212,6 @@ describe("WebAuthnLoginStrategy", () => { expect(authResult).toBeInstanceOf(AuthResult); expect(authResult).toMatchObject({ - resetMasterPassword: false, twoFactorProviders: null, requiresTwoFactor: false, }); diff --git a/libs/auth/src/common/services/login-strategies/login-strategy.service.spec.ts b/libs/auth/src/common/services/login-strategies/login-strategy.service.spec.ts index 20251e339a5..c7e18105a0d 100644 --- a/libs/auth/src/common/services/login-strategies/login-strategy.service.spec.ts +++ b/libs/auth/src/common/services/login-strategies/login-strategy.service.spec.ts @@ -491,7 +491,6 @@ describe("LoginStrategyService", () => { KdfParallelism: 1, Key: "KEY", PrivateKey: "PRIVATE_KEY", - ResetMasterPassword: false, access_token: "ACCESS_TOKEN", expires_in: 3600, refresh_token: "REFRESH_TOKEN", @@ -559,7 +558,6 @@ describe("LoginStrategyService", () => { KdfParallelism: 1, Key: "KEY", PrivateKey: "PRIVATE_KEY", - ResetMasterPassword: false, access_token: "ACCESS_TOKEN", expires_in: 3600, refresh_token: "REFRESH_TOKEN", @@ -625,7 +623,6 @@ describe("LoginStrategyService", () => { KdfIterations: PBKDF2KdfConfig.PRELOGIN_ITERATIONS_MIN - 1, Key: "KEY", PrivateKey: "PRIVATE_KEY", - ResetMasterPassword: false, access_token: "ACCESS_TOKEN", expires_in: 3600, refresh_token: "REFRESH_TOKEN", @@ -689,7 +686,6 @@ describe("LoginStrategyService", () => { KdfParallelism: 1, Key: "KEY", PrivateKey: "PRIVATE_KEY", - ResetMasterPassword: false, access_token: "ACCESS_TOKEN", expires_in: 3600, refresh_token: "REFRESH_TOKEN", diff --git a/libs/common/src/auth/models/domain/auth-result.ts b/libs/common/src/auth/models/domain/auth-result.ts index 178866901d3..34467a18f2c 100644 --- a/libs/common/src/auth/models/domain/auth-result.ts +++ b/libs/common/src/auth/models/domain/auth-result.ts @@ -7,14 +7,6 @@ import { TwoFactorProviderType } from "../../enums/two-factor-provider-type"; export class AuthResult { userId: UserId; - // TODO: PM-3287 - Remove this after 3 releases of backwards compatibility. - Target release 2023.12 for removal - /** - * @deprecated - * Replace with using UserDecryptionOptions to determine if the user does - * not have a master password and is not using Key Connector. - * */ - resetMasterPassword = false; - twoFactorProviders: Partial<Record<TwoFactorProviderType, Record<string, string>>> = null; ssoEmail2FaSessionToken?: string; email: string; diff --git a/libs/common/src/auth/models/response/identity-token.response.ts b/libs/common/src/auth/models/response/identity-token.response.ts index 59e7eb98ec2..958f520a2c6 100644 --- a/libs/common/src/auth/models/response/identity-token.response.ts +++ b/libs/common/src/auth/models/response/identity-token.response.ts @@ -18,7 +18,6 @@ export class IdentityTokenResponse extends BaseResponse { tokenType: string; // Decryption Information - resetMasterPassword: boolean; privateKey: string; // userKeyEncryptedPrivateKey key?: EncString; // masterKeyEncryptedUserKey twoFactorToken: string; @@ -52,7 +51,6 @@ export class IdentityTokenResponse extends BaseResponse { this.refreshToken = refreshToken; } - this.resetMasterPassword = this.getResponseProperty("ResetMasterPassword"); this.privateKey = this.getResponseProperty("PrivateKey"); const key = this.getResponseProperty("Key"); if (key) { From a12c7a31fd4759e2f61ff6c21d20b46dabc9f35b Mon Sep 17 00:00:00 2001 From: Shane Melton <smelton@bitwarden.com> Date: Wed, 17 Dec 2025 10:30:33 -0800 Subject: [PATCH 102/188] Fix broken valut-settings-v2 tests (#18029) --- .../vault-settings-v2.component.spec.ts | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/apps/browser/src/vault/popup/settings/vault-settings-v2.component.spec.ts b/apps/browser/src/vault/popup/settings/vault-settings-v2.component.spec.ts index fc30a3f8899..15ddb7507fd 100644 --- a/apps/browser/src/vault/popup/settings/vault-settings-v2.component.spec.ts +++ b/apps/browser/src/vault/popup/settings/vault-settings-v2.component.spec.ts @@ -77,11 +77,17 @@ describe("VaultSettingsV2Component", () => { }; beforeEach(async () => { + // Reset BehaviorSubjects to initial values + mockUserCanArchive$.next(false); + mockHasArchiveFlagEnabled$.next(true); + mockArchivedCiphers$.next([]); + mockShowNudgeBadge$.next(false); + mockCipherArchiveService = mock<CipherArchiveService>({ userCanArchive$: jest.fn().mockReturnValue(mockUserCanArchive$), - hasArchiveFlagEnabled$: jest.fn().mockReturnValue(mockHasArchiveFlagEnabled$), archivedCiphers$: jest.fn().mockReturnValue(mockArchivedCiphers$), }); + mockCipherArchiveService.hasArchiveFlagEnabled$ = mockHasArchiveFlagEnabled$.asObservable(); await TestBed.configureTestingModule({ imports: [VaultSettingsV2Component], @@ -133,7 +139,7 @@ describe("VaultSettingsV2Component", () => { const archiveLink = queryByTestId("archive-link"); - expect(archiveLink.nativeElement.getAttribute("routerLink")).toBe("/archive"); + expect(archiveLink?.nativeElement.getAttribute("routerLink")).toBe("/archive"); }); it("routes to archive when user has archived items but cannot archive", async () => { @@ -141,7 +147,7 @@ describe("VaultSettingsV2Component", () => { const premiumArchiveLink = queryByTestId("premium-archive-link"); - premiumArchiveLink.nativeElement.click(); + premiumArchiveLink?.nativeElement.click(); await fixture.whenStable(); expect(router.navigate).toHaveBeenCalledWith(["/archive"]); @@ -150,14 +156,14 @@ describe("VaultSettingsV2Component", () => { it("prompts for premium when user cannot archive and has no archived items", async () => { setArchiveState(false, []); const badge = component["premiumBadgeComponent"](); - jest.spyOn(badge, "promptForPremium"); + jest.spyOn(badge!, "promptForPremium"); const premiumArchiveLink = queryByTestId("premium-archive-link"); - premiumArchiveLink.nativeElement.click(); + premiumArchiveLink?.nativeElement.click(); await fixture.whenStable(); - expect(badge.promptForPremium).toHaveBeenCalled(); + expect(badge!.promptForPremium).toHaveBeenCalled(); }); }); From a10736844a6dae83cbfb3ca8d20042cb097a9388 Mon Sep 17 00:00:00 2001 From: Alex <55413326+AlexRubik@users.noreply.github.com> Date: Wed, 17 Dec 2025 14:06:47 -0500 Subject: [PATCH 103/188] clear card selection when switching tabs (#18001) Call closeDrawer() on tab change to reset invokerId, preventing cards from appearing selected after navigating away and back. (PM-29263) --- .../app/dirt/access-intelligence/risk-insights.component.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 9e6901572c3..4549c15e0c8 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 @@ -194,7 +194,9 @@ export class RiskInsightsComponent implements OnInit, OnDestroy { queryParamsHandling: "merge", }); - // close drawer when tabs are changed + // Reset drawer state and close drawer when tabs are changed + // This ensures card selection state is cleared (PM-29263) + this.dataService.closeDrawer(); this.currentDialogRef?.close(); } From e6e54a0a779adf64dc94f149c1b601e220472c3e Mon Sep 17 00:00:00 2001 From: "bw-ghapp[bot]" <178206702+bw-ghapp[bot]@users.noreply.github.com> Date: Wed, 17 Dec 2025 19:08:06 +0000 Subject: [PATCH 104/188] Autosync the updated translations (#18027) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/web/src/locales/af/messages.json | 3 +++ apps/web/src/locales/ar/messages.json | 3 +++ apps/web/src/locales/az/messages.json | 3 +++ apps/web/src/locales/be/messages.json | 3 +++ apps/web/src/locales/bg/messages.json | 3 +++ apps/web/src/locales/bn/messages.json | 3 +++ apps/web/src/locales/bs/messages.json | 3 +++ apps/web/src/locales/ca/messages.json | 3 +++ apps/web/src/locales/cs/messages.json | 3 +++ apps/web/src/locales/cy/messages.json | 3 +++ apps/web/src/locales/da/messages.json | 3 +++ apps/web/src/locales/de/messages.json | 3 +++ apps/web/src/locales/el/messages.json | 3 +++ apps/web/src/locales/en_GB/messages.json | 3 +++ apps/web/src/locales/en_IN/messages.json | 3 +++ apps/web/src/locales/eo/messages.json | 3 +++ apps/web/src/locales/es/messages.json | 3 +++ apps/web/src/locales/et/messages.json | 3 +++ apps/web/src/locales/eu/messages.json | 3 +++ apps/web/src/locales/fa/messages.json | 3 +++ apps/web/src/locales/fi/messages.json | 3 +++ apps/web/src/locales/fil/messages.json | 3 +++ apps/web/src/locales/fr/messages.json | 3 +++ apps/web/src/locales/gl/messages.json | 3 +++ apps/web/src/locales/he/messages.json | 3 +++ apps/web/src/locales/hi/messages.json | 3 +++ apps/web/src/locales/hr/messages.json | 3 +++ apps/web/src/locales/hu/messages.json | 3 +++ apps/web/src/locales/id/messages.json | 3 +++ apps/web/src/locales/it/messages.json | 3 +++ apps/web/src/locales/ja/messages.json | 3 +++ apps/web/src/locales/ka/messages.json | 3 +++ apps/web/src/locales/km/messages.json | 3 +++ apps/web/src/locales/kn/messages.json | 3 +++ apps/web/src/locales/ko/messages.json | 3 +++ apps/web/src/locales/lv/messages.json | 3 +++ apps/web/src/locales/ml/messages.json | 3 +++ apps/web/src/locales/mr/messages.json | 3 +++ apps/web/src/locales/my/messages.json | 3 +++ apps/web/src/locales/nb/messages.json | 3 +++ apps/web/src/locales/ne/messages.json | 3 +++ apps/web/src/locales/nl/messages.json | 3 +++ apps/web/src/locales/nn/messages.json | 3 +++ apps/web/src/locales/or/messages.json | 3 +++ apps/web/src/locales/pl/messages.json | 3 +++ apps/web/src/locales/pt_BR/messages.json | 3 +++ apps/web/src/locales/pt_PT/messages.json | 3 +++ apps/web/src/locales/ro/messages.json | 3 +++ apps/web/src/locales/ru/messages.json | 3 +++ apps/web/src/locales/si/messages.json | 3 +++ apps/web/src/locales/sk/messages.json | 3 +++ apps/web/src/locales/sl/messages.json | 3 +++ apps/web/src/locales/sr_CS/messages.json | 3 +++ apps/web/src/locales/sr_CY/messages.json | 3 +++ apps/web/src/locales/sv/messages.json | 3 +++ apps/web/src/locales/ta/messages.json | 3 +++ apps/web/src/locales/te/messages.json | 3 +++ apps/web/src/locales/th/messages.json | 3 +++ apps/web/src/locales/tr/messages.json | 3 +++ apps/web/src/locales/uk/messages.json | 3 +++ apps/web/src/locales/vi/messages.json | 3 +++ apps/web/src/locales/zh_CN/messages.json | 5 ++++- apps/web/src/locales/zh_TW/messages.json | 3 +++ 63 files changed, 190 insertions(+), 1 deletion(-) diff --git a/apps/web/src/locales/af/messages.json b/apps/web/src/locales/af/messages.json index d5314274775..0961a8dd5e2 100644 --- a/apps/web/src/locales/af/messages.json +++ b/apps/web/src/locales/af/messages.json @@ -11587,6 +11587,9 @@ "unArchive": { "message": "Unarchive" }, + "unArchiveAndSave": { + "message": "Unarchive and save" + }, "itemsInArchive": { "message": "Items in archive" }, diff --git a/apps/web/src/locales/ar/messages.json b/apps/web/src/locales/ar/messages.json index c85ad682d0a..311490950b6 100644 --- a/apps/web/src/locales/ar/messages.json +++ b/apps/web/src/locales/ar/messages.json @@ -11587,6 +11587,9 @@ "unArchive": { "message": "Unarchive" }, + "unArchiveAndSave": { + "message": "Unarchive and save" + }, "itemsInArchive": { "message": "Items in archive" }, diff --git a/apps/web/src/locales/az/messages.json b/apps/web/src/locales/az/messages.json index 9068514ce55..580bb3beed1 100644 --- a/apps/web/src/locales/az/messages.json +++ b/apps/web/src/locales/az/messages.json @@ -11587,6 +11587,9 @@ "unArchive": { "message": "Arxivdən çıxart" }, + "unArchiveAndSave": { + "message": "Arxivdən çıxart və saxla" + }, "itemsInArchive": { "message": "Arxivdəki elementlər" }, diff --git a/apps/web/src/locales/be/messages.json b/apps/web/src/locales/be/messages.json index b5f7a4dfa18..3bfaeea871c 100644 --- a/apps/web/src/locales/be/messages.json +++ b/apps/web/src/locales/be/messages.json @@ -11587,6 +11587,9 @@ "unArchive": { "message": "Unarchive" }, + "unArchiveAndSave": { + "message": "Unarchive and save" + }, "itemsInArchive": { "message": "Items in archive" }, diff --git a/apps/web/src/locales/bg/messages.json b/apps/web/src/locales/bg/messages.json index a57f9bcebb2..7ec68a1f962 100644 --- a/apps/web/src/locales/bg/messages.json +++ b/apps/web/src/locales/bg/messages.json @@ -11587,6 +11587,9 @@ "unArchive": { "message": "Изваждане от архива" }, + "unArchiveAndSave": { + "message": "Разархивиране и запазване" + }, "itemsInArchive": { "message": "Елементи в архива" }, diff --git a/apps/web/src/locales/bn/messages.json b/apps/web/src/locales/bn/messages.json index f2ff447005c..f6ea3edb275 100644 --- a/apps/web/src/locales/bn/messages.json +++ b/apps/web/src/locales/bn/messages.json @@ -11587,6 +11587,9 @@ "unArchive": { "message": "Unarchive" }, + "unArchiveAndSave": { + "message": "Unarchive and save" + }, "itemsInArchive": { "message": "Items in archive" }, diff --git a/apps/web/src/locales/bs/messages.json b/apps/web/src/locales/bs/messages.json index 63c22b86251..7cd48d3c6ed 100644 --- a/apps/web/src/locales/bs/messages.json +++ b/apps/web/src/locales/bs/messages.json @@ -11587,6 +11587,9 @@ "unArchive": { "message": "Unarchive" }, + "unArchiveAndSave": { + "message": "Unarchive and save" + }, "itemsInArchive": { "message": "Items in archive" }, diff --git a/apps/web/src/locales/ca/messages.json b/apps/web/src/locales/ca/messages.json index 7e6c8e20085..4f9d5e635dc 100644 --- a/apps/web/src/locales/ca/messages.json +++ b/apps/web/src/locales/ca/messages.json @@ -11587,6 +11587,9 @@ "unArchive": { "message": "Unarchive" }, + "unArchiveAndSave": { + "message": "Unarchive and save" + }, "itemsInArchive": { "message": "Items in archive" }, diff --git a/apps/web/src/locales/cs/messages.json b/apps/web/src/locales/cs/messages.json index 923cfb66a14..7cc37f70371 100644 --- a/apps/web/src/locales/cs/messages.json +++ b/apps/web/src/locales/cs/messages.json @@ -11587,6 +11587,9 @@ "unArchive": { "message": "Odebrat z archivu" }, + "unArchiveAndSave": { + "message": "Odebrat z archivu a uložit" + }, "itemsInArchive": { "message": "Položky v archivu" }, diff --git a/apps/web/src/locales/cy/messages.json b/apps/web/src/locales/cy/messages.json index 5a8845a48cc..2fe0598ef20 100644 --- a/apps/web/src/locales/cy/messages.json +++ b/apps/web/src/locales/cy/messages.json @@ -11587,6 +11587,9 @@ "unArchive": { "message": "Unarchive" }, + "unArchiveAndSave": { + "message": "Unarchive and save" + }, "itemsInArchive": { "message": "Items in archive" }, diff --git a/apps/web/src/locales/da/messages.json b/apps/web/src/locales/da/messages.json index 56ccbf6abdf..1d6b5e8df42 100644 --- a/apps/web/src/locales/da/messages.json +++ b/apps/web/src/locales/da/messages.json @@ -11587,6 +11587,9 @@ "unArchive": { "message": "Unarchive" }, + "unArchiveAndSave": { + "message": "Unarchive and save" + }, "itemsInArchive": { "message": "Items in archive" }, diff --git a/apps/web/src/locales/de/messages.json b/apps/web/src/locales/de/messages.json index d4ccac0d49f..d3dd0f712f8 100644 --- a/apps/web/src/locales/de/messages.json +++ b/apps/web/src/locales/de/messages.json @@ -11587,6 +11587,9 @@ "unArchive": { "message": "Nicht mehr archivieren" }, + "unArchiveAndSave": { + "message": "Nicht mehr archivieren und speichern" + }, "itemsInArchive": { "message": "Einträge im Archiv" }, diff --git a/apps/web/src/locales/el/messages.json b/apps/web/src/locales/el/messages.json index 92dd55f4c8b..04d28c866cc 100644 --- a/apps/web/src/locales/el/messages.json +++ b/apps/web/src/locales/el/messages.json @@ -11587,6 +11587,9 @@ "unArchive": { "message": "Unarchive" }, + "unArchiveAndSave": { + "message": "Unarchive and save" + }, "itemsInArchive": { "message": "Items in archive" }, diff --git a/apps/web/src/locales/en_GB/messages.json b/apps/web/src/locales/en_GB/messages.json index e49ac4ac0f6..9c77e913e3e 100644 --- a/apps/web/src/locales/en_GB/messages.json +++ b/apps/web/src/locales/en_GB/messages.json @@ -11587,6 +11587,9 @@ "unArchive": { "message": "Unarchive" }, + "unArchiveAndSave": { + "message": "Unarchive and save" + }, "itemsInArchive": { "message": "Items in archive" }, diff --git a/apps/web/src/locales/en_IN/messages.json b/apps/web/src/locales/en_IN/messages.json index cfcfa1681c8..cdd59507064 100644 --- a/apps/web/src/locales/en_IN/messages.json +++ b/apps/web/src/locales/en_IN/messages.json @@ -11587,6 +11587,9 @@ "unArchive": { "message": "Unarchive" }, + "unArchiveAndSave": { + "message": "Unarchive and save" + }, "itemsInArchive": { "message": "Items in archive" }, diff --git a/apps/web/src/locales/eo/messages.json b/apps/web/src/locales/eo/messages.json index 8dd7923b58a..a9ba4d042f0 100644 --- a/apps/web/src/locales/eo/messages.json +++ b/apps/web/src/locales/eo/messages.json @@ -11587,6 +11587,9 @@ "unArchive": { "message": "Unarchive" }, + "unArchiveAndSave": { + "message": "Unarchive and save" + }, "itemsInArchive": { "message": "Items in archive" }, diff --git a/apps/web/src/locales/es/messages.json b/apps/web/src/locales/es/messages.json index b0778ef02cc..f93bbc02cd4 100644 --- a/apps/web/src/locales/es/messages.json +++ b/apps/web/src/locales/es/messages.json @@ -11587,6 +11587,9 @@ "unArchive": { "message": "Unarchive" }, + "unArchiveAndSave": { + "message": "Unarchive and save" + }, "itemsInArchive": { "message": "Items in archive" }, diff --git a/apps/web/src/locales/et/messages.json b/apps/web/src/locales/et/messages.json index 03e56857413..83fa92c6985 100644 --- a/apps/web/src/locales/et/messages.json +++ b/apps/web/src/locales/et/messages.json @@ -11587,6 +11587,9 @@ "unArchive": { "message": "Unarchive" }, + "unArchiveAndSave": { + "message": "Unarchive and save" + }, "itemsInArchive": { "message": "Items in archive" }, diff --git a/apps/web/src/locales/eu/messages.json b/apps/web/src/locales/eu/messages.json index f28b334e9ff..9d3ba92bec9 100644 --- a/apps/web/src/locales/eu/messages.json +++ b/apps/web/src/locales/eu/messages.json @@ -11587,6 +11587,9 @@ "unArchive": { "message": "Unarchive" }, + "unArchiveAndSave": { + "message": "Unarchive and save" + }, "itemsInArchive": { "message": "Items in archive" }, diff --git a/apps/web/src/locales/fa/messages.json b/apps/web/src/locales/fa/messages.json index 75235dca1b3..be630d5c9f7 100644 --- a/apps/web/src/locales/fa/messages.json +++ b/apps/web/src/locales/fa/messages.json @@ -11587,6 +11587,9 @@ "unArchive": { "message": "Unarchive" }, + "unArchiveAndSave": { + "message": "Unarchive and save" + }, "itemsInArchive": { "message": "Items in archive" }, diff --git a/apps/web/src/locales/fi/messages.json b/apps/web/src/locales/fi/messages.json index adda39d725b..f9e6ea2f4c6 100644 --- a/apps/web/src/locales/fi/messages.json +++ b/apps/web/src/locales/fi/messages.json @@ -11587,6 +11587,9 @@ "unArchive": { "message": "Unarchive" }, + "unArchiveAndSave": { + "message": "Unarchive and save" + }, "itemsInArchive": { "message": "Items in archive" }, diff --git a/apps/web/src/locales/fil/messages.json b/apps/web/src/locales/fil/messages.json index 1a4720094b9..539059d79cf 100644 --- a/apps/web/src/locales/fil/messages.json +++ b/apps/web/src/locales/fil/messages.json @@ -11587,6 +11587,9 @@ "unArchive": { "message": "Unarchive" }, + "unArchiveAndSave": { + "message": "Unarchive and save" + }, "itemsInArchive": { "message": "Items in archive" }, diff --git a/apps/web/src/locales/fr/messages.json b/apps/web/src/locales/fr/messages.json index c2f107b9437..df617a0bff2 100644 --- a/apps/web/src/locales/fr/messages.json +++ b/apps/web/src/locales/fr/messages.json @@ -11587,6 +11587,9 @@ "unArchive": { "message": "Désarchiver" }, + "unArchiveAndSave": { + "message": "Désarchiver et enregistrer" + }, "itemsInArchive": { "message": "Éléments dans l'archive" }, diff --git a/apps/web/src/locales/gl/messages.json b/apps/web/src/locales/gl/messages.json index 6e33295162c..996aa0e899e 100644 --- a/apps/web/src/locales/gl/messages.json +++ b/apps/web/src/locales/gl/messages.json @@ -11587,6 +11587,9 @@ "unArchive": { "message": "Unarchive" }, + "unArchiveAndSave": { + "message": "Unarchive and save" + }, "itemsInArchive": { "message": "Items in archive" }, diff --git a/apps/web/src/locales/he/messages.json b/apps/web/src/locales/he/messages.json index baf7bafeb7a..6f3049535f9 100644 --- a/apps/web/src/locales/he/messages.json +++ b/apps/web/src/locales/he/messages.json @@ -11587,6 +11587,9 @@ "unArchive": { "message": "הסר מהארכיון" }, + "unArchiveAndSave": { + "message": "Unarchive and save" + }, "itemsInArchive": { "message": "פריטים בארכיון" }, diff --git a/apps/web/src/locales/hi/messages.json b/apps/web/src/locales/hi/messages.json index bd4089e240f..8c0f47f487c 100644 --- a/apps/web/src/locales/hi/messages.json +++ b/apps/web/src/locales/hi/messages.json @@ -11587,6 +11587,9 @@ "unArchive": { "message": "Unarchive" }, + "unArchiveAndSave": { + "message": "Unarchive and save" + }, "itemsInArchive": { "message": "Items in archive" }, diff --git a/apps/web/src/locales/hr/messages.json b/apps/web/src/locales/hr/messages.json index 1716bdd25e8..09cf0d78a06 100644 --- a/apps/web/src/locales/hr/messages.json +++ b/apps/web/src/locales/hr/messages.json @@ -11587,6 +11587,9 @@ "unArchive": { "message": "Poništi arhiviranje" }, + "unArchiveAndSave": { + "message": "Unarchive and save" + }, "itemsInArchive": { "message": "Stavke u arhivi" }, diff --git a/apps/web/src/locales/hu/messages.json b/apps/web/src/locales/hu/messages.json index 38720b614fc..395a87e4544 100644 --- a/apps/web/src/locales/hu/messages.json +++ b/apps/web/src/locales/hu/messages.json @@ -11587,6 +11587,9 @@ "unArchive": { "message": "Visszavétel archívumból" }, + "unArchiveAndSave": { + "message": "Archiválás visszavonása és mentés" + }, "itemsInArchive": { "message": "Archiválandó elemek" }, diff --git a/apps/web/src/locales/id/messages.json b/apps/web/src/locales/id/messages.json index 165deb7b917..98bf8edf291 100644 --- a/apps/web/src/locales/id/messages.json +++ b/apps/web/src/locales/id/messages.json @@ -11587,6 +11587,9 @@ "unArchive": { "message": "Unarchive" }, + "unArchiveAndSave": { + "message": "Unarchive and save" + }, "itemsInArchive": { "message": "Items in archive" }, diff --git a/apps/web/src/locales/it/messages.json b/apps/web/src/locales/it/messages.json index 87015d32666..34d17398c3e 100644 --- a/apps/web/src/locales/it/messages.json +++ b/apps/web/src/locales/it/messages.json @@ -11587,6 +11587,9 @@ "unArchive": { "message": "Togli dall'archivio" }, + "unArchiveAndSave": { + "message": "Unarchive and save" + }, "itemsInArchive": { "message": "Elementi archiviati" }, diff --git a/apps/web/src/locales/ja/messages.json b/apps/web/src/locales/ja/messages.json index b7f3426fa6e..0a79c1903e4 100644 --- a/apps/web/src/locales/ja/messages.json +++ b/apps/web/src/locales/ja/messages.json @@ -11587,6 +11587,9 @@ "unArchive": { "message": "Unarchive" }, + "unArchiveAndSave": { + "message": "Unarchive and save" + }, "itemsInArchive": { "message": "Items in archive" }, diff --git a/apps/web/src/locales/ka/messages.json b/apps/web/src/locales/ka/messages.json index 1018f2255fc..3fb27024064 100644 --- a/apps/web/src/locales/ka/messages.json +++ b/apps/web/src/locales/ka/messages.json @@ -11587,6 +11587,9 @@ "unArchive": { "message": "Unarchive" }, + "unArchiveAndSave": { + "message": "Unarchive and save" + }, "itemsInArchive": { "message": "Items in archive" }, diff --git a/apps/web/src/locales/km/messages.json b/apps/web/src/locales/km/messages.json index 26f146809b5..d84c2ae7b30 100644 --- a/apps/web/src/locales/km/messages.json +++ b/apps/web/src/locales/km/messages.json @@ -11587,6 +11587,9 @@ "unArchive": { "message": "Unarchive" }, + "unArchiveAndSave": { + "message": "Unarchive and save" + }, "itemsInArchive": { "message": "Items in archive" }, diff --git a/apps/web/src/locales/kn/messages.json b/apps/web/src/locales/kn/messages.json index 9b4407e3ec6..a90cde5f540 100644 --- a/apps/web/src/locales/kn/messages.json +++ b/apps/web/src/locales/kn/messages.json @@ -11587,6 +11587,9 @@ "unArchive": { "message": "Unarchive" }, + "unArchiveAndSave": { + "message": "Unarchive and save" + }, "itemsInArchive": { "message": "Items in archive" }, diff --git a/apps/web/src/locales/ko/messages.json b/apps/web/src/locales/ko/messages.json index db88018ce26..3b86738b797 100644 --- a/apps/web/src/locales/ko/messages.json +++ b/apps/web/src/locales/ko/messages.json @@ -11587,6 +11587,9 @@ "unArchive": { "message": "Unarchive" }, + "unArchiveAndSave": { + "message": "Unarchive and save" + }, "itemsInArchive": { "message": "Items in archive" }, diff --git a/apps/web/src/locales/lv/messages.json b/apps/web/src/locales/lv/messages.json index 4b1a5307179..7e232de913f 100644 --- a/apps/web/src/locales/lv/messages.json +++ b/apps/web/src/locales/lv/messages.json @@ -11587,6 +11587,9 @@ "unArchive": { "message": "Atcelt arhivēšanu" }, + "unArchiveAndSave": { + "message": "Atcelt arhivēšanu un saglabāt" + }, "itemsInArchive": { "message": "Vienumi arhīvā" }, diff --git a/apps/web/src/locales/ml/messages.json b/apps/web/src/locales/ml/messages.json index 5943f3630a3..01cc78f20e7 100644 --- a/apps/web/src/locales/ml/messages.json +++ b/apps/web/src/locales/ml/messages.json @@ -11587,6 +11587,9 @@ "unArchive": { "message": "Unarchive" }, + "unArchiveAndSave": { + "message": "Unarchive and save" + }, "itemsInArchive": { "message": "Items in archive" }, diff --git a/apps/web/src/locales/mr/messages.json b/apps/web/src/locales/mr/messages.json index 5d938996627..2ead65c89bd 100644 --- a/apps/web/src/locales/mr/messages.json +++ b/apps/web/src/locales/mr/messages.json @@ -11587,6 +11587,9 @@ "unArchive": { "message": "Unarchive" }, + "unArchiveAndSave": { + "message": "Unarchive and save" + }, "itemsInArchive": { "message": "Items in archive" }, diff --git a/apps/web/src/locales/my/messages.json b/apps/web/src/locales/my/messages.json index 26f146809b5..d84c2ae7b30 100644 --- a/apps/web/src/locales/my/messages.json +++ b/apps/web/src/locales/my/messages.json @@ -11587,6 +11587,9 @@ "unArchive": { "message": "Unarchive" }, + "unArchiveAndSave": { + "message": "Unarchive and save" + }, "itemsInArchive": { "message": "Items in archive" }, diff --git a/apps/web/src/locales/nb/messages.json b/apps/web/src/locales/nb/messages.json index 9e5c0a6cbf4..34521b2f58c 100644 --- a/apps/web/src/locales/nb/messages.json +++ b/apps/web/src/locales/nb/messages.json @@ -11587,6 +11587,9 @@ "unArchive": { "message": "Unarchive" }, + "unArchiveAndSave": { + "message": "Unarchive and save" + }, "itemsInArchive": { "message": "Items in archive" }, diff --git a/apps/web/src/locales/ne/messages.json b/apps/web/src/locales/ne/messages.json index 9de1b17c391..17118cef29b 100644 --- a/apps/web/src/locales/ne/messages.json +++ b/apps/web/src/locales/ne/messages.json @@ -11587,6 +11587,9 @@ "unArchive": { "message": "Unarchive" }, + "unArchiveAndSave": { + "message": "Unarchive and save" + }, "itemsInArchive": { "message": "Items in archive" }, diff --git a/apps/web/src/locales/nl/messages.json b/apps/web/src/locales/nl/messages.json index 51ac08fd814..d339f1887e7 100644 --- a/apps/web/src/locales/nl/messages.json +++ b/apps/web/src/locales/nl/messages.json @@ -11587,6 +11587,9 @@ "unArchive": { "message": "Dearchiveren" }, + "unArchiveAndSave": { + "message": "Dearchiveren en opslaan" + }, "itemsInArchive": { "message": "Items in archief" }, diff --git a/apps/web/src/locales/nn/messages.json b/apps/web/src/locales/nn/messages.json index 389bf4516e3..0fba039741e 100644 --- a/apps/web/src/locales/nn/messages.json +++ b/apps/web/src/locales/nn/messages.json @@ -11587,6 +11587,9 @@ "unArchive": { "message": "Unarchive" }, + "unArchiveAndSave": { + "message": "Unarchive and save" + }, "itemsInArchive": { "message": "Items in archive" }, diff --git a/apps/web/src/locales/or/messages.json b/apps/web/src/locales/or/messages.json index 26f146809b5..d84c2ae7b30 100644 --- a/apps/web/src/locales/or/messages.json +++ b/apps/web/src/locales/or/messages.json @@ -11587,6 +11587,9 @@ "unArchive": { "message": "Unarchive" }, + "unArchiveAndSave": { + "message": "Unarchive and save" + }, "itemsInArchive": { "message": "Items in archive" }, diff --git a/apps/web/src/locales/pl/messages.json b/apps/web/src/locales/pl/messages.json index 18207f16ca6..ca0aee0f039 100644 --- a/apps/web/src/locales/pl/messages.json +++ b/apps/web/src/locales/pl/messages.json @@ -11587,6 +11587,9 @@ "unArchive": { "message": "Unarchive" }, + "unArchiveAndSave": { + "message": "Unarchive and save" + }, "itemsInArchive": { "message": "Items in archive" }, diff --git a/apps/web/src/locales/pt_BR/messages.json b/apps/web/src/locales/pt_BR/messages.json index 297afd6f0bb..959de6d0f2a 100644 --- a/apps/web/src/locales/pt_BR/messages.json +++ b/apps/web/src/locales/pt_BR/messages.json @@ -11587,6 +11587,9 @@ "unArchive": { "message": "Desarquivar" }, + "unArchiveAndSave": { + "message": "Desarquivar e salvar" + }, "itemsInArchive": { "message": "Itens no arquivo" }, diff --git a/apps/web/src/locales/pt_PT/messages.json b/apps/web/src/locales/pt_PT/messages.json index a3a5a627215..0810368879c 100644 --- a/apps/web/src/locales/pt_PT/messages.json +++ b/apps/web/src/locales/pt_PT/messages.json @@ -11587,6 +11587,9 @@ "unArchive": { "message": "Desarquivar" }, + "unArchiveAndSave": { + "message": "Desarquivar e guardar" + }, "itemsInArchive": { "message": "Itens no arquivo" }, diff --git a/apps/web/src/locales/ro/messages.json b/apps/web/src/locales/ro/messages.json index 1a573ef82fe..2738efe1169 100644 --- a/apps/web/src/locales/ro/messages.json +++ b/apps/web/src/locales/ro/messages.json @@ -11587,6 +11587,9 @@ "unArchive": { "message": "Unarchive" }, + "unArchiveAndSave": { + "message": "Unarchive and save" + }, "itemsInArchive": { "message": "Items in archive" }, diff --git a/apps/web/src/locales/ru/messages.json b/apps/web/src/locales/ru/messages.json index 0d7ccc71c00..e62657ee432 100644 --- a/apps/web/src/locales/ru/messages.json +++ b/apps/web/src/locales/ru/messages.json @@ -11587,6 +11587,9 @@ "unArchive": { "message": "Разархивировать" }, + "unArchiveAndSave": { + "message": "Разархивировать и сохранить" + }, "itemsInArchive": { "message": "Элементы в архиве" }, diff --git a/apps/web/src/locales/si/messages.json b/apps/web/src/locales/si/messages.json index f896cc8c79a..e9ed3f035d3 100644 --- a/apps/web/src/locales/si/messages.json +++ b/apps/web/src/locales/si/messages.json @@ -11587,6 +11587,9 @@ "unArchive": { "message": "Unarchive" }, + "unArchiveAndSave": { + "message": "Unarchive and save" + }, "itemsInArchive": { "message": "Items in archive" }, diff --git a/apps/web/src/locales/sk/messages.json b/apps/web/src/locales/sk/messages.json index cb0adb25c0a..fc71b3caf00 100644 --- a/apps/web/src/locales/sk/messages.json +++ b/apps/web/src/locales/sk/messages.json @@ -11587,6 +11587,9 @@ "unArchive": { "message": "Zrušiť archiváciu" }, + "unArchiveAndSave": { + "message": "Unarchive and save" + }, "itemsInArchive": { "message": "Položky v archíve" }, diff --git a/apps/web/src/locales/sl/messages.json b/apps/web/src/locales/sl/messages.json index 8b86e7b6060..0ff309c5e0e 100644 --- a/apps/web/src/locales/sl/messages.json +++ b/apps/web/src/locales/sl/messages.json @@ -11587,6 +11587,9 @@ "unArchive": { "message": "Unarchive" }, + "unArchiveAndSave": { + "message": "Unarchive and save" + }, "itemsInArchive": { "message": "Items in archive" }, diff --git a/apps/web/src/locales/sr_CS/messages.json b/apps/web/src/locales/sr_CS/messages.json index 1ff92f67064..aa5436ed321 100644 --- a/apps/web/src/locales/sr_CS/messages.json +++ b/apps/web/src/locales/sr_CS/messages.json @@ -11587,6 +11587,9 @@ "unArchive": { "message": "Unarchive" }, + "unArchiveAndSave": { + "message": "Unarchive and save" + }, "itemsInArchive": { "message": "Items in archive" }, diff --git a/apps/web/src/locales/sr_CY/messages.json b/apps/web/src/locales/sr_CY/messages.json index e1d19054ac3..152ec208e16 100644 --- a/apps/web/src/locales/sr_CY/messages.json +++ b/apps/web/src/locales/sr_CY/messages.json @@ -11587,6 +11587,9 @@ "unArchive": { "message": "Врати из архиве" }, + "unArchiveAndSave": { + "message": "Unarchive and save" + }, "itemsInArchive": { "message": "Ставке у архиви" }, diff --git a/apps/web/src/locales/sv/messages.json b/apps/web/src/locales/sv/messages.json index 7cca1d484b7..db44953b83f 100644 --- a/apps/web/src/locales/sv/messages.json +++ b/apps/web/src/locales/sv/messages.json @@ -11587,6 +11587,9 @@ "unArchive": { "message": "Avarkivera" }, + "unArchiveAndSave": { + "message": "Avarkivera och spara" + }, "itemsInArchive": { "message": "Objekt i arkivet" }, diff --git a/apps/web/src/locales/ta/messages.json b/apps/web/src/locales/ta/messages.json index 62e28d08097..5e1441bc316 100644 --- a/apps/web/src/locales/ta/messages.json +++ b/apps/web/src/locales/ta/messages.json @@ -11587,6 +11587,9 @@ "unArchive": { "message": "Unarchive" }, + "unArchiveAndSave": { + "message": "Unarchive and save" + }, "itemsInArchive": { "message": "Items in archive" }, diff --git a/apps/web/src/locales/te/messages.json b/apps/web/src/locales/te/messages.json index 26f146809b5..d84c2ae7b30 100644 --- a/apps/web/src/locales/te/messages.json +++ b/apps/web/src/locales/te/messages.json @@ -11587,6 +11587,9 @@ "unArchive": { "message": "Unarchive" }, + "unArchiveAndSave": { + "message": "Unarchive and save" + }, "itemsInArchive": { "message": "Items in archive" }, diff --git a/apps/web/src/locales/th/messages.json b/apps/web/src/locales/th/messages.json index 431c7fc014c..a8b00018d8c 100644 --- a/apps/web/src/locales/th/messages.json +++ b/apps/web/src/locales/th/messages.json @@ -11587,6 +11587,9 @@ "unArchive": { "message": "Unarchive" }, + "unArchiveAndSave": { + "message": "Unarchive and save" + }, "itemsInArchive": { "message": "Items in archive" }, diff --git a/apps/web/src/locales/tr/messages.json b/apps/web/src/locales/tr/messages.json index 99e1225731f..079557adf81 100644 --- a/apps/web/src/locales/tr/messages.json +++ b/apps/web/src/locales/tr/messages.json @@ -11587,6 +11587,9 @@ "unArchive": { "message": "Arşivden çıkar" }, + "unArchiveAndSave": { + "message": "Arşivden çıkar ve kaydet" + }, "itemsInArchive": { "message": "Arşivdeki kayıtlar" }, diff --git a/apps/web/src/locales/uk/messages.json b/apps/web/src/locales/uk/messages.json index b269058799d..bcb7818fdbd 100644 --- a/apps/web/src/locales/uk/messages.json +++ b/apps/web/src/locales/uk/messages.json @@ -11587,6 +11587,9 @@ "unArchive": { "message": "Unarchive" }, + "unArchiveAndSave": { + "message": "Unarchive and save" + }, "itemsInArchive": { "message": "Items in archive" }, diff --git a/apps/web/src/locales/vi/messages.json b/apps/web/src/locales/vi/messages.json index 7397aed6f94..83c31ac0471 100644 --- a/apps/web/src/locales/vi/messages.json +++ b/apps/web/src/locales/vi/messages.json @@ -11587,6 +11587,9 @@ "unArchive": { "message": "Hủy lưu trữ" }, + "unArchiveAndSave": { + "message": "Unarchive and save" + }, "itemsInArchive": { "message": "Các mục 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 073afec7fe6..f60ad672ced 100644 --- a/apps/web/src/locales/zh_CN/messages.json +++ b/apps/web/src/locales/zh_CN/messages.json @@ -245,7 +245,7 @@ } }, "applicationsMarkedAsCriticalFail": { - "message": "无法将应用程序标记为关键" + "message": "将应用程序标记为关键失败" }, "application": { "message": "应用程序" @@ -11587,6 +11587,9 @@ "unArchive": { "message": "取消归档" }, + "unArchiveAndSave": { + "message": "取消归档并保存" + }, "itemsInArchive": { "message": "归档中的项目" }, diff --git a/apps/web/src/locales/zh_TW/messages.json b/apps/web/src/locales/zh_TW/messages.json index 364389a1535..7608d8f6b86 100644 --- a/apps/web/src/locales/zh_TW/messages.json +++ b/apps/web/src/locales/zh_TW/messages.json @@ -11587,6 +11587,9 @@ "unArchive": { "message": "取消封存" }, + "unArchiveAndSave": { + "message": "Unarchive and save" + }, "itemsInArchive": { "message": "封存中的項目" }, From e4a2e72616c0c041274eb58cfdecf0ed27a3ad15 Mon Sep 17 00:00:00 2001 From: Daniel James Smith <2670567+djsmith85@users.noreply.github.com> Date: Wed, 17 Dec 2025 20:09:07 +0100 Subject: [PATCH 105/188] Update command to reset nx (#18022) Co-authored-by: Daniel James Smith <djsmith85@users.noreply.github.com> --- libs/nx-plugin/docs/using-the-basic-lib-generator.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/nx-plugin/docs/using-the-basic-lib-generator.md b/libs/nx-plugin/docs/using-the-basic-lib-generator.md index 34834f9544f..b4a6d99ef2c 100644 --- a/libs/nx-plugin/docs/using-the-basic-lib-generator.md +++ b/libs/nx-plugin/docs/using-the-basic-lib-generator.md @@ -78,7 +78,7 @@ After generating your library: ### Issue: TypeScript path mapping not working -**Solution**: Run `nx reset` to clear the Nx cache, then try importing from your library again. +**Solution**: Run `npx nx reset` to clear the Nx cache, then try importing from your library again. ## Extending the Generated Code From 8087a972d52219f9b3aac1d181ac3bebaa12c885 Mon Sep 17 00:00:00 2001 From: "bw-ghapp[bot]" <178206702+bw-ghapp[bot]@users.noreply.github.com> Date: Wed, 17 Dec 2025 19:09:11 +0000 Subject: [PATCH 106/188] Autosync the updated translations (#18025) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/desktop/src/locales/es/messages.json | 128 +++++++++---------- apps/desktop/src/locales/sk/messages.json | 2 +- apps/desktop/src/locales/zh_CN/messages.json | 2 +- 3 files changed, 66 insertions(+), 66 deletions(-) diff --git a/apps/desktop/src/locales/es/messages.json b/apps/desktop/src/locales/es/messages.json index e8bf822b015..ab6bb68cc82 100644 --- a/apps/desktop/src/locales/es/messages.json +++ b/apps/desktop/src/locales/es/messages.json @@ -70,7 +70,7 @@ } }, "noEditPermissions": { - "message": "You don't have permission to edit this item" + "message": "No tienes permiso para editar este elemento" }, "welcomeBack": { "message": "Bienvenido de nuevo" @@ -706,22 +706,22 @@ "message": "El adjunto se ha guardado." }, "addAttachment": { - "message": "Add attachment" + "message": "Añadir adjunto" }, "itemsTransferred": { "message": "Items transferred" }, "fixEncryption": { - "message": "Fix encryption" + "message": "Corregir cifrado" }, "fixEncryptionTooltip": { - "message": "This file is using an outdated encryption method." + "message": "El archivo está usando un método de cifrado obsoleto." }, "attachmentUpdated": { - "message": "Attachment updated" + "message": "Adjunto actualizado" }, "maxFileSizeSansPunctuation": { - "message": "Maximum file size is 500 MB" + "message": "El tamaño máximo del archivo es de 500 MB" }, "file": { "message": "Archivo" @@ -921,10 +921,10 @@ "message": "Ha ocurrido un error inesperado." }, "unexpectedErrorShort": { - "message": "Unexpected error" + "message": "Error inesperado" }, "closeThisBitwardenWindow": { - "message": "Close this Bitwarden window and try again." + "message": "Cierra esta ventana de Bitwarden e inténtalo de nuevo." }, "itemInformation": { "message": "Información del elemento" @@ -1057,7 +1057,7 @@ "message": "Debes añadir o bien la URL del servidor base, o al menos un entorno personalizado." }, "selfHostedEnvMustUseHttps": { - "message": "URLs must use HTTPS." + "message": "Las URLs deben usar HTTPS." }, "customEnvironment": { "message": "Entorno personalizado" @@ -1112,7 +1112,7 @@ "message": "Más información" }, "migrationsFailed": { - "message": "An error occurred updating the encryption settings." + "message": "Se ha producido un error al actualizar los ajustes de cifrado." }, "updateEncryptionSettingsTitle": { "message": "Update your encryption settings" @@ -1121,13 +1121,13 @@ "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." }, "confirmIdentityToContinue": { - "message": "Confirm your identity to continue" + "message": "Confirma tu identidad para continuar" }, "enterYourMasterPassword": { - "message": "Enter your master password" + "message": "Introduce tu contraseña maestra" }, "updateSettings": { - "message": "Update settings" + "message": "Actualizar ajustes" }, "featureUnavailable": { "message": "Característica no disponible" @@ -1199,7 +1199,7 @@ "message": "Síguenos" }, "syncNow": { - "message": "Sync now" + "message": "Sincronizar ahora" }, "changeMasterPass": { "message": "Cambiar contraseña maestra" @@ -1268,7 +1268,7 @@ "message": "Contraseña maestra no válida" }, "invalidMasterPasswordConfirmEmailAndHost": { - "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "message": "Contraseña maestra incorrecta. Confirma que tu correo electrónico es correcto y que tu cuenta fue creada en $HOST$.", "placeholders": { "host": { "content": "$1", @@ -1776,10 +1776,10 @@ "message": "Exportar desde" }, "export": { - "message": "Export" + "message": "Exportar" }, "import": { - "message": "Import" + "message": "Importar" }, "fileFormat": { "message": "Formato de archivo" @@ -3139,18 +3139,18 @@ "message": "You denied a login attempt from another device. If this was you, try to log in with the device again." }, "webApp": { - "message": "Web app" + "message": "Aplicación web" }, "mobile": { - "message": "Mobile", + "message": "Móvil", "description": "Mobile app" }, "extension": { - "message": "Extension", + "message": "Extensión", "description": "Browser extension/addon" }, "desktop": { - "message": "Desktop", + "message": "Escritorio", "description": "Desktop app" }, "cli": { @@ -3161,7 +3161,7 @@ "description": "Software Development Kit" }, "server": { - "message": "Server" + "message": "Servidor" }, "loginRequest": { "message": "Login request" @@ -3901,7 +3901,7 @@ "message": "File saved to device. Manage from your device downloads." }, "importantNotice": { - "message": "Important notice" + "message": "Aviso importante" }, "setupTwoStepLogin": { "message": "Set up two-step login" @@ -3913,10 +3913,10 @@ "message": "You can set up two-step login as an alternative way to protect your account or change your email to one you can access." }, "remindMeLater": { - "message": "Remind me later" + "message": "Recuérdame más tarde" }, "newDeviceVerificationNoticePageOneFormContent": { - "message": "Do you have reliable access to your email, $EMAIL$?", + "message": "¿Tienes acceso fiable a tu correo electrónico, $EMAIL$?", "placeholders": { "email": { "content": "$1", @@ -3925,7 +3925,7 @@ } }, "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "No, I do not" + "message": "No, no lo tengo" }, "newDeviceVerificationNoticePageOneEmailAccessYes": { "message": "Yes, I can reliably access my email" @@ -3937,37 +3937,37 @@ "message": "Change account email" }, "passkeyLogin": { - "message": "Log in with passkey?" + "message": "¿Iniciar sesión con clave de acceso?" }, "savePasskeyQuestion": { - "message": "Save passkey?" + "message": "¿Guardar clave de acceso?" }, "saveNewPasskey": { - "message": "Save as new login" + "message": "Guardar como nuevo inicio de sesión" }, "savePasskeyNewLogin": { - "message": "Save passkey as new login" + "message": "Guardar clave de acceso como nuevo inicio de sesión" }, "noMatchingLoginsForSite": { - "message": "No matching logins for this site" + "message": "No hay inicios de sesión coincidentes para este sitio" }, "overwritePasskey": { - "message": "Overwrite passkey?" + "message": "¿Sobrescribir clave de acceso?" }, "unableToSavePasskey": { - "message": "Unable to save passkey" + "message": "No se puede guardar la clave de acceso" }, "alreadyContainsPasskey": { - "message": "This item already contains a passkey. Are you sure you want to overwrite the current passkey?" + "message": "Este elemento ya contiene una clave de acceso. ¿Estás seguro de que quieres sobrescribir la clave de acceso actual?" }, "passkeyAlreadyExists": { - "message": "A passkey already exists for this application." + "message": "Ya existe una clave de acceso para esta aplicación." }, "applicationDoesNotSupportDuplicates": { - "message": "This application does not support duplicates." + "message": "Esta aplicación no soporta duplicados." }, "closeThisWindow": { - "message": "Close this window" + "message": "Cerrar esta ventana" }, "allowScreenshots": { "message": "Permitir captura de pantalla" @@ -4232,10 +4232,10 @@ } }, "showMore": { - "message": "Show more" + "message": "Mostrar más" }, "showLess": { - "message": "Show less" + "message": "Mostrar menos" }, "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." @@ -4247,20 +4247,20 @@ "message": "Include one or two of the following modifiers: Ctrl, Alt, Win, or Shift, and a letter." }, "invalidShortcut": { - "message": "Invalid shortcut" + "message": "Atajo inválido" }, "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." }, "next": { - "message": "Next" + "message": "Siguiente" }, "confirmKeyConnectorDomain": { "message": "Confirm Key Connector domain" }, "confirm": { - "message": "Confirm" + "message": "Confirmar" }, "enableAutotypeShortcutPreview": { "message": "Enable autotype shortcut (Feature Preview)" @@ -4269,18 +4269,18 @@ "message": "Be sure you are in the correct field before using the shortcut to avoid filling data into the wrong place." }, "editShortcut": { - "message": "Edit shortcut" + "message": "Editar atajo" }, "archiveNoun": { - "message": "Archive", + "message": "Archivo", "description": "Noun" }, "archiveVerb": { - "message": "Archive", + "message": "Archivar", "description": "Verb" }, "unArchive": { - "message": "Unarchive" + "message": "Desarchivar" }, "itemsInArchive": { "message": "Items in archive" @@ -4304,13 +4304,13 @@ "message": "Archived items are excluded from general search results and autofill suggestions. Are you sure you want to archive this item?" }, "zipPostalCodeLabel": { - "message": "ZIP / Postal code" + "message": "ZIP / Código postal" }, "cardNumberLabel": { - "message": "Card number" + "message": "Número de tarjeta" }, "upgradeNow": { - "message": "Upgrade now" + "message": "Actualizar ahora" }, "builtInAuthenticator": { "message": "Built-in authenticator" @@ -4319,58 +4319,58 @@ "message": "Secure file storage" }, "emergencyAccess": { - "message": "Emergency access" + "message": "Acceso de emergencia" }, "breachMonitoring": { "message": "Breach monitoring" }, "andMoreFeatures": { - "message": "And more!" + "message": "¡Y más!" }, "advancedOnlineSecurity": { "message": "Advanced online security" }, "upgradeToPremium": { - "message": "Upgrade to Premium" + "message": "Actualizar a Premium" }, "removeMasterPasswordForOrgUserKeyConnector": { "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." }, "continueWithLogIn": { - "message": "Continue with log in" + "message": "Continuar con el inicio de sesión" }, "doNotContinue": { - "message": "Do not continue" + "message": "No continuar" }, "domain": { - "message": "Domain" + "message": "Dominio" }, "keyConnectorDomainTooltip": { "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." }, "verifyYourOrganization": { - "message": "Verify your organization to log in" + "message": "Verifica tu organización para iniciar sesión" }, "organizationVerified": { - "message": "Organization verified" + "message": "Organización verificada" }, "domainVerified": { - "message": "Domain verified" + "message": "Dominio verificado" }, "leaveOrganizationContent": { - "message": "If you don't verify your organization, your access to the organization will be revoked." + "message": "Si no verificas tu organización, tu acceso a la organización será revocado." }, "leaveNow": { - "message": "Leave now" + "message": "Salir ahora" }, "verifyYourDomainToLogin": { - "message": "Verify your domain to log in" + "message": "Verifica tu dominio para iniciar sesión" }, "verifyYourDomainDescription": { - "message": "To continue with log in, verify this domain." + "message": "Para continuar con el inicio de sesión, verifica este dominio." }, "confirmKeyConnectorOrganizationUserDescription": { - "message": "To continue with log in, verify the organization and domain." + "message": "Para continuar con el inicio de sesión, verifica la organización y el dominio." }, "sessionTimeoutSettingsAction": { "message": "Timeout action" @@ -4414,7 +4414,7 @@ } }, "sessionTimeoutOnRestart": { - "message": "On restart" + "message": "Al reiniciar" }, "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { "message": "Set an unlock method to change your timeout action" diff --git a/apps/desktop/src/locales/sk/messages.json b/apps/desktop/src/locales/sk/messages.json index d01591e1734..a26cd60b451 100644 --- a/apps/desktop/src/locales/sk/messages.json +++ b/apps/desktop/src/locales/sk/messages.json @@ -4462,7 +4462,7 @@ } }, "acceptTransfer": { - "message": "Prijať prevody" + "message": "Prijať prenos" }, "declineAndLeave": { "message": "Zamietnuť a odísť" diff --git a/apps/desktop/src/locales/zh_CN/messages.json b/apps/desktop/src/locales/zh_CN/messages.json index 507be48bf04..0709e19a468 100644 --- a/apps/desktop/src/locales/zh_CN/messages.json +++ b/apps/desktop/src/locales/zh_CN/messages.json @@ -1883,7 +1883,7 @@ "message": "无效的 PIN 码。" }, "tooManyInvalidPinEntryAttemptsLoggingOut": { - "message": "无效的 PIN 输入尝试次数过多,正在注销。" + "message": "无效的 PIN 输入尝试次数过多。正在注销。" }, "unlockWithWindowsHello": { "message": "使用 Windows Hello 解锁" From 930cb9ab96dd2b18ea094653779cc091dc49391b Mon Sep 17 00:00:00 2001 From: Alex Dragovich <46065570+itsadrago@users.noreply.github.com> Date: Wed, 17 Dec 2025 11:24:44 -0800 Subject: [PATCH 107/188] [PM-29896] specify noun or verb for Import / Export terms (#18012) * [PM-29896] specify noun or verb for Import / Export terms * [PM-29896] adjust verb to noun --- apps/browser/src/_locales/en/messages.json | 18 ++++++++++++++---- .../export/export-browser-v2.component.html | 4 ++-- .../import/import-browser-v2.component.html | 4 ++-- .../settings/vault-settings-v2.component.html | 4 ++-- .../tools/export/export-desktop.component.html | 4 ++-- .../tools/import/import-desktop.component.html | 4 ++-- apps/desktop/src/locales/en/messages.json | 18 ++++++++++++++---- apps/desktop/src/main/menu/menu.file.ts | 4 ++-- .../layouts/organization-layout.component.html | 4 ++-- .../organization-settings-routing.module.ts | 4 ++-- .../src/app/layouts/user-layout.component.html | 4 ++-- apps/web/src/app/oss-routing.module.ts | 4 ++-- .../app/tools/import/import-web.component.html | 2 +- .../app/tools/import/org-import.component.html | 2 +- .../vault-export/export-web.component.html | 2 +- .../org-vault-export.component.html | 2 +- apps/web/src/locales/en/messages.json | 18 ++++++++++++++---- .../layout/navigation.component.html | 4 ++-- .../settings/porting/sm-export.component.html | 2 +- .../settings/porting/sm-export.component.ts | 2 +- .../settings/porting/sm-import.component.html | 2 +- .../settings/settings-routing.module.ts | 4 ++-- .../dialog/file-password-prompt.component.html | 2 +- 23 files changed, 74 insertions(+), 44 deletions(-) diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index 2ace8ff8b96..2b103555604 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -1322,11 +1322,21 @@ "exportFrom": { "message": "Export from" }, - "export": { - "message": "Export" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, - "import": { - "message": "Import" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "File format" diff --git a/apps/browser/src/tools/popup/settings/export/export-browser-v2.component.html b/apps/browser/src/tools/popup/settings/export/export-browser-v2.component.html index 5473bbe620e..36c84d9b788 100644 --- a/apps/browser/src/tools/popup/settings/export/export-browser-v2.component.html +++ b/apps/browser/src/tools/popup/settings/export/export-browser-v2.component.html @@ -1,5 +1,5 @@ <popup-page> - <popup-header slot="header" [pageTitle]="'export' | i18n" showBackButton> + <popup-header slot="header" [pageTitle]="'exportNoun' | i18n" showBackButton> <ng-container slot="end"> <app-pop-out></app-pop-out> </ng-container> @@ -21,7 +21,7 @@ bitFormButton buttonType="primary" > - {{ "export" | i18n }} + {{ "exportVerb" | i18n }} </button> <button bitButton type="button" buttonType="secondary" [popupBackAction]> {{ "cancel" | i18n }} diff --git a/apps/browser/src/tools/popup/settings/import/import-browser-v2.component.html b/apps/browser/src/tools/popup/settings/import/import-browser-v2.component.html index db58e3f0227..f9a888a6ea8 100644 --- a/apps/browser/src/tools/popup/settings/import/import-browser-v2.component.html +++ b/apps/browser/src/tools/popup/settings/import/import-browser-v2.component.html @@ -1,5 +1,5 @@ <popup-page> - <popup-header slot="header" [pageTitle]="'import' | i18n" showBackButton> + <popup-header slot="header" [pageTitle]="'importNoun' | i18n" showBackButton> <ng-container slot="end"> <app-pop-out></app-pop-out> </ng-container> @@ -22,7 +22,7 @@ bitFormButton buttonType="primary" > - {{ "import" | i18n }} + {{ "importVerb" | i18n }} </button> </popup-footer> </popup-page> 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 407015d3a06..d5b94df5008 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 @@ -15,7 +15,7 @@ <bit-item> <button type="button" bit-item-content (click)="import()"> <div class="tw-flex tw-items-center tw-justify-center tw-gap-2"> - <p>{{ "import" | i18n }}</p> + <p>{{ "importNoun" | i18n }}</p> <span *ngIf="emptyVaultImportBadge$ | async" bitBadge @@ -30,7 +30,7 @@ </bit-item> <bit-item> <a bit-item-content routerLink="/export"> - {{ "export" | i18n }} + {{ "exportNoun" | i18n }} <i slot="end" class="bwi bwi-angle-right" aria-hidden="true"></i> </a> </bit-item> diff --git a/apps/desktop/src/app/tools/export/export-desktop.component.html b/apps/desktop/src/app/tools/export/export-desktop.component.html index a969b86b950..5f4f0058577 100644 --- a/apps/desktop/src/app/tools/export/export-desktop.component.html +++ b/apps/desktop/src/app/tools/export/export-desktop.component.html @@ -1,5 +1,5 @@ <bit-dialog #dialog dialogSize="large"> - <span bitDialogTitle>{{ "export" | i18n }}</span> + <span bitDialogTitle>{{ "exportNoun" | i18n }}</span> <ng-container bitDialogContent> <tools-export (formLoading)="this.loading = $event" @@ -17,7 +17,7 @@ bitFormButton buttonType="primary" > - {{ "export" | i18n }} + {{ "exportVerb" | i18n }} </button> <button type="button" bitButton bitFormButton buttonType="secondary" bitDialogClose> {{ "cancel" | i18n }} diff --git a/apps/desktop/src/app/tools/import/import-desktop.component.html b/apps/desktop/src/app/tools/import/import-desktop.component.html index b5011f4243e..0f6fb9a1c5e 100644 --- a/apps/desktop/src/app/tools/import/import-desktop.component.html +++ b/apps/desktop/src/app/tools/import/import-desktop.component.html @@ -1,5 +1,5 @@ <bit-dialog #dialog dialogSize="large" background="alt"> - <span bitDialogTitle>{{ "import" | i18n }}</span> + <span bitDialogTitle>{{ "importNoun" | i18n }}</span> <ng-container bitDialogContent> <div class="tw-relative"> <tools-import @@ -27,7 +27,7 @@ bitFormButton buttonType="primary" > - {{ "import" | i18n }} + {{ "importVerb" | i18n }} </button> <button type="button" bitButton bitFormButton buttonType="secondary" bitDialogClose> {{ "cancel" | i18n }} diff --git a/apps/desktop/src/locales/en/messages.json b/apps/desktop/src/locales/en/messages.json index 3659c75bfca..d26a46a9efe 100644 --- a/apps/desktop/src/locales/en/messages.json +++ b/apps/desktop/src/locales/en/messages.json @@ -1775,11 +1775,21 @@ "exportFrom": { "message": "Export from" }, - "export": { - "message": "Export" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" }, - "import": { - "message": "Import" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "File format" diff --git a/apps/desktop/src/main/menu/menu.file.ts b/apps/desktop/src/main/menu/menu.file.ts index eb5f5a9d747..d5e8cb468f9 100644 --- a/apps/desktop/src/main/menu/menu.file.ts +++ b/apps/desktop/src/main/menu/menu.file.ts @@ -156,7 +156,7 @@ export class FileMenu extends FirstMenu implements IMenubarMenu { private get importVault(): MenuItemConstructorOptions { return { id: "import", - label: this.localize("import"), + label: this.localize("importNoun"), click: () => this.sendMessage("importVault"), enabled: !this._isLocked, }; @@ -165,7 +165,7 @@ export class FileMenu extends FirstMenu implements IMenubarMenu { private get exportVault(): MenuItemConstructorOptions { return { id: "export", - label: this.localize("export"), + label: this.localize("exportNoun"), click: () => this.sendMessage("exportVault"), enabled: !this._isLocked, }; diff --git a/apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.html b/apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.html index 85bfe0bce0a..198cb3a47cd 100644 --- a/apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.html +++ b/apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.html @@ -104,12 +104,12 @@ *ngIf="organization.use2fa && organization.isOwner" ></bit-nav-item> <bit-nav-item - [text]="'import' | i18n" + [text]="'importNoun' | i18n" route="settings/tools/import" *ngIf="organization.canAccessImport" ></bit-nav-item> <bit-nav-item - [text]="'export' | i18n" + [text]="'exportNoun' | i18n" route="settings/tools/export" *ngIf="canAccessExport$ | async" ></bit-nav-item> diff --git a/apps/web/src/app/admin-console/organizations/settings/organization-settings-routing.module.ts b/apps/web/src/app/admin-console/organizations/settings/organization-settings-routing.module.ts index 61d7d0e04ac..2fb3703ee6b 100644 --- a/apps/web/src/app/admin-console/organizations/settings/organization-settings-routing.module.ts +++ b/apps/web/src/app/admin-console/organizations/settings/organization-settings-routing.module.ts @@ -57,7 +57,7 @@ const routes: Routes = [ ), canActivate: [organizationPermissionsGuard((org) => org.canAccessImport)], data: { - titleId: "import", + titleId: "importNoun", }, }, { @@ -68,7 +68,7 @@ const routes: Routes = [ ), canActivate: [organizationPermissionsGuard((org) => org.canAccessExport)], data: { - titleId: "export", + titleId: "exportNoun", }, }, ], diff --git a/apps/web/src/app/layouts/user-layout.component.html b/apps/web/src/app/layouts/user-layout.component.html index 27955a2b0ca..10f569e2558 100644 --- a/apps/web/src/app/layouts/user-layout.component.html +++ b/apps/web/src/app/layouts/user-layout.component.html @@ -6,8 +6,8 @@ <bit-nav-item icon="bwi-send" [text]="'send' | i18n" route="sends"></bit-nav-item> <bit-nav-group icon="bwi-wrench" [text]="'tools' | i18n" route="tools"> <bit-nav-item [text]="'generator' | i18n" route="tools/generator"></bit-nav-item> - <bit-nav-item [text]="'import' | i18n" route="tools/import"></bit-nav-item> - <bit-nav-item [text]="'export' | i18n" route="tools/export"></bit-nav-item> + <bit-nav-item [text]="'importNoun' | i18n" route="tools/import"></bit-nav-item> + <bit-nav-item [text]="'exportNoun' | i18n" route="tools/export"></bit-nav-item> </bit-nav-group> <bit-nav-item icon="bwi-sliders" [text]="'reports' | i18n" route="reports"></bit-nav-item> <bit-nav-group icon="bwi-cog" [text]="'settings' | i18n" route="settings"> diff --git a/apps/web/src/app/oss-routing.module.ts b/apps/web/src/app/oss-routing.module.ts index b97cbcac72a..f4fd55bd1e6 100644 --- a/apps/web/src/app/oss-routing.module.ts +++ b/apps/web/src/app/oss-routing.module.ts @@ -748,7 +748,7 @@ const routes: Routes = [ loadComponent: () => import("./tools/import/import-web.component").then((mod) => mod.ImportWebComponent), data: { - titleId: "import", + titleId: "importNoun", } satisfies RouteDataProperties, }, { @@ -758,7 +758,7 @@ const routes: Routes = [ (mod) => mod.ExportWebComponent, ), data: { - titleId: "export", + titleId: "exportNoun", } satisfies RouteDataProperties, }, { diff --git a/apps/web/src/app/tools/import/import-web.component.html b/apps/web/src/app/tools/import/import-web.component.html index d6f7e0db0cd..b5d079a7c8e 100644 --- a/apps/web/src/app/tools/import/import-web.component.html +++ b/apps/web/src/app/tools/import/import-web.component.html @@ -15,6 +15,6 @@ bitFormButton buttonType="primary" > - {{ "import" | i18n }} + {{ "importVerb" | i18n }} </button> </bit-container> diff --git a/apps/web/src/app/tools/import/org-import.component.html b/apps/web/src/app/tools/import/org-import.component.html index 00e4a7690a2..b579c41c4fd 100644 --- a/apps/web/src/app/tools/import/org-import.component.html +++ b/apps/web/src/app/tools/import/org-import.component.html @@ -16,6 +16,6 @@ bitFormButton buttonType="primary" > - {{ "import" | i18n }} + {{ "importVerb" | i18n }} </button> </bit-container> diff --git a/apps/web/src/app/tools/vault-export/export-web.component.html b/apps/web/src/app/tools/vault-export/export-web.component.html index 1ff34f4c988..9107640cf53 100644 --- a/apps/web/src/app/tools/vault-export/export-web.component.html +++ b/apps/web/src/app/tools/vault-export/export-web.component.html @@ -15,6 +15,6 @@ bitFormButton buttonType="primary" > - {{ "export" | i18n }} + {{ "exportVerb" | i18n }} </button> </bit-container> diff --git a/apps/web/src/app/tools/vault-export/org-vault-export.component.html b/apps/web/src/app/tools/vault-export/org-vault-export.component.html index e781a839896..e84cf41409b 100644 --- a/apps/web/src/app/tools/vault-export/org-vault-export.component.html +++ b/apps/web/src/app/tools/vault-export/org-vault-export.component.html @@ -16,6 +16,6 @@ bitFormButton buttonType="primary" > - {{ "export" | i18n }} + {{ "exportVerb" | i18n }} </button> </bit-container> diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 680c28f0747..09697c40e95 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -1969,8 +1969,13 @@ "encExportAccountWarningDesc": { "message": "Account encryption keys are unique to each Bitwarden user account, so you can't import an encrypted export into a different account." }, - "export": { - "message": "Export" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, "exportFrom": { "message": "Export from" @@ -2297,8 +2302,13 @@ "tools": { "message": "Tools" }, - "import": { - "message": "Import" + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "importData": { "message": "Import data" diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/layout/navigation.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/layout/navigation.component.html index ac70e1920ee..9cefdac685d 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/layout/navigation.component.html +++ b/bitwarden_license/bit-web/src/app/secrets-manager/layout/navigation.component.html @@ -52,12 +52,12 @@ [relativeTo]="route.parent" > <bit-nav-item - [text]="'import' | i18n" + [text]="'importNoun' | i18n" route="settings/import" [relativeTo]="route.parent" ></bit-nav-item> <bit-nav-item - [text]="'export' | i18n" + [text]="'exportNoun' | i18n" route="settings/export" [relativeTo]="route.parent" ></bit-nav-item> diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/settings/porting/sm-export.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/settings/porting/sm-export.component.html index 113c51327b2..9fd38408547 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/settings/porting/sm-export.component.html +++ b/bitwarden_license/bit-web/src/app/secrets-manager/settings/porting/sm-export.component.html @@ -17,6 +17,6 @@ </bit-form-field> <button bitButton bitFormButton type="submit" buttonType="primary"> - {{ "export" | i18n }} + {{ "exportVerb" | i18n }} </button> </form> diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/settings/porting/sm-export.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/settings/porting/sm-export.component.ts index 5e6f81d99d6..f2af3d4e8f8 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/settings/porting/sm-export.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/settings/porting/sm-export.component.ts @@ -124,7 +124,7 @@ export class SecretsManagerExportComponent implements OnInit, OnDestroy { const ref = openUserVerificationPrompt(this.dialogService, { data: { confirmDescription: "exportSecretsWarningDesc", - confirmButtonText: "export", + confirmButtonText: "exportVerb", modalTitle: "confirmSecretsExport", }, }); diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/settings/porting/sm-import.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/settings/porting/sm-import.component.html index 3a663dbcbe9..b1bdee73f90 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/settings/porting/sm-import.component.html +++ b/bitwarden_license/bit-web/src/app/secrets-manager/settings/porting/sm-import.component.html @@ -36,6 +36,6 @@ <bit-hint>{{ "acceptedFormats" | i18n }} Bitwarden (json)</bit-hint> </bit-form-field> <button bitButton bitFormButton type="submit" buttonType="primary"> - {{ "import" | i18n }} + {{ "importVerb" | i18n }} </button> </form> diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/settings/settings-routing.module.ts b/bitwarden_license/bit-web/src/app/secrets-manager/settings/settings-routing.module.ts index 31029d134fa..283f877722c 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/settings/settings-routing.module.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/settings/settings-routing.module.ts @@ -12,7 +12,7 @@ const routes: Routes = [ component: SecretsManagerImportComponent, canActivate: [organizationPermissionsGuard((org) => org.isAdmin)], data: { - titleId: "import", + titleId: "importNoun", }, }, { @@ -20,7 +20,7 @@ const routes: Routes = [ component: SecretsManagerExportComponent, canActivate: [organizationPermissionsGuard((org) => org.isAdmin)], data: { - titleId: "export", + titleId: "exportNoun", }, }, ]; diff --git a/libs/importer/src/components/dialog/file-password-prompt.component.html b/libs/importer/src/components/dialog/file-password-prompt.component.html index 1c0bcdca31d..e46c24053fa 100644 --- a/libs/importer/src/components/dialog/file-password-prompt.component.html +++ b/libs/importer/src/components/dialog/file-password-prompt.component.html @@ -21,7 +21,7 @@ <ng-container bitDialogFooter> <button bitButton buttonType="primary" type="submit"> - <span>{{ "import" | i18n }}</span> + <span>{{ "importVerb" | i18n }}</span> </button> <button bitButton bitDialogClose buttonType="secondary" type="button"> <span>{{ "cancel" | i18n }}</span> From 5504d4975121be79c493caf10288030a53370f81 Mon Sep 17 00:00:00 2001 From: Bryan Cunningham <bcunningham@bitwarden.com> Date: Wed, 17 Dec 2025 15:02:37 -0500 Subject: [PATCH 108/188] [CL-927] add popup header back to extension layout for now (#18023) * add popup header back to extension layout for now * conditionally add margin for now if not hiding logo --- ...tension-anon-layout-wrapper.component.html | 23 +++++++++++++------ .../anon-layout/anon-layout.component.html | 6 ++++- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/apps/browser/src/popup/components/extension-anon-layout-wrapper/extension-anon-layout-wrapper.component.html b/apps/browser/src/popup/components/extension-anon-layout-wrapper/extension-anon-layout-wrapper.component.html index 7a1815b86ed..484f9680519 100644 --- a/apps/browser/src/popup/components/extension-anon-layout-wrapper/extension-anon-layout-wrapper.component.html +++ b/apps/browser/src/popup/components/extension-anon-layout-wrapper/extension-anon-layout-wrapper.component.html @@ -1,21 +1,30 @@ <popup-page [disablePadding]="true"> + <popup-header + slot="header" + [background]="'alt'" + [showBackButton]="showBackButton" + [pageTitle]="''" + > + <div class="tw-w-32"> + <bit-icon *ngIf="showLogo" [icon]="logo" [ariaLabel]="'appLogoLabel' | i18n"></bit-icon> + </div> + + <ng-container slot="end"> + <app-pop-out></app-pop-out> + <app-current-account *ngIf="showAcctSwitcher && hasLoggedInAccount"></app-current-account> + </ng-container> + </popup-header> <auth-anon-layout [title]="pageTitle" [subtitle]="pageSubtitle" [icon]="pageIcon" [showReadonlyHostname]="showReadonlyHostname" - [hideLogo]="!showLogo" + [hideLogo]="true" [maxWidth]="maxWidth" [hideFooter]="hideFooter" [hideCardWrapper]="hideCardWrapper" > <router-outlet></router-outlet> - <div class="tw-flex tw-gap-2" slot="header-actions"> - <app-pop-out></app-pop-out> - @if (showAcctSwitcher && hasLoggedInAccount) { - <app-current-account></app-current-account> - } - </div> <router-outlet slot="secondary" name="secondary"></router-outlet> <router-outlet slot="environment-selector" name="environment-selector"></router-outlet> </auth-anon-layout> diff --git a/libs/components/src/anon-layout/anon-layout.component.html b/libs/components/src/anon-layout/anon-layout.component.html index edb73bbf588..6bd72a25382 100644 --- a/libs/components/src/anon-layout/anon-layout.component.html +++ b/libs/components/src/anon-layout/anon-layout.component.html @@ -5,7 +5,11 @@ 'tw-min-h-full': clientType === 'browser' || clientType === 'desktop', }" > - <div class="tw-flex tw-justify-between tw-items-center tw-w-full tw-mb-12"> + <div + [class]=" + 'tw-flex tw-justify-between tw-items-center tw-w-full' + (!hideLogo() ? ' tw-mb-12' : '') + " + > @if (!hideLogo()) { <a [routerLink]="['/']" From bcbf013cd99dd2d0baa8e624ecef0a87d6bf894d Mon Sep 17 00:00:00 2001 From: cyprain-okeke <108260115+cyprain-okeke@users.noreply.github.com> Date: Wed, 17 Dec 2025 21:13:18 +0100 Subject: [PATCH 109/188] [PM 27122] Individual subscription page for self-hosted customers (#17517) * implement the self-host subscription changes * Correct few ui changes * Update to h1 * PR review changes * Changes for the async cancel * Resolve the two bug issues * implement the review comments * Resolve the Active issue * Fix the space issues * Remove the tabs for billing and payment * revert the self-host changes * Fix the subtitle issue --- .../individual/individual-billing.module.ts | 2 + .../self-hosted-premium.component.html | 129 +++++++++++------ .../premium/self-hosted-premium.component.ts | 135 ++++++++++++++---- .../individual/subscription.component.html | 16 ++- .../user-subscription.component.html | 61 +++++--- .../individual/user-subscription.component.ts | 26 +++- .../update-license-dialog.component.html | 36 +++-- .../shared/update-license-dialog.component.ts | 43 +++++- apps/web/src/locales/en/messages.json | 51 +++++++ 9 files changed, 381 insertions(+), 118 deletions(-) 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 200df5d9f07..2a529d43416 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,6 @@ import { NgModule } from "@angular/core"; +import { BaseCardComponent } from "@bitwarden/components"; import { PricingCardComponent } from "@bitwarden/pricing"; import { EnterBillingAddressComponent, @@ -23,6 +24,7 @@ import { UserSubscriptionComponent } from "./user-subscription.component"; EnterPaymentMethodComponent, EnterBillingAddressComponent, PricingCardComponent, + BaseCardComponent, ], declarations: [ SubscriptionComponent, diff --git a/apps/web/src/app/billing/individual/premium/self-hosted-premium.component.html b/apps/web/src/app/billing/individual/premium/self-hosted-premium.component.html index 1e32e73c8f5..9efcd2d2e96 100644 --- a/apps/web/src/app/billing/individual/premium/self-hosted-premium.component.html +++ b/apps/web/src/app/billing/individual/premium/self-hosted-premium.component.html @@ -1,49 +1,88 @@ -<bit-container> - <bit-section> - <bit-callout type="success"> - <p>{{ "premiumUpgradeUnlockFeatures" | i18n }}</p> - <ul class="bwi-ul"> - <li> - <i class="bwi bwi-check tw-text-success bwi-li" aria-hidden="true"></i> - {{ "premiumSignUpStorage" | i18n }} - </li> - <li> - <i class="bwi bwi-check tw-text-success bwi-li" aria-hidden="true"></i> - {{ "premiumSignUpTwoStepOptions" | i18n }} - </li> - <li> - <i class="bwi bwi-check tw-text-success bwi-li" aria-hidden="true"></i> - {{ "premiumSignUpEmergency" | i18n }} - </li> - <li> - <i class="bwi bwi-check tw-text-success bwi-li" aria-hidden="true"></i> - {{ "premiumSignUpReports" | i18n }} - </li> - <li> - <i class="bwi bwi-check tw-text-success bwi-li" aria-hidden="true"></i> - {{ "premiumSignUpTotp" | i18n }} - </li> - <li> - <i class="bwi bwi-check tw-text-success bwi-li" aria-hidden="true"></i> - {{ "premiumSignUpSupport" | i18n }} - </li> - <li> - <i class="bwi bwi-check tw-text-success bwi-li" aria-hidden="true"></i> - {{ "premiumSignUpFuture" | i18n }} - </li> - </ul> +<div class="tw-max-w-3xl tw-mx-auto"> + <bit-section *ngIf="shouldShowUpgradeView$ | async"> + <!-- Free Plan Banner --> + <div class="tw-mt-10 tw-mb-4 tw-text-center"> + <span bitBadge variant="secondary" [truncate]="false"> + {{ "bitwardenFreeplanMessage" | i18n }} + </span> + </div> + + <!-- Main Heading --> + <div class="tw-text-center tw-rounded"> + <h1 class="tw-mt-2 tw-text-4xl"> + {{ "upgradeCompleteSecurity" | i18n }} + </h1> + <p class="tw-text-sm tw-text-muted tw-mb-6 tw-mt-4"> + {{ "individualUpgradeDescriptionMessage" | i18n }} + </p> + </div> + + <!-- Already have a subscription section --> + <div class="tw-bg-secondary-100 tw-p-4 tw-rounded-lg tw-border tw-border-secondary-300 tw-mb-6"> + <p class="tw-font-semibold tw-mb-0.5"> + {{ "alreadyHaveSubscriptionQuestion" | i18n }} + </p> + <p class="tw-text-sm tw-text-muted tw-mb-0.5"> + {{ "alreadyHaveSubscriptionSelfHostedMessage" | i18n }} + </p> <a - bitButton - href="{{ cloudPremiumPageUrl$ | async }}" - target="_blank" - rel="noreferrer" - buttonType="secondary" + bitLink + linkType="primary" + (click)="openUploadLicenseDialog()" + class="tw-cursor-pointer tw-text-sm" > - {{ "purchasePremium" | i18n }} + {{ "uploadYourLicenseFile" | i18n }} + <i class="bwi bwi-angle-right tw-ml-1" aria-hidden="true"></i> </a> - </bit-callout> + </div> + + <!-- Two-Card Layout --> + <div class="tw-grid tw-grid-cols-1 md:tw-grid-cols-2 tw-gap-6 tw-mt-6 tw-justify-center"> + <!-- Premium Card --> + <div> + <billing-pricing-card + [tagline]="'planDescPremium' | i18n" + [button]="{ + type: 'primary', + text: ('upgradeToPremium' | i18n), + icon: { type: 'bwi-external-link', position: 'after' }, + }" + [features]="premiumFeatures" + (buttonClick)="onPremiumUpgradeClick()" + > + <h3 slot="title" bitTypography="h3" class="tw-m-0">{{ "premium" | i18n }}</h3> + </billing-pricing-card> + </div> + + <!-- Families Card --> + <div> + <billing-pricing-card + [tagline]="'planDescFamiliesV2' | i18n" + [button]="{ + type: 'secondary', + text: ('upgradeToFamilies' | i18n), + icon: { type: 'bwi-external-link', position: 'after' }, + }" + [features]="familiesFeatures" + (buttonClick)="onFamiliesUpgradeClick()" + > + <h3 slot="title" bitTypography="h3" class="tw-m-0">{{ "families" | i18n }}</h3> + </billing-pricing-card> + </div> + </div> + + <!-- View all plans Link --> + <div class="tw-text-center tw-mt-6"> + <a + bitLink + linkType="primary" + href="https://bitwarden.com/pricing/" + target="_blank" + rel="noopener noreferrer" + > + {{ "viewAllPlans" | i18n }} + <i class="bwi bwi-external-link tw-ml-1" aria-hidden="true"></i> + </a> + </div> </bit-section> - <bit-section> - <individual-self-hosting-license-uploader (onLicenseFileUploaded)="onLicenseFileUploaded()" /> - </bit-section> -</bit-container> +</div> diff --git a/apps/web/src/app/billing/individual/premium/self-hosted-premium.component.ts b/apps/web/src/app/billing/individual/premium/self-hosted-premium.component.ts index c28f2d45b6f..9dcd75ac9b1 100644 --- a/apps/web/src/app/billing/individual/premium/self-hosted-premium.component.ts +++ b/apps/web/src/app/billing/individual/premium/self-hosted-premium.component.ts @@ -1,36 +1,61 @@ -import { Component } from "@angular/core"; +import { CommonModule } from "@angular/common"; +import { Component, DestroyRef, inject } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { ActivatedRoute, Router } from "@angular/router"; -import { combineLatest, map, of, switchMap } from "rxjs"; +import { firstValueFrom, lastValueFrom, map, Observable, of, switchMap } from "rxjs"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { ToastService } from "@bitwarden/components"; -import { BillingSharedModule } from "@bitwarden/web-vault/app/billing/shared"; -import { SharedModule } from "@bitwarden/web-vault/app/shared"; +import { + BadgeModule, + DialogService, + LinkModule, + SectionComponent, + ToastService, + TypographyModule, +} from "@bitwarden/components"; +import { PricingCardComponent } from "@bitwarden/pricing"; +import { I18nPipe } from "@bitwarden/ui-common"; + +import { UpdateLicenseDialogComponent } from "../../shared/update-license-dialog.component"; +import { UpdateLicenseDialogResult } from "../../shared/update-license-types"; // FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush // eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ templateUrl: "./self-hosted-premium.component.html", - imports: [SharedModule, BillingSharedModule], + standalone: true, + imports: [ + CommonModule, + SectionComponent, + BadgeModule, + TypographyModule, + LinkModule, + I18nPipe, + PricingCardComponent, + ], }) export class SelfHostedPremiumComponent { - cloudPremiumPageUrl$ = this.environmentService.cloudWebVaultUrl$.pipe( + protected cloudPremiumPageUrl$ = this.environmentService.cloudWebVaultUrl$.pipe( map((url) => `${url}/#/settings/subscription/premium`), ); - hasPremiumFromAnyOrganization$ = this.accountService.activeAccount$.pipe( - switchMap((account) => - account - ? this.billingAccountProfileStateService.hasPremiumFromAnyOrganization$(account.id) - : of(false), - ), + protected cloudFamiliesPageUrl$ = this.environmentService.cloudWebVaultUrl$.pipe( + map((url) => `${url}/#/settings/subscription/premium`), ); - hasPremiumPersonally$ = this.accountService.activeAccount$.pipe( + protected hasPremiumFromAnyOrganization$: Observable<boolean> = + this.accountService.activeAccount$.pipe( + switchMap((account) => + account + ? this.billingAccountProfileStateService.hasPremiumFromAnyOrganization$(account.id) + : of(false), + ), + ); + + protected hasPremiumPersonally$: Observable<boolean> = this.accountService.activeAccount$.pipe( switchMap((account) => account ? this.billingAccountProfileStateService.hasPremiumPersonally$(account.id) @@ -38,42 +63,90 @@ export class SelfHostedPremiumComponent { ), ); - onLicenseFileUploaded = async () => { - this.toastService.showToast({ - variant: "success", - title: "", - message: this.i18nService.t("premiumUpdated"), - }); - await this.navigateToSubscription(); - }; + protected shouldShowUpgradeView$: Observable<boolean> = this.hasPremiumPersonally$.pipe( + map((hasPremium) => !hasPremium), + ); + + protected premiumFeatures = [ + this.i18nService.t("builtInAuthenticator"), + this.i18nService.t("secureFileStorage"), + this.i18nService.t("emergencyAccess"), + this.i18nService.t("breachMonitoring"), + this.i18nService.t("andMoreFeatures"), + ]; + + protected familiesFeatures = [ + this.i18nService.t("premiumAccounts"), + this.i18nService.t("familiesUnlimitedSharing"), + this.i18nService.t("familiesUnlimitedCollections"), + this.i18nService.t("familiesSharedStorage"), + ]; + + private destroyRef = inject(DestroyRef); constructor( private accountService: AccountService, private activatedRoute: ActivatedRoute, private billingAccountProfileStateService: BillingAccountProfileStateService, + private dialogService: DialogService, private environmentService: EnvironmentService, private i18nService: I18nService, private router: Router, private toastService: ToastService, ) { - combineLatest([this.hasPremiumFromAnyOrganization$, this.hasPremiumPersonally$]) + // Redirect premium users to subscription page + this.hasPremiumPersonally$ .pipe( - takeUntilDestroyed(), - switchMap(([hasPremiumFromAnyOrganization, hasPremiumPersonally]) => { - if (hasPremiumFromAnyOrganization) { - return this.navigateToVault(); - } + takeUntilDestroyed(this.destroyRef), + switchMap((hasPremiumPersonally) => { if (hasPremiumPersonally) { return this.navigateToSubscription(); } - return of(true); }), ) .subscribe(); } - navigateToSubscription = () => + protected openUploadLicenseDialog = async () => { + const dialogRef = UpdateLicenseDialogComponent.open(this.dialogService); + const result = await lastValueFrom(dialogRef.closed); + if (result === UpdateLicenseDialogResult.Updated) { + this.toastService.showToast({ + variant: "success", + title: "", + message: this.i18nService.t("premiumUpdated"), + }); + await this.navigateToSubscription(); + } + }; + + protected navigateToSubscription = async (): Promise<boolean> => this.router.navigate(["../user-subscription"], { relativeTo: this.activatedRoute }); - navigateToVault = () => this.router.navigate(["/vault"]); + + protected onPremiumUpgradeClick = async () => { + const url = await firstValueFrom(this.cloudPremiumPageUrl$); + if (!url) { + this.toastService.showToast({ + variant: "error", + title: "", + message: this.i18nService.t("cloudUrlNotConfigured"), + }); + return; + } + window.open(url, "_blank", "noopener,noreferrer"); + }; + + protected onFamiliesUpgradeClick = async () => { + const url = await firstValueFrom(this.cloudFamiliesPageUrl$); + if (!url) { + this.toastService.showToast({ + variant: "error", + title: "", + message: this.i18nService.t("cloudUrlNotConfigured"), + }); + return; + } + window.open(url, "_blank", "noopener,noreferrer"); + }; } diff --git a/apps/web/src/app/billing/individual/subscription.component.html b/apps/web/src/app/billing/individual/subscription.component.html index 4cbec4b4338..7fd7beff109 100644 --- a/apps/web/src/app/billing/individual/subscription.component.html +++ b/apps/web/src/app/billing/individual/subscription.component.html @@ -1,11 +1,13 @@ <app-header> - <bit-tab-nav-bar slot="tabs" *ngIf="!selfHosted"> - <bit-tab-link [route]="(hasPremium$ | async) ? 'user-subscription' : 'premium'">{{ - "subscription" | i18n - }}</bit-tab-link> - <bit-tab-link route="payment-details">{{ "paymentDetails" | i18n }}</bit-tab-link> - <bit-tab-link route="billing-history">{{ "billingHistory" | i18n }}</bit-tab-link> - </bit-tab-nav-bar> + @if (!selfHosted) { + <bit-tab-nav-bar slot="tabs"> + <bit-tab-link [route]="(hasPremium$ | async) ? 'user-subscription' : 'premium'">{{ + "subscription" | i18n + }}</bit-tab-link> + <bit-tab-link route="payment-details">{{ "paymentDetails" | i18n }}</bit-tab-link> + <bit-tab-link route="billing-history">{{ "billingHistory" | i18n }}</bit-tab-link> + </bit-tab-nav-bar> + } </app-header> <router-outlet></router-outlet> diff --git a/apps/web/src/app/billing/individual/user-subscription.component.html b/apps/web/src/app/billing/individual/user-subscription.component.html index b7e490cdf2e..2d653ff200b 100644 --- a/apps/web/src/app/billing/individual/user-subscription.component.html +++ b/apps/web/src/app/billing/individual/user-subscription.component.html @@ -32,11 +32,6 @@ {{ "reinstateSubscription" | i18n }} </button> </bit-callout> - <dl *ngIf="selfHosted"> - <dt>{{ "expiration" | i18n }}</dt> - <dd *ngIf="sub.expiration">{{ sub.expiration | date: "mediumDate" }}</dd> - <dd *ngIf="!sub.expiration">{{ "neverExpires" | i18n }}</dd> - </dl> <div class="tw-flex tw-max-w-[1340px] tw-pt-6" *ngIf="!selfHosted"> <div class="tw-flex tw-gap-16 tw-justify-between tw-w-full"> <div class="tw-flex tw-flex-col"> @@ -97,19 +92,49 @@ </div> </div> <ng-container *ngIf="selfHosted"> - <div> - <button type="button" bitButton buttonType="secondary" (click)="updateLicense()"> - {{ "updateLicense" | i18n }} - </button> - <a - bitButton - buttonType="secondary" - href="{{ this.cloudWebVaultUrl }}/#/settings/subscription" - target="_blank" - rel="noreferrer" - > - {{ "launchCloudSubscription" | i18n }} - </a> + <div class="tw-mt-10 tw-text-center tw-pb-4"> + <h1 class="tw-text-4xl tw-my-0">{{ "youHaveBitwardenPremium" | i18n }}</h1> + <div class="tw-text-muted tw-text-xs tw-mb-4 tw-mt-2"> + {{ "viewAndManagePremiumSubscription" | i18n }} + </div> + </div> + <div class="tw-flex tw-justify-center"> + <bit-base-card class="tw-w-[800px] tw-p-4 sm:tw-p-6"> + <div class="tw-flex tw-flex-col tw-gap-5"> + <div class="tw-flex tw-items-center tw-justify-between"> + <div> + <h2 bitTypography="h2" class="tw-font-semibold tw-mb-0"> + {{ "premiumMembership" | i18n }} + </h2> + </div> + <span bitBadge variant="success" *ngIf="isSubscriptionActive">{{ + "active" | i18n + }}</span> + </div> + + <p bitTypography="body1" class="tw-m-0" *ngIf="sub.expiration"> + {{ "youNeedToUpdateLicenseFile" | i18n }} + <strong>{{ sub.expiration | date: "MMMM d, y" }}</strong + >. + </p> + + <div class="tw-flex tw-gap-4"> + <button type="button" bitButton buttonType="secondary" (click)="updateLicense()"> + {{ "updateLicense" | i18n }} + </button> + <a + bitButton + buttonType="secondary" + href="{{ this.cloudWebVaultUrl }}/#/settings/subscription" + target="_blank" + rel="noreferrer" + > + {{ "launchCloudSubscriptionSentenceCase" | i18n }} + <i class="bwi bwi-external-link tw-ml-1" aria-hidden="true"></i> + </a> + </div> + </div> + </bit-base-card> </div> </ng-container> <div class="tw-max-w-[1340px]" *ngIf="!selfHosted"> diff --git a/apps/web/src/app/billing/individual/user-subscription.component.ts b/apps/web/src/app/billing/individual/user-subscription.component.ts index c39b5d153b1..8d99b807540 100644 --- a/apps/web/src/app/billing/individual/user-subscription.component.ts +++ b/apps/web/src/app/billing/individual/user-subscription.component.ts @@ -159,7 +159,9 @@ export class UserSubscriptionComponent implements OnInit { if (this.loading) { return; } - const dialogRef = UpdateLicenseDialogComponent.open(this.dialogService); + const dialogRef = UpdateLicenseDialogComponent.open(this.dialogService, { + data: { fromUserSubscriptionPage: true }, + }); const result = await lastValueFrom(dialogRef.closed); if (result === UpdateLicenseDialogResult.Updated) { await this.load(); @@ -259,4 +261,26 @@ export class UserSubscriptionComponent implements OnInit { amountOff: discount.amountOff, }; } + + get isSubscriptionActive(): boolean { + if (!this.sub) { + return false; + } + + if (this.selfHosted) { + return true; + } + + const expiration = this.sub.expiration; + if (!expiration || expiration.trim() === "") { + return true; + } + + const expirationDate = new Date(expiration); + if (isNaN(expirationDate.getTime())) { + return true; + } + + return expirationDate > new Date(); + } } diff --git a/apps/web/src/app/billing/shared/update-license-dialog.component.html b/apps/web/src/app/billing/shared/update-license-dialog.component.html index 7535fe9b30b..95b2fc69295 100644 --- a/apps/web/src/app/billing/shared/update-license-dialog.component.html +++ b/apps/web/src/app/billing/shared/update-license-dialog.component.html @@ -1,16 +1,30 @@ <form [formGroup]="updateLicenseForm" [bitSubmit]="submitLicenseDialog"> - <bit-dialog dialogSize="default" [title]="'updateLicense' | i18n"> + <bit-dialog + dialogSize="default" + [title]="(fromUserSubscriptionPage ? 'uploadLicense' : 'uploadLicenseFile') | i18n" + > <ng-container bitDialogContent> - <bit-form-field> - <bit-label>{{ "licenseFile" | i18n }}</bit-label> - <div> - <button bitButton type="button" buttonType="secondary" (click)="fileSelector.click()"> + <p class="tw-mb-4">{{ "uploadLicenseFileDesc" | i18n: "bitwarden_license.json" }}</p> + <div class="tw-mb-4"> + <label class="tw-block tw-text-sm tw-text-muted tw-mb-2">{{ + (fromUserSubscriptionPage ? "uploadYourPremiumLicenseFile" : "uploadYourLicenseFile") + | i18n + }}</label> + <div class="tw-mb-2"> + <button + bitButton + type="button" + buttonType="unstyled" + class="tw-text-primary-600 tw-p-0 tw-border-0 tw-bg-transparent hover:tw-underline tw-cursor-pointer" + (click)="fileSelector.click()" + > {{ "chooseFile" | i18n }} </button> - {{ licenseFile ? licenseFile.name : ("noFileChosen" | i18n) }} + <span class="tw-ml-2 tw-text-muted">{{ + licenseFile ? licenseFile.name : ("noFileChosen" | i18n) + }}</span> </div> <input - bitInput #fileSelector type="file" formControlName="file" @@ -18,12 +32,12 @@ hidden class="tw-hidden" /> - <bit-hint>{{ "licenseFileDesc" | i18n: "bitwarden_premium_license.json" }}</bit-hint> - </bit-form-field> + <p class="tw-text-sm tw-text-muted">{{ "maxFileSizeSansPunctuation" | i18n }}</p> + </div> </ng-container> <ng-container bitDialogFooter> - <button type="submit" buttonType="primary" bitButton bitFormButton> - {{ "submit" | i18n }} + <button type="submit" buttonType="primary" bitButton bitFormButton [disabled]="!licenseFile"> + {{ "upload" | i18n }} </button> <button bitButton diff --git a/apps/web/src/app/billing/shared/update-license-dialog.component.ts b/apps/web/src/app/billing/shared/update-license-dialog.component.ts index d9c885c9819..5a96768d8b8 100644 --- a/apps/web/src/app/billing/shared/update-license-dialog.component.ts +++ b/apps/web/src/app/billing/shared/update-license-dialog.component.ts @@ -1,15 +1,28 @@ -import { Component } from "@angular/core"; +import { Component, Inject } from "@angular/core"; import { FormBuilder } from "@angular/forms"; +import { firstValueFrom } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; +import { Account, AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { DialogRef, DialogService, ToastService } from "@bitwarden/components"; +import { + DIALOG_DATA, + DialogConfig, + DialogRef, + DialogService, + ToastService, +} from "@bitwarden/components"; import { UpdateLicenseDialogResult } from "./update-license-types"; import { UpdateLicenseComponent } from "./update-license.component"; +export interface UpdateLicenseDialogData { + fromUserSubscriptionPage?: boolean; +} + // FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush // eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ @@ -17,6 +30,8 @@ import { UpdateLicenseComponent } from "./update-license.component"; standalone: false, }) export class UpdateLicenseDialogComponent extends UpdateLicenseComponent { + fromUserSubscriptionPage: boolean; + constructor( private dialogRef: DialogRef, apiService: ApiService, @@ -25,6 +40,9 @@ export class UpdateLicenseDialogComponent extends UpdateLicenseComponent { organizationApiService: OrganizationApiServiceAbstraction, formBuilder: FormBuilder, toastService: ToastService, + private accountService: AccountService, + private billingAccountProfileStateService: BillingAccountProfileStateService, + @Inject(DIALOG_DATA) private dialogData: UpdateLicenseDialogData = {}, ) { super( apiService, @@ -34,10 +52,25 @@ export class UpdateLicenseDialogComponent extends UpdateLicenseComponent { formBuilder, toastService, ); + this.fromUserSubscriptionPage = dialogData?.fromUserSubscriptionPage ?? false; } async submitLicense() { const result = await this.submit(); if (result === UpdateLicenseDialogResult.Updated) { + // Update billing state after successful upload (only for personal licenses) + if (this.organizationId == null) { + const account: Account | null = await firstValueFrom(this.accountService.activeAccount$); + if (account) { + const hasPremiumFromAnyOrganization = await firstValueFrom( + this.billingAccountProfileStateService.hasPremiumFromAnyOrganization$(account.id), + ); + await this.billingAccountProfileStateService.setHasPremium( + true, + hasPremiumFromAnyOrganization, + account.id, + ); + } + } this.dialogRef.close(UpdateLicenseDialogResult.Updated); } } @@ -47,10 +80,10 @@ export class UpdateLicenseDialogComponent extends UpdateLicenseComponent { }; cancel = async () => { - await this.cancel(); + this.onCanceled.emit(); this.dialogRef.close(UpdateLicenseDialogResult.Cancelled); }; - static open(dialogService: DialogService) { - return dialogService.open<UpdateLicenseDialogResult>(UpdateLicenseDialogComponent); + static open(dialogService: DialogService, config?: DialogConfig<UpdateLicenseDialogData>) { + return dialogService.open<UpdateLicenseDialogResult>(UpdateLicenseDialogComponent, config); } } diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 09697c40e95..16c274b5b83 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -3293,6 +3293,9 @@ "launchCloudSubscription": { "message": "Launch Cloud Subscription" }, + "launchCloudSubscriptionSentenceCase": { + "message": "Launch cloud subscription" + }, "storage": { "message": "Storage" }, @@ -12429,5 +12432,53 @@ }, "whyAmISeeingThis": { "message": "Why am I seeing this?" + }, + "youHaveBitwardenPremium": { + "message": "You have Bitwarden Premium" + }, + "viewAndManagePremiumSubscription": { + "message": "View and manage your Premium subscription" + }, + "youNeedToUpdateLicenseFile": { + "message": "You'll need to update your license file" + }, + "youNeedToUpdateLicenseFileDate": { + "message": "$DATE$.", + "placeholders": { + "date": { + "content": "$1", + "example": "June 12, 2026" + } + } + }, + "uploadLicenseFile": { + "message": "Upload license file" + }, + "uploadYourLicenseFile": { + "message": "Upload your license file" + }, + "uploadYourPremiumLicenseFile": { + "message": "Upload your Premium license file" + }, + "uploadLicenseFileDesc": { + "message": "Your license file name will be similar to: $FILE_NAME$", + "placeholders": { + "file_name": { + "content": "$1", + "example": "bitwarden_license.json" + } + } + }, + "alreadyHaveSubscriptionQuestion": { + "message": "Already have a subscription?" + }, + "alreadyHaveSubscriptionSelfHostedMessage": { + "message": "Open the subscription page on your Bitwarden cloud account and download your license file. Then return to this screen and upload it below." + }, + "viewAllPlans": { + "message": "View all plans" + }, + "planDescPremium":{ + "message": "Complete online security" } } From 4f0b69ab640ae3d7783e95fbf9e86a2c2374183e Mon Sep 17 00:00:00 2001 From: neuronull <9162534+neuronull@users.noreply.github.com> Date: Wed, 17 Dec 2025 13:37:04 -0700 Subject: [PATCH 110/188] Desktop Autotype add GA FF (#17896) --- libs/common/src/enums/feature-flag.enum.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts index f2037f1a89f..7357e73a89e 100644 --- a/libs/common/src/enums/feature-flag.enum.ts +++ b/libs/common/src/enums/feature-flag.enum.ts @@ -22,6 +22,7 @@ export enum FeatureFlag { /* Autofill */ MacOsNativeCredentialSync = "macos-native-credential-sync", WindowsDesktopAutotype = "windows-desktop-autotype", + WindowsDesktopAutotypeGA = "windows-desktop-autotype-ga", /* Billing */ TrialPaymentOptional = "PM-8163-trial-payment", @@ -106,6 +107,7 @@ export const DefaultFeatureFlagValue = { /* Autofill */ [FeatureFlag.MacOsNativeCredentialSync]: FALSE, [FeatureFlag.WindowsDesktopAutotype]: FALSE, + [FeatureFlag.WindowsDesktopAutotypeGA]: FALSE, /* Tools */ [FeatureFlag.DesktopSendUIRefresh]: FALSE, From ea45c5d3c0266abcafe73701d1f56e26788f0971 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann <mail@quexten.com> Date: Wed, 17 Dec 2025 22:04:08 +0100 Subject: [PATCH 111/188] [PM-27315] Add account cryptographic state service (#17589) * Update account init and save signed public key * Add account cryptographic state service * Fix build * Cleanup * Fix build * Fix import * Fix build on browser * Fix * Fix DI * Fix * Fix * Fix * Fix * Fix * Fix test * Fix desktop build * Fix * Address nits * Cleanup setting private key * Add tests * Add tests * Add test coverage * Relative imports * Fix web build * Cleanup setting of private key --- .../browser/src/background/main.background.ts | 7 + .../service-container/service-container.ts | 8 ++ .../src/app/services/services.module.ts | 2 + ...sktop-set-initial-password.service.spec.ts | 4 + .../desktop-set-initial-password.service.ts | 3 + .../web-set-initial-password.service.spec.ts | 4 + .../web-set-initial-password.service.ts | 3 + apps/web/src/app/core/core.module.ts | 2 + ...initial-password.service.implementation.ts | 10 ++ ...fault-set-initial-password.service.spec.ts | 14 ++ .../src/services/jslib-services.module.ts | 10 ++ .../auth-request-login.strategy.spec.ts | 43 +++++- .../auth-request-login.strategy.ts | 6 + .../login-strategies/login.strategy.spec.ts | 6 + .../common/login-strategies/login.strategy.ts | 2 + .../password-login.strategy.spec.ts | 45 +++++- .../password-login.strategy.ts | 6 + .../sso-login.strategy.spec.ts | 41 +++++- .../login-strategies/sso-login.strategy.ts | 7 + .../user-api-login.strategy.spec.ts | 38 +++++ .../user-api-login.strategy.ts | 6 + .../webauthn-login.strategy.spec.ts | 51 +++++++ .../webauthn-login.strategy.ts | 6 + .../login-strategy.service.spec.ts | 4 + .../login-strategy.service.ts | 3 + .../response/identity-token.response.spec.ts | 32 +++++ .../response/identity-token.response.ts | 7 + .../account-cryptographic-state.service.ts | 22 +++ ...ccount-cryptographic-state.service.spec.ts | 133 ++++++++++++++++++ ...ult-account-cryptographic-state.service.ts | 35 +++++ .../keys/response/private-keys.response.ts | 29 ++++ .../services/key-state/user-key.state.spec.ts | 6 +- .../sync/default-sync.service.spec.ts | 8 +- .../src/platform/sync/default-sync.service.ts | 14 +- 34 files changed, 607 insertions(+), 10 deletions(-) create mode 100644 libs/common/src/key-management/account-cryptography/account-cryptographic-state.service.ts create mode 100644 libs/common/src/key-management/account-cryptography/default-account-cryptographic-state.service.spec.ts create mode 100644 libs/common/src/key-management/account-cryptography/default-account-cryptographic-state.service.ts diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index 7b509380f6d..c224dcf581e 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -87,6 +87,8 @@ import { HibpApiService } from "@bitwarden/common/dirt/services/hibp-api.service import { PhishingDetectionSettingsService } from "@bitwarden/common/dirt/services/phishing-detection/phishing-detection-settings.service"; import { ClientType } from "@bitwarden/common/enums"; import { ProcessReloadServiceAbstraction } from "@bitwarden/common/key-management/abstractions/process-reload.service"; +import { AccountCryptographicStateService } from "@bitwarden/common/key-management/account-cryptography/account-cryptographic-state.service"; +import { DefaultAccountCryptographicStateService } from "@bitwarden/common/key-management/account-cryptography/default-account-cryptographic-state.service"; import { DefaultKeyGenerationService, KeyGenerationService, @@ -455,6 +457,7 @@ export default class MainBackground { syncServiceListener: SyncServiceListener; browserInitialInstallService: BrowserInitialInstallService; backgroundSyncService: BackgroundSyncService; + accountCryptographicStateService: AccountCryptographicStateService; webPushConnectionService: WorkerWebPushConnectionService | UnsupportedWebPushConnectionService; themeStateService: DefaultThemeStateService; @@ -1010,6 +1013,9 @@ export default class MainBackground { this.avatarService = new AvatarService(this.apiService, this.stateProvider); this.providerService = new ProviderService(this.stateProvider); + this.accountCryptographicStateService = new DefaultAccountCryptographicStateService( + this.stateProvider, + ); this.syncService = new DefaultSyncService( this.masterPasswordService, this.accountService, @@ -1037,6 +1043,7 @@ export default class MainBackground { this.stateProvider, this.securityStateService, this.kdfConfigService, + this.accountCryptographicStateService, ); this.syncServiceListener = new SyncServiceListener( diff --git a/apps/cli/src/service-container/service-container.ts b/apps/cli/src/service-container/service-container.ts index f22f7cb6a00..2f1e92d14fc 100644 --- a/apps/cli/src/service-container/service-container.ts +++ b/apps/cli/src/service-container/service-container.ts @@ -69,6 +69,7 @@ import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abs import { DefaultBillingAccountProfileStateService } from "@bitwarden/common/billing/services/account/billing-account-profile-state.service"; import { HibpApiService } from "@bitwarden/common/dirt/services/hibp-api.service"; import { ClientType } from "@bitwarden/common/enums"; +import { DefaultAccountCryptographicStateService } from "@bitwarden/common/key-management/account-cryptography/default-account-cryptographic-state.service"; import { DefaultKeyGenerationService, KeyGenerationService, @@ -334,6 +335,7 @@ export class ServiceContainer { masterPasswordUnlockService: MasterPasswordUnlockService; cipherArchiveService: CipherArchiveService; lockService: LockService; + private accountCryptographicStateService: DefaultAccountCryptographicStateService; constructor() { let p = null; @@ -717,6 +719,10 @@ export class ServiceContainer { this.accountService, ); + this.accountCryptographicStateService = new DefaultAccountCryptographicStateService( + this.stateProvider, + ); + this.loginStrategyService = new LoginStrategyService( this.accountService, this.masterPasswordService, @@ -744,6 +750,7 @@ export class ServiceContainer { this.kdfConfigService, this.taskSchedulerService, this.configService, + this.accountCryptographicStateService, ); this.restrictedItemTypesService = new RestrictedItemTypesService( @@ -879,6 +886,7 @@ export class ServiceContainer { this.stateProvider, this.securityStateService, this.kdfConfigService, + this.accountCryptographicStateService, ); this.totpService = new TotpService(this.sdkService); diff --git a/apps/desktop/src/app/services/services.module.ts b/apps/desktop/src/app/services/services.module.ts index 874a4d851da..382b680efbc 100644 --- a/apps/desktop/src/app/services/services.module.ts +++ b/apps/desktop/src/app/services/services.module.ts @@ -56,6 +56,7 @@ import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/s import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions"; import { ClientType } from "@bitwarden/common/enums"; import { ProcessReloadServiceAbstraction } from "@bitwarden/common/key-management/abstractions/process-reload.service"; +import { AccountCryptographicStateService } from "@bitwarden/common/key-management/account-cryptography/account-cryptographic-state.service"; import { KeyGenerationService } from "@bitwarden/common/key-management/crypto"; import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; @@ -416,6 +417,7 @@ const safeProviders: SafeProvider[] = [ OrganizationUserApiService, InternalUserDecryptionOptionsServiceAbstraction, MessagingServiceAbstraction, + AccountCryptographicStateService, ], }), safeProvider({ diff --git a/apps/desktop/src/app/services/set-initial-password/desktop-set-initial-password.service.spec.ts b/apps/desktop/src/app/services/set-initial-password/desktop-set-initial-password.service.spec.ts index 717af25a1dc..6b29a464e2c 100644 --- a/apps/desktop/src/app/services/set-initial-password/desktop-set-initial-password.service.spec.ts +++ b/apps/desktop/src/app/services/set-initial-password/desktop-set-initial-password.service.spec.ts @@ -15,6 +15,7 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; import { MasterPasswordApiService } from "@bitwarden/common/auth/abstractions/master-password-api.service.abstraction"; import { SetPasswordRequest } from "@bitwarden/common/auth/models/request/set-password.request"; +import { AccountCryptographicStateService } from "@bitwarden/common/key-management/account-cryptography/account-cryptographic-state.service"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string"; import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; @@ -43,6 +44,7 @@ describe("DesktopSetInitialPasswordService", () => { let organizationUserApiService: MockProxy<OrganizationUserApiService>; let userDecryptionOptionsService: MockProxy<InternalUserDecryptionOptionsServiceAbstraction>; let messagingService: MockProxy<MessagingService>; + let accountCryptographicStateService: MockProxy<AccountCryptographicStateService>; beforeEach(() => { apiService = mock<ApiService>(); @@ -56,6 +58,7 @@ describe("DesktopSetInitialPasswordService", () => { organizationUserApiService = mock<OrganizationUserApiService>(); userDecryptionOptionsService = mock<InternalUserDecryptionOptionsServiceAbstraction>(); messagingService = mock<MessagingService>(); + accountCryptographicStateService = mock<AccountCryptographicStateService>(); sut = new DesktopSetInitialPasswordService( apiService, @@ -69,6 +72,7 @@ describe("DesktopSetInitialPasswordService", () => { organizationUserApiService, userDecryptionOptionsService, messagingService, + accountCryptographicStateService, ); }); diff --git a/apps/desktop/src/app/services/set-initial-password/desktop-set-initial-password.service.ts b/apps/desktop/src/app/services/set-initial-password/desktop-set-initial-password.service.ts index 8de7e73fafe..cedfa3fe589 100644 --- a/apps/desktop/src/app/services/set-initial-password/desktop-set-initial-password.service.ts +++ b/apps/desktop/src/app/services/set-initial-password/desktop-set-initial-password.service.ts @@ -9,6 +9,7 @@ import { InternalUserDecryptionOptionsServiceAbstraction } from "@bitwarden/auth import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; import { MasterPasswordApiService } from "@bitwarden/common/auth/abstractions/master-password-api.service.abstraction"; +import { AccountCryptographicStateService } from "@bitwarden/common/key-management/account-cryptography/account-cryptographic-state.service"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -32,6 +33,7 @@ export class DesktopSetInitialPasswordService protected organizationUserApiService: OrganizationUserApiService, protected userDecryptionOptionsService: InternalUserDecryptionOptionsServiceAbstraction, private messagingService: MessagingService, + protected accountCryptographicStateService: AccountCryptographicStateService, ) { super( apiService, @@ -44,6 +46,7 @@ export class DesktopSetInitialPasswordService organizationApiService, organizationUserApiService, userDecryptionOptionsService, + accountCryptographicStateService, ); } diff --git a/apps/web/src/app/auth/core/services/password-management/set-initial-password/web-set-initial-password.service.spec.ts b/apps/web/src/app/auth/core/services/password-management/set-initial-password/web-set-initial-password.service.spec.ts index 647c9ae83d9..1bbaa0ec236 100644 --- a/apps/web/src/app/auth/core/services/password-management/set-initial-password/web-set-initial-password.service.spec.ts +++ b/apps/web/src/app/auth/core/services/password-management/set-initial-password/web-set-initial-password.service.spec.ts @@ -16,6 +16,7 @@ import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-conso import { MasterPasswordApiService } from "@bitwarden/common/auth/abstractions/master-password-api.service.abstraction"; import { SetPasswordRequest } from "@bitwarden/common/auth/models/request/set-password.request"; import { OrganizationInviteService } from "@bitwarden/common/auth/services/organization-invite/organization-invite.service"; +import { AccountCryptographicStateService } from "@bitwarden/common/key-management/account-cryptography/account-cryptographic-state.service"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string"; import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; @@ -45,6 +46,7 @@ describe("WebSetInitialPasswordService", () => { let userDecryptionOptionsService: MockProxy<InternalUserDecryptionOptionsServiceAbstraction>; let organizationInviteService: MockProxy<OrganizationInviteService>; let routerService: MockProxy<RouterService>; + let accountCryptographicStateService: MockProxy<AccountCryptographicStateService>; beforeEach(() => { apiService = mock<ApiService>(); @@ -59,6 +61,7 @@ describe("WebSetInitialPasswordService", () => { userDecryptionOptionsService = mock<InternalUserDecryptionOptionsServiceAbstraction>(); organizationInviteService = mock<OrganizationInviteService>(); routerService = mock<RouterService>(); + accountCryptographicStateService = mock<AccountCryptographicStateService>(); sut = new WebSetInitialPasswordService( apiService, @@ -73,6 +76,7 @@ describe("WebSetInitialPasswordService", () => { userDecryptionOptionsService, organizationInviteService, routerService, + accountCryptographicStateService, ); }); diff --git a/apps/web/src/app/auth/core/services/password-management/set-initial-password/web-set-initial-password.service.ts b/apps/web/src/app/auth/core/services/password-management/set-initial-password/web-set-initial-password.service.ts index 19ddbf5e260..303b9148e8e 100644 --- a/apps/web/src/app/auth/core/services/password-management/set-initial-password/web-set-initial-password.service.ts +++ b/apps/web/src/app/auth/core/services/password-management/set-initial-password/web-set-initial-password.service.ts @@ -10,6 +10,7 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; import { MasterPasswordApiService } from "@bitwarden/common/auth/abstractions/master-password-api.service.abstraction"; import { OrganizationInviteService } from "@bitwarden/common/auth/services/organization-invite/organization-invite.service"; +import { AccountCryptographicStateService } from "@bitwarden/common/key-management/account-cryptography/account-cryptographic-state.service"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -34,6 +35,7 @@ export class WebSetInitialPasswordService protected userDecryptionOptionsService: InternalUserDecryptionOptionsServiceAbstraction, private organizationInviteService: OrganizationInviteService, private routerService: RouterService, + protected accountCryptographicStateService: AccountCryptographicStateService, ) { super( apiService, @@ -46,6 +48,7 @@ export class WebSetInitialPasswordService organizationApiService, organizationUserApiService, userDecryptionOptionsService, + accountCryptographicStateService, ); } diff --git a/apps/web/src/app/core/core.module.ts b/apps/web/src/app/core/core.module.ts index fb42e19f863..bd557dc5947 100644 --- a/apps/web/src/app/core/core.module.ts +++ b/apps/web/src/app/core/core.module.ts @@ -66,6 +66,7 @@ import { OrganizationInviteService } from "@bitwarden/common/auth/services/organ import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { ClientType } from "@bitwarden/common/enums"; import { ProcessReloadServiceAbstraction } from "@bitwarden/common/key-management/abstractions/process-reload.service"; +import { AccountCryptographicStateService } from "@bitwarden/common/key-management/account-cryptography/account-cryptographic-state.service"; import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; @@ -314,6 +315,7 @@ const safeProviders: SafeProvider[] = [ InternalUserDecryptionOptionsServiceAbstraction, OrganizationInviteService, RouterService, + AccountCryptographicStateService, ], }), safeProvider({ diff --git a/libs/angular/src/auth/password-management/set-initial-password/default-set-initial-password.service.implementation.ts b/libs/angular/src/auth/password-management/set-initial-password/default-set-initial-password.service.implementation.ts index df5220b5255..bd3f78b9290 100644 --- a/libs/angular/src/auth/password-management/set-initial-password/default-set-initial-password.service.implementation.ts +++ b/libs/angular/src/auth/password-management/set-initial-password/default-set-initial-password.service.implementation.ts @@ -15,6 +15,7 @@ import { MasterPasswordApiService } from "@bitwarden/common/auth/abstractions/ma import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason"; import { SetPasswordRequest } from "@bitwarden/common/auth/models/request/set-password.request"; import { UpdateTdeOffboardingPasswordRequest } from "@bitwarden/common/auth/models/request/update-tde-offboarding-password.request"; +import { AccountCryptographicStateService } from "@bitwarden/common/key-management/account-cryptography/account-cryptographic-state.service"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string"; import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; @@ -44,6 +45,7 @@ export class DefaultSetInitialPasswordService implements SetInitialPasswordServi protected organizationApiService: OrganizationApiServiceAbstraction, protected organizationUserApiService: OrganizationUserApiService, protected userDecryptionOptionsService: InternalUserDecryptionOptionsServiceAbstraction, + protected accountCryptographicStateService: AccountCryptographicStateService, ) {} async setInitialPassword( @@ -162,6 +164,14 @@ export class DefaultSetInitialPasswordService implements SetInitialPasswordServi throw new Error("encrypted private key not found. Could not set private key in state."); } await this.keyService.setPrivateKey(keyPair[1].encryptedString, userId); + await this.accountCryptographicStateService.setAccountCryptographicState( + { + V1: { + private_key: keyPair[1].encryptedString, + }, + }, + userId, + ); } await this.masterPasswordService.setMasterKeyHash(newLocalMasterKeyHash, userId); diff --git a/libs/angular/src/auth/password-management/set-initial-password/default-set-initial-password.service.spec.ts b/libs/angular/src/auth/password-management/set-initial-password/default-set-initial-password.service.spec.ts index 8b95090e776..cfea011d0d9 100644 --- a/libs/angular/src/auth/password-management/set-initial-password/default-set-initial-password.service.spec.ts +++ b/libs/angular/src/auth/password-management/set-initial-password/default-set-initial-password.service.spec.ts @@ -20,6 +20,7 @@ import { MasterPasswordApiService } from "@bitwarden/common/auth/abstractions/ma import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason"; import { SetPasswordRequest } from "@bitwarden/common/auth/models/request/set-password.request"; import { UpdateTdeOffboardingPasswordRequest } from "@bitwarden/common/auth/models/request/update-tde-offboarding-password.request"; +import { AccountCryptographicStateService } from "@bitwarden/common/key-management/account-cryptography/account-cryptographic-state.service"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { EncryptedString, @@ -56,6 +57,7 @@ describe("DefaultSetInitialPasswordService", () => { let organizationApiService: MockProxy<OrganizationApiServiceAbstraction>; let organizationUserApiService: MockProxy<OrganizationUserApiService>; let userDecryptionOptionsService: MockProxy<InternalUserDecryptionOptionsServiceAbstraction>; + let accountCryptographicStateService: MockProxy<AccountCryptographicStateService>; let userId: UserId; let userKey: UserKey; @@ -73,6 +75,7 @@ describe("DefaultSetInitialPasswordService", () => { organizationApiService = mock<OrganizationApiServiceAbstraction>(); organizationUserApiService = mock<OrganizationUserApiService>(); userDecryptionOptionsService = mock<InternalUserDecryptionOptionsServiceAbstraction>(); + accountCryptographicStateService = mock<AccountCryptographicStateService>(); userId = "userId" as UserId; userKey = new SymmetricCryptoKey(new Uint8Array(64).buffer as CsprngArray) as UserKey; @@ -90,6 +93,7 @@ describe("DefaultSetInitialPasswordService", () => { organizationApiService, organizationUserApiService, userDecryptionOptionsService, + accountCryptographicStateService, ); }); @@ -386,6 +390,16 @@ describe("DefaultSetInitialPasswordService", () => { // Assert expect(masterPasswordApiService.setPassword).toHaveBeenCalledWith(setPasswordRequest); expect(keyService.setPrivateKey).toHaveBeenCalledWith(keyPair[1].encryptedString, userId); + expect( + accountCryptographicStateService.setAccountCryptographicState, + ).toHaveBeenCalledWith( + { + V1: { + private_key: keyPair[1].encryptedString as EncryptedString, + }, + }, + userId, + ); }); it("should set the local master key hash to state", async () => { diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index 6881862615d..ab6ca7295e3 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -168,6 +168,8 @@ import { OrganizationBillingService } from "@bitwarden/common/billing/services/o import { DefaultSubscriptionPricingService } from "@bitwarden/common/billing/services/subscription-pricing.service"; import { HibpApiService } from "@bitwarden/common/dirt/services/hibp-api.service"; import { ProcessReloadServiceAbstraction } from "@bitwarden/common/key-management/abstractions/process-reload.service"; +import { AccountCryptographicStateService } from "@bitwarden/common/key-management/account-cryptography/account-cryptographic-state.service"; +import { DefaultAccountCryptographicStateService } from "@bitwarden/common/key-management/account-cryptography/default-account-cryptographic-state.service"; import { DefaultKeyGenerationService, KeyGenerationService, @@ -572,6 +574,7 @@ const safeProviders: SafeProvider[] = [ KdfConfigService, TaskSchedulerService, ConfigService, + AccountCryptographicStateService, ], }), safeProvider({ @@ -894,8 +897,14 @@ const safeProviders: SafeProvider[] = [ StateProvider, SecurityStateService, KdfConfigService, + AccountCryptographicStateService, ], }), + safeProvider({ + provide: AccountCryptographicStateService, + useClass: DefaultAccountCryptographicStateService, + deps: [StateProvider], + }), safeProvider({ provide: BroadcasterService, useClass: DefaultBroadcasterService, @@ -1565,6 +1574,7 @@ const safeProviders: SafeProvider[] = [ OrganizationApiServiceAbstraction, OrganizationUserApiService, InternalUserDecryptionOptionsServiceAbstraction, + AccountCryptographicStateService, ], }), safeProvider({ diff --git a/libs/auth/src/common/login-strategies/auth-request-login.strategy.spec.ts b/libs/auth/src/common/login-strategies/auth-request-login.strategy.spec.ts index b536ae0dc4b..4703472d480 100644 --- a/libs/auth/src/common/login-strategies/auth-request-login.strategy.spec.ts +++ b/libs/auth/src/common/login-strategies/auth-request-login.strategy.spec.ts @@ -6,6 +6,7 @@ import { TokenService } from "@bitwarden/common/auth/abstractions/token.service" import { IdentityTokenResponse } from "@bitwarden/common/auth/models/response/identity-token.response"; import { TwoFactorService } from "@bitwarden/common/auth/two-factor"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; +import { AccountCryptographicStateService } from "@bitwarden/common/key-management/account-cryptography/account-cryptographic-state.service"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { DeviceTrustServiceAbstraction } from "@bitwarden/common/key-management/device-trust/abstractions/device-trust.service.abstraction"; import { FakeMasterPasswordService } from "@bitwarden/common/key-management/master-password/services/fake-master-password.service"; @@ -22,7 +23,7 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; -import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec"; +import { makeEncString, FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec"; import { CsprngArray } from "@bitwarden/common/types/csprng"; import { UserId } from "@bitwarden/common/types/guid"; import { MasterKey, UserKey } from "@bitwarden/common/types/key"; @@ -57,6 +58,7 @@ describe("AuthRequestLoginStrategy", () => { let kdfConfigService: MockProxy<KdfConfigService>; let environmentService: MockProxy<EnvironmentService>; let configService: MockProxy<ConfigService>; + let accountCryptographicStateService: MockProxy<AccountCryptographicStateService>; const mockUserId = Utils.newGuid() as UserId; let accountService: FakeAccountService; @@ -94,6 +96,7 @@ describe("AuthRequestLoginStrategy", () => { kdfConfigService = mock<KdfConfigService>(); environmentService = mock<EnvironmentService>(); configService = mock<ConfigService>(); + accountCryptographicStateService = mock<AccountCryptographicStateService>(); accountService = mockAccountServiceWith(mockUserId); masterPasswordService = new FakeMasterPasswordService(); @@ -125,6 +128,7 @@ describe("AuthRequestLoginStrategy", () => { kdfConfigService, environmentService, configService, + accountCryptographicStateService, ); tokenResponse = identityTokenResponseFactory(); @@ -208,4 +212,41 @@ describe("AuthRequestLoginStrategy", () => { // trustDeviceIfRequired should be called expect(deviceTrustService.trustDeviceIfRequired).not.toHaveBeenCalled(); }); + + it("sets account cryptographic state when accountKeysResponseModel is present", async () => { + const accountKeysData = { + publicKeyEncryptionKeyPair: { + publicKey: "testPublicKey", + wrappedPrivateKey: "testPrivateKey", + }, + }; + + tokenResponse = identityTokenResponseFactory(); + tokenResponse.key = makeEncString("mockEncryptedUserKey"); + // Add accountKeysResponseModel to the response + (tokenResponse as any).accountKeysResponseModel = { + publicKeyEncryptionKeyPair: accountKeysData.publicKeyEncryptionKeyPair, + toWrappedAccountCryptographicState: jest.fn().mockReturnValue({ + V1: { + private_key: "testPrivateKey", + }, + }), + }; + + apiService.postIdentityToken.mockResolvedValue(tokenResponse); + masterPasswordService.masterKeySubject.next(decMasterKey); + masterPasswordService.mock.decryptUserKeyWithMasterKey.mockResolvedValue(decUserKey); + + await authRequestLoginStrategy.logIn(credentials); + + expect(accountCryptographicStateService.setAccountCryptographicState).toHaveBeenCalledTimes(1); + expect(accountCryptographicStateService.setAccountCryptographicState).toHaveBeenCalledWith( + { + V1: { + private_key: "testPrivateKey", + }, + }, + mockUserId, + ); + }); }); diff --git a/libs/auth/src/common/login-strategies/auth-request-login.strategy.ts b/libs/auth/src/common/login-strategies/auth-request-login.strategy.ts index 7337b6733f8..16af5fa77dc 100644 --- a/libs/auth/src/common/login-strategies/auth-request-login.strategy.ts +++ b/libs/auth/src/common/login-strategies/auth-request-login.strategy.ts @@ -128,6 +128,12 @@ export class AuthRequestLoginStrategy extends LoginStrategy { response.privateKey ?? (await this.createKeyPairForOldAccount(userId)), userId, ); + if (response.accountKeysResponseModel) { + await this.accountCryptographicStateService.setAccountCryptographicState( + response.accountKeysResponseModel.toWrappedAccountCryptographicState(), + userId, + ); + } } exportCache(): CacheData { diff --git a/libs/auth/src/common/login-strategies/login.strategy.spec.ts b/libs/auth/src/common/login-strategies/login.strategy.spec.ts index 4bf72b69e1f..113f9f3f0d9 100644 --- a/libs/auth/src/common/login-strategies/login.strategy.spec.ts +++ b/libs/auth/src/common/login-strategies/login.strategy.spec.ts @@ -17,6 +17,7 @@ import { MasterPasswordPolicyResponse } from "@bitwarden/common/auth/models/resp import { IUserDecryptionOptionsServerResponse } from "@bitwarden/common/auth/models/response/user-decryption-options/user-decryption-options.response"; import { TwoFactorService } from "@bitwarden/common/auth/two-factor"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; +import { AccountCryptographicStateService } from "@bitwarden/common/key-management/account-cryptography/account-cryptographic-state.service"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string"; import { FakeMasterPasswordService } from "@bitwarden/common/key-management/master-password/services/fake-master-password.service"; @@ -136,6 +137,7 @@ describe("LoginStrategy", () => { let kdfConfigService: MockProxy<KdfConfigService>; let environmentService: MockProxy<EnvironmentService>; let configService: MockProxy<ConfigService>; + let accountCryptographicStateService: MockProxy<AccountCryptographicStateService>; let passwordLoginStrategy: PasswordLoginStrategy; let credentials: PasswordLoginCredentials; @@ -162,6 +164,7 @@ describe("LoginStrategy", () => { billingAccountProfileStateService = mock<BillingAccountProfileStateService>(); environmentService = mock<EnvironmentService>(); configService = mock<ConfigService>(); + accountCryptographicStateService = mock<AccountCryptographicStateService>(); vaultTimeoutSettingsService = mock<VaultTimeoutSettingsService>(); @@ -192,6 +195,7 @@ describe("LoginStrategy", () => { kdfConfigService, environmentService, configService, + accountCryptographicStateService, ); credentials = new PasswordLoginCredentials(email, masterPassword); }); @@ -518,6 +522,7 @@ describe("LoginStrategy", () => { kdfConfigService, environmentService, configService, + accountCryptographicStateService, ); apiService.postIdentityToken.mockResolvedValue(identityTokenResponseFactory()); @@ -579,6 +584,7 @@ describe("LoginStrategy", () => { kdfConfigService, environmentService, configService, + accountCryptographicStateService, ); const result = await passwordLoginStrategy.logIn(credentials); diff --git a/libs/auth/src/common/login-strategies/login.strategy.ts b/libs/auth/src/common/login-strategies/login.strategy.ts index 90a36284d3b..16f5e1e4320 100644 --- a/libs/auth/src/common/login-strategies/login.strategy.ts +++ b/libs/auth/src/common/login-strategies/login.strategy.ts @@ -18,6 +18,7 @@ import { IdentityTokenResponse } from "@bitwarden/common/auth/models/response/id import { IdentityTwoFactorResponse } from "@bitwarden/common/auth/models/response/identity-two-factor.response"; import { TwoFactorService } from "@bitwarden/common/auth/two-factor"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; +import { AccountCryptographicStateService } from "@bitwarden/common/key-management/account-cryptography/account-cryptographic-state.service"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; import { @@ -89,6 +90,7 @@ export abstract class LoginStrategy { protected KdfConfigService: KdfConfigService, protected environmentService: EnvironmentService, protected configService: ConfigService, + protected accountCryptographicStateService: AccountCryptographicStateService, ) {} abstract exportCache(): CacheData; diff --git a/libs/auth/src/common/login-strategies/password-login.strategy.spec.ts b/libs/auth/src/common/login-strategies/password-login.strategy.spec.ts index 2287bfb43e3..4188b779d81 100644 --- a/libs/auth/src/common/login-strategies/password-login.strategy.spec.ts +++ b/libs/auth/src/common/login-strategies/password-login.strategy.spec.ts @@ -12,6 +12,7 @@ import { IdentityTwoFactorResponse } from "@bitwarden/common/auth/models/respons import { MasterPasswordPolicyResponse } from "@bitwarden/common/auth/models/response/master-password-policy.response"; import { TwoFactorService } from "@bitwarden/common/auth/two-factor"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; +import { AccountCryptographicStateService } from "@bitwarden/common/key-management/account-cryptography/account-cryptographic-state.service"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { FakeMasterPasswordService } from "@bitwarden/common/key-management/master-password/services/fake-master-password.service"; import { @@ -28,7 +29,7 @@ import { StateService } from "@bitwarden/common/platform/abstractions/state.serv import { HashPurpose } from "@bitwarden/common/platform/enums"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; -import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec"; +import { FakeAccountService, makeEncString, mockAccountServiceWith } from "@bitwarden/common/spec"; import { PasswordStrengthServiceAbstraction, PasswordStrengthService, @@ -85,6 +86,7 @@ describe("PasswordLoginStrategy", () => { let kdfConfigService: MockProxy<KdfConfigService>; let environmentService: MockProxy<EnvironmentService>; let configService: MockProxy<ConfigService>; + let accountCryptographicStateService: MockProxy<AccountCryptographicStateService>; let passwordLoginStrategy: PasswordLoginStrategy; let credentials: PasswordLoginCredentials; @@ -113,6 +115,7 @@ describe("PasswordLoginStrategy", () => { kdfConfigService = mock<KdfConfigService>(); environmentService = mock<EnvironmentService>(); configService = mock<ConfigService>(); + accountCryptographicStateService = mock<AccountCryptographicStateService>(); appIdService.getAppId.mockResolvedValue(deviceId); tokenService.decodeAccessToken.mockResolvedValue({ @@ -153,6 +156,7 @@ describe("PasswordLoginStrategy", () => { kdfConfigService, environmentService, configService, + accountCryptographicStateService, ); credentials = new PasswordLoginCredentials(email, masterPassword); tokenResponse = identityTokenResponseFactory(masterPasswordPolicyResponse); @@ -392,4 +396,43 @@ describe("PasswordLoginStrategy", () => { ); expect(result.userId).toBe(userId); }); + + it("sets account cryptographic state when accountKeysResponseModel is present", async () => { + const accountKeysData = { + publicKeyEncryptionKeyPair: { + publicKey: "testPublicKey", + wrappedPrivateKey: "testPrivateKey", + }, + }; + + tokenResponse = identityTokenResponseFactory(); + tokenResponse.key = makeEncString("mockEncryptedUserKey"); + // Add accountKeysResponseModel to the response + (tokenResponse as any).accountKeysResponseModel = { + publicKeyEncryptionKeyPair: accountKeysData.publicKeyEncryptionKeyPair, + toWrappedAccountCryptographicState: jest.fn().mockReturnValue({ + V1: { + private_key: "testPrivateKey", + }, + }), + }; + + apiService.postIdentityToken.mockResolvedValue(tokenResponse); + masterPasswordService.masterKeySubject.next(masterKey); + masterPasswordService.mock.decryptUserKeyWithMasterKey.mockResolvedValue( + new SymmetricCryptoKey(new Uint8Array(64).buffer as CsprngArray) as UserKey, + ); + + await passwordLoginStrategy.logIn(credentials); + + expect(accountCryptographicStateService.setAccountCryptographicState).toHaveBeenCalledTimes(1); + expect(accountCryptographicStateService.setAccountCryptographicState).toHaveBeenCalledWith( + { + V1: { + private_key: "testPrivateKey", + }, + }, + userId, + ); + }); }); diff --git a/libs/auth/src/common/login-strategies/password-login.strategy.ts b/libs/auth/src/common/login-strategies/password-login.strategy.ts index 842a48e28cd..63fff52194b 100644 --- a/libs/auth/src/common/login-strategies/password-login.strategy.ts +++ b/libs/auth/src/common/login-strategies/password-login.strategy.ts @@ -156,6 +156,12 @@ export class PasswordLoginStrategy extends LoginStrategy { response.privateKey ?? (await this.createKeyPairForOldAccount(userId)), userId, ); + if (response.accountKeysResponseModel) { + await this.accountCryptographicStateService.setAccountCryptographicState( + response.accountKeysResponseModel.toWrappedAccountCryptographicState(), + userId, + ); + } } protected override encryptionKeyMigrationRequired(response: IdentityTokenResponse): boolean { diff --git a/libs/auth/src/common/login-strategies/sso-login.strategy.spec.ts b/libs/auth/src/common/login-strategies/sso-login.strategy.spec.ts index 3dbce6500a8..484beb785d3 100644 --- a/libs/auth/src/common/login-strategies/sso-login.strategy.spec.ts +++ b/libs/auth/src/common/login-strategies/sso-login.strategy.spec.ts @@ -10,6 +10,7 @@ import { IdentityTokenResponse } from "@bitwarden/common/auth/models/response/id import { IUserDecryptionOptionsServerResponse } from "@bitwarden/common/auth/models/response/user-decryption-options/user-decryption-options.response"; import { TwoFactorService } from "@bitwarden/common/auth/two-factor"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; +import { AccountCryptographicStateService } from "@bitwarden/common/key-management/account-cryptography/account-cryptographic-state.service"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { EncryptedString } from "@bitwarden/common/key-management/crypto/models/enc-string"; import { DeviceTrustServiceAbstraction } from "@bitwarden/common/key-management/device-trust/abstractions/device-trust.service.abstraction"; @@ -30,7 +31,7 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; -import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec"; +import { FakeAccountService, makeEncString, mockAccountServiceWith } from "@bitwarden/common/spec"; import { CsprngArray } from "@bitwarden/common/types/csprng"; import { UserId } from "@bitwarden/common/types/guid"; import { DeviceKey, MasterKey, UserKey } from "@bitwarden/common/types/key"; @@ -70,6 +71,7 @@ describe("SsoLoginStrategy", () => { let kdfConfigService: MockProxy<KdfConfigService>; let environmentService: MockProxy<EnvironmentService>; let configService: MockProxy<ConfigService>; + let accountCryptographicStateService: MockProxy<AccountCryptographicStateService>; let ssoLoginStrategy: SsoLoginStrategy; let credentials: SsoLoginCredentials; @@ -108,6 +110,7 @@ describe("SsoLoginStrategy", () => { kdfConfigService = mock<KdfConfigService>(); environmentService = mock<EnvironmentService>(); configService = mock<ConfigService>(); + accountCryptographicStateService = mock<AccountCryptographicStateService>(); tokenService.getTwoFactorToken.mockResolvedValue(null); appIdService.getAppId.mockResolvedValue(deviceId); @@ -162,6 +165,7 @@ describe("SsoLoginStrategy", () => { kdfConfigService, environmentService, configService, + accountCryptographicStateService, ); credentials = new SsoLoginCredentials(ssoCode, ssoCodeVerifier, ssoRedirectUrl, ssoOrgId); }); @@ -556,4 +560,39 @@ describe("SsoLoginStrategy", () => { expect(keyService.setUserKey).toHaveBeenCalledWith(userKey, userId); }); }); + + it("sets account cryptographic state when accountKeysResponseModel is present", async () => { + const accountKeysData = { + publicKeyEncryptionKeyPair: { + publicKey: "testPublicKey", + wrappedPrivateKey: "testPrivateKey", + }, + }; + + const tokenResponse = identityTokenResponseFactory(); + tokenResponse.key = makeEncString("mockEncryptedUserKey"); + // Add accountKeysResponseModel to the response + (tokenResponse as any).accountKeysResponseModel = { + publicKeyEncryptionKeyPair: accountKeysData.publicKeyEncryptionKeyPair, + toWrappedAccountCryptographicState: jest.fn().mockReturnValue({ + V1: { + private_key: "testPrivateKey", + }, + }), + }; + + apiService.postIdentityToken.mockResolvedValue(tokenResponse); + + await ssoLoginStrategy.logIn(credentials); + + expect(accountCryptographicStateService.setAccountCryptographicState).toHaveBeenCalledTimes(1); + expect(accountCryptographicStateService.setAccountCryptographicState).toHaveBeenCalledWith( + { + V1: { + private_key: "testPrivateKey", + }, + }, + userId, + ); + }); }); diff --git a/libs/auth/src/common/login-strategies/sso-login.strategy.ts b/libs/auth/src/common/login-strategies/sso-login.strategy.ts index 33802765aca..a9d60fca21a 100644 --- a/libs/auth/src/common/login-strategies/sso-login.strategy.ts +++ b/libs/auth/src/common/login-strategies/sso-login.strategy.ts @@ -339,6 +339,13 @@ export class SsoLoginStrategy extends LoginStrategy { tokenResponse: IdentityTokenResponse, userId: UserId, ): Promise<void> { + if (tokenResponse.accountKeysResponseModel) { + await this.accountCryptographicStateService.setAccountCryptographicState( + tokenResponse.accountKeysResponseModel.toWrappedAccountCryptographicState(), + userId, + ); + } + if (tokenResponse.hasMasterKeyEncryptedUserKey()) { // User has masterKeyEncryptedUserKey, so set the userKeyEncryptedPrivateKey // Note: new JIT provisioned SSO users will not yet have a user asymmetric key pair diff --git a/libs/auth/src/common/login-strategies/user-api-login.strategy.spec.ts b/libs/auth/src/common/login-strategies/user-api-login.strategy.spec.ts index 9bf282dee11..02613e527ec 100644 --- a/libs/auth/src/common/login-strategies/user-api-login.strategy.spec.ts +++ b/libs/auth/src/common/login-strategies/user-api-login.strategy.spec.ts @@ -5,6 +5,7 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; import { TwoFactorService } from "@bitwarden/common/auth/two-factor"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; +import { AccountCryptographicStateService } from "@bitwarden/common/key-management/account-cryptography/account-cryptographic-state.service"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { KeyConnectorService } from "@bitwarden/common/key-management/key-connector/abstractions/key-connector.service"; import { FakeMasterPasswordService } from "@bitwarden/common/key-management/master-password/services/fake-master-password.service"; @@ -58,6 +59,7 @@ describe("UserApiLoginStrategy", () => { let vaultTimeoutSettingsService: MockProxy<VaultTimeoutSettingsService>; let kdfConfigService: MockProxy<KdfConfigService>; let configService: MockProxy<ConfigService>; + let accountCryptographicStateService: MockProxy<AccountCryptographicStateService>; let apiLogInStrategy: UserApiLoginStrategy; let credentials: UserApiLoginCredentials; @@ -91,6 +93,7 @@ describe("UserApiLoginStrategy", () => { vaultTimeoutSettingsService = mock<VaultTimeoutSettingsService>(); kdfConfigService = mock<KdfConfigService>(); configService = mock<ConfigService>(); + accountCryptographicStateService = mock<AccountCryptographicStateService>(); appIdService.getAppId.mockResolvedValue(deviceId); tokenService.getTwoFactorToken.mockResolvedValue(null); @@ -119,6 +122,7 @@ describe("UserApiLoginStrategy", () => { kdfConfigService, environmentService, configService, + accountCryptographicStateService, ); credentials = new UserApiLoginCredentials(apiClientId, apiClientSecret); @@ -226,4 +230,38 @@ describe("UserApiLoginStrategy", () => { ); expect(keyService.setUserKey).toHaveBeenCalledWith(userKey, userId); }); + + it("sets account cryptographic state when accountKeysResponseModel is present", async () => { + const accountKeysData = { + publicKeyEncryptionKeyPair: { + publicKey: "testPublicKey", + wrappedPrivateKey: "testPrivateKey", + }, + }; + + const tokenResponse = identityTokenResponseFactory(); + // Add accountKeysResponseModel to the response + (tokenResponse as any).accountKeysResponseModel = { + publicKeyEncryptionKeyPair: accountKeysData.publicKeyEncryptionKeyPair, + toWrappedAccountCryptographicState: jest.fn().mockReturnValue({ + V1: { + private_key: "testPrivateKey", + }, + }), + }; + + apiService.postIdentityToken.mockResolvedValue(tokenResponse); + + await apiLogInStrategy.logIn(credentials); + + expect(accountCryptographicStateService.setAccountCryptographicState).toHaveBeenCalledTimes(1); + expect(accountCryptographicStateService.setAccountCryptographicState).toHaveBeenCalledWith( + { + V1: { + private_key: "testPrivateKey", + }, + }, + userId, + ); + }); }); diff --git a/libs/auth/src/common/login-strategies/user-api-login.strategy.ts b/libs/auth/src/common/login-strategies/user-api-login.strategy.ts index df532799576..c5a9110d63e 100644 --- a/libs/auth/src/common/login-strategies/user-api-login.strategy.ts +++ b/libs/auth/src/common/login-strategies/user-api-login.strategy.ts @@ -87,6 +87,12 @@ export class UserApiLoginStrategy extends LoginStrategy { response.privateKey ?? (await this.createKeyPairForOldAccount(userId)), userId, ); + if (response.accountKeysResponseModel) { + await this.accountCryptographicStateService.setAccountCryptographicState( + response.accountKeysResponseModel.toWrappedAccountCryptographicState(), + userId, + ); + } } // Overridden to save client ID and secret to token service 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 9d664a7837c..2ae79f46d7c 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 @@ -9,6 +9,7 @@ import { IUserDecryptionOptionsServerResponse } from "@bitwarden/common/auth/mod import { WebAuthnLoginAssertionResponseRequest } from "@bitwarden/common/auth/services/webauthn-login/request/webauthn-login-assertion-response.request"; import { TwoFactorService } from "@bitwarden/common/auth/two-factor"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; +import { AccountCryptographicStateService } from "@bitwarden/common/key-management/account-cryptography/account-cryptographic-state.service"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { FakeMasterPasswordService } from "@bitwarden/common/key-management/master-password/services/fake-master-password.service"; import { @@ -56,6 +57,7 @@ describe("WebAuthnLoginStrategy", () => { let kdfConfigService: MockProxy<KdfConfigService>; let environmentService: MockProxy<EnvironmentService>; let configService: MockProxy<ConfigService>; + let accountCryptographicStateService: MockProxy<AccountCryptographicStateService>; let webAuthnLoginStrategy!: WebAuthnLoginStrategy; @@ -101,6 +103,7 @@ describe("WebAuthnLoginStrategy", () => { kdfConfigService = mock<KdfConfigService>(); environmentService = mock<EnvironmentService>(); configService = mock<ConfigService>(); + accountCryptographicStateService = mock<AccountCryptographicStateService>(); tokenService.getTwoFactorToken.mockResolvedValue(null); appIdService.getAppId.mockResolvedValue(deviceId); @@ -128,6 +131,7 @@ describe("WebAuthnLoginStrategy", () => { kdfConfigService, environmentService, configService, + accountCryptographicStateService, ); // Create credentials @@ -340,6 +344,53 @@ describe("WebAuthnLoginStrategy", () => { // Assert expect(keyService.setUserKey).not.toHaveBeenCalled(); }); + + it("sets account cryptographic state when accountKeysResponseModel is present", async () => { + // Arrange + const accountKeysData = { + publicKeyEncryptionKeyPair: { + publicKey: "testPublicKey", + wrappedPrivateKey: "testPrivateKey", + }, + }; + + const idTokenResponse: IdentityTokenResponse = identityTokenResponseFactory( + null, + userDecryptionOptsServerResponseWithWebAuthnPrfOption, + ); + // Add accountKeysResponseModel to the response + (idTokenResponse as any).accountKeysResponseModel = { + publicKeyEncryptionKeyPair: accountKeysData.publicKeyEncryptionKeyPair, + toWrappedAccountCryptographicState: jest.fn().mockReturnValue({ + V1: { + private_key: "testPrivateKey", + }, + }), + }; + + apiService.postIdentityToken.mockResolvedValue(idTokenResponse); + + const mockPrfPrivateKey: Uint8Array = randomBytes(32); + const mockUserKeyArray: Uint8Array = randomBytes(32); + encryptService.unwrapDecapsulationKey.mockResolvedValue(mockPrfPrivateKey); + encryptService.decapsulateKeyUnsigned.mockResolvedValue( + new SymmetricCryptoKey(mockUserKeyArray), + ); + + // Act + await webAuthnLoginStrategy.logIn(webAuthnCredentials); + + // Assert + expect(accountCryptographicStateService.setAccountCryptographicState).toHaveBeenCalledTimes(1); + expect(accountCryptographicStateService.setAccountCryptographicState).toHaveBeenCalledWith( + { + V1: { + private_key: "testPrivateKey", + }, + }, + userId, + ); + }); }); // Helpers and mocks diff --git a/libs/auth/src/common/login-strategies/webauthn-login.strategy.ts b/libs/auth/src/common/login-strategies/webauthn-login.strategy.ts index dbce7628335..77a881b5964 100644 --- a/libs/auth/src/common/login-strategies/webauthn-login.strategy.ts +++ b/libs/auth/src/common/login-strategies/webauthn-login.strategy.ts @@ -107,6 +107,12 @@ export class WebAuthnLoginStrategy extends LoginStrategy { response.privateKey ?? (await this.createKeyPairForOldAccount(userId)), userId, ); + if (response.accountKeysResponseModel) { + await this.accountCryptographicStateService.setAccountCryptographicState( + response.accountKeysResponseModel.toWrappedAccountCryptographicState(), + userId, + ); + } } exportCache(): CacheData { diff --git a/libs/auth/src/common/services/login-strategies/login-strategy.service.spec.ts b/libs/auth/src/common/services/login-strategies/login-strategy.service.spec.ts index c7e18105a0d..a79d6bb0514 100644 --- a/libs/auth/src/common/services/login-strategies/login-strategy.service.spec.ts +++ b/libs/auth/src/common/services/login-strategies/login-strategy.service.spec.ts @@ -13,6 +13,7 @@ import { PreloginResponse } from "@bitwarden/common/auth/models/response/prelogi import { UserDecryptionOptionsResponse } from "@bitwarden/common/auth/models/response/user-decryption-options/user-decryption-options.response"; import { TwoFactorService } from "@bitwarden/common/auth/two-factor"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; +import { DefaultAccountCryptographicStateService } from "@bitwarden/common/key-management/account-cryptography/default-account-cryptographic-state.service"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { DeviceTrustServiceAbstraction } from "@bitwarden/common/key-management/device-trust/abstractions/device-trust.service.abstraction"; import { KeyConnectorService } from "@bitwarden/common/key-management/key-connector/abstractions/key-connector.service"; @@ -84,6 +85,7 @@ describe("LoginStrategyService", () => { let kdfConfigService: MockProxy<KdfConfigService>; let taskSchedulerService: MockProxy<TaskSchedulerService>; let configService: MockProxy<ConfigService>; + let accountCryptographicStateService: MockProxy<DefaultAccountCryptographicStateService>; let stateProvider: FakeGlobalStateProvider; let loginStrategyCacheExpirationState: FakeGlobalState<Date | null>; @@ -117,6 +119,7 @@ describe("LoginStrategyService", () => { kdfConfigService = mock<KdfConfigService>(); taskSchedulerService = mock<TaskSchedulerService>(); configService = mock<ConfigService>(); + accountCryptographicStateService = mock<DefaultAccountCryptographicStateService>(); sut = new LoginStrategyService( accountService, @@ -145,6 +148,7 @@ describe("LoginStrategyService", () => { kdfConfigService, taskSchedulerService, configService, + accountCryptographicStateService, ); loginStrategyCacheExpirationState = stateProvider.getFake(CACHE_EXPIRATION_KEY); diff --git a/libs/auth/src/common/services/login-strategies/login-strategy.service.ts b/libs/auth/src/common/services/login-strategies/login-strategy.service.ts index 5720fce254e..5f8cd304a18 100644 --- a/libs/auth/src/common/services/login-strategies/login-strategy.service.ts +++ b/libs/auth/src/common/services/login-strategies/login-strategy.service.ts @@ -19,6 +19,7 @@ import { TokenTwoFactorRequest } from "@bitwarden/common/auth/models/request/ide import { TwoFactorService } from "@bitwarden/common/auth/two-factor"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; +import { AccountCryptographicStateService } from "@bitwarden/common/key-management/account-cryptography/account-cryptographic-state.service"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { DeviceTrustServiceAbstraction } from "@bitwarden/common/key-management/device-trust/abstractions/device-trust.service.abstraction"; import { KeyConnectorService } from "@bitwarden/common/key-management/key-connector/abstractions/key-connector.service"; @@ -160,6 +161,7 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction { protected kdfConfigService: KdfConfigService, protected taskSchedulerService: TaskSchedulerService, protected configService: ConfigService, + protected accountCryptographicStateService: AccountCryptographicStateService, ) { this.currentAuthnTypeState = this.stateProvider.get(CURRENT_LOGIN_STRATEGY_KEY); this.loginStrategyCacheState = this.stateProvider.get(CACHE_KEY); @@ -509,6 +511,7 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction { this.kdfConfigService, this.environmentService, this.configService, + this.accountCryptographicStateService, ]; return source.pipe( diff --git a/libs/common/src/auth/models/response/identity-token.response.spec.ts b/libs/common/src/auth/models/response/identity-token.response.spec.ts index 4f8454968dc..6a32b89f1df 100644 --- a/libs/common/src/auth/models/response/identity-token.response.spec.ts +++ b/libs/common/src/auth/models/response/identity-token.response.spec.ts @@ -116,4 +116,36 @@ describe("IdentityTokenResponse", () => { const identityTokenResponse = new IdentityTokenResponse(response); expect(identityTokenResponse.userDecryptionOptions).toBeDefined(); }); + + it("should create response with accountKeys not present", () => { + const response = { + access_token: accessToken, + token_type: tokenType, + AccountKeys: null as unknown, + }; + + const identityTokenResponse = new IdentityTokenResponse(response); + expect(identityTokenResponse.accountKeysResponseModel).toBeNull(); + }); + + it("should create response with accountKeys present", () => { + const accountKeysData = { + publicKeyEncryptionKeyPair: { + publicKey: "testPublicKey", + wrappedPrivateKey: "testPrivateKey", + }, + }; + + const response = { + access_token: accessToken, + token_type: tokenType, + AccountKeys: accountKeysData, + }; + + const identityTokenResponse = new IdentityTokenResponse(response); + expect(identityTokenResponse.accountKeysResponseModel).toBeDefined(); + expect( + identityTokenResponse.accountKeysResponseModel?.publicKeyEncryptionKeyPair, + ).toBeDefined(); + }); }); diff --git a/libs/common/src/auth/models/response/identity-token.response.ts b/libs/common/src/auth/models/response/identity-token.response.ts index 958f520a2c6..ae208ef1a36 100644 --- a/libs/common/src/auth/models/response/identity-token.response.ts +++ b/libs/common/src/auth/models/response/identity-token.response.ts @@ -5,6 +5,7 @@ import { Argon2KdfConfig, KdfConfig, KdfType, PBKDF2KdfConfig } from "@bitwarden/key-management"; import { EncString } from "../../../key-management/crypto/models/enc-string"; +import { PrivateKeysResponseModel } from "../../../key-management/keys/response/private-keys.response"; import { BaseResponse } from "../../../models/response/base.response"; import { MasterPasswordPolicyResponse } from "./master-password-policy.response"; @@ -19,6 +20,7 @@ export class IdentityTokenResponse extends BaseResponse { // Decryption Information privateKey: string; // userKeyEncryptedPrivateKey + accountKeysResponseModel: PrivateKeysResponseModel | null = null; key?: EncString; // masterKeyEncryptedUserKey twoFactorToken: string; kdfConfig: KdfConfig; @@ -52,6 +54,11 @@ export class IdentityTokenResponse extends BaseResponse { } this.privateKey = this.getResponseProperty("PrivateKey"); + if (this.getResponseProperty("AccountKeys") != null) { + this.accountKeysResponseModel = new PrivateKeysResponseModel( + this.getResponseProperty("AccountKeys"), + ); + } const key = this.getResponseProperty("Key"); if (key) { this.key = new EncString(key); diff --git a/libs/common/src/key-management/account-cryptography/account-cryptographic-state.service.ts b/libs/common/src/key-management/account-cryptography/account-cryptographic-state.service.ts new file mode 100644 index 00000000000..cc3306a849a --- /dev/null +++ b/libs/common/src/key-management/account-cryptography/account-cryptographic-state.service.ts @@ -0,0 +1,22 @@ +import { Observable } from "rxjs"; + +import { WrappedAccountCryptographicState } from "@bitwarden/sdk-internal"; +import { UserId } from "@bitwarden/user-core"; + +export abstract class AccountCryptographicStateService { + /** + * Emits the provided user's account cryptographic state or null if there is no account cryptographic state present for the user. + */ + abstract accountCryptographicState$( + userId: UserId, + ): Observable<WrappedAccountCryptographicState | null>; + + /** + * Sets the account cryptographic state. + * This is not yet validated, and is only validated upon SDK initialization. + */ + abstract setAccountCryptographicState( + accountCryptographicState: WrappedAccountCryptographicState, + userId: UserId, + ): Promise<void>; +} diff --git a/libs/common/src/key-management/account-cryptography/default-account-cryptographic-state.service.spec.ts b/libs/common/src/key-management/account-cryptography/default-account-cryptographic-state.service.spec.ts new file mode 100644 index 00000000000..62710073c00 --- /dev/null +++ b/libs/common/src/key-management/account-cryptography/default-account-cryptographic-state.service.spec.ts @@ -0,0 +1,133 @@ +import { firstValueFrom } from "rxjs"; + +import { WrappedAccountCryptographicState } from "@bitwarden/sdk-internal"; +import { FakeStateProvider } from "@bitwarden/state-test-utils"; +import { UserId } from "@bitwarden/user-core"; + +import { FakeAccountService, mockAccountServiceWith } from "../../../spec"; + +import { + ACCOUNT_CRYPTOGRAPHIC_STATE, + DefaultAccountCryptographicStateService, +} from "./default-account-cryptographic-state.service"; + +describe("DefaultAccountCryptographicStateService", () => { + let service: DefaultAccountCryptographicStateService; + let stateProvider: FakeStateProvider; + let accountService: FakeAccountService; + + const mockUserId = "user-id" as UserId; + + beforeEach(() => { + accountService = mockAccountServiceWith(mockUserId); + stateProvider = new FakeStateProvider(accountService); + service = new DefaultAccountCryptographicStateService(stateProvider); + }); + + describe("accountCryptographicState$", () => { + it("returns null when no state is set", async () => { + const result = await firstValueFrom(service.accountCryptographicState$(mockUserId)); + expect(result).toBeNull(); + }); + + it("returns the account cryptographic state when set (V1)", async () => { + const mockState: WrappedAccountCryptographicState = { + V1: { + private_key: "test-wrapped-state" as any, + }, + }; + await stateProvider.setUserState(ACCOUNT_CRYPTOGRAPHIC_STATE, mockState, mockUserId); + const result = await firstValueFrom(service.accountCryptographicState$(mockUserId)); + expect(result).toEqual(mockState); + }); + + it("returns the account cryptographic state when set (V2)", async () => { + const mockState: WrappedAccountCryptographicState = { + V2: { + private_key: "test-wrapped-private-key" as any, + signing_key: "test-wrapped-signing-key" as any, + signed_public_key: "test-signed-public-key" as any, + security_state: "test-security-state", + }, + }; + await stateProvider.setUserState(ACCOUNT_CRYPTOGRAPHIC_STATE, mockState, mockUserId); + const result = await firstValueFrom(service.accountCryptographicState$(mockUserId)); + expect(result).toEqual(mockState); + }); + + it("emits updated state when state changes", async () => { + const mockState1: any = { + V1: { + private_key: "test-state-1" as any, + }, + }; + const mockState2: any = { + V1: { + private_key: "test-state-2" as any, + }, + }; + + await stateProvider.setUserState(ACCOUNT_CRYPTOGRAPHIC_STATE, mockState1, mockUserId); + + const observable = service.accountCryptographicState$(mockUserId); + const results: (WrappedAccountCryptographicState | null)[] = []; + const subscription = observable.subscribe((state) => results.push(state)); + + await stateProvider.setUserState(ACCOUNT_CRYPTOGRAPHIC_STATE, mockState2, mockUserId); + + subscription.unsubscribe(); + + expect(results).toHaveLength(2); + expect(results[0]).toEqual(mockState1); + expect(results[1]).toEqual(mockState2); + }); + }); + + describe("setAccountCryptographicState", () => { + it("sets the account cryptographic state", async () => { + const mockState: WrappedAccountCryptographicState = { + V1: { + private_key: "test-wrapped-state" as any, + }, + }; + + await service.setAccountCryptographicState(mockState, mockUserId); + + const result = await firstValueFrom(service.accountCryptographicState$(mockUserId)); + + expect(result).toEqual(mockState); + }); + + it("overwrites existing state", async () => { + const mockState1: WrappedAccountCryptographicState = { + V1: { + private_key: "test-state-1" as any, + }, + }; + const mockState2: WrappedAccountCryptographicState = { + V1: { + private_key: "test-state-2" as any, + }, + }; + + await service.setAccountCryptographicState(mockState1, mockUserId); + await service.setAccountCryptographicState(mockState2, mockUserId); + + const result = await firstValueFrom(service.accountCryptographicState$(mockUserId)); + + expect(result).toEqual(mockState2); + }); + }); + + describe("ACCOUNT_CRYPTOGRAPHIC_STATE key definition", () => { + it("deserializer returns object as-is", () => { + const mockState: any = { + V1: { + private_key: "test" as any, + }, + }; + const result = ACCOUNT_CRYPTOGRAPHIC_STATE.deserializer(mockState); + expect(result).toBe(mockState); + }); + }); +}); diff --git a/libs/common/src/key-management/account-cryptography/default-account-cryptographic-state.service.ts b/libs/common/src/key-management/account-cryptography/default-account-cryptographic-state.service.ts new file mode 100644 index 00000000000..c37ac3c4fd1 --- /dev/null +++ b/libs/common/src/key-management/account-cryptography/default-account-cryptographic-state.service.ts @@ -0,0 +1,35 @@ +import { Observable } from "rxjs"; + +import { WrappedAccountCryptographicState } from "@bitwarden/sdk-internal"; +import { CRYPTO_DISK, StateProvider, UserKeyDefinition } from "@bitwarden/state"; +import { UserId } from "@bitwarden/user-core"; + +import { AccountCryptographicStateService } from "./account-cryptographic-state.service"; + +export const ACCOUNT_CRYPTOGRAPHIC_STATE = new UserKeyDefinition<WrappedAccountCryptographicState>( + CRYPTO_DISK, + "accountCryptographicState", + { + deserializer: (obj) => obj as WrappedAccountCryptographicState, + clearOn: ["logout"], + }, +); + +export class DefaultAccountCryptographicStateService implements AccountCryptographicStateService { + constructor(protected stateProvider: StateProvider) {} + + accountCryptographicState$(userId: UserId): Observable<WrappedAccountCryptographicState | null> { + return this.stateProvider.getUserState$(ACCOUNT_CRYPTOGRAPHIC_STATE, userId); + } + + async setAccountCryptographicState( + accountCryptographicState: WrappedAccountCryptographicState, + userId: UserId, + ): Promise<void> { + await this.stateProvider.setUserState( + ACCOUNT_CRYPTOGRAPHIC_STATE, + accountCryptographicState, + userId, + ); + } +} diff --git a/libs/common/src/key-management/keys/response/private-keys.response.ts b/libs/common/src/key-management/keys/response/private-keys.response.ts index 2bd723fb455..acf3cc1423a 100644 --- a/libs/common/src/key-management/keys/response/private-keys.response.ts +++ b/libs/common/src/key-management/keys/response/private-keys.response.ts @@ -1,3 +1,5 @@ +import { SignedPublicKey, WrappedAccountCryptographicState } from "@bitwarden/sdk-internal"; + import { SecurityStateResponse } from "../../security-state/response/security-state.response"; import { PublicKeyEncryptionKeyPairResponse } from "./public-key-encryption-key-pair.response"; @@ -52,4 +54,31 @@ export class PrivateKeysResponseModel { ); } } + + toWrappedAccountCryptographicState(): WrappedAccountCryptographicState { + if (this.signatureKeyPair === null && this.securityState === null) { + // V1 user + return { + V1: { + private_key: this.publicKeyEncryptionKeyPair.wrappedPrivateKey, + }, + }; + } else if (this.signatureKeyPair !== null && this.securityState !== null) { + // V2 user + return { + V2: { + private_key: this.publicKeyEncryptionKeyPair.wrappedPrivateKey, + signing_key: this.signatureKeyPair.wrappedSigningKey, + signed_public_key: this.publicKeyEncryptionKeyPair.signedPublicKey as SignedPublicKey, + security_state: this.securityState.securityState as string, + }, + }; + } else { + throw new Error("Both signatureKeyPair and securityState must be present or absent together"); + } + } + + isV2Encryption(): boolean { + return this.signatureKeyPair !== null && this.securityState !== null; + } } diff --git a/libs/common/src/platform/services/key-state/user-key.state.spec.ts b/libs/common/src/platform/services/key-state/user-key.state.spec.ts index 0af83ccf88a..2ea3c31bc1b 100644 --- a/libs/common/src/platform/services/key-state/user-key.state.spec.ts +++ b/libs/common/src/platform/services/key-state/user-key.state.spec.ts @@ -1,4 +1,4 @@ -import { EncString } from "../../../key-management/crypto/models/enc-string"; +import { EncryptedString, EncString } from "../../../key-management/crypto/models/enc-string"; import { EncryptionType } from "../../enums"; import { Utils } from "../../misc/utils"; @@ -27,7 +27,9 @@ describe("Encrypted private key", () => { it("should deserialize encrypted private key", () => { const encryptedPrivateKey = makeEncString().encryptedString; - const result = sut.deserializer(JSON.parse(JSON.stringify(encryptedPrivateKey))); + const result = sut.deserializer( + JSON.parse(JSON.stringify(encryptedPrivateKey as unknown)) as unknown as EncryptedString, + ); expect(result).toEqual(encryptedPrivateKey); }); diff --git a/libs/common/src/platform/sync/default-sync.service.spec.ts b/libs/common/src/platform/sync/default-sync.service.spec.ts index 621033ced65..fc83954ee7d 100644 --- a/libs/common/src/platform/sync/default-sync.service.spec.ts +++ b/libs/common/src/platform/sync/default-sync.service.spec.ts @@ -11,8 +11,6 @@ import { UserDecryptionOptions, UserDecryptionOptionsServiceAbstraction, } from "@bitwarden/auth/common"; -import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string"; -import { SecurityStateService } from "@bitwarden/common/key-management/security-state/abstractions/security-state.service"; // 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 { KdfConfigService, KeyService, PBKDF2KdfConfig } from "@bitwarden/key-management"; @@ -29,6 +27,8 @@ import { TokenService } from "../../auth/abstractions/token.service"; import { AuthenticationStatus } from "../../auth/enums/authentication-status"; import { DomainSettingsService } from "../../autofill/services/domain-settings.service"; import { BillingAccountProfileStateService } from "../../billing/abstractions"; +import { AccountCryptographicStateService } from "../../key-management/account-cryptography/account-cryptographic-state.service"; +import { EncString } from "../../key-management/crypto/models/enc-string"; import { KeyConnectorService } from "../../key-management/key-connector/abstractions/key-connector.service"; import { InternalMasterPasswordServiceAbstraction } from "../../key-management/master-password/abstractions/master-password.service.abstraction"; import { @@ -36,6 +36,7 @@ import { MasterPasswordSalt, MasterPasswordUnlockData, } from "../../key-management/master-password/types/master-password.types"; +import { SecurityStateService } from "../../key-management/security-state/abstractions/security-state.service"; import { SendApiService } from "../../tools/send/services/send-api.service.abstraction"; import { InternalSendService } from "../../tools/send/services/send.service.abstraction"; import { UserId } from "../../types/guid"; @@ -76,6 +77,7 @@ describe("DefaultSyncService", () => { let stateProvider: MockProxy<StateProvider>; let securityStateService: MockProxy<SecurityStateService>; let kdfConfigService: MockProxy<KdfConfigService>; + let accountCryptographicStateService: MockProxy<AccountCryptographicStateService>; let sut: DefaultSyncService; @@ -107,6 +109,7 @@ describe("DefaultSyncService", () => { stateProvider = mock(); securityStateService = mock(); kdfConfigService = mock(); + accountCryptographicStateService = mock(); sut = new DefaultSyncService( masterPasswordAbstraction, @@ -135,6 +138,7 @@ describe("DefaultSyncService", () => { stateProvider, securityStateService, kdfConfigService, + accountCryptographicStateService, ); }); diff --git a/libs/common/src/platform/sync/default-sync.service.ts b/libs/common/src/platform/sync/default-sync.service.ts index 8d2ccaffa18..49fd33b8035 100644 --- a/libs/common/src/platform/sync/default-sync.service.ts +++ b/libs/common/src/platform/sync/default-sync.service.ts @@ -9,8 +9,9 @@ import { CollectionDetailsResponse, CollectionService, } from "@bitwarden/admin-console/common"; -// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +import { AccountCryptographicStateService } from "@bitwarden/common/key-management/account-cryptography/account-cryptographic-state.service"; import { SecurityStateService } from "@bitwarden/common/key-management/security-state/abstractions/security-state.service"; +// 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 { KdfConfigService, KeyService } from "@bitwarden/key-management"; @@ -101,6 +102,7 @@ export class DefaultSyncService extends CoreSyncService { stateProvider: StateProvider, private securityStateService: SecurityStateService, private kdfConfigService: KdfConfigService, + private accountCryptographicStateService: AccountCryptographicStateService, ) { super( tokenService, @@ -239,12 +241,18 @@ export class DefaultSyncService extends CoreSyncService { // Cleanup: Only the first branch should be kept after the server always returns accountKeys https://bitwarden.atlassian.net/browse/PM-21768 if (response.accountKeys != null) { + await this.accountCryptographicStateService.setAccountCryptographicState( + response.accountKeys.toWrappedAccountCryptographicState(), + response.id, + ); + + // V1 and V2 users await this.keyService.setPrivateKey( response.accountKeys.publicKeyEncryptionKeyPair.wrappedPrivateKey, response.id, ); - if (response.accountKeys.signatureKeyPair !== null) { - // User is V2 user + // V2 users only + if (response.accountKeys.isV2Encryption()) { await this.keyService.setUserSigningKey( response.accountKeys.signatureKeyPair.wrappedSigningKey, response.id, From 4061710790f6d97ba345baa008f39baadebed651 Mon Sep 17 00:00:00 2001 From: Vijay Oommen <voommen@livefront.com> Date: Wed, 17 Dec 2025 15:19:06 -0600 Subject: [PATCH 112/188] [PM-26971] Fix DataDog and Crowdstrike Integration (#18024) --- .../configuration/datadog-configuration.ts | 14 +++-- .../models/configuration/hec-configuration.ts | 14 +++-- .../configuration/webhook-configuration.ts | 6 +-- .../models/integration-builder.ts | 50 +++++++++++++----- .../datadog-template.ts | 51 ++++++++++++++++--- .../configuration-template/hec-template.ts | 11 ++-- .../webhook-template.ts | 6 +-- .../organization-integration-service.ts | 2 +- 8 files changed, 116 insertions(+), 38 deletions(-) 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 index 51217a85877..4f84c0bcef6 100644 --- 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 @@ -4,15 +4,19 @@ import { OrganizationIntegrationServiceName } from "../organization-integration- export class DatadogConfiguration implements OrgIntegrationConfiguration { uri: string; apiKey: string; - service: OrganizationIntegrationServiceName; + bw_serviceName: OrganizationIntegrationServiceName; - constructor(uri: string, apiKey: string, service: OrganizationIntegrationServiceName) { + constructor(uri: string, apiKey: string, bw_serviceName: OrganizationIntegrationServiceName) { this.uri = uri; this.apiKey = apiKey; - this.service = service; + this.bw_serviceName = bw_serviceName; } - toString(): string { - return JSON.stringify(this); + toString() { + return JSON.stringify({ + Uri: this.uri, + ApiKey: this.apiKey, + bw_serviceName: this.bw_serviceName, + }); } } diff --git a/bitwarden_license/bit-common/src/dirt/organization-integrations/models/configuration/hec-configuration.ts b/bitwarden_license/bit-common/src/dirt/organization-integrations/models/configuration/hec-configuration.ts index d7e0cec1840..275ff82e9bd 100644 --- a/bitwarden_license/bit-common/src/dirt/organization-integrations/models/configuration/hec-configuration.ts +++ b/bitwarden_license/bit-common/src/dirt/organization-integrations/models/configuration/hec-configuration.ts @@ -5,15 +5,21 @@ export class HecConfiguration implements OrgIntegrationConfiguration { uri: string; scheme = "Bearer"; token: string; - service: OrganizationIntegrationServiceName; + service?: string; + bw_serviceName: OrganizationIntegrationServiceName; - constructor(uri: string, token: string, service: OrganizationIntegrationServiceName) { + constructor(uri: string, token: string, bw_serviceName: OrganizationIntegrationServiceName) { this.uri = uri; this.token = token; - this.service = service; + this.bw_serviceName = bw_serviceName; } toString(): string { - return JSON.stringify(this); + return JSON.stringify({ + Uri: this.uri, + Scheme: this.scheme, + Token: this.token, + bw_serviceName: this.bw_serviceName, + }); } } diff --git a/bitwarden_license/bit-common/src/dirt/organization-integrations/models/configuration/webhook-configuration.ts b/bitwarden_license/bit-common/src/dirt/organization-integrations/models/configuration/webhook-configuration.ts index 2b9ed6f7bda..f8243d3aac6 100644 --- a/bitwarden_license/bit-common/src/dirt/organization-integrations/models/configuration/webhook-configuration.ts +++ b/bitwarden_license/bit-common/src/dirt/organization-integrations/models/configuration/webhook-configuration.ts @@ -5,12 +5,12 @@ import { OrganizationIntegrationServiceName } from "../organization-integration- export class WebhookConfiguration implements OrgIntegrationConfiguration { propA: string; propB: string; - service: OrganizationIntegrationServiceName; + bw_serviceName: OrganizationIntegrationServiceName; - constructor(propA: string, propB: string, service: OrganizationIntegrationServiceName) { + constructor(propA: string, propB: string, bw_serviceName: OrganizationIntegrationServiceName) { this.propA = propA; this.propB = propB; - this.service = service; + this.bw_serviceName = bw_serviceName; } toString(): string { diff --git a/bitwarden_license/bit-common/src/dirt/organization-integrations/models/integration-builder.ts b/bitwarden_license/bit-common/src/dirt/organization-integrations/models/integration-builder.ts index ae790a67408..db682d58db4 100644 --- a/bitwarden_license/bit-common/src/dirt/organization-integrations/models/integration-builder.ts +++ b/bitwarden_license/bit-common/src/dirt/organization-integrations/models/integration-builder.ts @@ -9,7 +9,7 @@ import { OrganizationIntegrationType } from "./organization-integration-type"; * Defines the structure for organization integration configuration */ export interface OrgIntegrationConfiguration { - service: OrganizationIntegrationServiceName; + bw_serviceName: OrganizationIntegrationServiceName; toString(): string; } @@ -17,7 +17,7 @@ export interface OrgIntegrationConfiguration { * Defines the structure for organization integration template */ export interface OrgIntegrationTemplate { - service: OrganizationIntegrationServiceName; + bw_serviceName: OrganizationIntegrationServiceName; toString(): string; } @@ -28,24 +28,26 @@ export class OrgIntegrationBuilder { static buildHecConfiguration( uri: string, token: string, - service: OrganizationIntegrationServiceName, + bw_serviceName: OrganizationIntegrationServiceName, ): OrgIntegrationConfiguration { - return new HecConfiguration(uri, token, service); + return new HecConfiguration(uri, token, bw_serviceName); } static buildHecTemplate( index: string, - service: OrganizationIntegrationServiceName, + bw_serviceName: OrganizationIntegrationServiceName, ): OrgIntegrationTemplate { - return new HecTemplate(index, service); + return new HecTemplate(index, bw_serviceName); } static buildDataDogConfiguration(uri: string, apiKey: string): OrgIntegrationConfiguration { return new DatadogConfiguration(uri, apiKey, OrganizationIntegrationServiceName.Datadog); } - static buildDataDogTemplate(service: OrganizationIntegrationServiceName): OrgIntegrationTemplate { - return new DatadogTemplate(service); + static buildDataDogTemplate( + bw_serviceName: OrganizationIntegrationServiceName, + ): OrgIntegrationTemplate { + return new DatadogTemplate(bw_serviceName); } static buildConfiguration( @@ -55,7 +57,7 @@ export class OrgIntegrationBuilder { switch (type) { case OrganizationIntegrationType.Hec: { const hecConfig = this.convertToJson<HecConfiguration>(configuration); - return this.buildHecConfiguration(hecConfig.uri, hecConfig.token, hecConfig.service); + return this.buildHecConfiguration(hecConfig.uri, hecConfig.token, hecConfig.bw_serviceName); } case OrganizationIntegrationType.Datadog: { const datadogConfig = this.convertToJson<DatadogConfiguration>(configuration); @@ -73,11 +75,11 @@ export class OrgIntegrationBuilder { switch (type) { case OrganizationIntegrationType.Hec: { const hecTemplate = this.convertToJson<HecTemplate>(template); - return this.buildHecTemplate(hecTemplate.index, hecTemplate.service); + return this.buildHecTemplate(hecTemplate.index, hecTemplate.bw_serviceName); } case OrganizationIntegrationType.Datadog: { const datadogTemplate = this.convertToJson<DatadogTemplate>(template); - return this.buildDataDogTemplate(datadogTemplate.service); + return this.buildDataDogTemplate(datadogTemplate.bw_serviceName); } default: throw new Error(`Unsupported integration type: ${type}`); @@ -86,9 +88,33 @@ export class OrgIntegrationBuilder { private static convertToJson<T>(jsonString?: string): T { try { - return JSON.parse(jsonString || "{}") as T; + const parsed = JSON.parse(jsonString || "{}"); + return this.normalizePropertyCase(parsed) as T; } catch { throw new Error("Invalid integration configuration: JSON parse error"); } } + + /** + * Recursively normalizes object property names to camelCase + * Converts the first character of each property to lowercase + */ + private static normalizePropertyCase(obj: any): any { + if (obj === null || typeof obj !== "object") { + return obj; + } + + if (Array.isArray(obj)) { + return obj.map((item) => this.normalizePropertyCase(item)); + } + + const normalized: any = {}; + for (const key in obj) { + if (Object.prototype.hasOwnProperty.call(obj, key)) { + const normalizedKey = key.charAt(0).toLowerCase() + key.slice(1); + normalized[normalizedKey] = this.normalizePropertyCase(obj[key]); + } + } + return normalized; + } } 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 index d8e168aacbe..b5816ba34a2 100644 --- 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 @@ -2,17 +2,54 @@ import { OrgIntegrationTemplate } from "../../integration-builder"; import { OrganizationIntegrationServiceName } from "../../organization-integration-service-type"; export class DatadogTemplate implements OrgIntegrationTemplate { - 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: OrganizationIntegrationServiceName; + bw_serviceName: OrganizationIntegrationServiceName; constructor(service: OrganizationIntegrationServiceName) { - this.service = service; + this.bw_serviceName = service; + } + + private toJSON() { + return { + bw_serviceName: this.bw_serviceName, + ddsource: "bitwarden", + service: "event-logs", + event: { + service: "payments", + object: "event", + type: "#Type#", + itemId: "#CipherId#", + collectionId: "#CollectionId#", + groupId: "#GroupId#", + policyId: "#PolicyId#", + memberId: "#UserId#", + actingUserId: "#ActingUserId#", + installationId: "#InstallationId#", + date: "#DateIso8601#", + device: "#DeviceType#", + ipAddress: "#IpAddress#", + secretId: "#SecretId#", + projectId: "#ProjectId#", + serviceAccountId: "#ServiceAccountId#", + }, + enrichment_details: { + actingUser: { + name: "#ActingUserName#", + email: "#ActingUserEmail#", + type: "#ActingUserType#", + }, + member: { + name: "#UserName#", + email: "#UserEmail#", + type: "#UserType#", + }, + group: { + name: "#GroupName#", + }, + }, + }; } toString(): string { - return JSON.stringify(this); + return JSON.stringify(this.toJSON()); } } diff --git a/bitwarden_license/bit-common/src/dirt/organization-integrations/models/integration-configuration-config/configuration-template/hec-template.ts b/bitwarden_license/bit-common/src/dirt/organization-integrations/models/integration-configuration-config/configuration-template/hec-template.ts index e1b474d0e77..27d71f29e59 100644 --- a/bitwarden_license/bit-common/src/dirt/organization-integrations/models/integration-configuration-config/configuration-template/hec-template.ts +++ b/bitwarden_license/bit-common/src/dirt/organization-integrations/models/integration-configuration-config/configuration-template/hec-template.ts @@ -5,14 +5,19 @@ export class HecTemplate implements OrgIntegrationTemplate { event = "#EventMessage#"; source = "Bitwarden"; index: string; - service: OrganizationIntegrationServiceName; + bw_serviceName: OrganizationIntegrationServiceName; constructor(index: string, service: OrganizationIntegrationServiceName) { this.index = index; - this.service = service; + this.bw_serviceName = service; } toString(): string { - return JSON.stringify(this); + return JSON.stringify({ + Event: this.event, + Source: this.source, + Index: this.index, + bw_serviceName: this.bw_serviceName, + }); } } diff --git a/bitwarden_license/bit-common/src/dirt/organization-integrations/models/integration-configuration-config/configuration-template/webhook-template.ts b/bitwarden_license/bit-common/src/dirt/organization-integrations/models/integration-configuration-config/configuration-template/webhook-template.ts index fb482d1f367..2472fb5912e 100644 --- a/bitwarden_license/bit-common/src/dirt/organization-integrations/models/integration-configuration-config/configuration-template/webhook-template.ts +++ b/bitwarden_license/bit-common/src/dirt/organization-integrations/models/integration-configuration-config/configuration-template/webhook-template.ts @@ -3,12 +3,12 @@ import { OrganizationIntegrationServiceName } from "../../organization-integrati // Added to reflect how future webhook integrations could be structured within the OrganizationIntegration export class WebhookTemplate implements OrgIntegrationTemplate { - service: OrganizationIntegrationServiceName; + bw_serviceName: OrganizationIntegrationServiceName; propA: string; propB: string; - constructor(service: OrganizationIntegrationServiceName, propA: string, propB: string) { - this.service = service; + constructor(bw_serviceName: OrganizationIntegrationServiceName, propA: string, propB: string) { + this.bw_serviceName = bw_serviceName; this.propA = propA; this.propB = propB; } diff --git a/bitwarden_license/bit-common/src/dirt/organization-integrations/services/organization-integration-service.ts b/bitwarden_license/bit-common/src/dirt/organization-integrations/services/organization-integration-service.ts index cd153bc1133..c9457f4bcfc 100644 --- a/bitwarden_license/bit-common/src/dirt/organization-integrations/services/organization-integration-service.ts +++ b/bitwarden_license/bit-common/src/dirt/organization-integrations/services/organization-integration-service.ts @@ -266,7 +266,7 @@ export class OrganizationIntegrationService { return new OrganizationIntegration( integrationResponse.id, integrationResponse.type, - config.service, + config.bw_serviceName, config, [integrationConfig], ); From 9db632146bf71aed5189be370949793bf1a82aa4 Mon Sep 17 00:00:00 2001 From: John Harrington <84741727+harr1424@users.noreply.github.com> Date: Wed, 17 Dec 2025 14:38:18 -0700 Subject: [PATCH 113/188] PM-22836 Import popout crashes unexpectedly (#16928) * follow existing popout guard pattern to force popout on firefox when filepicker is exposed * move firefox guard to tools ownership & revert changes to auth owned file * removed redundant test case --- apps/browser/src/popup/app-routing.module.ts | 7 +- .../popup/guards/firefox-popout.guard.spec.ts | 194 ++++++++++++++++++ .../popup/guards/firefox-popout.guard.ts | 38 ++++ 3 files changed, 236 insertions(+), 3 deletions(-) create mode 100644 apps/browser/src/tools/popup/guards/firefox-popout.guard.spec.ts create mode 100644 apps/browser/src/tools/popup/guards/firefox-popout.guard.ts diff --git a/apps/browser/src/popup/app-routing.module.ts b/apps/browser/src/popup/app-routing.module.ts index eb64c076192..12e1288e806 100644 --- a/apps/browser/src/popup/app-routing.module.ts +++ b/apps/browser/src/popup/app-routing.module.ts @@ -68,6 +68,7 @@ import { popupRouterCacheGuard } from "../platform/popup/view-cache/popup-router import { RouteCacheOptions } from "../platform/services/popup-view-cache-background.service"; import { CredentialGeneratorHistoryComponent } from "../tools/popup/generator/credential-generator-history.component"; import { CredentialGeneratorComponent } from "../tools/popup/generator/credential-generator.component"; +import { firefoxPopoutGuard } from "../tools/popup/guards/firefox-popout.guard"; import { SendAddEditComponent as SendAddEditV2Component } from "../tools/popup/send-v2/add-edit/send-add-edit.component"; import { SendCreatedComponent } from "../tools/popup/send-v2/send-created/send-created.component"; import { SendV2Component } from "../tools/popup/send-v2/send-v2.component"; @@ -262,7 +263,7 @@ const routes: Routes = [ { path: "import", component: ImportBrowserV2Component, - canActivate: [authGuard], + canActivate: [authGuard, firefoxPopoutGuard()], data: { elevation: 1 } satisfies RouteDataProperties, }, { @@ -340,13 +341,13 @@ const routes: Routes = [ { path: "add-send", component: SendAddEditV2Component, - canActivate: [authGuard], + canActivate: [authGuard, firefoxPopoutGuard()], data: { elevation: 1 } satisfies RouteDataProperties, }, { path: "edit-send", component: SendAddEditV2Component, - canActivate: [authGuard], + canActivate: [authGuard, firefoxPopoutGuard()], data: { elevation: 1 } satisfies RouteDataProperties, }, { diff --git a/apps/browser/src/tools/popup/guards/firefox-popout.guard.spec.ts b/apps/browser/src/tools/popup/guards/firefox-popout.guard.spec.ts new file mode 100644 index 00000000000..df04b965d4c --- /dev/null +++ b/apps/browser/src/tools/popup/guards/firefox-popout.guard.spec.ts @@ -0,0 +1,194 @@ +import { TestBed } from "@angular/core/testing"; +import { ActivatedRouteSnapshot, RouterStateSnapshot } from "@angular/router"; + +import { DeviceType } from "@bitwarden/common/enums"; + +import { BrowserApi } from "../../../platform/browser/browser-api"; +import BrowserPopupUtils from "../../../platform/browser/browser-popup-utils"; +import { BrowserPlatformUtilsService } from "../../../platform/services/platform-utils/browser-platform-utils.service"; + +import { firefoxPopoutGuard } from "./firefox-popout.guard"; + +describe("firefoxPopoutGuard", () => { + let getDeviceSpy: jest.SpyInstance; + let inPopoutSpy: jest.SpyInstance; + let inSidebarSpy: jest.SpyInstance; + let openPopoutSpy: jest.SpyInstance; + let closePopupSpy: jest.SpyInstance; + + const mockRoute = {} as ActivatedRouteSnapshot; + const mockState: RouterStateSnapshot = { + url: "/import?param=value", + } as RouterStateSnapshot; + + beforeEach(() => { + getDeviceSpy = jest.spyOn(BrowserPlatformUtilsService, "getDevice"); + inPopoutSpy = jest.spyOn(BrowserPopupUtils, "inPopout"); + inSidebarSpy = jest.spyOn(BrowserPopupUtils, "inSidebar"); + openPopoutSpy = jest.spyOn(BrowserPopupUtils, "openPopout").mockImplementation(); + closePopupSpy = jest.spyOn(BrowserApi, "closePopup").mockImplementation(); + + TestBed.configureTestingModule({}); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + describe("when browser is Firefox", () => { + beforeEach(() => { + getDeviceSpy.mockReturnValue(DeviceType.FirefoxExtension); + inPopoutSpy.mockReturnValue(false); + inSidebarSpy.mockReturnValue(false); + }); + + it("should open popout and block navigation when not already in popout or sidebar", async () => { + const guard = firefoxPopoutGuard(); + const result = await TestBed.runInInjectionContext(() => guard(mockRoute, mockState)); + + expect(getDeviceSpy).toHaveBeenCalledWith(window); + expect(inPopoutSpy).toHaveBeenCalledWith(window); + expect(inSidebarSpy).toHaveBeenCalledWith(window); + expect(openPopoutSpy).toHaveBeenCalledWith("popup/index.html#/import?param=value"); + expect(closePopupSpy).toHaveBeenCalledWith(window); + expect(result).toBe(false); + }); + + it("should not add autoClosePopout parameter to the url", async () => { + const guard = firefoxPopoutGuard(); + await TestBed.runInInjectionContext(() => guard(mockRoute, mockState)); + + expect(openPopoutSpy).toHaveBeenCalledWith("popup/index.html#/import?param=value"); + expect(openPopoutSpy).not.toHaveBeenCalledWith(expect.stringContaining("autoClosePopout")); + }); + + it("should allow navigation when already in popout", async () => { + inPopoutSpy.mockReturnValue(true); + + const guard = firefoxPopoutGuard(); + const result = await TestBed.runInInjectionContext(() => guard(mockRoute, mockState)); + + expect(openPopoutSpy).not.toHaveBeenCalled(); + expect(closePopupSpy).not.toHaveBeenCalled(); + expect(result).toBe(true); + }); + + it("should allow navigation when already in sidebar", async () => { + inSidebarSpy.mockReturnValue(true); + + const guard = firefoxPopoutGuard(); + const result = await TestBed.runInInjectionContext(() => guard(mockRoute, mockState)); + + expect(openPopoutSpy).not.toHaveBeenCalled(); + expect(closePopupSpy).not.toHaveBeenCalled(); + expect(result).toBe(true); + }); + }); + + describe("when browser is not Firefox", () => { + beforeEach(() => { + inPopoutSpy.mockReturnValue(false); + inSidebarSpy.mockReturnValue(false); + }); + + it.each([ + { deviceType: DeviceType.ChromeExtension, name: "ChromeExtension" }, + { deviceType: DeviceType.EdgeExtension, name: "EdgeExtension" }, + { deviceType: DeviceType.OperaExtension, name: "OperaExtension" }, + { deviceType: DeviceType.SafariExtension, name: "SafariExtension" }, + { deviceType: DeviceType.VivaldiExtension, name: "VivaldiExtension" }, + ])( + "should allow navigation without opening popout when device is $name", + async ({ deviceType }) => { + getDeviceSpy.mockReturnValue(deviceType); + + const guard = firefoxPopoutGuard(); + const result = await TestBed.runInInjectionContext(() => guard(mockRoute, mockState)); + + expect(getDeviceSpy).toHaveBeenCalledWith(window); + expect(openPopoutSpy).not.toHaveBeenCalled(); + expect(closePopupSpy).not.toHaveBeenCalled(); + expect(result).toBe(true); + }, + ); + }); + + describe("file picker routes", () => { + beforeEach(() => { + getDeviceSpy.mockReturnValue(DeviceType.FirefoxExtension); + inPopoutSpy.mockReturnValue(false); + inSidebarSpy.mockReturnValue(false); + }); + + it("should open popout for /import route", async () => { + const importState: RouterStateSnapshot = { + url: "/import", + } as RouterStateSnapshot; + + const guard = firefoxPopoutGuard(); + const result = await TestBed.runInInjectionContext(() => guard(mockRoute, importState)); + + expect(openPopoutSpy).toHaveBeenCalledWith("popup/index.html#/import"); + expect(closePopupSpy).toHaveBeenCalledWith(window); + expect(result).toBe(false); + }); + + it("should open popout for /add-send route", async () => { + const addSendState: RouterStateSnapshot = { + url: "/add-send", + } as RouterStateSnapshot; + + const guard = firefoxPopoutGuard(); + const result = await TestBed.runInInjectionContext(() => guard(mockRoute, addSendState)); + + expect(openPopoutSpy).toHaveBeenCalledWith("popup/index.html#/add-send"); + expect(closePopupSpy).toHaveBeenCalledWith(window); + expect(result).toBe(false); + }); + + it("should open popout for /edit-send route", async () => { + const editSendState: RouterStateSnapshot = { + url: "/edit-send", + } as RouterStateSnapshot; + + const guard = firefoxPopoutGuard(); + const result = await TestBed.runInInjectionContext(() => guard(mockRoute, editSendState)); + + expect(openPopoutSpy).toHaveBeenCalledWith("popup/index.html#/edit-send"); + expect(closePopupSpy).toHaveBeenCalledWith(window); + expect(result).toBe(false); + }); + }); + + describe("url handling", () => { + beforeEach(() => { + getDeviceSpy.mockReturnValue(DeviceType.FirefoxExtension); + inPopoutSpy.mockReturnValue(false); + inSidebarSpy.mockReturnValue(false); + }); + + it("should preserve query parameters in the popout url", async () => { + const stateWithQuery: RouterStateSnapshot = { + url: "/import?foo=bar&baz=qux", + } as RouterStateSnapshot; + + const guard = firefoxPopoutGuard(); + await TestBed.runInInjectionContext(() => guard(mockRoute, stateWithQuery)); + + expect(openPopoutSpy).toHaveBeenCalledWith("popup/index.html#/import?foo=bar&baz=qux"); + expect(closePopupSpy).toHaveBeenCalledWith(window); + }); + + it("should handle urls without query parameters", async () => { + const stateWithoutQuery: RouterStateSnapshot = { + url: "/simple-path", + } as RouterStateSnapshot; + + const guard = firefoxPopoutGuard(); + await TestBed.runInInjectionContext(() => guard(mockRoute, stateWithoutQuery)); + + expect(openPopoutSpy).toHaveBeenCalledWith("popup/index.html#/simple-path"); + expect(closePopupSpy).toHaveBeenCalledWith(window); + }); + }); +}); diff --git a/apps/browser/src/tools/popup/guards/firefox-popout.guard.ts b/apps/browser/src/tools/popup/guards/firefox-popout.guard.ts new file mode 100644 index 00000000000..821f1b7a5bc --- /dev/null +++ b/apps/browser/src/tools/popup/guards/firefox-popout.guard.ts @@ -0,0 +1,38 @@ +import { ActivatedRouteSnapshot, CanActivateFn, RouterStateSnapshot } from "@angular/router"; + +import { BrowserApi } from "@bitwarden/browser/platform/browser/browser-api"; +import BrowserPopupUtils from "@bitwarden/browser/platform/browser/browser-popup-utils"; +import { BrowserPlatformUtilsService } from "@bitwarden/browser/platform/services/platform-utils/browser-platform-utils.service"; +import { DeviceType } from "@bitwarden/common/enums"; + +/** + * Guard that forces a popout window on Firefox browser when a file picker could be exposed. + * Necessary to avoid a crash: https://bugzilla.mozilla.org/show_bug.cgi?id=1292701 + * Also disallows the user from closing a popout and re-opening the view exposing the file picker. + * + * @returns CanActivateFn that opens popout and blocks navigation on Firefox + */ +export function firefoxPopoutGuard(): CanActivateFn { + return async (_route: ActivatedRouteSnapshot, state: RouterStateSnapshot) => { + // Check if browser is Firefox using the platform utils service + const deviceType = BrowserPlatformUtilsService.getDevice(window); + const isFirefox = deviceType === DeviceType.FirefoxExtension; + + // Check if already in popout/sidebar + const inPopout = BrowserPopupUtils.inPopout(window); + const inSidebar = BrowserPopupUtils.inSidebar(window); + + // Open popout if on Firefox and not already in popout/sidebar + if (isFirefox && !inPopout && !inSidebar) { + // Don't add autoClosePopout for file picker scenarios - user should manually close + await BrowserPopupUtils.openPopout(`popup/index.html#${state.url}`); + + // Close the original popup window + BrowserApi.closePopup(window); + + return false; // Block navigation - popout will reload + } + + return true; // Allow navigation + }; +} From ad4b6a30a0ad86c63945673391f8559c377bd9f7 Mon Sep 17 00:00:00 2001 From: Oscar Hinton <Hinton@users.noreply.github.com> Date: Thu, 18 Dec 2025 07:49:26 +0100 Subject: [PATCH 114/188] [UIF] Prefer static string properties (#17966) --- libs/components/src/button/button.stories.ts | 8 +++--- .../src/copy-click/copy-click.stories.ts | 8 +++--- .../src/form-field/form-field.stories.ts | 27 +++++++++---------- libs/components/src/item/item.stories.ts | 2 +- .../src/navigation/nav-group.component.html | 2 +- .../src/navigation/nav-group.stories.ts | 4 +-- .../src/navigation/nav-item.stories.ts | 12 ++++----- .../src/navigation/nav-logo.component.html | 2 +- libs/components/src/popover/popover.mdx | 2 +- .../components/kitchen-sink-form.component.ts | 4 +-- .../components/kitchen-sink-main.component.ts | 2 +- 11 files changed, 36 insertions(+), 37 deletions(-) diff --git a/libs/components/src/button/button.stories.ts b/libs/components/src/button/button.stories.ts index 29c4dea3088..24c263f240a 100644 --- a/libs/components/src/button/button.stories.ts +++ b/libs/components/src/button/button.stories.ts @@ -81,10 +81,10 @@ export const Small: Story = { props: args, template: /*html*/ ` <div class="tw-flex tw-gap-4 tw-mb-6 tw-items-center"> - <button type="button" bitButton [disabled]="disabled" [loading]="loading" [buttonType]="'primary'" [size]="size" [block]="block">Primary small</button> - <button type="button" bitButton [disabled]="disabled" [loading]="loading" [buttonType]="'secondary'" [size]="size" [block]="block">Secondary small</button> - <button type="button" bitButton [disabled]="disabled" [loading]="loading" [buttonType]="'danger'" [size]="size" [block]="block">Danger small</button> - <button type="button" bitButton [disabled]="disabled" [loading]="loading" [buttonType]="'dangerPrimary'" [size]="size" [block]="block">Danger Primary small</button> + <button type="button" bitButton [disabled]="disabled" [loading]="loading" buttonType="primary" [size]="size" [block]="block">Primary small</button> + <button type="button" bitButton [disabled]="disabled" [loading]="loading" buttonType="secondary" [size]="size" [block]="block">Secondary small</button> + <button type="button" bitButton [disabled]="disabled" [loading]="loading" buttonType="danger" [size]="size" [block]="block">Danger small</button> + <button type="button" bitButton [disabled]="disabled" [loading]="loading" buttonType="dangerPrimary" [size]="size" [block]="block">Danger Primary small</button> </div> `, }), diff --git a/libs/components/src/copy-click/copy-click.stories.ts b/libs/components/src/copy-click/copy-click.stories.ts index 208d42b44c4..5de150d7e3d 100644 --- a/libs/components/src/copy-click/copy-click.stories.ts +++ b/libs/components/src/copy-click/copy-click.stories.ts @@ -64,7 +64,7 @@ export const Default: Story = { type="button" bitSuffix bitIconButton="bwi-clone" - [label]="'Copy'" + label="Copy" [appCopyClick]="value" ></button> </bit-form-field> @@ -86,7 +86,7 @@ export const WithDefaultToast: Story = { type="button" bitSuffix bitIconButton="bwi-clone" - [label]="'Copy'" + label="Copy" [appCopyClick]="value" showToast ></button> @@ -109,7 +109,7 @@ export const WithCustomToastVariant: Story = { type="button" bitSuffix bitIconButton="bwi-clone" - [label]="'Copy'" + label="Copy" [appCopyClick]="value" showToast="info" ></button> @@ -132,7 +132,7 @@ export const WithCustomValueLabel: Story = { type="button" bitSuffix bitIconButton="bwi-clone" - [label]="'Copy'" + label="Copy" [appCopyClick]="value" showToast valueLabel="API Key" diff --git a/libs/components/src/form-field/form-field.stories.ts b/libs/components/src/form-field/form-field.stories.ts index 01aa56f896c..6a72f7d018f 100644 --- a/libs/components/src/form-field/form-field.stories.ts +++ b/libs/components/src/form-field/form-field.stories.ts @@ -241,7 +241,7 @@ export const Readonly: Story = { <bit-label>Input</bit-label> <input bitInput type="password" value="Foobar" [readonly]="true" /> <button type="button" label="Toggle password" bitIconButton bitSuffix bitPasswordInputToggle></button> - <button type="button" bitSuffix bitIconButton="bwi-clone" [label]="'Clone Input'"></button> + <button type="button" bitSuffix bitIconButton="bwi-clone" label="Clone Input"></button> </bit-form-field> <bit-form-field> @@ -262,7 +262,7 @@ export const Readonly: Story = { <bit-label>Input</bit-label> <input bitInput type="password" value="Foobar" readonly /> <button type="button" bitIconButton bitSuffix bitPasswordInputToggle></button> - <button type="button" bitSuffix bitIconButton="bwi-clone" [label]="'Clone Input'"></button> + <button type="button" bitSuffix bitIconButton="bwi-clone" label="Clone Input"></button> </bit-form-field> <bit-form-field> @@ -310,11 +310,11 @@ export const ButtonInputGroup: Story = { <i class="bwi bwi-question-circle" aria-hidden="true"></i> </a> </bit-label> - <button type="button" bitPrefix bitIconButton="bwi-star" [label]="'Favorite Label'"></button> + <button type="button" bitPrefix bitIconButton="bwi-star" label="Favorite Label"></button> <input bitInput placeholder="Placeholder" /> - <button type="button" bitSuffix bitIconButton="bwi-eye" [label]="'Hide Label'"></button> - <button type="button" bitSuffix bitIconButton="bwi-clone" [label]="'Clone Label'"></button> - <button type="button" bitSuffix bitIconButton="bwi-ellipsis-v" [label]="'Menu Label'"></button> + <button type="button" bitSuffix bitIconButton="bwi-eye" label="Hide Label"></button> + <button type="button" bitSuffix bitIconButton="bwi-clone" label="Clone Label"></button> + <button type="button" bitSuffix bitIconButton="bwi-ellipsis-v" label="Menu Label"></button> </bit-form-field> `, }), @@ -327,12 +327,11 @@ export const DisabledButtonInputGroup: Story = { template: /*html*/ ` <bit-form-field> <bit-label>Label</bit-label> - <button type="button" bitPrefix bitIconButton="bwi-star" disabled [label]="'Favorite Label'"></button> + <button type="button" bitPrefix bitIconButton="bwi-star" disabled label="Favorite Label"></button> <input bitInput placeholder="Placeholder" disabled /> - <button type="button" bitSuffix bitIconButton="bwi-eye" disabled [label]="'Hide Label'"></button> - <button type="button" bitSuffix bitIconButton="bwi-clone" disabled [label]="'Clone Label'"></button> - <button type="button" bitSuffix bitIconButton="bwi-ellipsis-v" disabled [label]="'Menu Label'"></button> - + <button type="button" bitSuffix bitIconButton="bwi-eye" disabled label="Hide Label"></button> + <button type="button" bitSuffix bitIconButton="bwi-clone" disabled label="Clone Label"></button> + <button type="button" bitSuffix bitIconButton="bwi-ellipsis-v" disabled label="Menu Label"></button> </bit-form-field> `, }), @@ -346,9 +345,9 @@ export const PartiallyDisabledButtonInputGroup: Story = { <bit-form-field> <bit-label>Label</bit-label> <input bitInput placeholder="Placeholder" disabled /> - <button type="button" bitSuffix bitIconButton="bwi-eye" [label]="'Hide Label'"></button> - <button type="button" bitSuffix bitIconButton="bwi-clone" [label]="'Clone Label'"></button> - <button type="button" bitSuffix bitIconButton="bwi-ellipsis-v" disabled [label]="'Menu Label'"></button> + <button type="button" bitSuffix bitIconButton="bwi-eye" label="Hide Label"></button> + <button type="button" bitSuffix bitIconButton="bwi-clone" label="Clone Label"></button> + <button type="button" bitSuffix bitIconButton="bwi-ellipsis-v" disabled label="Menu Label"></button> </bit-form-field> `, }), diff --git a/libs/components/src/item/item.stories.ts b/libs/components/src/item/item.stories.ts index 36bbebee940..d2c197d0088 100644 --- a/libs/components/src/item/item.stories.ts +++ b/libs/components/src/item/item.stories.ts @@ -97,7 +97,7 @@ export const ContentSlots: Story = { <button bit-item-content type="button"> <bit-avatar slot="start" - [text]="'Foo'" + text="Foo" ></bit-avatar> foo&#64;bitwarden.com <ng-container slot="secondary"> diff --git a/libs/components/src/navigation/nav-group.component.html b/libs/components/src/navigation/nav-group.component.html index 5a66eeafbf7..a5cb1d5a6b9 100644 --- a/libs/components/src/navigation/nav-group.component.html +++ b/libs/components/src/navigation/nav-group.component.html @@ -23,7 +23,7 @@ 'tw-transform tw-rotate-[90deg]': variantValue === 'tree' && !open(), }" [bitIconButton]="toggleButtonIcon()" - [buttonType]="'nav-contrast'" + buttonType="nav-contrast" (click)="toggle($event)" size="small" [attr.aria-expanded]="open().toString()" diff --git a/libs/components/src/navigation/nav-group.stories.ts b/libs/components/src/navigation/nav-group.stories.ts index e3033e4b40a..c0111c23fc1 100644 --- a/libs/components/src/navigation/nav-group.stories.ts +++ b/libs/components/src/navigation/nav-group.stories.ts @@ -144,8 +144,8 @@ export const Tree: StoryObj<NavGroupComponent> = { template: /*html*/ ` <bit-side-nav> <bit-nav-group text="Tree example" icon="bwi-collection-shared" [open]="true"> - <bit-nav-item text="Level 1 - no children" route="t2" icon="bwi-collection-shared" [variant]="'tree'"></bit-nav-item> - <bit-nav-group text="Level 1 - with children" route="t3" icon="bwi-collection-shared" [variant]="'tree'" [open]="true"> + <bit-nav-item text="Level 1 - no children" route="t2" icon="bwi-collection-shared" variant="tree"></bit-nav-item> + <bit-nav-group text="Level 1 - with children" route="t3" icon="bwi-collection-shared" variant="tree" [open]="true"> <bit-nav-group text="Level 2 - with children" route="t4" icon="bwi-collection-shared" variant="tree" [open]="true"> <bit-nav-item text="Level 3 - no children, no icon" route="t5" variant="tree"></bit-nav-item> <bit-nav-group text="Level 3 - with children" route="t6" icon="bwi-collection-shared" variant="tree" [open]="true"> diff --git a/libs/components/src/navigation/nav-item.stories.ts b/libs/components/src/navigation/nav-item.stories.ts index 91fc0b02e89..131dacc8142 100644 --- a/libs/components/src/navigation/nav-item.stories.ts +++ b/libs/components/src/navigation/nav-item.stories.ts @@ -90,20 +90,20 @@ export const WithChildButtons: Story = { template: /*html*/ ` <bit-nav-item text="Hello World Very Cool World" [route]="['']" icon="bwi-collection-shared"> <button - type="button" + type="button" slot="end" class="tw-ms-auto" - [bitIconButton]="'bwi-pencil-square'" - [buttonType]="'nav-contrast'" + bitIconButton="bwi-pencil-square" + buttonType="nav-contrast" size="small" label="Edit" ></button> <button - type="button" + type="button" slot="end" class="tw-ms-auto" - [bitIconButton]="'bwi-check'" - [buttonType]="'nav-contrast'" + bitIconButton="bwi-check" + buttonType="nav-contrast" size="small" label="Confirm" ></button> diff --git a/libs/components/src/navigation/nav-logo.component.html b/libs/components/src/navigation/nav-logo.component.html index 391e62ec8fd..0ea5d2fcfa8 100644 --- a/libs/components/src/navigation/nav-logo.component.html +++ b/libs/components/src/navigation/nav-logo.component.html @@ -17,7 +17,7 @@ [attr.aria-label]="label()" [title]="label()" routerLinkActive - [ariaCurrentWhenActive]="'page'" + ariaCurrentWhenActive="page" > <bit-icon [icon]="sideNavService.open ? openIcon() : closedIcon()"></bit-icon> </a> diff --git a/libs/components/src/popover/popover.mdx b/libs/components/src/popover/popover.mdx index d56f00b10ae..8488b56f28c 100644 --- a/libs/components/src/popover/popover.mdx +++ b/libs/components/src/popover/popover.mdx @@ -81,7 +81,7 @@ You can manually set the initial position of the Popover by binding a `[position Popover's trigger element, such as: ```html -<button [bitPopoverTriggerFor]="myPopover" [position]="'above-end'">Open Popover</button> +<button [bitPopoverTriggerFor]="myPopover" position="above-end">Open Popover</button> ``` <Canvas of={stories.AboveEnd} /> diff --git a/libs/components/src/stories/kitchen-sink/components/kitchen-sink-form.component.ts b/libs/components/src/stories/kitchen-sink/components/kitchen-sink-form.component.ts index 0a9581fb375..23c95cafb8a 100644 --- a/libs/components/src/stories/kitchen-sink/components/kitchen-sink-form.component.ts +++ b/libs/components/src/stories/kitchen-sink/components/kitchen-sink-form.component.ts @@ -99,7 +99,7 @@ import { KitchenSinkSharedModule } from "../kitchen-sink-shared.module"; <bit-color-password class="tw-text-base" - [password]="'Wq$Jk😀7j DX#rS5Sdi!z'" + password="Wq$Jk😀7j DX#rS5Sdi!z" [showCount]="true" ></bit-color-password> </div> @@ -123,7 +123,7 @@ import { KitchenSinkSharedModule } from "../kitchen-sink-shared.module"; <button bitButton bitFormButton buttonType="primary" type="submit">Submit</button> <bit-error-summary [formGroup]="formObj"></bit-error-summary> - <bit-popover [title]="'Password help'" #myPopover> + <bit-popover title="Password help" #myPopover> <div>A strong password has the following:</div> <ul class="tw-mt-2 tw-mb-0 tw-ps-4"> <li>Letters</li> diff --git a/libs/components/src/stories/kitchen-sink/components/kitchen-sink-main.component.ts b/libs/components/src/stories/kitchen-sink/components/kitchen-sink-main.component.ts index 0819ae3f06b..81b5e1b49c5 100644 --- a/libs/components/src/stories/kitchen-sink/components/kitchen-sink-main.component.ts +++ b/libs/components/src/stories/kitchen-sink/components/kitchen-sink-main.component.ts @@ -172,7 +172,7 @@ class KitchenSinkDialogComponent { </bit-tab> </bit-tab-group> - <bit-popover [title]="'Educational Popover'" #myPopover> + <bit-popover title="Educational Popover" #myPopover> <div>You can learn more things at:</div> <ul class="tw-mt-2 tw-mb-0 tw-ps-4"> <li>Help center</li> From 14c0ca766c6242d9bb2fd9be0175ac9402683fd6 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann <mail@quexten.com> Date: Thu, 18 Dec 2025 11:49:56 +0100 Subject: [PATCH 115/188] Update sdk to 433 (#18016) --- package-lock.json | 16 ++++++++-------- package.json | 4 ++-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6d67a0172bf..3e4400772b2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,8 +23,8 @@ "@angular/platform-browser": "20.3.15", "@angular/platform-browser-dynamic": "20.3.15", "@angular/router": "20.3.15", - "@bitwarden/commercial-sdk-internal": "0.2.0-main.409", - "@bitwarden/sdk-internal": "0.2.0-main.409", + "@bitwarden/commercial-sdk-internal": "0.2.0-main.433", + "@bitwarden/sdk-internal": "0.2.0-main.433", "@electron/fuses": "1.8.0", "@emotion/css": "11.13.5", "@koa/multer": "4.0.0", @@ -4863,9 +4863,9 @@ "link": true }, "node_modules/@bitwarden/commercial-sdk-internal": { - "version": "0.2.0-main.409", - "resolved": "https://registry.npmjs.org/@bitwarden/commercial-sdk-internal/-/commercial-sdk-internal-0.2.0-main.409.tgz", - "integrity": "sha512-86AVuOG5S9Te9ZMvozCV1FN8v98ROnUwr24nTeocqD/5OJIKoWWXk1t+g4YoVF+8AItpD6hFfO/uL05mG80jDA==", + "version": "0.2.0-main.433", + "resolved": "https://registry.npmjs.org/@bitwarden/commercial-sdk-internal/-/commercial-sdk-internal-0.2.0-main.433.tgz", + "integrity": "sha512-/eFzw+BUHxAmT75kKUn1r9MFsJH/GZpc3ljkjNjAqtvb3L+fz8VTHTe7FoloSoZEnAnp8OWOZy7n4DavT/XDiw==", "license": "BITWARDEN SOFTWARE DEVELOPMENT KIT LICENSE AGREEMENT", "dependencies": { "type-fest": "^4.41.0" @@ -4968,9 +4968,9 @@ "link": true }, "node_modules/@bitwarden/sdk-internal": { - "version": "0.2.0-main.409", - "resolved": "https://registry.npmjs.org/@bitwarden/sdk-internal/-/sdk-internal-0.2.0-main.409.tgz", - "integrity": "sha512-3e3hZenNos1xACJ2Bsq14RrzCnWdIilJuJhFwZYQBI6PQ+R38UGGQhGJDandKvQggfR8bDCY3re8x8n9G7MDiA==", + "version": "0.2.0-main.433", + "resolved": "https://registry.npmjs.org/@bitwarden/sdk-internal/-/sdk-internal-0.2.0-main.433.tgz", + "integrity": "sha512-m2PnYR0ifF0BgZ63aAt8eag0v7LeEGTJ0sa7UMbTWLwmsNnHug4u7jxIJl0WaVILNeWWK8iD/WSiw3EJeb7Fmw==", "license": "GPL-3.0", "dependencies": { "type-fest": "^4.41.0" diff --git a/package.json b/package.json index 1bb7c5dbe40..5346614814a 100644 --- a/package.json +++ b/package.json @@ -157,8 +157,8 @@ "@angular/platform-browser": "20.3.15", "@angular/platform-browser-dynamic": "20.3.15", "@angular/router": "20.3.15", - "@bitwarden/sdk-internal": "0.2.0-main.409", - "@bitwarden/commercial-sdk-internal": "0.2.0-main.409", + "@bitwarden/sdk-internal": "0.2.0-main.433", + "@bitwarden/commercial-sdk-internal": "0.2.0-main.433", "@electron/fuses": "1.8.0", "@emotion/css": "11.13.5", "@koa/multer": "4.0.0", From a4e5c9ab3719e4fc600305e205a122278083531f Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann <mail@quexten.com> Date: Thu, 18 Dec 2025 14:35:30 +0100 Subject: [PATCH 116/188] Add .desktop file for flatpak dev build (#18018) --- apps/desktop/resources/com.bitwarden.desktop.devel.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apps/desktop/resources/com.bitwarden.desktop.devel.yaml b/apps/desktop/resources/com.bitwarden.desktop.devel.yaml index e72df98e22b..0f6e3ea370d 100644 --- a/apps/desktop/resources/com.bitwarden.desktop.devel.yaml +++ b/apps/desktop/resources/com.bitwarden.desktop.devel.yaml @@ -33,7 +33,10 @@ modules: - mkdir -p /app/bin - mkdir -p /app/bin/Bitwarden/ - cp -r ./* /app/bin/ + - mkdir -p ${FLATPAK_DEST}/share/applications/ + - cp com.bitwarden.desktop.desktop ${FLATPAK_DEST}/share/applications/ - install bitwarden.sh /app/bin/bitwarden.sh + - desktop-file-edit --set-key=Exec --set-value="bitwarden.sh %U" ${FLATPAK_DEST}/share/applications/com.bitwarden.desktop.desktop sources: - type: dir only-arches: [x86_64] @@ -41,6 +44,8 @@ modules: - type: dir only-arches: [aarch64] path: ../dist/linux-arm64-unpacked + - type: file + path: ./com.bitwarden.desktop.desktop - type: script dest-filename: bitwarden.sh commands: From c710fb338cfd9478bc4713ad4fbecf2cde5dfebd Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 18 Dec 2025 15:00:52 +0100 Subject: [PATCH 117/188] [deps]: Update taiki-e/install-action action to v2.62.67 (#17576) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index f2e6db96b30..47c5e9faef0 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -142,7 +142,7 @@ jobs: run: cargo +nightly udeps --workspace --all-features --all-targets - name: Install cargo-deny - uses: taiki-e/install-action@81ee1d48d9194cdcab880cbdc7d36e87d39874cb # v2.62.45 + uses: taiki-e/install-action@073d46cba2cde38f6698c798566c1b3e24feeb44 # v2.62.67 with: tool: cargo-deny@0.18.5 From 42e7fdf48a8d2458fa2b0e4e801de361ed7e20fa Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 18 Dec 2025 08:02:02 -0700 Subject: [PATCH 118/188] [deps] Platform: Update @types/node to v22.19.3 (#17991) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .../native-messaging-test-runner/package-lock.json | 8 ++++---- apps/desktop/native-messaging-test-runner/package.json | 2 +- package-lock.json | 8 ++++---- package.json | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/apps/desktop/native-messaging-test-runner/package-lock.json b/apps/desktop/native-messaging-test-runner/package-lock.json index 1f4a56de18a..9e3b6ba23e0 100644 --- a/apps/desktop/native-messaging-test-runner/package-lock.json +++ b/apps/desktop/native-messaging-test-runner/package-lock.json @@ -19,7 +19,7 @@ "yargs": "18.0.0" }, "devDependencies": { - "@types/node": "22.19.2", + "@types/node": "22.19.3", "typescript": "5.4.2" } }, @@ -117,9 +117,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "22.19.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.2.tgz", - "integrity": "sha512-LPM2G3Syo1GLzXLGJAKdqoU35XvrWzGJ21/7sgZTUpbkBaOasTj8tjwn6w+hCkqaa1TfJ/w67rJSwYItlJ2mYw==", + "version": "22.19.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.3.tgz", + "integrity": "sha512-1N9SBnWYOJTrNZCdh/yJE+t910Y128BoyY+zBLWhL3r0TYzlTmFdXrPwHL9DyFZmlEXNQQolTZh3KHV31QDhyA==", "license": "MIT", "peer": true, "dependencies": { diff --git a/apps/desktop/native-messaging-test-runner/package.json b/apps/desktop/native-messaging-test-runner/package.json index 83e9f01afed..050bb653445 100644 --- a/apps/desktop/native-messaging-test-runner/package.json +++ b/apps/desktop/native-messaging-test-runner/package.json @@ -24,7 +24,7 @@ "yargs": "18.0.0" }, "devDependencies": { - "@types/node": "22.19.2", + "@types/node": "22.19.3", "typescript": "5.4.2" }, "_moduleAliases": { diff --git a/package-lock.json b/package-lock.json index 3e4400772b2..dab01bdb56a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -109,7 +109,7 @@ "@types/koa-json": "2.0.23", "@types/lowdb": "1.0.15", "@types/lunr": "2.3.7", - "@types/node": "22.19.2", + "@types/node": "22.19.3", "@types/node-fetch": "2.6.4", "@types/node-forge": "1.3.14", "@types/papaparse": "5.5.0", @@ -15499,9 +15499,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "22.19.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.2.tgz", - "integrity": "sha512-LPM2G3Syo1GLzXLGJAKdqoU35XvrWzGJ21/7sgZTUpbkBaOasTj8tjwn6w+hCkqaa1TfJ/w67rJSwYItlJ2mYw==", + "version": "22.19.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.3.tgz", + "integrity": "sha512-1N9SBnWYOJTrNZCdh/yJE+t910Y128BoyY+zBLWhL3r0TYzlTmFdXrPwHL9DyFZmlEXNQQolTZh3KHV31QDhyA==", "license": "MIT", "dependencies": { "undici-types": "~6.21.0" diff --git a/package.json b/package.json index 5346614814a..555ed0d5de8 100644 --- a/package.json +++ b/package.json @@ -71,7 +71,7 @@ "@types/koa-json": "2.0.23", "@types/lowdb": "1.0.15", "@types/lunr": "2.3.7", - "@types/node": "22.19.2", + "@types/node": "22.19.3", "@types/node-fetch": "2.6.4", "@types/node-forge": "1.3.14", "@types/papaparse": "5.5.0", From 2afa36f598aba4e32cc88b57bb8e60339614c792 Mon Sep 17 00:00:00 2001 From: Alex Morask <144709477+amorask-bitwarden@users.noreply.github.com> Date: Thu, 18 Dec 2025 09:12:23 -0600 Subject: [PATCH 119/188] [PM-21421] Show current plan instead of legacy plan when resubscribing (#17949) * Show current plan instead of legacy plan when resubscribing * Claude / Kyle feedback --- .../change-plan-dialog.component.ts | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) 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 978bb35c5c7..d14f627127a 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 @@ -387,6 +387,8 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { this.focusedIndex = this.selectableProducts.length - 1; if (!this.isSubscriptionCanceled) { await this.selectPlan(this.getPlanByType(ProductTierType.Enterprise)); + } else { + await this.selectPlan(this.reSubscribablePlan); } } @@ -547,10 +549,28 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { return this.selectedPlan.isAnnual ? "year" : "month"; } + get reSubscribablePlan() { + if (!this.currentPlan) { + throw new Error( + "Current plan must be set to find the re-subscribable plan for a cancelled subscription.", + ); + } + if (!this.currentPlan.disabled) { + return this.currentPlan; + } + return ( + this.passwordManagerPlans.find( + (plan) => + plan.productTier === this.currentPlan.productTier && + plan.isAnnual === this.currentPlan.isAnnual && + !plan.disabled, + ) ?? this.currentPlan + ); + } + get selectableProducts() { if (this.isSubscriptionCanceled) { - // Return only the current plan if the subscription is canceled - return [this.currentPlan]; + return [this.reSubscribablePlan]; } if (this.acceptingSponsorship) { From 735af3c89048338039c4934c7f704da9e9df857a Mon Sep 17 00:00:00 2001 From: Alex <55413326+AlexRubik@users.noreply.github.com> Date: Thu, 18 Dec 2025 11:00:48 -0500 Subject: [PATCH 120/188] [PM-28222] delay progress bar (#17507) --- .../activity/all-activity.component.html | 2 +- .../activity/all-activity.component.ts | 4 +- .../all-applications.component.html | 2 +- .../all-applications.component.ts | 4 +- .../risk-insights.component.html | 150 +++++++++--------- .../risk-insights.component.ts | 58 ++++++- ...ent.html => report-loading.component.html} | 4 +- .../shared/report-loading.component.ts | 33 ++++ .../shared/risk-insights-loading.component.ts | 57 ------- 9 files changed, 170 insertions(+), 144 deletions(-) rename bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/{risk-insights-loading.component.html => report-loading.component.html} (86%) create mode 100644 bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/report-loading.component.ts delete mode 100644 bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/risk-insights-loading.component.ts diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/all-activity.component.html b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/all-activity.component.html index c9b930b3b50..49b65c1916b 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/all-activity.component.html +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/all-activity.component.html @@ -1,5 +1,5 @@ @if ((dataService.reportStatus$ | async) == ReportStatusEnum.Loading) { - <dirt-risk-insights-loading></dirt-risk-insights-loading> + <dirt-report-loading></dirt-report-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/activity/all-activity.component.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/all-activity.component.ts index 907e8883a43..f9194bd0110 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/all-activity.component.ts +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/all-activity.component.ts @@ -14,7 +14,7 @@ import { OrganizationId } from "@bitwarden/common/types/guid"; import { DialogService } from "@bitwarden/components"; import { SharedModule } from "@bitwarden/web-vault/app/shared"; -import { ApplicationsLoadingComponent } from "../shared/risk-insights-loading.component"; +import { ReportLoadingComponent } from "../shared/report-loading.component"; import { ActivityCardComponent } from "./activity-card.component"; import { PasswordChangeMetricComponent } from "./activity-cards/password-change-metric.component"; @@ -25,7 +25,7 @@ import { NewApplicationsDialogComponent } from "./application-review-dialog/new- @Component({ selector: "dirt-all-activity", imports: [ - ApplicationsLoadingComponent, + ReportLoadingComponent, SharedModule, ActivityCardComponent, PasswordChangeMetricComponent, diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications/all-applications.component.html b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications/all-applications.component.html index 73ded40388d..6b0c1266048 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications/all-applications.component.html +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications/all-applications.component.html @@ -1,5 +1,5 @@ @if ((dataService.reportStatus$ | async) == ReportStatusEnum.Loading) { - <dirt-risk-insights-loading></dirt-risk-insights-loading> + <dirt-report-loading></dirt-report-loading> } @else { @let drawerDetails = dataService.drawerDetails$ | async; <div class="tw-mt-4 tw-flex tw-flex-col"> diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications/all-applications.component.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications/all-applications.component.ts index 95453ffa41a..1c363e9dd93 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications/all-applications.component.ts +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications/all-applications.component.ts @@ -36,14 +36,14 @@ import { ApplicationTableDataSource, AppTableRowScrollableComponent, } from "../shared/app-table-row-scrollable.component"; -import { ApplicationsLoadingComponent } from "../shared/risk-insights-loading.component"; +import { ReportLoadingComponent } from "../shared/report-loading.component"; @Component({ changeDetection: ChangeDetectionStrategy.OnPush, selector: "dirt-all-applications", templateUrl: "./all-applications.component.html", imports: [ - ApplicationsLoadingComponent, + ReportLoadingComponent, HeaderModule, LinkModule, SearchModule, 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 19b655a8b23..d4342d24cd5 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 @@ -3,86 +3,86 @@ <ng-container> @let status = dataService.reportStatus$ | async; @let hasCiphers = dataService.hasCiphers$ | async; - @let isGeneratingReport = dataService.isGeneratingReport$ | async; @if (status == ReportStatusEnum.Initializing || hasCiphers === null) { <!-- Show page loading state when initializing risk insights (quick page load) --> <dirt-page-loading></dirt-page-loading> } @else { - <!-- Check final states after initial calls have been completed --> - @if (isRiskInsightsActivityTabFeatureEnabled && !(dataService.hasReportData$ | async)) { - <!-- Show empty state only when feature flag is enabled and there's no report data --> - <div @fadeIn class="tw-flex tw-justify-center tw-items-center tw-min-h-[70vh] tw-w-full"> - @if (!hasCiphers) { - <!-- Show Empty state when there are no applications (no ciphers to make reports on) --> - <empty-state-card - [videoSrc]="emptyStateVideoSrc" - [title]="this.i18nService.t('noDataInOrgTitle')" - [description]="this.i18nService.t('noDataInOrgDescription')" - [benefits]="emptyStateBenefits" - [buttonText]="this.i18nService.t('importData')" - [buttonIcon]="IMPORT_ICON" - [buttonAction]="this.goToImportPage" - ></empty-state-card> - } @else { - <!-- Show empty state for no reports run --> - <empty-state-card - [videoSrc]="emptyStateVideoSrc" - [title]="this.i18nService.t('noReportsRunTitle')" - [description]="this.i18nService.t('noReportsRunDescription')" - [benefits]="emptyStateBenefits" - [buttonText]="this.i18nService.t('riskInsightsRunReport')" - [buttonIcon]="" - [buttonAction]="this.generateReport.bind(this)" - ></empty-state-card> - } - </div> + <!-- Show loading component at top level so it's not destroyed when hasReportData$ changes --> + <!-- currentProgressStep is non-null when report generation is in progress --> + @if (currentProgressStep(); as step) { + <dirt-report-loading [progressStep]="step"></dirt-report-loading> } @else { - <!-- Show screen when there is report data OR when feature flag is disabled (show tabs even without data) --> - <div @fadeIn class="tw-min-h-screen tw-flex tw-flex-col"> - <div> - <div class="tw-text-main tw-max-w-4xl tw-mb-2" *ngIf="appsCount > 0"> - {{ "reviewAtRiskPasswords" | i18n }} - </div> - @let isRunningReport = dataService.isGeneratingReport$ | async; - <div - 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> - @if (dataLastUpdated) { - <span class="tw-mx-4">{{ - "dataLastUpdated" | i18n: (dataLastUpdated | date: "MMMM d, y 'at' h:mm a") - }}</span> - } - <span class="tw-flex tw-justify-center"> - <button - *ngIf="!isRunningReport" - type="button" - bitButton - buttonType="secondary" - class="tw-border-none !tw-font-normal tw-cursor-pointer !tw-py-0" - tabindex="0" - [bitAction]="generateReport.bind(this)" - > - {{ "riskInsightsRunReport" | i18n }} - </button> - <span> - <i - *ngIf="isRunningReport" - class="bwi bwi-spinner bwi-spin tw-text-muted tw-text-[1.2rem]" - aria-hidden="true" - ></i> - </span> - </span> - </div> + <!-- Check final states after initial calls have been completed --> + @if (isRiskInsightsActivityTabFeatureEnabled && !(dataService.hasReportData$ | async)) { + <!-- Show empty state only when feature flag is enabled and there's no report data --> + <div @fadeIn class="tw-flex tw-justify-center tw-items-center tw-min-h-[70vh] tw-w-full"> + @if (!hasCiphers) { + <!-- Show Empty state when there are no applications (no ciphers to make reports on) --> + <empty-state-card + [videoSrc]="emptyStateVideoSrc" + [title]="this.i18nService.t('noDataInOrgTitle')" + [description]="this.i18nService.t('noDataInOrgDescription')" + [benefits]="emptyStateBenefits" + [buttonText]="this.i18nService.t('importData')" + [buttonIcon]="IMPORT_ICON" + [buttonAction]="this.goToImportPage" + ></empty-state-card> + } @else { + <!-- Show empty state for no reports run --> + <empty-state-card + [videoSrc]="emptyStateVideoSrc" + [title]="this.i18nService.t('noReportsRunTitle')" + [description]="this.i18nService.t('noReportsRunDescription')" + [benefits]="emptyStateBenefits" + [buttonText]="this.i18nService.t('riskInsightsRunReport')" + [buttonIcon]="" + [buttonAction]="this.generateReport.bind(this)" + ></empty-state-card> + } </div> + } @else { + <!-- Show screen when there is report data OR when feature flag is disabled (show tabs even without data) --> + <div @fadeIn class="tw-min-h-screen tw-flex tw-flex-col"> + <div> + <div class="tw-text-main tw-max-w-4xl tw-mb-2" *ngIf="appsCount > 0"> + {{ "reviewAtRiskPasswords" | i18n }} + </div> + @let isRunningReport = dataService.isGeneratingReport$ | async; + <div + 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> + @if (dataLastUpdated) { + <span class="tw-mx-4">{{ + "dataLastUpdated" | i18n: (dataLastUpdated | date: "MMMM d, y 'at' h:mm a") + }}</span> + } + <span class="tw-flex tw-justify-center"> + <button + *ngIf="!isRunningReport" + type="button" + bitButton + buttonType="secondary" + class="tw-border-none !tw-font-normal tw-cursor-pointer !tw-py-0" + tabindex="0" + [bitAction]="generateReport.bind(this)" + > + {{ "riskInsightsRunReport" | i18n }} + </button> + <span> + <i + *ngIf="isRunningReport" + class="bwi bwi-spinner bwi-spin tw-text-muted tw-text-[1.2rem]" + aria-hidden="true" + ></i> + </span> + </span> + </div> + </div> - @if (status == ReportStatusEnum.Loading && isGeneratingReport) { - <!-- Show detailed progress when generating report (longer operation) --> - <dirt-risk-insights-loading></dirt-risk-insights-loading> - } @else { <div class="tw-flex-1 tw-flex tw-flex-col"> <bit-tab-group [(selectedIndex)]="tabIndex" (selectedIndexChange)="onTabChange($event)"> @if (isRiskInsightsActivityTabFeatureEnabled) { @@ -105,8 +105,8 @@ </bit-tab> </bit-tab-group> </div> - } - </div> + </div> + } } } </ng-container> 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 4549c15e0c8..18afdf8c8ab 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 @@ -6,16 +6,18 @@ import { OnDestroy, OnInit, inject, + signal, ChangeDetectionStrategy, } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { ActivatedRoute, Router } from "@angular/router"; -import { EMPTY, firstValueFrom } from "rxjs"; -import { distinctUntilChanged, map, tap } from "rxjs/operators"; +import { concat, EMPTY, firstValueFrom, of } from "rxjs"; +import { concatMap, delay, distinctUntilChanged, map, skip, tap } from "rxjs/operators"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { DrawerType, + ReportProgress, ReportStatus, RiskInsightsDataService, } from "@bitwarden/bit-common/dirt/reports/risk-insights"; @@ -42,8 +44,11 @@ import { CriticalApplicationsComponent } from "./critical-applications/critical- import { EmptyStateCardComponent } from "./empty-state-card.component"; import { RiskInsightsTabType } from "./models/risk-insights.models"; import { PageLoadingComponent } from "./shared/page-loading.component"; +import { ReportLoadingComponent } from "./shared/report-loading.component"; import { RiskInsightsDrawerDialogComponent } from "./shared/risk-insights-drawer-dialog.component"; -import { ApplicationsLoadingComponent } from "./shared/risk-insights-loading.component"; + +// Type alias for progress step (used in concatMap emissions) +type ProgressStep = ReportProgress | null; @Component({ changeDetection: ChangeDetectionStrategy.OnPush, @@ -59,7 +64,7 @@ import { ApplicationsLoadingComponent } from "./shared/risk-insights-loading.com HeaderModule, TabsModule, AllActivityComponent, - ApplicationsLoadingComponent, + ReportLoadingComponent, PageLoadingComponent, ], animations: [ @@ -95,6 +100,13 @@ export class RiskInsightsComponent implements OnInit, OnDestroy { protected IMPORT_ICON = "bwi bwi-download"; protected currentDialogRef: DialogRef<unknown, RiskInsightsDrawerDialogComponent> | null = null; + // Current progress step for loading component (null = not loading) + // Uses concatMap with delay to ensure each step is displayed for a minimum time + protected readonly currentProgressStep = signal<ProgressStep>(null); + + // Minimum time to display each progress step (in milliseconds) + private readonly STEP_DISPLAY_DELAY_MS = 250; + // TODO: See https://github.com/bitwarden/clients/pull/16832#discussion_r2474523235 constructor( @@ -170,6 +182,44 @@ export class RiskInsightsComponent implements OnInit, OnDestroy { // this happens when navigating between orgs // or just navigating away from the page and back this.currentDialogRef?.close(); + + // Subscribe to progress steps with delay to ensure each step is displayed for a minimum time + // - skip(1): Skip initial BehaviorSubject emission (may contain stale Complete from previous run) + // - concatMap: Queue steps and process them sequentially + // - First visible step (FetchingMembers) shows immediately so loading appears instantly + // - Subsequent steps are delayed to prevent jarring quick transitions + // - After Complete step is shown, emit null to hide loading + this.dataService.reportProgress$ + .pipe( + // Skip the initial emission from _reportProgressSubject (BehaviorSubject in orchestrator). + // Without this, navigating to the page would flash the loading component briefly + // because BehaviorSubject emits its current value (e.g., Complete from last run) to new subscribers. + skip(1), + concatMap((step) => { + // Show null and FetchingMembers immediately (first visible step) + // This ensures loading component appears instantly when user clicks "Run Report" + if (step === null || step === ReportProgress.FetchingMembers) { + return of(step); + } + // Delay subsequent steps to prevent jarring quick transitions + if (step === ReportProgress.Complete) { + // Show Complete step, wait, then emit null to hide loading + // Why concat is needed: + // - The orchestrator emits Complete but never emits null afterward + // - Without this concat, the loading would stay on "Compiling insights..." forever + // - The concat automatically emits null to hide the loader + return concat( + of(step as ProgressStep).pipe(delay(this.STEP_DISPLAY_DELAY_MS)), + of(null as ProgressStep).pipe(delay(this.STEP_DISPLAY_DELAY_MS)), + ); + } + return of(step).pipe(delay(this.STEP_DISPLAY_DELAY_MS)); + }), + takeUntilDestroyed(this.destroyRef), + ) + .subscribe((step) => { + this.currentProgressStep.set(step); + }); } ngOnDestroy(): void { diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/risk-insights-loading.component.html b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/report-loading.component.html similarity index 86% rename from bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/risk-insights-loading.component.html rename to bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/report-loading.component.html index 6e6bb786336..0b5a63c8f03 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/risk-insights-loading.component.html +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/report-loading.component.html @@ -3,7 +3,7 @@ <!-- Progress bar --> <div class="tw-w-64" role="progressbar" attr.aria-label="{{ 'loadingProgress' | i18n }}"> <bit-progress - [barWidth]="progress()" + [barWidth]="stepConfig[progressStep()].progress" [showText]="false" size="default" bgColor="primary" @@ -13,7 +13,7 @@ <!-- Status message and subtitle --> <div class="tw-text-center tw-flex tw-flex-col tw-gap-1"> <span class="tw-text-main tw-text-base tw-font-medium tw-leading-4"> - {{ currentMessage() | i18n }} + {{ stepConfig[progressStep()].message | i18n }} </span> <span class="tw-text-muted tw-text-sm tw-font-normal tw-leading-4"> {{ "thisMightTakeFewMinutes" | i18n }} diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/report-loading.component.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/report-loading.component.ts new file mode 100644 index 00000000000..f3cb89dff55 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/report-loading.component.ts @@ -0,0 +1,33 @@ +import { CommonModule } from "@angular/common"; +import { Component, input } from "@angular/core"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { ReportProgress } from "@bitwarden/bit-common/dirt/reports/risk-insights"; +import { ProgressModule } from "@bitwarden/components"; + +// Map of progress step to display config +const ProgressStepConfig = Object.freeze({ + [ReportProgress.FetchingMembers]: { message: "fetchingMemberData", progress: 20 }, + [ReportProgress.AnalyzingPasswords]: { message: "analyzingPasswordHealth", progress: 40 }, + [ReportProgress.CalculatingRisks]: { message: "calculatingRiskScores", progress: 60 }, + [ReportProgress.GeneratingReport]: { message: "generatingReportData", progress: 80 }, + [ReportProgress.Saving]: { message: "savingReport", progress: 95 }, + [ReportProgress.Complete]: { message: "compilingInsights", progress: 100 }, +} as const); + +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection +@Component({ + selector: "dirt-report-loading", + imports: [CommonModule, JslibModule, ProgressModule], + templateUrl: "./report-loading.component.html", +}) +export class ReportLoadingComponent { + // Progress step input from parent component. + // Recommended: delay emissions to this input to ensure each step displays for a minimum time. + // Refer to risk-insights.component for implementation example. + readonly progressStep = input<ReportProgress>(ReportProgress.FetchingMembers); + + // Expose config map to template for direct lookup + protected readonly stepConfig = ProgressStepConfig; +} diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/risk-insights-loading.component.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/risk-insights-loading.component.ts deleted file mode 100644 index d4c97a6fd5c..00000000000 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/risk-insights-loading.component.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { CommonModule } from "@angular/common"; -import { Component, DestroyRef, inject, OnInit, signal } from "@angular/core"; -import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; - -import { JslibModule } from "@bitwarden/angular/jslib.module"; -import { - ReportProgress, - RiskInsightsDataService, -} from "@bitwarden/bit-common/dirt/reports/risk-insights"; -import { ProgressModule } from "@bitwarden/components"; - -const PROGRESS_STEPS = [ - { step: ReportProgress.FetchingMembers, message: "fetchingMemberData", progress: 20 }, - { step: ReportProgress.AnalyzingPasswords, message: "analyzingPasswordHealth", progress: 40 }, - { step: ReportProgress.CalculatingRisks, message: "calculatingRiskScores", progress: 60 }, - { step: ReportProgress.GeneratingReport, message: "generatingReportData", progress: 80 }, - { step: ReportProgress.Saving, message: "savingReport", progress: 95 }, - { step: ReportProgress.Complete, message: "compilingInsights", progress: 100 }, -] as const; - -type LoadingMessage = (typeof PROGRESS_STEPS)[number]["message"]; - -// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush -// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection -@Component({ - selector: "dirt-risk-insights-loading", - imports: [CommonModule, JslibModule, ProgressModule], - templateUrl: "./risk-insights-loading.component.html", -}) -export class ApplicationsLoadingComponent implements OnInit { - private dataService = inject(RiskInsightsDataService); - private destroyRef = inject(DestroyRef); - - readonly currentMessage = signal<LoadingMessage>(PROGRESS_STEPS[0].message); - readonly progress = signal<number>(PROGRESS_STEPS[0].progress); - - ngOnInit(): void { - // Subscribe to actual progress events from the orchestrator - this.dataService.reportProgress$ - .pipe(takeUntilDestroyed(this.destroyRef)) - .subscribe((progressStep) => { - if (progressStep === null) { - // Reset to initial state - this.currentMessage.set(PROGRESS_STEPS[0].message); - this.progress.set(PROGRESS_STEPS[0].progress); - return; - } - - // Find the matching step configuration - const stepConfig = PROGRESS_STEPS.find((config) => config.step === progressStep); - if (stepConfig) { - this.currentMessage.set(stepConfig.message); - this.progress.set(stepConfig.progress); - } - }); - } -} From ef7b66ad0d67233a462946f55197a129df7b92be Mon Sep 17 00:00:00 2001 From: cyprain-okeke <108260115+cyprain-okeke@users.noreply.github.com> Date: Thu, 18 Dec 2025 17:35:48 +0100 Subject: [PATCH 121/188] [PM 29079]Remove code for pm-24033-updat-premium-subscription-page (#17905) * Remove the feature flag * delete and rename CloudHostedPremiumVNextComponent --- .../individual-billing-routing.module.ts | 32 +- .../individual/individual-billing.module.ts | 8 +- .../cloud-hosted-premium-vnext.component.html | 68 ---- .../cloud-hosted-premium-vnext.component.ts | 242 ------------ .../cloud-hosted-premium.component.html | 207 ++++------ .../premium/cloud-hosted-premium.component.ts | 361 +++++++++--------- libs/common/src/enums/feature-flag.enum.ts | 2 - 7 files changed, 252 insertions(+), 668 deletions(-) delete mode 100644 apps/web/src/app/billing/individual/premium/cloud-hosted-premium-vnext.component.html delete mode 100644 apps/web/src/app/billing/individual/premium/cloud-hosted-premium-vnext.component.ts 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 cdccaaab8ab..fbaf65d1839 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 @@ -1,15 +1,11 @@ import { inject, NgModule } from "@angular/core"; import { RouterModule, Routes } from "@angular/router"; -import { map } from "rxjs"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { AccountPaymentDetailsComponent } from "@bitwarden/web-vault/app/billing/individual/payment-details/account-payment-details.component"; import { SelfHostedPremiumComponent } from "@bitwarden/web-vault/app/billing/individual/premium/self-hosted-premium.component"; import { BillingHistoryViewComponent } from "./billing-history-view.component"; -import { CloudHostedPremiumVNextComponent } from "./premium/cloud-hosted-premium-vnext.component"; import { CloudHostedPremiumComponent } from "./premium/cloud-hosted-premium.component"; import { SubscriptionComponent } from "./subscription.component"; import { UserSubscriptionComponent } from "./user-subscription.component"; @@ -27,20 +23,15 @@ const routes: Routes = [ data: { titleId: "premiumMembership" }, }, /** - * Three-Route Matching Strategy for /premium: + * Two-Route Matching Strategy for /premium: * * Routes are evaluated in order using canMatch guards. The first route that matches will be selected. * * 1. Self-Hosted Environment → SelfHostedPremiumComponent * - Matches when platformUtilsService.isSelfHost() === true * - * 2. Cloud-Hosted + Feature Flag Enabled → CloudHostedPremiumVNextComponent - * - Only evaluated if Route 1 doesn't match (not self-hosted) - * - Matches when PM24033PremiumUpgradeNewDesign feature flag === true - * - * 3. Cloud-Hosted + Feature Flag Disabled → CloudHostedPremiumComponent (Fallback) - * - No canMatch guard, so this always matches as the fallback route - * - Used when neither Route 1 nor Route 2 match + * 2. Cloud-Hosted (default) → CloudHostedPremiumComponent + * - Evaluated when Route 1 doesn't match (not self-hosted) */ // Route 1: Self-Hosted -> SelfHostedPremiumComponent { @@ -54,22 +45,7 @@ const routes: Routes = [ }, ], }, - // Route 2: Cloud Hosted + FF -> CloudHostedPremiumVNextComponent - { - path: "premium", - component: CloudHostedPremiumVNextComponent, - data: { titleId: "goPremium" }, - canMatch: [ - () => { - const configService = inject(ConfigService); - - return configService - .getFeatureFlag$(FeatureFlag.PM24033PremiumUpgradeNewDesign) - .pipe(map((flagValue) => flagValue === true)); - }, - ], - }, - // Route 3: Cloud Hosted + FF Disabled -> CloudHostedPremiumComponent (Fallback) + // Route 2: Cloud Hosted (default) -> CloudHostedPremiumComponent { path: "premium", component: CloudHostedPremiumComponent, 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 2a529d43416..35c08aa40a2 100644 --- a/apps/web/src/app/billing/individual/individual-billing.module.ts +++ b/apps/web/src/app/billing/individual/individual-billing.module.ts @@ -12,7 +12,6 @@ import { BillingSharedModule } from "../shared"; import { BillingHistoryViewComponent } from "./billing-history-view.component"; import { IndividualBillingRoutingModule } from "./individual-billing-routing.module"; -import { CloudHostedPremiumComponent } from "./premium/cloud-hosted-premium.component"; import { SubscriptionComponent } from "./subscription.component"; import { UserSubscriptionComponent } from "./user-subscription.component"; @@ -26,11 +25,6 @@ import { UserSubscriptionComponent } from "./user-subscription.component"; PricingCardComponent, BaseCardComponent, ], - declarations: [ - SubscriptionComponent, - BillingHistoryViewComponent, - UserSubscriptionComponent, - CloudHostedPremiumComponent, - ], + declarations: [SubscriptionComponent, BillingHistoryViewComponent, UserSubscriptionComponent], }) export class IndividualBillingModule {} diff --git a/apps/web/src/app/billing/individual/premium/cloud-hosted-premium-vnext.component.html b/apps/web/src/app/billing/individual/premium/cloud-hosted-premium-vnext.component.html deleted file mode 100644 index e182659acbb..00000000000 --- a/apps/web/src/app/billing/individual/premium/cloud-hosted-premium-vnext.component.html +++ /dev/null @@ -1,68 +0,0 @@ -<div class="tw-max-w-3xl tw-mx-auto"> - <bit-section *ngIf="shouldShowNewDesign$ | async"> - <div class="tw-text-center"> - <div class="tw-mt-8 tw-mb-6"> - <span bitBadge variant="secondary" [truncate]="false"> - {{ "bitwardenFreeplanMessage" | i18n }} - </span> - </div> - - <h2 class="tw-mt-2 tw-text-4xl"> - {{ "upgradeCompleteSecurity" | i18n }} - </h2> - <p class="tw-text-muted tw-mb-6 tw-mt-4"> - {{ "individualUpgradeDescriptionMessage" | i18n }} - </p> - </div> - - <!-- Two-Card Layout --> - <div class="tw-grid tw-grid-cols-1 md:tw-grid-cols-2 tw-gap-6 tw-mt-6 tw-justify-center"> - <!-- Premium Card --> - <div> - @if (premiumCardData$ | async; as premiumData) { - <billing-pricing-card - [tagline]="'advancedOnlineSecurity' | i18n" - [price]="{ amount: premiumData.price, cadence: 'monthly' }" - [button]="{ type: 'primary', text: ('upgradeToPremium' | i18n) }" - [features]="premiumData.features" - (buttonClick)="openUpgradeDialog('Premium')" - > - <h3 slot="title" bitTypography="h3" class="tw-m-0">{{ "premium" | i18n }}</h3> - </billing-pricing-card> - } - </div> - - <!-- Families Card --> - <div> - @if (familiesCardData$ | async; as familiesData) { - <billing-pricing-card - [tagline]="'planDescFamiliesV2' | i18n" - [price]="{ amount: familiesData.price, cadence: 'monthly' }" - [button]="{ type: 'secondary', text: ('startFreeFamiliesTrial' | i18n) }" - [features]="familiesData.features" - (buttonClick)="openUpgradeDialog('Families')" - > - <h3 slot="title" bitTypography="h3" class="tw-m-0">{{ "families" | i18n }}</h3> - </billing-pricing-card> - } - </div> - </div> - - <!-- Business Plans Link --> - <div class="tw-text-center tw-mt-6"> - <p class="tw-text-muted tw-mb-2 tw-italic"> - {{ "individualUpgradeTaxInformationMessage" | i18n }} - </p> - <a - bitLink - linkType="primary" - href="https://bitwarden.com/pricing/business/" - target="_blank" - rel="noopener noreferrer" - > - {{ "viewbusinessplans" | i18n }} - <i class="bwi bwi-external-link tw-ml-1" aria-hidden="true"></i> - </a> - </div> - </bit-section> -</div> diff --git a/apps/web/src/app/billing/individual/premium/cloud-hosted-premium-vnext.component.ts b/apps/web/src/app/billing/individual/premium/cloud-hosted-premium-vnext.component.ts deleted file mode 100644 index aac7fd3156f..00000000000 --- a/apps/web/src/app/billing/individual/premium/cloud-hosted-premium-vnext.component.ts +++ /dev/null @@ -1,242 +0,0 @@ -import { CommonModule } from "@angular/common"; -import { Component, DestroyRef, inject } from "@angular/core"; -import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; -import { ActivatedRoute, Router } from "@angular/router"; -import { - combineLatest, - firstValueFrom, - from, - map, - Observable, - of, - shareReplay, - switchMap, - take, -} from "rxjs"; - -import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions"; -import { SubscriptionPricingServiceAbstraction } from "@bitwarden/common/billing/abstractions/subscription-pricing.service.abstraction"; -import { - PersonalSubscriptionPricingTier, - PersonalSubscriptionPricingTierIds, -} from "@bitwarden/common/billing/types/subscription-pricing-tier"; -import { SyncService } from "@bitwarden/common/platform/sync"; -import { - BadgeModule, - DialogService, - LinkModule, - SectionComponent, - TypographyModule, -} from "@bitwarden/components"; -import { PricingCardComponent } from "@bitwarden/pricing"; -import { I18nPipe } from "@bitwarden/ui-common"; - -import { BitwardenSubscriber, mapAccountToSubscriber } from "../../types"; -import { - UnifiedUpgradeDialogComponent, - UnifiedUpgradeDialogParams, - UnifiedUpgradeDialogResult, - UnifiedUpgradeDialogStatus, - UnifiedUpgradeDialogStep, -} from "../upgrade/unified-upgrade-dialog/unified-upgrade-dialog.component"; - -const RouteParams = { - callToAction: "callToAction", -} as const; -const RouteParamValues = { - upgradeToPremium: "upgradeToPremium", -} as const; - -// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush -// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection -@Component({ - templateUrl: "./cloud-hosted-premium-vnext.component.html", - standalone: true, - imports: [ - CommonModule, - SectionComponent, - BadgeModule, - TypographyModule, - LinkModule, - I18nPipe, - PricingCardComponent, - ], -}) -export class CloudHostedPremiumVNextComponent { - protected hasPremiumFromAnyOrganization$: Observable<boolean>; - protected hasPremiumPersonally$: Observable<boolean>; - protected shouldShowNewDesign$: Observable<boolean>; - protected shouldShowUpgradeDialogOnInit$: Observable<boolean>; - protected personalPricingTiers$: Observable<PersonalSubscriptionPricingTier[]>; - protected premiumCardData$: Observable<{ - tier: PersonalSubscriptionPricingTier | undefined; - price: number; - features: string[]; - }>; - protected familiesCardData$: Observable<{ - tier: PersonalSubscriptionPricingTier | undefined; - price: number; - features: string[]; - }>; - protected subscriber!: BitwardenSubscriber; - private destroyRef = inject(DestroyRef); - - constructor( - private accountService: AccountService, - private apiService: ApiService, - private dialogService: DialogService, - private syncService: SyncService, - private billingAccountProfileStateService: BillingAccountProfileStateService, - private subscriptionPricingService: SubscriptionPricingServiceAbstraction, - private router: Router, - private activatedRoute: ActivatedRoute, - ) { - this.hasPremiumFromAnyOrganization$ = this.accountService.activeAccount$.pipe( - switchMap((account) => - account - ? this.billingAccountProfileStateService.hasPremiumFromAnyOrganization$(account.id) - : of(false), - ), - ); - - this.hasPremiumPersonally$ = this.accountService.activeAccount$.pipe( - switchMap((account) => - account - ? this.billingAccountProfileStateService.hasPremiumPersonally$(account.id) - : of(false), - ), - ); - - this.accountService.activeAccount$ - .pipe(mapAccountToSubscriber, takeUntilDestroyed(this.destroyRef)) - .subscribe((subscriber) => { - this.subscriber = subscriber; - }); - - this.shouldShowNewDesign$ = combineLatest([ - this.hasPremiumFromAnyOrganization$, - this.hasPremiumPersonally$, - ]).pipe(map(([hasOrgPremium, hasPersonalPremium]) => !hasOrgPremium && !hasPersonalPremium)); - - // redirect to user subscription page if they already have premium personally - // redirect to individual vault if they already have premium from an org - combineLatest([this.hasPremiumFromAnyOrganization$, this.hasPremiumPersonally$]) - .pipe( - takeUntilDestroyed(this.destroyRef), - switchMap(([hasPremiumFromOrg, hasPremiumPersonally]) => { - if (hasPremiumPersonally) { - return from(this.navigateToSubscriptionPage()); - } - if (hasPremiumFromOrg) { - return from(this.navigateToIndividualVault()); - } - return of(true); - }), - ) - .subscribe(); - - this.shouldShowUpgradeDialogOnInit$ = combineLatest([ - this.hasPremiumFromAnyOrganization$, - this.hasPremiumPersonally$, - this.activatedRoute.queryParams, - ]).pipe( - map(([hasOrgPremium, hasPersonalPremium, queryParams]) => { - const cta = queryParams[RouteParams.callToAction]; - return !hasOrgPremium && !hasPersonalPremium && cta === RouteParamValues.upgradeToPremium; - }), - ); - - this.personalPricingTiers$ = - this.subscriptionPricingService.getPersonalSubscriptionPricingTiers$(); - - this.premiumCardData$ = this.personalPricingTiers$.pipe( - map((tiers) => { - const tier = tiers.find((t) => t.id === PersonalSubscriptionPricingTierIds.Premium); - return { - tier, - price: - tier?.passwordManager.type === "standalone" && tier.passwordManager.annualPrice - ? Number((tier.passwordManager.annualPrice / 12).toFixed(2)) - : 0, - features: tier?.passwordManager.features.map((f) => f.value) || [], - }; - }), - shareReplay({ bufferSize: 1, refCount: true }), - ); - - this.familiesCardData$ = this.personalPricingTiers$.pipe( - map((tiers) => { - const tier = tiers.find((t) => t.id === PersonalSubscriptionPricingTierIds.Families); - return { - tier, - price: - tier?.passwordManager.type === "packaged" && tier.passwordManager.annualPrice - ? Number((tier.passwordManager.annualPrice / 12).toFixed(2)) - : 0, - features: tier?.passwordManager.features.map((f) => f.value) || [], - }; - }), - shareReplay({ bufferSize: 1, refCount: true }), - ); - - this.shouldShowUpgradeDialogOnInit$ - .pipe( - take(1), - switchMap((shouldShowUpgradeDialogOnInit) => { - if (shouldShowUpgradeDialogOnInit) { - return from(this.openUpgradeDialog("Premium")); - } - // Return an Observable that completes immediately when dialog should not be shown - return of(void 0); - }), - takeUntilDestroyed(this.destroyRef), - ) - .subscribe(); - } - - private navigateToSubscriptionPage = (): Promise<boolean> => - this.router.navigate(["../user-subscription"], { relativeTo: this.activatedRoute }); - - private navigateToIndividualVault = (): Promise<boolean> => this.router.navigate(["/vault"]); - - finalizeUpgrade = async () => { - await this.apiService.refreshIdentityToken(); - await this.syncService.fullSync(true); - }; - - protected async openUpgradeDialog(planType: "Premium" | "Families"): Promise<void> { - const account = await firstValueFrom(this.accountService.activeAccount$); - if (!account) { - return; - } - - const selectedPlan = - planType === "Premium" - ? PersonalSubscriptionPricingTierIds.Premium - : PersonalSubscriptionPricingTierIds.Families; - - const dialogParams: UnifiedUpgradeDialogParams = { - account, - initialStep: UnifiedUpgradeDialogStep.Payment, - selectedPlan: selectedPlan, - redirectOnCompletion: true, - }; - - const dialogRef = UnifiedUpgradeDialogComponent.open(this.dialogService, { - data: dialogParams, - }); - - dialogRef.closed - .pipe(takeUntilDestroyed(this.destroyRef)) - .subscribe((result: UnifiedUpgradeDialogResult | undefined) => { - if ( - result?.status === UnifiedUpgradeDialogStatus.UpgradedToPremium || - result?.status === UnifiedUpgradeDialogStatus.UpgradedToFamilies - ) { - void this.finalizeUpgrade(); - } - }); - } -} diff --git a/apps/web/src/app/billing/individual/premium/cloud-hosted-premium.component.html b/apps/web/src/app/billing/individual/premium/cloud-hosted-premium.component.html index 33e89f21fc0..e182659acbb 100644 --- a/apps/web/src/app/billing/individual/premium/cloud-hosted-premium.component.html +++ b/apps/web/src/app/billing/individual/premium/cloud-hosted-premium.component.html @@ -1,141 +1,68 @@ -@if (isLoadingPrices$ | async) { - <ng-container> - <i - class="bwi bwi-spinner bwi-spin tw-text-muted" - title="{{ 'loading' | i18n }}" - aria-hidden="true" - ></i> - <span class="tw-sr-only">{{ "loading" | i18n }}</span> - </ng-container> -} @else { - <bit-container> - <bit-section> - <h2 bitTypography="h2">{{ "goPremium" | i18n }}</h2> - <bit-callout - type="info" - *ngIf="hasPremiumFromAnyOrganization$ | async" - title="{{ 'youHavePremiumAccess' | i18n }}" - icon="bwi bwi-star-f" +<div class="tw-max-w-3xl tw-mx-auto"> + <bit-section *ngIf="shouldShowNewDesign$ | async"> + <div class="tw-text-center"> + <div class="tw-mt-8 tw-mb-6"> + <span bitBadge variant="secondary" [truncate]="false"> + {{ "bitwardenFreeplanMessage" | i18n }} + </span> + </div> + + <h2 class="tw-mt-2 tw-text-4xl"> + {{ "upgradeCompleteSecurity" | i18n }} + </h2> + <p class="tw-text-muted tw-mb-6 tw-mt-4"> + {{ "individualUpgradeDescriptionMessage" | i18n }} + </p> + </div> + + <!-- Two-Card Layout --> + <div class="tw-grid tw-grid-cols-1 md:tw-grid-cols-2 tw-gap-6 tw-mt-6 tw-justify-center"> + <!-- Premium Card --> + <div> + @if (premiumCardData$ | async; as premiumData) { + <billing-pricing-card + [tagline]="'advancedOnlineSecurity' | i18n" + [price]="{ amount: premiumData.price, cadence: 'monthly' }" + [button]="{ type: 'primary', text: ('upgradeToPremium' | i18n) }" + [features]="premiumData.features" + (buttonClick)="openUpgradeDialog('Premium')" + > + <h3 slot="title" bitTypography="h3" class="tw-m-0">{{ "premium" | i18n }}</h3> + </billing-pricing-card> + } + </div> + + <!-- Families Card --> + <div> + @if (familiesCardData$ | async; as familiesData) { + <billing-pricing-card + [tagline]="'planDescFamiliesV2' | i18n" + [price]="{ amount: familiesData.price, cadence: 'monthly' }" + [button]="{ type: 'secondary', text: ('startFreeFamiliesTrial' | i18n) }" + [features]="familiesData.features" + (buttonClick)="openUpgradeDialog('Families')" + > + <h3 slot="title" bitTypography="h3" class="tw-m-0">{{ "families" | i18n }}</h3> + </billing-pricing-card> + } + </div> + </div> + + <!-- Business Plans Link --> + <div class="tw-text-center tw-mt-6"> + <p class="tw-text-muted tw-mb-2 tw-italic"> + {{ "individualUpgradeTaxInformationMessage" | i18n }} + </p> + <a + bitLink + linkType="primary" + href="https://bitwarden.com/pricing/business/" + target="_blank" + rel="noopener noreferrer" > - {{ "alreadyPremiumFromOrg" | i18n }} - </bit-callout> - <bit-callout type="success"> - <p>{{ "premiumUpgradeUnlockFeatures" | i18n }}</p> - <ul class="bwi-ul"> - <li> - <i class="bwi bwi-check tw-text-success bwi-li" aria-hidden="true"></i> - {{ "premiumSignUpStorageV2" | i18n: `${(providedStorageGb$ | async)} GB` }} - </li> - <li> - <i class="bwi bwi-check tw-text-success bwi-li" aria-hidden="true"></i> - {{ "premiumSignUpTwoStepOptions" | i18n }} - </li> - <li> - <i class="bwi bwi-check tw-text-success bwi-li" aria-hidden="true"></i> - {{ "premiumSignUpEmergency" | i18n }} - </li> - <li> - <i class="bwi bwi-check tw-text-success bwi-li" aria-hidden="true"></i> - {{ "premiumSignUpReports" | i18n }} - </li> - <li> - <i class="bwi bwi-check tw-text-success bwi-li" aria-hidden="true"></i> - {{ "premiumSignUpTotp" | i18n }} - </li> - <li> - <i class="bwi bwi-check tw-text-success bwi-li" aria-hidden="true"></i> - {{ "premiumSignUpSupport" | i18n }} - </li> - <li> - <i class="bwi bwi-check tw-text-success bwi-li" aria-hidden="true"></i> - {{ "premiumSignUpFuture" | i18n }} - </li> - </ul> - <p bitTypography="body1" class="tw-mb-0"> - {{ - "premiumPriceWithFamilyPlan" - | i18n: (premiumPrice$ | async | currency: "$") : familyPlanMaxUserCount - }} - <a - bitLink - linkType="primary" - routerLink="/create-organization" - [queryParams]="{ plan: 'families' }" - > - {{ "bitwardenFamiliesPlan" | i18n }} - </a> - </p> - </bit-callout> - </bit-section> - <form [formGroup]="formGroup" [bitSubmit]="submitPayment"> - <bit-section> - <h2 bitTypography="h2">{{ "addons" | i18n }}</h2> - <div class="tw-grid tw-grid-cols-12 tw-gap-4"> - <bit-form-field class="tw-col-span-6"> - <bit-label>{{ "additionalStorageGb" | i18n }}</bit-label> - <input - bitInput - formControlName="additionalStorage" - type="number" - step="1" - placeholder="{{ 'additionalStorageGbDesc' | i18n }}" - /> - <bit-hint>{{ - "additionalStorageIntervalDesc" - | i18n - : `${(providedStorageGb$ | async)} GB` - : (storagePrice$ | async | currency: "$") - : ("year" | i18n) - }}</bit-hint> - </bit-form-field> - </div> - </bit-section> - <bit-section> - <h2 bitTypography="h2">{{ "summary" | i18n }}</h2> - {{ "premiumMembership" | i18n }}: {{ premiumPrice$ | async | currency: "$" }} <br /> - {{ "additionalStorageGb" | i18n }}: {{ formGroup.value.additionalStorage || 0 }} GB &times; - {{ storagePrice$ | async | currency: "$" }} = - {{ storageCost$ | async | currency: "$" }} - <hr class="tw-my-3" /> - </bit-section> - <bit-section> - <h3 bitTypography="h2">{{ "paymentInformation" | i18n }}</h3> - <div class="tw-mb-4"> - <app-enter-payment-method - [group]="formGroup.controls.paymentMethod" - [showBankAccount]="false" - [showAccountCredit]="true" - [hasEnoughAccountCredit]="hasEnoughAccountCredit$ | async" - > - </app-enter-payment-method> - <app-enter-billing-address - [group]="formGroup.controls.billingAddress" - [scenario]="{ type: 'checkout', supportsTaxId: false }" - > - </app-enter-billing-address> - </div> - <div class="tw-mb-4"> - <div class="tw-text-muted tw-text-sm tw-flex tw-flex-col"> - <span>{{ "planPrice" | i18n }}: {{ subtotal$ | async | currency: "USD $" }}</span> - <span>{{ "estimatedTax" | i18n }}: {{ tax$ | async | currency: "USD $" }}</span> - </div> - </div> - <hr class="tw-my-1 tw-w-1/4 tw-ml-0" /> - <p bitTypography="body1"> - <strong>{{ "total" | i18n }}:</strong> {{ total$ | async | currency: "USD $" }}/{{ - "year" | i18n - }} - </p> - <button - type="submit" - buttonType="primary" - bitButton - bitFormButton - [disabled]="!(hasEnoughAccountCredit$ | async)" - > - {{ "submit" | i18n }} - </button> - </bit-section> - </form> - </bit-container> -} + {{ "viewbusinessplans" | i18n }} + <i class="bwi bwi-external-link tw-ml-1" aria-hidden="true"></i> + </a> + </div> + </bit-section> +</div> diff --git a/apps/web/src/app/billing/individual/premium/cloud-hosted-premium.component.ts b/apps/web/src/app/billing/individual/premium/cloud-hosted-premium.component.ts index 86a508d2701..7e219c44d90 100644 --- a/apps/web/src/app/billing/individual/premium/cloud-hosted-premium.component.ts +++ b/apps/web/src/app/billing/individual/premium/cloud-hosted-premium.component.ts @@ -1,243 +1,242 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Component, ViewChild } from "@angular/core"; +import { CommonModule } from "@angular/common"; +import { Component, DestroyRef, inject } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; -import { FormControl, FormGroup, Validators } from "@angular/forms"; import { ActivatedRoute, Router } from "@angular/router"; import { - catchError, combineLatest, - concatMap, - filter, + firstValueFrom, from, map, Observable, of, shareReplay, - startWith, switchMap, + take, } from "rxjs"; -import { debounceTime } from "rxjs/operators"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions"; import { SubscriptionPricingServiceAbstraction } from "@bitwarden/common/billing/abstractions/subscription-pricing.service.abstraction"; -import { PaymentMethodType } from "@bitwarden/common/billing/enums"; -import { PersonalSubscriptionPricingTierIds } from "@bitwarden/common/billing/types/subscription-pricing-tier"; -import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { + PersonalSubscriptionPricingTier, + PersonalSubscriptionPricingTierIds, +} from "@bitwarden/common/billing/types/subscription-pricing-tier"; import { SyncService } from "@bitwarden/common/platform/sync"; -import { ToastService } from "@bitwarden/components"; -import { SubscriberBillingClient, TaxClient } from "@bitwarden/web-vault/app/billing/clients"; import { - EnterBillingAddressComponent, - EnterPaymentMethodComponent, - getBillingAddressFromForm, -} from "@bitwarden/web-vault/app/billing/payment/components"; + BadgeModule, + DialogService, + LinkModule, + SectionComponent, + TypographyModule, +} from "@bitwarden/components"; +import { PricingCardComponent } from "@bitwarden/pricing"; +import { I18nPipe } from "@bitwarden/ui-common"; + +import { BitwardenSubscriber, mapAccountToSubscriber } from "../../types"; import { - NonTokenizablePaymentMethods, - tokenizablePaymentMethodToLegacyEnum, -} from "@bitwarden/web-vault/app/billing/payment/types"; -import { mapAccountToSubscriber } from "@bitwarden/web-vault/app/billing/types"; + UnifiedUpgradeDialogComponent, + UnifiedUpgradeDialogParams, + UnifiedUpgradeDialogResult, + UnifiedUpgradeDialogStatus, + UnifiedUpgradeDialogStep, +} from "../upgrade/unified-upgrade-dialog/unified-upgrade-dialog.component"; + +const RouteParams = { + callToAction: "callToAction", +} as const; +const RouteParamValues = { + upgradeToPremium: "upgradeToPremium", +} as const; // FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush // eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ templateUrl: "./cloud-hosted-premium.component.html", - standalone: false, - providers: [SubscriberBillingClient, TaxClient], + standalone: true, + imports: [ + CommonModule, + SectionComponent, + BadgeModule, + TypographyModule, + LinkModule, + I18nPipe, + PricingCardComponent, + ], }) export class CloudHostedPremiumComponent { - // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals - // eslint-disable-next-line @angular-eslint/prefer-signals - @ViewChild(EnterPaymentMethodComponent) enterPaymentMethodComponent!: EnterPaymentMethodComponent; - protected hasPremiumFromAnyOrganization$: Observable<boolean>; - protected hasEnoughAccountCredit$: Observable<boolean>; - - protected formGroup = new FormGroup({ - additionalStorage: new FormControl<number>(0, [Validators.min(0), Validators.max(99)]), - paymentMethod: EnterPaymentMethodComponent.getFormGroup(), - billingAddress: EnterBillingAddressComponent.getFormGroup(), - }); - - premiumPrices$ = this.subscriptionPricingService.getPersonalSubscriptionPricingTiers$().pipe( - map((tiers) => { - const premiumPlan = tiers.find( - (tier) => tier.id === PersonalSubscriptionPricingTierIds.Premium, - ); - - if (!premiumPlan) { - throw new Error("Could not find Premium plan"); - } - - return { - seat: premiumPlan.passwordManager.annualPrice, - storage: premiumPlan.passwordManager.annualPricePerAdditionalStorageGB, - providedStorageGb: premiumPlan.passwordManager.providedStorageGB, - }; - }), - shareReplay({ bufferSize: 1, refCount: true }), - ); - - premiumPrice$ = this.premiumPrices$.pipe(map((prices) => prices.seat)); - - storagePrice$ = this.premiumPrices$.pipe(map((prices) => prices.storage)); - - providedStorageGb$ = this.premiumPrices$.pipe(map((prices) => prices.providedStorageGb)); - - protected isLoadingPrices$ = this.premiumPrices$.pipe( - map(() => false), - startWith(true), - catchError(() => of(false)), - ); - - storageCost$ = combineLatest([ - this.storagePrice$, - this.formGroup.controls.additionalStorage.valueChanges.pipe( - startWith(this.formGroup.value.additionalStorage), - ), - ]).pipe(map(([storagePrice, additionalStorage]) => storagePrice * additionalStorage)); - - subtotal$ = combineLatest([this.premiumPrice$, this.storageCost$]).pipe( - map(([premiumPrice, storageCost]) => premiumPrice + storageCost), - ); - - tax$ = this.formGroup.valueChanges.pipe( - filter(() => this.formGroup.valid), - debounceTime(1000), - switchMap(async () => { - const billingAddress = getBillingAddressFromForm(this.formGroup.controls.billingAddress); - const taxAmounts = await this.taxClient.previewTaxForPremiumSubscriptionPurchase( - this.formGroup.value.additionalStorage, - billingAddress, - ); - return taxAmounts.tax; - }), - startWith(0), - ); - - total$ = combineLatest([this.subtotal$, this.tax$]).pipe( - map(([subtotal, tax]) => subtotal + tax), - ); - - protected cloudWebVaultURL: string; - protected readonly familyPlanMaxUserCount = 6; + protected hasPremiumPersonally$: Observable<boolean>; + protected shouldShowNewDesign$: Observable<boolean>; + protected shouldShowUpgradeDialogOnInit$: Observable<boolean>; + protected personalPricingTiers$: Observable<PersonalSubscriptionPricingTier[]>; + protected premiumCardData$: Observable<{ + tier: PersonalSubscriptionPricingTier | undefined; + price: number; + features: string[]; + }>; + protected familiesCardData$: Observable<{ + tier: PersonalSubscriptionPricingTier | undefined; + price: number; + features: string[]; + }>; + protected subscriber!: BitwardenSubscriber; + private destroyRef = inject(DestroyRef); constructor( - private activatedRoute: ActivatedRoute, - private apiService: ApiService, - private billingAccountProfileStateService: BillingAccountProfileStateService, - private environmentService: EnvironmentService, - private i18nService: I18nService, - private router: Router, - private syncService: SyncService, - private toastService: ToastService, private accountService: AccountService, - private subscriberBillingClient: SubscriberBillingClient, - private taxClient: TaxClient, + private apiService: ApiService, + private dialogService: DialogService, + private syncService: SyncService, + private billingAccountProfileStateService: BillingAccountProfileStateService, private subscriptionPricingService: SubscriptionPricingServiceAbstraction, + private router: Router, + private activatedRoute: ActivatedRoute, ) { this.hasPremiumFromAnyOrganization$ = this.accountService.activeAccount$.pipe( switchMap((account) => - this.billingAccountProfileStateService.hasPremiumFromAnyOrganization$(account.id), + account + ? this.billingAccountProfileStateService.hasPremiumFromAnyOrganization$(account.id) + : of(false), ), ); - const accountCredit$ = this.accountService.activeAccount$.pipe( - mapAccountToSubscriber, - switchMap((account) => this.subscriberBillingClient.getCredit(account)), + this.hasPremiumPersonally$ = this.accountService.activeAccount$.pipe( + switchMap((account) => + account + ? this.billingAccountProfileStateService.hasPremiumPersonally$(account.id) + : of(false), + ), ); - this.hasEnoughAccountCredit$ = combineLatest([ - accountCredit$, - this.total$, - this.formGroup.controls.paymentMethod.controls.type.valueChanges.pipe( - startWith(this.formGroup.value.paymentMethod.type), - ), - ]).pipe( - map(([credit, total, paymentMethod]) => { - if (paymentMethod !== NonTokenizablePaymentMethods.accountCredit) { - return true; - } - return credit >= total; - }), - ); + this.accountService.activeAccount$ + .pipe(mapAccountToSubscriber, takeUntilDestroyed(this.destroyRef)) + .subscribe((subscriber) => { + this.subscriber = subscriber; + }); - combineLatest([ - this.accountService.activeAccount$.pipe( - switchMap((account) => - this.billingAccountProfileStateService.hasPremiumPersonally$(account.id), - ), - ), - this.environmentService.cloudWebVaultUrl$, - ]) + this.shouldShowNewDesign$ = combineLatest([ + this.hasPremiumFromAnyOrganization$, + this.hasPremiumPersonally$, + ]).pipe(map(([hasOrgPremium, hasPersonalPremium]) => !hasOrgPremium && !hasPersonalPremium)); + + // redirect to user subscription page if they already have premium personally + // redirect to individual vault if they already have premium from an org + combineLatest([this.hasPremiumFromAnyOrganization$, this.hasPremiumPersonally$]) .pipe( - takeUntilDestroyed(), - concatMap(([hasPremiumPersonally, cloudWebVaultURL]) => { + takeUntilDestroyed(this.destroyRef), + switchMap(([hasPremiumFromOrg, hasPremiumPersonally]) => { if (hasPremiumPersonally) { return from(this.navigateToSubscriptionPage()); } - - this.cloudWebVaultURL = cloudWebVaultURL; + if (hasPremiumFromOrg) { + return from(this.navigateToIndividualVault()); + } return of(true); }), ) .subscribe(); + + this.shouldShowUpgradeDialogOnInit$ = combineLatest([ + this.hasPremiumFromAnyOrganization$, + this.hasPremiumPersonally$, + this.activatedRoute.queryParams, + ]).pipe( + map(([hasOrgPremium, hasPersonalPremium, queryParams]) => { + const cta = queryParams[RouteParams.callToAction]; + return !hasOrgPremium && !hasPersonalPremium && cta === RouteParamValues.upgradeToPremium; + }), + ); + + this.personalPricingTiers$ = + this.subscriptionPricingService.getPersonalSubscriptionPricingTiers$(); + + this.premiumCardData$ = this.personalPricingTiers$.pipe( + map((tiers) => { + const tier = tiers.find((t) => t.id === PersonalSubscriptionPricingTierIds.Premium); + return { + tier, + price: + tier?.passwordManager.type === "standalone" && tier.passwordManager.annualPrice + ? Number((tier.passwordManager.annualPrice / 12).toFixed(2)) + : 0, + features: tier?.passwordManager.features.map((f) => f.value) || [], + }; + }), + shareReplay({ bufferSize: 1, refCount: true }), + ); + + this.familiesCardData$ = this.personalPricingTiers$.pipe( + map((tiers) => { + const tier = tiers.find((t) => t.id === PersonalSubscriptionPricingTierIds.Families); + return { + tier, + price: + tier?.passwordManager.type === "packaged" && tier.passwordManager.annualPrice + ? Number((tier.passwordManager.annualPrice / 12).toFixed(2)) + : 0, + features: tier?.passwordManager.features.map((f) => f.value) || [], + }; + }), + shareReplay({ bufferSize: 1, refCount: true }), + ); + + this.shouldShowUpgradeDialogOnInit$ + .pipe( + take(1), + switchMap((shouldShowUpgradeDialogOnInit) => { + if (shouldShowUpgradeDialogOnInit) { + return from(this.openUpgradeDialog("Premium")); + } + // Return an Observable that completes immediately when dialog should not be shown + return of(void 0); + }), + takeUntilDestroyed(this.destroyRef), + ) + .subscribe(); } + private navigateToSubscriptionPage = (): Promise<boolean> => + this.router.navigate(["../user-subscription"], { relativeTo: this.activatedRoute }); + + private navigateToIndividualVault = (): Promise<boolean> => this.router.navigate(["/vault"]); + finalizeUpgrade = async () => { await this.apiService.refreshIdentityToken(); await this.syncService.fullSync(true); }; - postFinalizeUpgrade = async () => { - this.toastService.showToast({ - variant: "success", - title: null, - message: this.i18nService.t("premiumUpdated"), - }); - await this.navigateToSubscriptionPage(); - }; - - navigateToSubscriptionPage = (): Promise<boolean> => - this.router.navigate(["../user-subscription"], { relativeTo: this.activatedRoute }); - - submitPayment = async (): Promise<void> => { - if (this.formGroup.invalid) { + protected async openUpgradeDialog(planType: "Premium" | "Families"): Promise<void> { + const account = await firstValueFrom(this.accountService.activeAccount$); + if (!account) { return; } - // Check if account credit is selected - const selectedPaymentType = this.formGroup.value.paymentMethod.type; + const selectedPlan = + planType === "Premium" + ? PersonalSubscriptionPricingTierIds.Premium + : PersonalSubscriptionPricingTierIds.Families; - let paymentMethodType: number; - let paymentToken: string; + const dialogParams: UnifiedUpgradeDialogParams = { + account, + initialStep: UnifiedUpgradeDialogStep.Payment, + selectedPlan: selectedPlan, + redirectOnCompletion: true, + }; - if (selectedPaymentType === NonTokenizablePaymentMethods.accountCredit) { - // Account credit doesn't need tokenization - paymentMethodType = PaymentMethodType.Credit; - paymentToken = ""; - } else { - // Tokenize for card, bank account, or PayPal - const paymentMethod = await this.enterPaymentMethodComponent.tokenize(); - paymentMethodType = tokenizablePaymentMethodToLegacyEnum(paymentMethod.type); - paymentToken = paymentMethod.token; - } + const dialogRef = UnifiedUpgradeDialogComponent.open(this.dialogService, { + data: dialogParams, + }); - const formData = new FormData(); - formData.append("paymentMethodType", paymentMethodType.toString()); - formData.append("paymentToken", paymentToken); - formData.append( - "additionalStorageGb", - (this.formGroup.value.additionalStorage ?? 0).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(); - await this.postFinalizeUpgrade(); - }; + dialogRef.closed + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe((result: UnifiedUpgradeDialogResult | undefined) => { + if ( + result?.status === UnifiedUpgradeDialogStatus.UpgradedToPremium || + result?.status === UnifiedUpgradeDialogStatus.UpgradedToFamilies + ) { + void this.finalizeUpgrade(); + } + }); + } } diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts index 7357e73a89e..15618ab3279 100644 --- a/libs/common/src/enums/feature-flag.enum.ts +++ b/libs/common/src/enums/feature-flag.enum.ts @@ -30,7 +30,6 @@ export enum FeatureFlag { PM24032_NewNavigationPremiumUpgradeButton = "pm-24032-new-navigation-premium-upgrade-button", PM25379_UseNewOrganizationMetadataStructure = "pm-25379-use-new-organization-metadata-structure", PM24996_ImplementUpgradeFromFreeDialog = "pm-24996-implement-upgrade-from-free-dialog", - PM24033PremiumUpgradeNewDesign = "pm-24033-updat-premium-subscription-page", PM26793_FetchPremiumPriceFromPricingService = "pm-26793-fetch-premium-price-from-pricing-service", PM23713_PremiumBadgeOpensNewPremiumUpgradeDialog = "pm-23713-premium-badge-opens-new-premium-upgrade-dialog", PM26462_Milestone_3 = "pm-26462-milestone-3", @@ -140,7 +139,6 @@ export const DefaultFeatureFlagValue = { [FeatureFlag.PM24032_NewNavigationPremiumUpgradeButton]: FALSE, [FeatureFlag.PM25379_UseNewOrganizationMetadataStructure]: FALSE, [FeatureFlag.PM24996_ImplementUpgradeFromFreeDialog]: FALSE, - [FeatureFlag.PM24033PremiumUpgradeNewDesign]: FALSE, [FeatureFlag.PM26793_FetchPremiumPriceFromPricingService]: FALSE, [FeatureFlag.PM23713_PremiumBadgeOpensNewPremiumUpgradeDialog]: FALSE, [FeatureFlag.PM26462_Milestone_3]: FALSE, From 799644c5aa276b7ee9a7b5ea72ada9c047c69464 Mon Sep 17 00:00:00 2001 From: Vijay Oommen <voommen@livefront.com> Date: Thu, 18 Dec 2025 11:06:54 -0600 Subject: [PATCH 122/188] PM-29935 delete button text needs to be fixed (#18048) --- .../connect-dialog/connect-dialog-datadog.component.html | 4 ++-- .../connect-dialog/connect-dialog-hec.component.html | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) 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 index c129216b694..ddc108201b0 100644 --- 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 @@ -47,8 +47,8 @@ bitIconButton="bwi-trash" type="button" buttonType="danger" - label="'delete' | i18n" - [appA11yTitle]="'delete' | i18n" + label="{{ 'delete' | i18n }}" + appA11yTitle="{{ 'delete' | i18n }}" [bitAction]="delete" ></button> </div> diff --git a/bitwarden_license/bit-web/src/app/dirt/organization-integrations/integration-dialog/connect-dialog/connect-dialog-hec.component.html b/bitwarden_license/bit-web/src/app/dirt/organization-integrations/integration-dialog/connect-dialog/connect-dialog-hec.component.html index 557e0fc382f..0dad1621440 100644 --- a/bitwarden_license/bit-web/src/app/dirt/organization-integrations/integration-dialog/connect-dialog/connect-dialog-hec.component.html +++ b/bitwarden_license/bit-web/src/app/dirt/organization-integrations/integration-dialog/connect-dialog/connect-dialog-hec.component.html @@ -53,8 +53,8 @@ bitIconButton="bwi-trash" type="button" buttonType="danger" - label="'delete' | i18n" - [appA11yTitle]="'delete' | i18n" + label="{{ 'delete' | i18n }}" + appA11yTitle="{{ 'delete' | i18n }}" [bitAction]="delete" ></button> </div> From 524bd9a484642ce76e4f0df9780b48cc5f90ce80 Mon Sep 17 00:00:00 2001 From: Mick Letofsky <mletofsky@bitwarden.com> Date: Thu, 18 Dec 2025 19:19:09 +0100 Subject: [PATCH 123/188] Remove review-prompt and migrate the only unique BW instruction to the CLAUDE.md (#18049) --- .claude/CLAUDE.md | 3 ++ .claude/prompts/review-code.md | 57 ---------------------------------- 2 files changed, 3 insertions(+), 57 deletions(-) delete mode 100644 .claude/prompts/review-code.md diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index dd3b6445edd..fadaabf57bb 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -18,6 +18,9 @@ - **NEVER** commit secrets, credentials, or sensitive information. +- **CRITICAL**: Tailwind CSS classes MUST use the `tw-` prefix (e.g., `tw-flex`, `tw-p-4`). + - Missing prefix breaks styling completely. + - **NEVER** log decrypted data, encryption keys, or PII - No vault data in error messages or console logs diff --git a/.claude/prompts/review-code.md b/.claude/prompts/review-code.md deleted file mode 100644 index 1888b7cd503..00000000000 --- a/.claude/prompts/review-code.md +++ /dev/null @@ -1,57 +0,0 @@ -# Bitwarden Clients Repo Code Review - Careful Consideration Required - -## Think Twice Before Recommending - -Angular has multiple valid patterns. Before suggesting changes: - -- **Consider the context** - Is this code part of an active modernization effort? -- **Check for established patterns** - Look for similar implementations in the codebase -- **Avoid premature optimization** - Don't suggest refactoring stable, working code without clear benefit -- **Respect incremental progress** - Teams may be modernizing gradually with feature flags - -## Angular Modernization - Handle with Care - -**Control Flow Syntax (@if, @for, @switch):** - -- When you see legacy structural directives (*ngIf, *ngFor), consider whether modernization is in scope -- Do not mandate changes to stable code unless part of the PR's objective -- If suggesting modernization, acknowledge it's optional unless required by PR goals - -**Standalone Components:** - -- New components should be standalone whenever feasible, but do not flag existing NgModule components as issues -- Legacy patterns exist for valid reasons - consider modernization effort vs benefit - -**Typed Forms:** - -- Recommend typed forms for NEW form code -- Don't suggest rewriting working untyped forms unless they're being modified - -## Tailwind CSS - Critical Pattern - -**tw- prefix is mandatory** - This is non-negotiable and should be flagged as ❌ major finding: - -- Missing tw- prefix breaks styling completely -- Check ALL Tailwind classes in modified files - -## Rust SDK Adoption - Tread Carefully - -When reviewing cipher operations: - -- Look for breaking changes in the TypeScript → Rust boundary -- Verify error handling matches established patterns -- Don't suggest alternative SDK patterns without strong justification - -## Component Library First - -Before suggesting custom implementations: - -- Check if Bitwarden's component library already provides the functionality -- Prefer existing components over custom Tailwind styling -- Don't add UI complexity that the component library already solves - -## When in Doubt - -- **Ask questions** (💭) rather than making definitive recommendations -- **Flag for human review** (⚠️) if you're uncertain -- **Acknowledge alternatives** exist when suggesting improvements From ff3582109c088f99168121f37152ff2d0424bc3f Mon Sep 17 00:00:00 2001 From: Shane Melton <smelton@bitwarden.com> Date: Thu, 18 Dec 2025 10:20:59 -0800 Subject: [PATCH 124/188] [PM-28746] Item transfer event logs (#18032) * [PM-28746] Add item organization event types and i18n strings * [PM-28746] Log event when transfer is accepted or declined --- apps/web/src/app/core/event.service.ts | 6 ++ apps/web/src/locales/en/messages.json | 10 ++- libs/common/src/enums/event-type.enum.ts | 2 + ...fault-vault-items-transfer.service.spec.ts | 62 +++++++++++++++++++ .../default-vault-items-transfer.service.ts | 17 +++++ 5 files changed, 95 insertions(+), 2 deletions(-) diff --git a/apps/web/src/app/core/event.service.ts b/apps/web/src/app/core/event.service.ts index 55d5524c2fa..c8c6a54f2a6 100644 --- a/apps/web/src/app/core/event.service.ts +++ b/apps/web/src/app/core/event.service.ts @@ -452,6 +452,12 @@ export class EventService { this.getShortId(ev.organizationId), ); break; + case EventType.Organization_ItemOrganization_Accepted: + msg = humanReadableMsg = this.i18nService.t("userAcceptedTransfer"); + break; + case EventType.Organization_ItemOrganization_Declined: + msg = humanReadableMsg = this.i18nService.t("userDeclinedTransfer"); + break; // Policies case EventType.Policy_Updated: { diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 16c274b5b83..ac40f78e43f 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -4214,6 +4214,12 @@ } } }, + "userAcceptedTransfer": { + "message": "Accepted transfer to organization ownership." + }, + "userDeclinedTransfer": { + "message": "Revoked for declining transfer to organization ownership." + }, "invitedUserId": { "message": "Invited user $ID$.", "placeholders": { @@ -12411,7 +12417,7 @@ "placeholders": { "organization": { "content": "$1", - "example": "My Org Name" + "example": "My Org Name" } } }, @@ -12420,7 +12426,7 @@ "placeholders": { "organization": { "content": "$1", - "example": "My Org Name" + "example": "My Org Name" } } }, diff --git a/libs/common/src/enums/event-type.enum.ts b/libs/common/src/enums/event-type.enum.ts index b3b12118ede..4750c881f06 100644 --- a/libs/common/src/enums/event-type.enum.ts +++ b/libs/common/src/enums/event-type.enum.ts @@ -79,6 +79,8 @@ export enum EventType { Organization_CollectionManagement_LimitItemDeletionDisabled = 1615, Organization_CollectionManagement_AllowAdminAccessToAllCollectionItemsEnabled = 1616, Organization_CollectionManagement_AllowAdminAccessToAllCollectionItemsDisabled = 1617, + Organization_ItemOrganization_Accepted = 1618, + Organization_ItemOrganization_Declined = 1619, Policy_Updated = 1700, diff --git a/libs/vault/src/services/default-vault-items-transfer.service.spec.ts b/libs/vault/src/services/default-vault-items-transfer.service.spec.ts index d78cf95ebf2..c0afa950c41 100644 --- a/libs/vault/src/services/default-vault-items-transfer.service.spec.ts +++ b/libs/vault/src/services/default-vault-items-transfer.service.spec.ts @@ -3,11 +3,13 @@ import { firstValueFrom, of, Subject } from "rxjs"; // eslint-disable-next-line no-restricted-imports import { CollectionService, CollectionView } from "@bitwarden/admin-console/common"; +import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.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"; import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; +import { 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 { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -37,6 +39,7 @@ describe("DefaultVaultItemsTransferService", () => { let mockI18nService: MockProxy<I18nService>; let mockDialogService: MockProxy<DialogService>; let mockToastService: MockProxy<ToastService>; + let mockEventCollectionService: MockProxy<EventCollectionService>; let mockConfigService: MockProxy<ConfigService>; const userId = "user-id" as UserId; @@ -71,6 +74,7 @@ describe("DefaultVaultItemsTransferService", () => { mockI18nService = mock<I18nService>(); mockDialogService = mock<DialogService>(); mockToastService = mock<ToastService>(); + mockEventCollectionService = mock<EventCollectionService>(); mockConfigService = mock<ConfigService>(); mockI18nService.t.mockImplementation((key) => key); @@ -85,6 +89,7 @@ describe("DefaultVaultItemsTransferService", () => { mockI18nService, mockDialogService, mockToastService, + mockEventCollectionService, mockConfigService, ); }); @@ -774,6 +779,63 @@ describe("DefaultVaultItemsTransferService", () => { expect(mockDialogService.open).toHaveBeenCalledTimes(4); expect(mockCipherService.shareManyWithServer).not.toHaveBeenCalled(); }); + + describe("event logs", () => { + it("logs accepted event when user accepts transfer", async () => { + const personalCiphers = [{ id: "cipher-1" } as CipherView]; + setupMocksForEnforcementScenario({ + policies: [policy], + organizations: [organization], + ciphers: personalCiphers, + defaultCollection: { + id: collectionId, + organizationId: organizationId, + isDefaultCollection: true, + } as CollectionView, + }); + + mockDialogService.open.mockReturnValueOnce( + createMockDialogRef(TransferItemsDialogResult.Accepted), + ); + mockCipherService.shareManyWithServer.mockResolvedValue(undefined); + + await service.enforceOrganizationDataOwnership(userId); + + expect(mockEventCollectionService.collect).toHaveBeenCalledWith( + EventType.Organization_ItemOrganization_Accepted, + undefined, + undefined, + organizationId, + ); + }); + + it("logs declined event when user rejects transfer", async () => { + const personalCiphers = [{ id: "cipher-1" } as CipherView]; + setupMocksForEnforcementScenario({ + policies: [policy], + organizations: [organization], + ciphers: personalCiphers, + defaultCollection: { + id: collectionId, + organizationId: organizationId, + isDefaultCollection: true, + } as CollectionView, + }); + + mockDialogService.open + .mockReturnValueOnce(createMockDialogRef(TransferItemsDialogResult.Declined)) + .mockReturnValueOnce(createMockDialogRef(LeaveConfirmationDialogResult.Confirmed)); + + await service.enforceOrganizationDataOwnership(userId); + + expect(mockEventCollectionService.collect).toHaveBeenCalledWith( + EventType.Organization_ItemOrganization_Declined, + undefined, + undefined, + organizationId, + ); + }); + }); }); describe("transferInProgress$", () => { diff --git a/libs/vault/src/services/default-vault-items-transfer.service.ts b/libs/vault/src/services/default-vault-items-transfer.service.ts index d7088873071..c184b2c902e 100644 --- a/libs/vault/src/services/default-vault-items-transfer.service.ts +++ b/libs/vault/src/services/default-vault-items-transfer.service.ts @@ -11,10 +11,12 @@ import { // eslint-disable-next-line no-restricted-imports import { CollectionService } from "@bitwarden/admin-console/common"; +import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.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"; import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { 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 { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -49,6 +51,7 @@ export class DefaultVaultItemsTransferService implements VaultItemsTransferServi private i18nService: I18nService, private dialogService: DialogService, private toastService: ToastService, + private eventCollectionService: EventCollectionService, private configService: ConfigService, ) {} @@ -160,6 +163,13 @@ export class DefaultVaultItemsTransferService implements VaultItemsTransferServi if (!userAcceptedTransfer) { // TODO: Revoke user from organization if they decline migration and show toast PM-29465 + + await this.eventCollectionService.collect( + EventType.Organization_ItemOrganization_Declined, + undefined, + undefined, + migrationInfo.enforcingOrganization.id, + ); return; } @@ -175,6 +185,13 @@ export class DefaultVaultItemsTransferService implements VaultItemsTransferServi variant: "success", message: this.i18nService.t("itemsTransferred"), }); + + await this.eventCollectionService.collect( + EventType.Organization_ItemOrganization_Accepted, + undefined, + undefined, + migrationInfo.enforcingOrganization.id, + ); } catch (error) { this._transferInProgressSubject.next(false); this.logService.error("Error transferring personal items to organization", error); From bb71390da4d0d6cca915d15029ebadd756e7b6e6 Mon Sep 17 00:00:00 2001 From: neuronull <9162534+neuronull@users.noreply.github.com> Date: Thu, 18 Dec 2025 12:06:04 -0700 Subject: [PATCH 125/188] Desktop Autotype fix feature triggering in settings menu (#17808) --- apps/desktop/src/app/accounts/settings.component.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/apps/desktop/src/app/accounts/settings.component.ts b/apps/desktop/src/app/accounts/settings.component.ts index e3022428421..3952335af48 100644 --- a/apps/desktop/src/app/accounts/settings.component.ts +++ b/apps/desktop/src/app/accounts/settings.component.ts @@ -970,10 +970,20 @@ export class SettingsComponent implements OnInit, OnDestroy { } async saveAutotypeShortcut() { + // disable the shortcut so that the user can't re-enter the existing + // shortcut and trigger the feature during the settings menu. + // it is not necessary to check if it's already enabled, because + // the edit shortcut is only avaialble if the feature is enabled + // in the settings. + await this.desktopAutotypeService.setAutotypeEnabledState(false); + const dialogRef = AutotypeShortcutComponent.open(this.dialogService); const newShortcutArray = await firstValueFrom(dialogRef.closed); + // re-enable + await this.desktopAutotypeService.setAutotypeEnabledState(true); + if (!newShortcutArray) { return; } From 0527171f3cab18355ca6ea67f56dcb4224627757 Mon Sep 17 00:00:00 2001 From: Mike Amirault <mamirault@bitwarden.com> Date: Thu, 18 Dec 2025 14:12:52 -0500 Subject: [PATCH 126/188] [PM-29780] Add feature flag for Send email OTP verification (#18005) * [PM-29780] Add feature flag for Send email OTP verification * [PM-29780] Add default flag value --- libs/common/src/enums/feature-flag.enum.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts index 15618ab3279..3bde0eada0a 100644 --- a/libs/common/src/enums/feature-flag.enum.ts +++ b/libs/common/src/enums/feature-flag.enum.ts @@ -51,6 +51,7 @@ export enum FeatureFlag { UseSdkPasswordGenerators = "pm-19976-use-sdk-password-generators", ChromiumImporterWithABE = "pm-25855-chromium-importer-abe", SendUIRefresh = "pm-28175-send-ui-refresh", + SendEmailOTP = "pm-19051-send-email-verification", /* DIRT */ EventManagementForDataDogAndCrowdStrike = "event-management-for-datadog-and-crowdstrike", @@ -113,6 +114,7 @@ export const DefaultFeatureFlagValue = { [FeatureFlag.UseSdkPasswordGenerators]: FALSE, [FeatureFlag.ChromiumImporterWithABE]: FALSE, [FeatureFlag.SendUIRefresh]: FALSE, + [FeatureFlag.SendEmailOTP]: FALSE, /* DIRT */ [FeatureFlag.EventManagementForDataDogAndCrowdStrike]: FALSE, From 39bc4fb789e48707759426007db5023d3ecfd59d Mon Sep 17 00:00:00 2001 From: Mike Amirault <mamirault@bitwarden.com> Date: Thu, 18 Dec 2025 14:48:48 -0500 Subject: [PATCH 127/188] [PM-29875] Close Send drawer on navigation (#18031) --- apps/web/src/app/tools/send/send.component.ts | 1 + libs/components/src/dialog/dialog.service.ts | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/apps/web/src/app/tools/send/send.component.ts b/apps/web/src/app/tools/send/send.component.ts index 11559976fbf..928945f9782 100644 --- a/apps/web/src/app/tools/send/send.component.ts +++ b/apps/web/src/app/tools/send/send.component.ts @@ -119,6 +119,7 @@ export class SendComponent extends BaseSendComponent implements OnInit, OnDestro ngOnDestroy() { this.dialogService.closeAll(); + this.dialogService.closeDrawer(); this.broadcasterService.unsubscribe(BroadcasterSubscriptionId); } diff --git a/libs/components/src/dialog/dialog.service.ts b/libs/components/src/dialog/dialog.service.ts index 804b650beab..88dee499e07 100644 --- a/libs/components/src/dialog/dialog.service.ts +++ b/libs/components/src/dialog/dialog.service.ts @@ -300,6 +300,11 @@ export class DialogService { return this.dialog.closeAll(); } + /** Close the open drawer */ + closeDrawer(): void { + return this.activeDrawer?.close(); + } + /** The injector that is passed to the opened dialog */ private createInjector(opts: { data: unknown; dialogRef: DialogRef }): Injector { return Injector.create({ From 3b84d256c81fee32ad95649f7d82a1cb4a9e0b21 Mon Sep 17 00:00:00 2001 From: "bw-ghapp[bot]" <178206702+bw-ghapp[bot]@users.noreply.github.com> Date: Fri, 19 Dec 2025 12:12:55 +0100 Subject: [PATCH 128/188] Autosync the updated translations (#18045) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/desktop/src/locales/af/messages.json | 18 ++++++-- apps/desktop/src/locales/ar/messages.json | 18 ++++++-- apps/desktop/src/locales/az/messages.json | 18 ++++++-- apps/desktop/src/locales/be/messages.json | 18 ++++++-- apps/desktop/src/locales/bg/messages.json | 18 ++++++-- apps/desktop/src/locales/bn/messages.json | 18 ++++++-- apps/desktop/src/locales/bs/messages.json | 18 ++++++-- apps/desktop/src/locales/ca/messages.json | 18 ++++++-- apps/desktop/src/locales/cs/messages.json | 18 ++++++-- apps/desktop/src/locales/cy/messages.json | 18 ++++++-- apps/desktop/src/locales/da/messages.json | 18 ++++++-- apps/desktop/src/locales/de/messages.json | 18 ++++++-- apps/desktop/src/locales/el/messages.json | 18 ++++++-- apps/desktop/src/locales/en_GB/messages.json | 18 ++++++-- apps/desktop/src/locales/en_IN/messages.json | 18 ++++++-- apps/desktop/src/locales/eo/messages.json | 18 ++++++-- apps/desktop/src/locales/es/messages.json | 18 ++++++-- apps/desktop/src/locales/et/messages.json | 18 ++++++-- apps/desktop/src/locales/eu/messages.json | 18 ++++++-- apps/desktop/src/locales/fa/messages.json | 18 ++++++-- apps/desktop/src/locales/fi/messages.json | 18 ++++++-- apps/desktop/src/locales/fil/messages.json | 18 ++++++-- apps/desktop/src/locales/fr/messages.json | 18 ++++++-- apps/desktop/src/locales/gl/messages.json | 18 ++++++-- apps/desktop/src/locales/he/messages.json | 18 ++++++-- apps/desktop/src/locales/hi/messages.json | 18 ++++++-- apps/desktop/src/locales/hr/messages.json | 18 ++++++-- apps/desktop/src/locales/hu/messages.json | 18 ++++++-- apps/desktop/src/locales/id/messages.json | 18 ++++++-- apps/desktop/src/locales/it/messages.json | 46 ++++++++++++-------- apps/desktop/src/locales/ja/messages.json | 18 ++++++-- apps/desktop/src/locales/ka/messages.json | 18 ++++++-- apps/desktop/src/locales/km/messages.json | 18 ++++++-- apps/desktop/src/locales/kn/messages.json | 18 ++++++-- apps/desktop/src/locales/ko/messages.json | 18 ++++++-- apps/desktop/src/locales/lt/messages.json | 18 ++++++-- apps/desktop/src/locales/lv/messages.json | 18 ++++++-- apps/desktop/src/locales/me/messages.json | 18 ++++++-- apps/desktop/src/locales/ml/messages.json | 18 ++++++-- apps/desktop/src/locales/mr/messages.json | 18 ++++++-- apps/desktop/src/locales/my/messages.json | 18 ++++++-- apps/desktop/src/locales/nb/messages.json | 18 ++++++-- apps/desktop/src/locales/ne/messages.json | 18 ++++++-- apps/desktop/src/locales/nl/messages.json | 18 ++++++-- apps/desktop/src/locales/nn/messages.json | 18 ++++++-- apps/desktop/src/locales/or/messages.json | 18 ++++++-- apps/desktop/src/locales/pl/messages.json | 18 ++++++-- apps/desktop/src/locales/pt_BR/messages.json | 18 ++++++-- apps/desktop/src/locales/pt_PT/messages.json | 18 ++++++-- apps/desktop/src/locales/ro/messages.json | 18 ++++++-- apps/desktop/src/locales/ru/messages.json | 18 ++++++-- apps/desktop/src/locales/si/messages.json | 18 ++++++-- apps/desktop/src/locales/sk/messages.json | 20 ++++++--- apps/desktop/src/locales/sl/messages.json | 18 ++++++-- apps/desktop/src/locales/sr/messages.json | 18 ++++++-- apps/desktop/src/locales/sv/messages.json | 18 ++++++-- apps/desktop/src/locales/ta/messages.json | 18 ++++++-- apps/desktop/src/locales/te/messages.json | 18 ++++++-- apps/desktop/src/locales/th/messages.json | 18 ++++++-- apps/desktop/src/locales/tr/messages.json | 18 ++++++-- apps/desktop/src/locales/uk/messages.json | 18 ++++++-- apps/desktop/src/locales/vi/messages.json | 18 ++++++-- apps/desktop/src/locales/zh_CN/messages.json | 18 ++++++-- apps/desktop/src/locales/zh_TW/messages.json | 18 ++++++-- 64 files changed, 911 insertions(+), 271 deletions(-) diff --git a/apps/desktop/src/locales/af/messages.json b/apps/desktop/src/locales/af/messages.json index 73a05ce79a6..720e0dde7c2 100644 --- a/apps/desktop/src/locales/af/messages.json +++ b/apps/desktop/src/locales/af/messages.json @@ -1775,11 +1775,21 @@ "exportFrom": { "message": "Export from" }, - "export": { - "message": "Export" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" }, - "import": { - "message": "Import" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "Lêerformaat" diff --git a/apps/desktop/src/locales/ar/messages.json b/apps/desktop/src/locales/ar/messages.json index 7373bb671fa..d6c42f5883a 100644 --- a/apps/desktop/src/locales/ar/messages.json +++ b/apps/desktop/src/locales/ar/messages.json @@ -1775,11 +1775,21 @@ "exportFrom": { "message": "تصدير من" }, - "export": { - "message": "Export" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" }, - "import": { - "message": "Import" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "صيغة الملف" diff --git a/apps/desktop/src/locales/az/messages.json b/apps/desktop/src/locales/az/messages.json index fb818fac642..a031860334d 100644 --- a/apps/desktop/src/locales/az/messages.json +++ b/apps/desktop/src/locales/az/messages.json @@ -1775,11 +1775,21 @@ "exportFrom": { "message": "Buradan xaricə köçür" }, - "export": { - "message": "Xaricə köçür" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" }, - "import": { - "message": "Daxilə köçür" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "Fayl formatı" diff --git a/apps/desktop/src/locales/be/messages.json b/apps/desktop/src/locales/be/messages.json index 982c5ba2956..50f7bdc668e 100644 --- a/apps/desktop/src/locales/be/messages.json +++ b/apps/desktop/src/locales/be/messages.json @@ -1775,11 +1775,21 @@ "exportFrom": { "message": "Export from" }, - "export": { - "message": "Export" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" }, - "import": { - "message": "Import" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "Фармат файла" diff --git a/apps/desktop/src/locales/bg/messages.json b/apps/desktop/src/locales/bg/messages.json index b223a4d42b3..539cfb344aa 100644 --- a/apps/desktop/src/locales/bg/messages.json +++ b/apps/desktop/src/locales/bg/messages.json @@ -1775,11 +1775,21 @@ "exportFrom": { "message": "Изнасяне от" }, - "export": { - "message": "Изнасяне" + "exportNoun": { + "message": "Изнасяне", + "description": "The noun form of the word Export" }, - "import": { - "message": "Внасяне" + "exportVerb": { + "message": "Изнасяне", + "description": "The verb form of the word Export" + }, + "importNoun": { + "message": "Внасяне", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Внасяне", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "Формат на файла" diff --git a/apps/desktop/src/locales/bn/messages.json b/apps/desktop/src/locales/bn/messages.json index 07233587ba8..bde81f83b42 100644 --- a/apps/desktop/src/locales/bn/messages.json +++ b/apps/desktop/src/locales/bn/messages.json @@ -1775,11 +1775,21 @@ "exportFrom": { "message": "Export from" }, - "export": { - "message": "Export" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" }, - "import": { - "message": "Import" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "ফাইলের ধরণ" diff --git a/apps/desktop/src/locales/bs/messages.json b/apps/desktop/src/locales/bs/messages.json index 432f466ebe1..3ed7dee9e92 100644 --- a/apps/desktop/src/locales/bs/messages.json +++ b/apps/desktop/src/locales/bs/messages.json @@ -1775,11 +1775,21 @@ "exportFrom": { "message": "Export from" }, - "export": { - "message": "Export" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" }, - "import": { - "message": "Import" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "Format datoteke" diff --git a/apps/desktop/src/locales/ca/messages.json b/apps/desktop/src/locales/ca/messages.json index b91438416dd..57041a87a31 100644 --- a/apps/desktop/src/locales/ca/messages.json +++ b/apps/desktop/src/locales/ca/messages.json @@ -1775,11 +1775,21 @@ "exportFrom": { "message": "Exporta des de" }, - "export": { - "message": "Export" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" }, - "import": { - "message": "Import" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "Format de fitxer" diff --git a/apps/desktop/src/locales/cs/messages.json b/apps/desktop/src/locales/cs/messages.json index dffe7b34f8f..fe0c814f2a2 100644 --- a/apps/desktop/src/locales/cs/messages.json +++ b/apps/desktop/src/locales/cs/messages.json @@ -1775,11 +1775,21 @@ "exportFrom": { "message": "Exportovat z" }, - "export": { - "message": "Exportovat" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" }, - "import": { - "message": "Importovat" + "exportVerb": { + "message": "Exportovat", + "description": "The verb form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Importovat", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "Formát souboru" diff --git a/apps/desktop/src/locales/cy/messages.json b/apps/desktop/src/locales/cy/messages.json index 45461838f58..af6bb971ab3 100644 --- a/apps/desktop/src/locales/cy/messages.json +++ b/apps/desktop/src/locales/cy/messages.json @@ -1775,11 +1775,21 @@ "exportFrom": { "message": "Export from" }, - "export": { - "message": "Export" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" }, - "import": { - "message": "Import" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "File format" diff --git a/apps/desktop/src/locales/da/messages.json b/apps/desktop/src/locales/da/messages.json index 38396658400..5b864c57d62 100644 --- a/apps/desktop/src/locales/da/messages.json +++ b/apps/desktop/src/locales/da/messages.json @@ -1775,11 +1775,21 @@ "exportFrom": { "message": "Eksportér fra" }, - "export": { - "message": "Export" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" }, - "import": { - "message": "Import" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "Filformat" diff --git a/apps/desktop/src/locales/de/messages.json b/apps/desktop/src/locales/de/messages.json index 52e92a9dbfe..b49654c4dbe 100644 --- a/apps/desktop/src/locales/de/messages.json +++ b/apps/desktop/src/locales/de/messages.json @@ -1775,11 +1775,21 @@ "exportFrom": { "message": "Export aus" }, - "export": { - "message": "Export" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" }, - "import": { - "message": "Import" + "exportVerb": { + "message": "Exportieren", + "description": "The verb form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Importieren", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "Dateiformat" diff --git a/apps/desktop/src/locales/el/messages.json b/apps/desktop/src/locales/el/messages.json index cc6d1909e94..68b773c74f4 100644 --- a/apps/desktop/src/locales/el/messages.json +++ b/apps/desktop/src/locales/el/messages.json @@ -1775,11 +1775,21 @@ "exportFrom": { "message": "Εξαγωγή από" }, - "export": { - "message": "Export" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" }, - "import": { - "message": "Import" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "Τύπος αρχείου" diff --git a/apps/desktop/src/locales/en_GB/messages.json b/apps/desktop/src/locales/en_GB/messages.json index 66840673b77..3ad78742666 100644 --- a/apps/desktop/src/locales/en_GB/messages.json +++ b/apps/desktop/src/locales/en_GB/messages.json @@ -1775,11 +1775,21 @@ "exportFrom": { "message": "Export from" }, - "export": { - "message": "Export" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" }, - "import": { - "message": "Import" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "File format" diff --git a/apps/desktop/src/locales/en_IN/messages.json b/apps/desktop/src/locales/en_IN/messages.json index 50e6671b2fa..987d8d0925c 100644 --- a/apps/desktop/src/locales/en_IN/messages.json +++ b/apps/desktop/src/locales/en_IN/messages.json @@ -1775,11 +1775,21 @@ "exportFrom": { "message": "Export from" }, - "export": { - "message": "Export" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" }, - "import": { - "message": "Import" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "File format" diff --git a/apps/desktop/src/locales/eo/messages.json b/apps/desktop/src/locales/eo/messages.json index a9ed68bffd8..1e70ad9a180 100644 --- a/apps/desktop/src/locales/eo/messages.json +++ b/apps/desktop/src/locales/eo/messages.json @@ -1775,11 +1775,21 @@ "exportFrom": { "message": "Elporti el" }, - "export": { - "message": "Export" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" }, - "import": { - "message": "Import" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "Dosierformato" diff --git a/apps/desktop/src/locales/es/messages.json b/apps/desktop/src/locales/es/messages.json index ab6bb68cc82..9ce531994a0 100644 --- a/apps/desktop/src/locales/es/messages.json +++ b/apps/desktop/src/locales/es/messages.json @@ -1775,11 +1775,21 @@ "exportFrom": { "message": "Exportar desde" }, - "export": { - "message": "Exportar" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" }, - "import": { - "message": "Importar" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "Formato de archivo" diff --git a/apps/desktop/src/locales/et/messages.json b/apps/desktop/src/locales/et/messages.json index 3b92748908b..0c96d531e40 100644 --- a/apps/desktop/src/locales/et/messages.json +++ b/apps/desktop/src/locales/et/messages.json @@ -1775,11 +1775,21 @@ "exportFrom": { "message": "Ekspordi asukohast" }, - "export": { - "message": "Export" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" }, - "import": { - "message": "Import" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "Failivorming" diff --git a/apps/desktop/src/locales/eu/messages.json b/apps/desktop/src/locales/eu/messages.json index 831636ee240..e86f29770d6 100644 --- a/apps/desktop/src/locales/eu/messages.json +++ b/apps/desktop/src/locales/eu/messages.json @@ -1775,11 +1775,21 @@ "exportFrom": { "message": "Export from" }, - "export": { - "message": "Export" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" }, - "import": { - "message": "Import" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "Fitxategiaren formatua" diff --git a/apps/desktop/src/locales/fa/messages.json b/apps/desktop/src/locales/fa/messages.json index 3f2b28cece0..45cda22a45f 100644 --- a/apps/desktop/src/locales/fa/messages.json +++ b/apps/desktop/src/locales/fa/messages.json @@ -1775,11 +1775,21 @@ "exportFrom": { "message": "برون ریزی از" }, - "export": { - "message": "Export" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" }, - "import": { - "message": "Import" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "فرمت پرونده" diff --git a/apps/desktop/src/locales/fi/messages.json b/apps/desktop/src/locales/fi/messages.json index d93e08e759c..7cee4e0c5e5 100644 --- a/apps/desktop/src/locales/fi/messages.json +++ b/apps/desktop/src/locales/fi/messages.json @@ -1775,11 +1775,21 @@ "exportFrom": { "message": "Vie lähteestä" }, - "export": { - "message": "Export" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" }, - "import": { - "message": "Import" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "Tiedostomuoto" diff --git a/apps/desktop/src/locales/fil/messages.json b/apps/desktop/src/locales/fil/messages.json index e82e5698f9f..39651e16f4a 100644 --- a/apps/desktop/src/locales/fil/messages.json +++ b/apps/desktop/src/locales/fil/messages.json @@ -1775,11 +1775,21 @@ "exportFrom": { "message": "Export from" }, - "export": { - "message": "Export" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" }, - "import": { - "message": "Import" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "Format ng file" diff --git a/apps/desktop/src/locales/fr/messages.json b/apps/desktop/src/locales/fr/messages.json index b7792522567..503c93f9f19 100644 --- a/apps/desktop/src/locales/fr/messages.json +++ b/apps/desktop/src/locales/fr/messages.json @@ -1775,11 +1775,21 @@ "exportFrom": { "message": "Exporter depuis" }, - "export": { - "message": "Export" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" }, - "import": { - "message": "Import" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "Format de fichier" diff --git a/apps/desktop/src/locales/gl/messages.json b/apps/desktop/src/locales/gl/messages.json index 315272ae464..2d0019a1f31 100644 --- a/apps/desktop/src/locales/gl/messages.json +++ b/apps/desktop/src/locales/gl/messages.json @@ -1775,11 +1775,21 @@ "exportFrom": { "message": "Export from" }, - "export": { - "message": "Export" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" }, - "import": { - "message": "Import" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "File format" diff --git a/apps/desktop/src/locales/he/messages.json b/apps/desktop/src/locales/he/messages.json index 5a6d486d723..cde90fcc7ef 100644 --- a/apps/desktop/src/locales/he/messages.json +++ b/apps/desktop/src/locales/he/messages.json @@ -1775,11 +1775,21 @@ "exportFrom": { "message": "ייצוא מ־" }, - "export": { - "message": "Export" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" }, - "import": { - "message": "Import" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "תבנית קובץ" diff --git a/apps/desktop/src/locales/hi/messages.json b/apps/desktop/src/locales/hi/messages.json index b193e645425..d3f65bef701 100644 --- a/apps/desktop/src/locales/hi/messages.json +++ b/apps/desktop/src/locales/hi/messages.json @@ -1775,11 +1775,21 @@ "exportFrom": { "message": "Export from" }, - "export": { - "message": "Export" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" }, - "import": { - "message": "Import" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "File format" diff --git a/apps/desktop/src/locales/hr/messages.json b/apps/desktop/src/locales/hr/messages.json index 4c7ceb732e7..10cf3fac635 100644 --- a/apps/desktop/src/locales/hr/messages.json +++ b/apps/desktop/src/locales/hr/messages.json @@ -1775,11 +1775,21 @@ "exportFrom": { "message": "Izvezi iz" }, - "export": { - "message": "Export" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" }, - "import": { - "message": "Import" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "Format datoteke" diff --git a/apps/desktop/src/locales/hu/messages.json b/apps/desktop/src/locales/hu/messages.json index ca03a7d11a9..8cb8897ab09 100644 --- a/apps/desktop/src/locales/hu/messages.json +++ b/apps/desktop/src/locales/hu/messages.json @@ -1775,11 +1775,21 @@ "exportFrom": { "message": "Exportálás innen:" }, - "export": { - "message": "Exportálás" + "exportNoun": { + "message": "Exportálás", + "description": "The noun form of the word Export" }, - "import": { - "message": "Importálás" + "exportVerb": { + "message": "Exportálás", + "description": "The verb form of the word Export" + }, + "importNoun": { + "message": "Importálás", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Importálás", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "Fájlformátum" diff --git a/apps/desktop/src/locales/id/messages.json b/apps/desktop/src/locales/id/messages.json index 9da92c35d11..32a030e584c 100644 --- a/apps/desktop/src/locales/id/messages.json +++ b/apps/desktop/src/locales/id/messages.json @@ -1775,11 +1775,21 @@ "exportFrom": { "message": "Export from" }, - "export": { - "message": "Export" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" }, - "import": { - "message": "Import" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "File Format" diff --git a/apps/desktop/src/locales/it/messages.json b/apps/desktop/src/locales/it/messages.json index ed46bd2763a..bc3076bbc3c 100644 --- a/apps/desktop/src/locales/it/messages.json +++ b/apps/desktop/src/locales/it/messages.json @@ -1199,7 +1199,7 @@ "message": "Seguici" }, "syncNow": { - "message": "Sync now" + "message": "Sincronizza" }, "changeMasterPass": { "message": "Cambia password principale" @@ -1775,11 +1775,21 @@ "exportFrom": { "message": "Esporta da" }, - "export": { - "message": "Export" + "exportNoun": { + "message": "Esporta", + "description": "The noun form of the word Export" }, - "import": { - "message": "Import" + "exportVerb": { + "message": "Esporta", + "description": "The verb form of the word Export" + }, + "importNoun": { + "message": "Importa", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Importa", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "Formato file" @@ -4334,43 +4344,43 @@ "message": "Aggiorna a Premium" }, "removeMasterPasswordForOrgUserKeyConnector": { - "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + "message": "La tua organizzazione non utilizza più le password principali per accedere a Bitwarden. Per continuare, verifica l'organizzazione e il dominio." }, "continueWithLogIn": { - "message": "Continue with log in" + "message": "Accedi e continua" }, "doNotContinue": { - "message": "Do not continue" + "message": "Non continuare" }, "domain": { - "message": "Domain" + "message": "Dominio" }, "keyConnectorDomainTooltip": { - "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + "message": "Questo dominio memorizzerà le chiavi di crittografia del tuo account, quindi assicurati di impostarlo come affidabile. Se non hai la certezza che lo sia, verifica con l'amministratore." }, "verifyYourOrganization": { - "message": "Verify your organization to log in" + "message": "Verifica la tua organizzazione per accedere" }, "organizationVerified": { - "message": "Organization verified" + "message": "Organizzazione verificata" }, "domainVerified": { - "message": "Domain verified" + "message": "Dominio verificato" }, "leaveOrganizationContent": { - "message": "If you don't verify your organization, your access to the organization will be revoked." + "message": "Se non verifichi l'organizzazione, il tuo accesso sarà revocato." }, "leaveNow": { - "message": "Leave now" + "message": "Abbandona" }, "verifyYourDomainToLogin": { - "message": "Verify your domain to log in" + "message": "Verifica il tuo dominio per accedere" }, "verifyYourDomainDescription": { - "message": "To continue with log in, verify this domain." + "message": "Per continuare con l'accesso, verifica questo dominio." }, "confirmKeyConnectorOrganizationUserDescription": { - "message": "To continue with log in, verify the organization and domain." + "message": "Per continuare con l'accesso, verifica l'organizzazione e il dominio." }, "sessionTimeoutSettingsAction": { "message": "Azione al timeout" diff --git a/apps/desktop/src/locales/ja/messages.json b/apps/desktop/src/locales/ja/messages.json index d930bd22b9c..e517bdcbe72 100644 --- a/apps/desktop/src/locales/ja/messages.json +++ b/apps/desktop/src/locales/ja/messages.json @@ -1775,11 +1775,21 @@ "exportFrom": { "message": "エクスポート元" }, - "export": { - "message": "Export" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" }, - "import": { - "message": "Import" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "ファイル形式" diff --git a/apps/desktop/src/locales/ka/messages.json b/apps/desktop/src/locales/ka/messages.json index d4fae58fca4..caf75a81f38 100644 --- a/apps/desktop/src/locales/ka/messages.json +++ b/apps/desktop/src/locales/ka/messages.json @@ -1775,11 +1775,21 @@ "exportFrom": { "message": "Export from" }, - "export": { - "message": "Export" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" }, - "import": { - "message": "Import" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "File format" diff --git a/apps/desktop/src/locales/km/messages.json b/apps/desktop/src/locales/km/messages.json index 315272ae464..2d0019a1f31 100644 --- a/apps/desktop/src/locales/km/messages.json +++ b/apps/desktop/src/locales/km/messages.json @@ -1775,11 +1775,21 @@ "exportFrom": { "message": "Export from" }, - "export": { - "message": "Export" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" }, - "import": { - "message": "Import" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "File format" diff --git a/apps/desktop/src/locales/kn/messages.json b/apps/desktop/src/locales/kn/messages.json index 0f880c88f1f..edfb1b6dd6e 100644 --- a/apps/desktop/src/locales/kn/messages.json +++ b/apps/desktop/src/locales/kn/messages.json @@ -1775,11 +1775,21 @@ "exportFrom": { "message": "Export from" }, - "export": { - "message": "Export" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" }, - "import": { - "message": "Import" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "ಕಡತದ ಮಾದರಿ" diff --git a/apps/desktop/src/locales/ko/messages.json b/apps/desktop/src/locales/ko/messages.json index 2d3c0bba871..4d4e3221651 100644 --- a/apps/desktop/src/locales/ko/messages.json +++ b/apps/desktop/src/locales/ko/messages.json @@ -1775,11 +1775,21 @@ "exportFrom": { "message": "Export from" }, - "export": { - "message": "Export" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" }, - "import": { - "message": "Import" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "파일 형식" diff --git a/apps/desktop/src/locales/lt/messages.json b/apps/desktop/src/locales/lt/messages.json index cce5cd5d223..ead4c1d89f9 100644 --- a/apps/desktop/src/locales/lt/messages.json +++ b/apps/desktop/src/locales/lt/messages.json @@ -1775,11 +1775,21 @@ "exportFrom": { "message": "Export from" }, - "export": { - "message": "Export" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" }, - "import": { - "message": "Import" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "Failo formatas" diff --git a/apps/desktop/src/locales/lv/messages.json b/apps/desktop/src/locales/lv/messages.json index edfab735fbd..427a6ac2e16 100644 --- a/apps/desktop/src/locales/lv/messages.json +++ b/apps/desktop/src/locales/lv/messages.json @@ -1775,11 +1775,21 @@ "exportFrom": { "message": "Izgūt no" }, - "export": { - "message": "Izgūt" + "exportNoun": { + "message": "Izgūšana", + "description": "The noun form of the word Export" }, - "import": { - "message": "Ievietot" + "exportVerb": { + "message": "Izgūt", + "description": "The verb form of the word Export" + }, + "importNoun": { + "message": "Ievietošana", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Ievietot", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "Datnes veids" diff --git a/apps/desktop/src/locales/me/messages.json b/apps/desktop/src/locales/me/messages.json index f2e8df3449b..a5a2212b2b1 100644 --- a/apps/desktop/src/locales/me/messages.json +++ b/apps/desktop/src/locales/me/messages.json @@ -1775,11 +1775,21 @@ "exportFrom": { "message": "Export from" }, - "export": { - "message": "Export" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" }, - "import": { - "message": "Import" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "Format datoteke" diff --git a/apps/desktop/src/locales/ml/messages.json b/apps/desktop/src/locales/ml/messages.json index 97294b878fd..d71af9f752a 100644 --- a/apps/desktop/src/locales/ml/messages.json +++ b/apps/desktop/src/locales/ml/messages.json @@ -1775,11 +1775,21 @@ "exportFrom": { "message": "Export from" }, - "export": { - "message": "Export" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" }, - "import": { - "message": "Import" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "ഫയൽ ഫോർമാറ്റ്" diff --git a/apps/desktop/src/locales/mr/messages.json b/apps/desktop/src/locales/mr/messages.json index 315272ae464..2d0019a1f31 100644 --- a/apps/desktop/src/locales/mr/messages.json +++ b/apps/desktop/src/locales/mr/messages.json @@ -1775,11 +1775,21 @@ "exportFrom": { "message": "Export from" }, - "export": { - "message": "Export" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" }, - "import": { - "message": "Import" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "File format" diff --git a/apps/desktop/src/locales/my/messages.json b/apps/desktop/src/locales/my/messages.json index 78cce9590cb..12c10bbcd3a 100644 --- a/apps/desktop/src/locales/my/messages.json +++ b/apps/desktop/src/locales/my/messages.json @@ -1775,11 +1775,21 @@ "exportFrom": { "message": "Export from" }, - "export": { - "message": "Export" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" }, - "import": { - "message": "Import" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "File format" diff --git a/apps/desktop/src/locales/nb/messages.json b/apps/desktop/src/locales/nb/messages.json index 01bab1584af..069250a562a 100644 --- a/apps/desktop/src/locales/nb/messages.json +++ b/apps/desktop/src/locales/nb/messages.json @@ -1775,11 +1775,21 @@ "exportFrom": { "message": "Eksporter fra" }, - "export": { - "message": "Export" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" }, - "import": { - "message": "Import" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "Filformat" diff --git a/apps/desktop/src/locales/ne/messages.json b/apps/desktop/src/locales/ne/messages.json index a3d42ed7f57..a37dc2f0b5f 100644 --- a/apps/desktop/src/locales/ne/messages.json +++ b/apps/desktop/src/locales/ne/messages.json @@ -1775,11 +1775,21 @@ "exportFrom": { "message": "Export from" }, - "export": { - "message": "Export" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" }, - "import": { - "message": "Import" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "File format" diff --git a/apps/desktop/src/locales/nl/messages.json b/apps/desktop/src/locales/nl/messages.json index 0e8b96d747d..824bad9508d 100644 --- a/apps/desktop/src/locales/nl/messages.json +++ b/apps/desktop/src/locales/nl/messages.json @@ -1775,11 +1775,21 @@ "exportFrom": { "message": "Exporteren vanuit" }, - "export": { - "message": "Exporteren" + "exportNoun": { + "message": "Exporteren", + "description": "The noun form of the word Export" }, - "import": { - "message": "Importeren" + "exportVerb": { + "message": "Exporteren", + "description": "The verb form of the word Export" + }, + "importNoun": { + "message": "Importeren", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Importeren", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "Bestandsindeling" diff --git a/apps/desktop/src/locales/nn/messages.json b/apps/desktop/src/locales/nn/messages.json index 5354db9004f..3fc7075911c 100644 --- a/apps/desktop/src/locales/nn/messages.json +++ b/apps/desktop/src/locales/nn/messages.json @@ -1775,11 +1775,21 @@ "exportFrom": { "message": "Export from" }, - "export": { - "message": "Export" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" }, - "import": { - "message": "Import" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "Filformat" diff --git a/apps/desktop/src/locales/or/messages.json b/apps/desktop/src/locales/or/messages.json index 2447b568d49..2307e43adab 100644 --- a/apps/desktop/src/locales/or/messages.json +++ b/apps/desktop/src/locales/or/messages.json @@ -1775,11 +1775,21 @@ "exportFrom": { "message": "Export from" }, - "export": { - "message": "Export" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" }, - "import": { - "message": "Import" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "File format" diff --git a/apps/desktop/src/locales/pl/messages.json b/apps/desktop/src/locales/pl/messages.json index ad0610aad5c..1724a713a30 100644 --- a/apps/desktop/src/locales/pl/messages.json +++ b/apps/desktop/src/locales/pl/messages.json @@ -1775,11 +1775,21 @@ "exportFrom": { "message": "Eksportuj z" }, - "export": { - "message": "Export" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" }, - "import": { - "message": "Import" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "Format pliku" diff --git a/apps/desktop/src/locales/pt_BR/messages.json b/apps/desktop/src/locales/pt_BR/messages.json index 8350392709c..2246233a560 100644 --- a/apps/desktop/src/locales/pt_BR/messages.json +++ b/apps/desktop/src/locales/pt_BR/messages.json @@ -1775,11 +1775,21 @@ "exportFrom": { "message": "Exportar de" }, - "export": { - "message": "Exportar" + "exportNoun": { + "message": "Exportação", + "description": "The noun form of the word Export" }, - "import": { - "message": "Importar" + "exportVerb": { + "message": "Exportar", + "description": "The verb form of the word Export" + }, + "importNoun": { + "message": "Importação", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Importar", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "Formato do arquivo" diff --git a/apps/desktop/src/locales/pt_PT/messages.json b/apps/desktop/src/locales/pt_PT/messages.json index f8d329480e0..d0e216df217 100644 --- a/apps/desktop/src/locales/pt_PT/messages.json +++ b/apps/desktop/src/locales/pt_PT/messages.json @@ -1775,11 +1775,21 @@ "exportFrom": { "message": "Exportar de" }, - "export": { - "message": "Exportar" + "exportNoun": { + "message": "Exportação", + "description": "The noun form of the word Export" }, - "import": { - "message": "Importar" + "exportVerb": { + "message": "Exportar", + "description": "The verb form of the word Export" + }, + "importNoun": { + "message": "Importação", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Importar", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "Formato do ficheiro" diff --git a/apps/desktop/src/locales/ro/messages.json b/apps/desktop/src/locales/ro/messages.json index f68fb7fc86f..173cf6f5f5b 100644 --- a/apps/desktop/src/locales/ro/messages.json +++ b/apps/desktop/src/locales/ro/messages.json @@ -1775,11 +1775,21 @@ "exportFrom": { "message": "Export from" }, - "export": { - "message": "Export" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" }, - "import": { - "message": "Import" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "Format de fișier" diff --git a/apps/desktop/src/locales/ru/messages.json b/apps/desktop/src/locales/ru/messages.json index 2c8a5052988..c0a838d86e2 100644 --- a/apps/desktop/src/locales/ru/messages.json +++ b/apps/desktop/src/locales/ru/messages.json @@ -1775,11 +1775,21 @@ "exportFrom": { "message": "Экспорт из" }, - "export": { - "message": "Экспорт" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" }, - "import": { - "message": "Импорт" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "Формат файла" diff --git a/apps/desktop/src/locales/si/messages.json b/apps/desktop/src/locales/si/messages.json index 0c3b61c35cf..d53bb073f49 100644 --- a/apps/desktop/src/locales/si/messages.json +++ b/apps/desktop/src/locales/si/messages.json @@ -1775,11 +1775,21 @@ "exportFrom": { "message": "Export from" }, - "export": { - "message": "Export" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" }, - "import": { - "message": "Import" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "File format" diff --git a/apps/desktop/src/locales/sk/messages.json b/apps/desktop/src/locales/sk/messages.json index a26cd60b451..6e76a04a9fa 100644 --- a/apps/desktop/src/locales/sk/messages.json +++ b/apps/desktop/src/locales/sk/messages.json @@ -709,7 +709,7 @@ "message": "Priložiť prílohu" }, "itemsTransferred": { - "message": "Items transferred" + "message": "Položky boli prenesené" }, "fixEncryption": { "message": "Opraviť šifrovanie" @@ -1775,11 +1775,21 @@ "exportFrom": { "message": "Exportovať z" }, - "export": { - "message": "Exportovať" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" }, - "import": { - "message": "Importovať" + "exportVerb": { + "message": "Exportovať", + "description": "The verb form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Importovať", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "Formát súboru" diff --git a/apps/desktop/src/locales/sl/messages.json b/apps/desktop/src/locales/sl/messages.json index f336c8c1261..b0acd957d68 100644 --- a/apps/desktop/src/locales/sl/messages.json +++ b/apps/desktop/src/locales/sl/messages.json @@ -1775,11 +1775,21 @@ "exportFrom": { "message": "Export from" }, - "export": { - "message": "Export" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" }, - "import": { - "message": "Import" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "Format datoteke" diff --git a/apps/desktop/src/locales/sr/messages.json b/apps/desktop/src/locales/sr/messages.json index e5b6a9e2762..0142a1bc719 100644 --- a/apps/desktop/src/locales/sr/messages.json +++ b/apps/desktop/src/locales/sr/messages.json @@ -1775,11 +1775,21 @@ "exportFrom": { "message": "Извоз од" }, - "export": { - "message": "Извези" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" }, - "import": { - "message": "Увоз" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "Формат датотеке" diff --git a/apps/desktop/src/locales/sv/messages.json b/apps/desktop/src/locales/sv/messages.json index 34b55de166b..e19328075d1 100644 --- a/apps/desktop/src/locales/sv/messages.json +++ b/apps/desktop/src/locales/sv/messages.json @@ -1775,11 +1775,21 @@ "exportFrom": { "message": "Exportera från" }, - "export": { - "message": "Exportera" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" }, - "import": { - "message": "Importera" + "exportVerb": { + "message": "Exportera", + "description": "The verb form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Importera", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "Filformat" diff --git a/apps/desktop/src/locales/ta/messages.json b/apps/desktop/src/locales/ta/messages.json index 3abb4eb17c1..01f8f04f945 100644 --- a/apps/desktop/src/locales/ta/messages.json +++ b/apps/desktop/src/locales/ta/messages.json @@ -1775,11 +1775,21 @@ "exportFrom": { "message": "இருந்து ஏற்றுமதி" }, - "export": { - "message": "Export" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" }, - "import": { - "message": "Import" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "கோப்பு வடிவம்" diff --git a/apps/desktop/src/locales/te/messages.json b/apps/desktop/src/locales/te/messages.json index 315272ae464..2d0019a1f31 100644 --- a/apps/desktop/src/locales/te/messages.json +++ b/apps/desktop/src/locales/te/messages.json @@ -1775,11 +1775,21 @@ "exportFrom": { "message": "Export from" }, - "export": { - "message": "Export" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" }, - "import": { - "message": "Import" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "File format" diff --git a/apps/desktop/src/locales/th/messages.json b/apps/desktop/src/locales/th/messages.json index 1eac91a6c79..a9b08eda022 100644 --- a/apps/desktop/src/locales/th/messages.json +++ b/apps/desktop/src/locales/th/messages.json @@ -1775,11 +1775,21 @@ "exportFrom": { "message": "Export from" }, - "export": { - "message": "Export" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" }, - "import": { - "message": "Import" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "File Format" diff --git a/apps/desktop/src/locales/tr/messages.json b/apps/desktop/src/locales/tr/messages.json index a7ed829ce32..fb867fba82c 100644 --- a/apps/desktop/src/locales/tr/messages.json +++ b/apps/desktop/src/locales/tr/messages.json @@ -1775,11 +1775,21 @@ "exportFrom": { "message": "Dışa aktarılacak konum" }, - "export": { - "message": "Dışa aktar" + "exportNoun": { + "message": "Dışa aktar", + "description": "The noun form of the word Export" }, - "import": { - "message": "İçe aktar" + "exportVerb": { + "message": "Dışa aktar", + "description": "The verb form of the word Export" + }, + "importNoun": { + "message": "İçe aktar", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "İçe aktar", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "Dosya biçimi" diff --git a/apps/desktop/src/locales/uk/messages.json b/apps/desktop/src/locales/uk/messages.json index 9e4cc948e37..9f86087f883 100644 --- a/apps/desktop/src/locales/uk/messages.json +++ b/apps/desktop/src/locales/uk/messages.json @@ -1775,11 +1775,21 @@ "exportFrom": { "message": "Експортувати з" }, - "export": { - "message": "Export" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" }, - "import": { - "message": "Import" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "Формат файлу" diff --git a/apps/desktop/src/locales/vi/messages.json b/apps/desktop/src/locales/vi/messages.json index 7591b92f8ee..2c248671fbb 100644 --- a/apps/desktop/src/locales/vi/messages.json +++ b/apps/desktop/src/locales/vi/messages.json @@ -1775,11 +1775,21 @@ "exportFrom": { "message": "Xuất từ" }, - "export": { - "message": "Export" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" }, - "import": { - "message": "Import" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "Định dạng tập tin" diff --git a/apps/desktop/src/locales/zh_CN/messages.json b/apps/desktop/src/locales/zh_CN/messages.json index 0709e19a468..f4640ea9d00 100644 --- a/apps/desktop/src/locales/zh_CN/messages.json +++ b/apps/desktop/src/locales/zh_CN/messages.json @@ -1775,11 +1775,21 @@ "exportFrom": { "message": "导出自" }, - "export": { - "message": "导出" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" }, - "import": { - "message": "导入" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "文件格式" diff --git a/apps/desktop/src/locales/zh_TW/messages.json b/apps/desktop/src/locales/zh_TW/messages.json index 3e00280b364..ea4d8cbc1b0 100644 --- a/apps/desktop/src/locales/zh_TW/messages.json +++ b/apps/desktop/src/locales/zh_TW/messages.json @@ -1775,11 +1775,21 @@ "exportFrom": { "message": "匯出自" }, - "export": { - "message": "Export" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" }, - "import": { - "message": "Import" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "檔案格式" From e989df475a9f555e2f7a8321adffb4a1f831f5f7 Mon Sep 17 00:00:00 2001 From: "bw-ghapp[bot]" <178206702+bw-ghapp[bot]@users.noreply.github.com> Date: Fri, 19 Dec 2025 12:18:02 +0100 Subject: [PATCH 129/188] Autosync the updated translations (#18026) 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/browser/src/_locales/ar/messages.json | 18 +- apps/browser/src/_locales/az/messages.json | 24 +- apps/browser/src/_locales/be/messages.json | 18 +- apps/browser/src/_locales/bg/messages.json | 24 +- apps/browser/src/_locales/bn/messages.json | 18 +- apps/browser/src/_locales/bs/messages.json | 18 +- apps/browser/src/_locales/ca/messages.json | 18 +- apps/browser/src/_locales/cs/messages.json | 18 +- apps/browser/src/_locales/cy/messages.json | 18 +- apps/browser/src/_locales/da/messages.json | 18 +- apps/browser/src/_locales/de/messages.json | 24 +- apps/browser/src/_locales/el/messages.json | 18 +- apps/browser/src/_locales/en_GB/messages.json | 18 +- apps/browser/src/_locales/en_IN/messages.json | 18 +- apps/browser/src/_locales/es/messages.json | 18 +- apps/browser/src/_locales/et/messages.json | 18 +- apps/browser/src/_locales/eu/messages.json | 18 +- apps/browser/src/_locales/fa/messages.json | 18 +- apps/browser/src/_locales/fi/messages.json | 18 +- apps/browser/src/_locales/fil/messages.json | 18 +- apps/browser/src/_locales/fr/messages.json | 18 +- apps/browser/src/_locales/gl/messages.json | 18 +- apps/browser/src/_locales/he/messages.json | 18 +- apps/browser/src/_locales/hi/messages.json | 18 +- apps/browser/src/_locales/hr/messages.json | 18 +- apps/browser/src/_locales/hu/messages.json | 18 +- apps/browser/src/_locales/id/messages.json | 18 +- apps/browser/src/_locales/it/messages.json | 24 +- apps/browser/src/_locales/ja/messages.json | 18 +- apps/browser/src/_locales/ka/messages.json | 18 +- apps/browser/src/_locales/km/messages.json | 18 +- apps/browser/src/_locales/kn/messages.json | 18 +- apps/browser/src/_locales/ko/messages.json | 18 +- apps/browser/src/_locales/lt/messages.json | 18 +- apps/browser/src/_locales/lv/messages.json | 24 +- apps/browser/src/_locales/ml/messages.json | 18 +- apps/browser/src/_locales/mr/messages.json | 18 +- apps/browser/src/_locales/my/messages.json | 18 +- apps/browser/src/_locales/nb/messages.json | 18 +- apps/browser/src/_locales/ne/messages.json | 18 +- apps/browser/src/_locales/nl/messages.json | 18 +- apps/browser/src/_locales/nn/messages.json | 18 +- apps/browser/src/_locales/or/messages.json | 18 +- apps/browser/src/_locales/pl/messages.json | 20 +- apps/browser/src/_locales/pt_BR/messages.json | 18 +- apps/browser/src/_locales/pt_PT/messages.json | 18 +- apps/browser/src/_locales/ro/messages.json | 18 +- apps/browser/src/_locales/ru/messages.json | 18 +- apps/browser/src/_locales/si/messages.json | 18 +- apps/browser/src/_locales/sk/messages.json | 18 +- apps/browser/src/_locales/sl/messages.json | 18 +- apps/browser/src/_locales/sr/messages.json | 18 +- apps/browser/src/_locales/sv/messages.json | 24 +- apps/browser/src/_locales/ta/messages.json | 18 +- apps/browser/src/_locales/te/messages.json | 18 +- apps/browser/src/_locales/th/messages.json | 2864 +++++++++-------- apps/browser/src/_locales/tr/messages.json | 18 +- apps/browser/src/_locales/uk/messages.json | 146 +- apps/browser/src/_locales/vi/messages.json | 18 +- apps/browser/src/_locales/zh_CN/messages.json | 28 +- apps/browser/src/_locales/zh_TW/messages.json | 18 +- apps/browser/store/locales/th/copy.resx | 67 +- 62 files changed, 2398 insertions(+), 1789 deletions(-) diff --git a/apps/browser/src/_locales/ar/messages.json b/apps/browser/src/_locales/ar/messages.json index f1e2da53768..25cc1e31425 100644 --- a/apps/browser/src/_locales/ar/messages.json +++ b/apps/browser/src/_locales/ar/messages.json @@ -1322,11 +1322,21 @@ "exportFrom": { "message": "التصدير من" }, - "export": { - "message": "Export" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, - "import": { - "message": "Import" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "صيغة الملف" diff --git a/apps/browser/src/_locales/az/messages.json b/apps/browser/src/_locales/az/messages.json index 81c9fb6a131..3efc2627018 100644 --- a/apps/browser/src/_locales/az/messages.json +++ b/apps/browser/src/_locales/az/messages.json @@ -1322,11 +1322,21 @@ "exportFrom": { "message": "Buradan xaricə köçür" }, - "export": { - "message": "Xaricə köçür" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, - "import": { - "message": "Daxilə köçür" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "Fayl formatı" @@ -4802,13 +4812,13 @@ "message": "Hesab güvənliyi" }, "phishingBlocker": { - "message": "Phishing Blocker" + "message": "Fişinq əngəlləyici" }, "enablePhishingDetection": { - "message": "Phishing detection" + "message": "Fişinq aşkarlama" }, "enablePhishingDetectionDesc": { - "message": "Display warning before accessing suspected phishing sites" + "message": "Şübhəli fişinq saytlarına erişməzdən əvvəl xəbərdarlıq nümayiş etdir" }, "notifications": { "message": "Bildirişlər" diff --git a/apps/browser/src/_locales/be/messages.json b/apps/browser/src/_locales/be/messages.json index afe26551760..fd4dbb780da 100644 --- a/apps/browser/src/_locales/be/messages.json +++ b/apps/browser/src/_locales/be/messages.json @@ -1322,11 +1322,21 @@ "exportFrom": { "message": "Экспартаванне з" }, - "export": { - "message": "Export" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, - "import": { - "message": "Import" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "Фармат файла" diff --git a/apps/browser/src/_locales/bg/messages.json b/apps/browser/src/_locales/bg/messages.json index adaa86369c6..1dc20d6ab65 100644 --- a/apps/browser/src/_locales/bg/messages.json +++ b/apps/browser/src/_locales/bg/messages.json @@ -1322,11 +1322,21 @@ "exportFrom": { "message": "Изнасяне от" }, - "export": { - "message": "Изнасяне" + "exportVerb": { + "message": "Изнасяне", + "description": "The verb form of the word Export" }, - "import": { - "message": "Внасяне" + "exportNoun": { + "message": "Изнасяне", + "description": "The noun form of the word Export" + }, + "importNoun": { + "message": "Внасяне", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Внасяне", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "Формат на файла" @@ -4802,13 +4812,13 @@ "message": "Защита на регистрацията" }, "phishingBlocker": { - "message": "Phishing Blocker" + "message": "Блокатор на измами" }, "enablePhishingDetection": { - "message": "Phishing detection" + "message": "Разпознаване на измами" }, "enablePhishingDetectionDesc": { - "message": "Display warning before accessing suspected phishing sites" + "message": "Показване на предупреждение преди зареждане на уеб сайтове подозирани за измамни" }, "notifications": { "message": "Известия" diff --git a/apps/browser/src/_locales/bn/messages.json b/apps/browser/src/_locales/bn/messages.json index 9b2248da59b..baf98bcb50a 100644 --- a/apps/browser/src/_locales/bn/messages.json +++ b/apps/browser/src/_locales/bn/messages.json @@ -1322,11 +1322,21 @@ "exportFrom": { "message": "Export from" }, - "export": { - "message": "Export" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, - "import": { - "message": "Import" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "ফাইলের ধরণ" diff --git a/apps/browser/src/_locales/bs/messages.json b/apps/browser/src/_locales/bs/messages.json index 5c3c01a0ab5..df473b0192d 100644 --- a/apps/browser/src/_locales/bs/messages.json +++ b/apps/browser/src/_locales/bs/messages.json @@ -1322,11 +1322,21 @@ "exportFrom": { "message": "Export from" }, - "export": { - "message": "Export" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, - "import": { - "message": "Import" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "File format" diff --git a/apps/browser/src/_locales/ca/messages.json b/apps/browser/src/_locales/ca/messages.json index 61a12bfb50c..4bbfb9a418e 100644 --- a/apps/browser/src/_locales/ca/messages.json +++ b/apps/browser/src/_locales/ca/messages.json @@ -1322,11 +1322,21 @@ "exportFrom": { "message": "Exporta des de" }, - "export": { - "message": "Export" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, - "import": { - "message": "Import" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "Format de fitxer" diff --git a/apps/browser/src/_locales/cs/messages.json b/apps/browser/src/_locales/cs/messages.json index 309c7361de2..e40edbb8091 100644 --- a/apps/browser/src/_locales/cs/messages.json +++ b/apps/browser/src/_locales/cs/messages.json @@ -1322,11 +1322,21 @@ "exportFrom": { "message": "Exportovat z" }, - "export": { - "message": "Exportovat" + "exportVerb": { + "message": "Exportovat", + "description": "The verb form of the word Export" }, - "import": { - "message": "Importovat" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Importovat", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "Formát souboru" diff --git a/apps/browser/src/_locales/cy/messages.json b/apps/browser/src/_locales/cy/messages.json index b5d090cc505..896eee5af4f 100644 --- a/apps/browser/src/_locales/cy/messages.json +++ b/apps/browser/src/_locales/cy/messages.json @@ -1322,11 +1322,21 @@ "exportFrom": { "message": "Export from" }, - "export": { - "message": "Export" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, - "import": { - "message": "Import" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "Fformat y ffeil" diff --git a/apps/browser/src/_locales/da/messages.json b/apps/browser/src/_locales/da/messages.json index 0627c97766f..ad6e5b0e90d 100644 --- a/apps/browser/src/_locales/da/messages.json +++ b/apps/browser/src/_locales/da/messages.json @@ -1322,11 +1322,21 @@ "exportFrom": { "message": "Eksportér fra" }, - "export": { - "message": "Export" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, - "import": { - "message": "Import" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "Filformat" diff --git a/apps/browser/src/_locales/de/messages.json b/apps/browser/src/_locales/de/messages.json index dc972697dc6..5ba6dd419a1 100644 --- a/apps/browser/src/_locales/de/messages.json +++ b/apps/browser/src/_locales/de/messages.json @@ -1322,11 +1322,21 @@ "exportFrom": { "message": "Export aus" }, - "export": { - "message": "Export" + "exportVerb": { + "message": "Exportieren", + "description": "The verb form of the word Export" }, - "import": { - "message": "Import" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Importieren", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "Dateiformat" @@ -4802,13 +4812,13 @@ "message": "Kontosicherheit" }, "phishingBlocker": { - "message": "Phishing Blocker" + "message": "Phishing-Blocker" }, "enablePhishingDetection": { - "message": "Phishing detection" + "message": "Phishing-Erkennung" }, "enablePhishingDetectionDesc": { - "message": "Display warning before accessing suspected phishing sites" + "message": "Warnung vor dem Zugriff auf verdächtige Phishing-Seiten anzeigen" }, "notifications": { "message": "Benachrichtigungen" diff --git a/apps/browser/src/_locales/el/messages.json b/apps/browser/src/_locales/el/messages.json index e4b7b28a512..29593380dd9 100644 --- a/apps/browser/src/_locales/el/messages.json +++ b/apps/browser/src/_locales/el/messages.json @@ -1322,11 +1322,21 @@ "exportFrom": { "message": "Εξαγωγή από" }, - "export": { - "message": "Export" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, - "import": { - "message": "Import" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "Τύπος αρχείου" diff --git a/apps/browser/src/_locales/en_GB/messages.json b/apps/browser/src/_locales/en_GB/messages.json index 1abb01fcb14..f64bf387a5e 100644 --- a/apps/browser/src/_locales/en_GB/messages.json +++ b/apps/browser/src/_locales/en_GB/messages.json @@ -1322,11 +1322,21 @@ "exportFrom": { "message": "Export from" }, - "export": { - "message": "Export" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, - "import": { - "message": "Import" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "File format" diff --git a/apps/browser/src/_locales/en_IN/messages.json b/apps/browser/src/_locales/en_IN/messages.json index 8b5d976a5e9..34f2f0b1105 100644 --- a/apps/browser/src/_locales/en_IN/messages.json +++ b/apps/browser/src/_locales/en_IN/messages.json @@ -1322,11 +1322,21 @@ "exportFrom": { "message": "Export from" }, - "export": { - "message": "Export" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, - "import": { - "message": "Import" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "File format" diff --git a/apps/browser/src/_locales/es/messages.json b/apps/browser/src/_locales/es/messages.json index 80139915ff0..276d0dbedb2 100644 --- a/apps/browser/src/_locales/es/messages.json +++ b/apps/browser/src/_locales/es/messages.json @@ -1322,11 +1322,21 @@ "exportFrom": { "message": "Exportar desde" }, - "export": { - "message": "Export" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, - "import": { - "message": "Import" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "Formato de archivo" diff --git a/apps/browser/src/_locales/et/messages.json b/apps/browser/src/_locales/et/messages.json index 113336c18cb..3efb771966d 100644 --- a/apps/browser/src/_locales/et/messages.json +++ b/apps/browser/src/_locales/et/messages.json @@ -1322,11 +1322,21 @@ "exportFrom": { "message": "Export from" }, - "export": { - "message": "Export" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, - "import": { - "message": "Import" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "Failivorming" diff --git a/apps/browser/src/_locales/eu/messages.json b/apps/browser/src/_locales/eu/messages.json index 87469554bea..d8f8c5230c6 100644 --- a/apps/browser/src/_locales/eu/messages.json +++ b/apps/browser/src/_locales/eu/messages.json @@ -1322,11 +1322,21 @@ "exportFrom": { "message": "Export from" }, - "export": { - "message": "Export" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, - "import": { - "message": "Import" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "Fitxategiaren formatua" diff --git a/apps/browser/src/_locales/fa/messages.json b/apps/browser/src/_locales/fa/messages.json index 5d4ff7a6d2b..c264b292761 100644 --- a/apps/browser/src/_locales/fa/messages.json +++ b/apps/browser/src/_locales/fa/messages.json @@ -1322,11 +1322,21 @@ "exportFrom": { "message": "برون ریزی از" }, - "export": { - "message": "Export" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, - "import": { - "message": "Import" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "فرمت پرونده" diff --git a/apps/browser/src/_locales/fi/messages.json b/apps/browser/src/_locales/fi/messages.json index 06157846f90..38003bb31b2 100644 --- a/apps/browser/src/_locales/fi/messages.json +++ b/apps/browser/src/_locales/fi/messages.json @@ -1322,11 +1322,21 @@ "exportFrom": { "message": "Vie lähteestä" }, - "export": { - "message": "Export" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, - "import": { - "message": "Import" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "Tiedostomuoto" diff --git a/apps/browser/src/_locales/fil/messages.json b/apps/browser/src/_locales/fil/messages.json index 918acdac775..38702b940c4 100644 --- a/apps/browser/src/_locales/fil/messages.json +++ b/apps/browser/src/_locales/fil/messages.json @@ -1322,11 +1322,21 @@ "exportFrom": { "message": "Export from" }, - "export": { - "message": "Export" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, - "import": { - "message": "Import" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "Format ng file" diff --git a/apps/browser/src/_locales/fr/messages.json b/apps/browser/src/_locales/fr/messages.json index cb611df41a1..afc71011900 100644 --- a/apps/browser/src/_locales/fr/messages.json +++ b/apps/browser/src/_locales/fr/messages.json @@ -1322,11 +1322,21 @@ "exportFrom": { "message": "Exporter à partir de" }, - "export": { - "message": "Exporter" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, - "import": { - "message": "Importer" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "Format de fichier" diff --git a/apps/browser/src/_locales/gl/messages.json b/apps/browser/src/_locales/gl/messages.json index e83baff136f..7bfe70bded5 100644 --- a/apps/browser/src/_locales/gl/messages.json +++ b/apps/browser/src/_locales/gl/messages.json @@ -1322,11 +1322,21 @@ "exportFrom": { "message": "Exportar dende" }, - "export": { - "message": "Export" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, - "import": { - "message": "Import" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "Formato de ficheiro" diff --git a/apps/browser/src/_locales/he/messages.json b/apps/browser/src/_locales/he/messages.json index 64dddc4db04..fd4e010ce60 100644 --- a/apps/browser/src/_locales/he/messages.json +++ b/apps/browser/src/_locales/he/messages.json @@ -1322,11 +1322,21 @@ "exportFrom": { "message": "ייצא מ־" }, - "export": { - "message": "Export" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, - "import": { - "message": "Import" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "פורמט הקובץ" diff --git a/apps/browser/src/_locales/hi/messages.json b/apps/browser/src/_locales/hi/messages.json index 4602d099b59..dc91aa89197 100644 --- a/apps/browser/src/_locales/hi/messages.json +++ b/apps/browser/src/_locales/hi/messages.json @@ -1322,11 +1322,21 @@ "exportFrom": { "message": "Export from" }, - "export": { - "message": "Export" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, - "import": { - "message": "Import" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "File Format" diff --git a/apps/browser/src/_locales/hr/messages.json b/apps/browser/src/_locales/hr/messages.json index 3a42b22eb45..c472bfa50cd 100644 --- a/apps/browser/src/_locales/hr/messages.json +++ b/apps/browser/src/_locales/hr/messages.json @@ -1322,11 +1322,21 @@ "exportFrom": { "message": "Izvezi iz" }, - "export": { - "message": "Export" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, - "import": { - "message": "Import" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "Format datoteke" diff --git a/apps/browser/src/_locales/hu/messages.json b/apps/browser/src/_locales/hu/messages.json index dc34f3b2166..438c574bba9 100644 --- a/apps/browser/src/_locales/hu/messages.json +++ b/apps/browser/src/_locales/hu/messages.json @@ -1322,11 +1322,21 @@ "exportFrom": { "message": "Exportálás innen:" }, - "export": { - "message": "Exportálás" + "exportVerb": { + "message": "Exportálás", + "description": "The verb form of the word Export" }, - "import": { - "message": "Importálás" + "exportNoun": { + "message": "Exportálás", + "description": "The noun form of the word Export" + }, + "importNoun": { + "message": "Importálás", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Importálás", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "Fájlformátum" diff --git a/apps/browser/src/_locales/id/messages.json b/apps/browser/src/_locales/id/messages.json index 36df2e733f8..4d636a5a79d 100644 --- a/apps/browser/src/_locales/id/messages.json +++ b/apps/browser/src/_locales/id/messages.json @@ -1322,11 +1322,21 @@ "exportFrom": { "message": "Ekspor dari" }, - "export": { - "message": "Export" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, - "import": { - "message": "Import" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "Format Berkas" diff --git a/apps/browser/src/_locales/it/messages.json b/apps/browser/src/_locales/it/messages.json index a64b3e0f351..2615640df0a 100644 --- a/apps/browser/src/_locales/it/messages.json +++ b/apps/browser/src/_locales/it/messages.json @@ -1322,11 +1322,21 @@ "exportFrom": { "message": "Esporta da" }, - "export": { - "message": "Esporta" + "exportVerb": { + "message": "Esporta", + "description": "The verb form of the word Export" }, - "import": { - "message": "Importa" + "exportNoun": { + "message": "Esporta", + "description": "The noun form of the word Export" + }, + "importNoun": { + "message": "Importa", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Importa", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "Formato file" @@ -4802,13 +4812,13 @@ "message": "Sicurezza dell'account" }, "phishingBlocker": { - "message": "Phishing Blocker" + "message": "Blocco phishing" }, "enablePhishingDetection": { - "message": "Phishing detection" + "message": "Rilevazione phishing" }, "enablePhishingDetectionDesc": { - "message": "Display warning before accessing suspected phishing sites" + "message": "Mostra un avviso prima di accedere ai siti sospetti di phishing" }, "notifications": { "message": "Notifiche" diff --git a/apps/browser/src/_locales/ja/messages.json b/apps/browser/src/_locales/ja/messages.json index e89c58ae4c3..91c006fccca 100644 --- a/apps/browser/src/_locales/ja/messages.json +++ b/apps/browser/src/_locales/ja/messages.json @@ -1322,11 +1322,21 @@ "exportFrom": { "message": "エクスポート元" }, - "export": { - "message": "Export" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, - "import": { - "message": "Import" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "ファイル形式" diff --git a/apps/browser/src/_locales/ka/messages.json b/apps/browser/src/_locales/ka/messages.json index 6e1a5c556d9..85c2b04f469 100644 --- a/apps/browser/src/_locales/ka/messages.json +++ b/apps/browser/src/_locales/ka/messages.json @@ -1322,11 +1322,21 @@ "exportFrom": { "message": "Export from" }, - "export": { - "message": "Export" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, - "import": { - "message": "Import" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "File format" diff --git a/apps/browser/src/_locales/km/messages.json b/apps/browser/src/_locales/km/messages.json index 02945c6ff9b..04386b72930 100644 --- a/apps/browser/src/_locales/km/messages.json +++ b/apps/browser/src/_locales/km/messages.json @@ -1322,11 +1322,21 @@ "exportFrom": { "message": "Export from" }, - "export": { - "message": "Export" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, - "import": { - "message": "Import" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "File format" diff --git a/apps/browser/src/_locales/kn/messages.json b/apps/browser/src/_locales/kn/messages.json index 5d37dbf41b7..c29ecc2d04f 100644 --- a/apps/browser/src/_locales/kn/messages.json +++ b/apps/browser/src/_locales/kn/messages.json @@ -1322,11 +1322,21 @@ "exportFrom": { "message": "Export from" }, - "export": { - "message": "Export" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, - "import": { - "message": "Import" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "ಕಡತದ ಮಾದರಿ" diff --git a/apps/browser/src/_locales/ko/messages.json b/apps/browser/src/_locales/ko/messages.json index 247133a4295..ccf3d96d366 100644 --- a/apps/browser/src/_locales/ko/messages.json +++ b/apps/browser/src/_locales/ko/messages.json @@ -1322,11 +1322,21 @@ "exportFrom": { "message": "~(으)로부터 내보내기" }, - "export": { - "message": "Export" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, - "import": { - "message": "Import" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "파일 형식" diff --git a/apps/browser/src/_locales/lt/messages.json b/apps/browser/src/_locales/lt/messages.json index 25c830574c6..a1e3e287892 100644 --- a/apps/browser/src/_locales/lt/messages.json +++ b/apps/browser/src/_locales/lt/messages.json @@ -1322,11 +1322,21 @@ "exportFrom": { "message": "Export from" }, - "export": { - "message": "Export" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, - "import": { - "message": "Import" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "Failo formatas" diff --git a/apps/browser/src/_locales/lv/messages.json b/apps/browser/src/_locales/lv/messages.json index 65c4591db80..95e5d2399e6 100644 --- a/apps/browser/src/_locales/lv/messages.json +++ b/apps/browser/src/_locales/lv/messages.json @@ -1322,11 +1322,21 @@ "exportFrom": { "message": "Izgūt no" }, - "export": { - "message": "Izgūt" + "exportVerb": { + "message": "Izgūt", + "description": "The verb form of the word Export" }, - "import": { - "message": "Ievietot" + "exportNoun": { + "message": "Izgūšana", + "description": "The noun form of the word Export" + }, + "importNoun": { + "message": "Ievietošana", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Ievietot", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "Datnes veids" @@ -4802,13 +4812,13 @@ "message": "Konta drošība" }, "phishingBlocker": { - "message": "Phishing Blocker" + "message": "Pikšķerēšanas aizturētājs" }, "enablePhishingDetection": { - "message": "Phishing detection" + "message": "Pikšķerēšanas noteikšana" }, "enablePhishingDetectionDesc": { - "message": "Display warning before accessing suspected phishing sites" + "message": "Attēlot brīdinājumu pirms piekļūšanas iespējamām piekšķerēšanas vietnēm" }, "notifications": { "message": "Paziņojumi" diff --git a/apps/browser/src/_locales/ml/messages.json b/apps/browser/src/_locales/ml/messages.json index 895ae7baa01..3257c4c745c 100644 --- a/apps/browser/src/_locales/ml/messages.json +++ b/apps/browser/src/_locales/ml/messages.json @@ -1322,11 +1322,21 @@ "exportFrom": { "message": "Export from" }, - "export": { - "message": "Export" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, - "import": { - "message": "Import" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "ഫയൽ ഫോർമാറ്റ്" diff --git a/apps/browser/src/_locales/mr/messages.json b/apps/browser/src/_locales/mr/messages.json index 2f2f9bbefe8..38487d00f9d 100644 --- a/apps/browser/src/_locales/mr/messages.json +++ b/apps/browser/src/_locales/mr/messages.json @@ -1322,11 +1322,21 @@ "exportFrom": { "message": "Export from" }, - "export": { - "message": "Export" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, - "import": { - "message": "Import" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "File format" diff --git a/apps/browser/src/_locales/my/messages.json b/apps/browser/src/_locales/my/messages.json index 02945c6ff9b..04386b72930 100644 --- a/apps/browser/src/_locales/my/messages.json +++ b/apps/browser/src/_locales/my/messages.json @@ -1322,11 +1322,21 @@ "exportFrom": { "message": "Export from" }, - "export": { - "message": "Export" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, - "import": { - "message": "Import" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "File format" diff --git a/apps/browser/src/_locales/nb/messages.json b/apps/browser/src/_locales/nb/messages.json index 027e28d1f2c..9056ce4ad8e 100644 --- a/apps/browser/src/_locales/nb/messages.json +++ b/apps/browser/src/_locales/nb/messages.json @@ -1322,11 +1322,21 @@ "exportFrom": { "message": "Eksporter fra" }, - "export": { - "message": "Export" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, - "import": { - "message": "Import" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "Filformat" diff --git a/apps/browser/src/_locales/ne/messages.json b/apps/browser/src/_locales/ne/messages.json index 02945c6ff9b..04386b72930 100644 --- a/apps/browser/src/_locales/ne/messages.json +++ b/apps/browser/src/_locales/ne/messages.json @@ -1322,11 +1322,21 @@ "exportFrom": { "message": "Export from" }, - "export": { - "message": "Export" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, - "import": { - "message": "Import" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "File format" diff --git a/apps/browser/src/_locales/nl/messages.json b/apps/browser/src/_locales/nl/messages.json index f91f28e5c1c..ea040cb057b 100644 --- a/apps/browser/src/_locales/nl/messages.json +++ b/apps/browser/src/_locales/nl/messages.json @@ -1322,11 +1322,21 @@ "exportFrom": { "message": "Exporteren vanuit" }, - "export": { - "message": "Exporteren" + "exportVerb": { + "message": "Exporteren", + "description": "The verb form of the word Export" }, - "import": { - "message": "Importeren" + "exportNoun": { + "message": "Exporteren", + "description": "The noun form of the word Export" + }, + "importNoun": { + "message": "Importeren", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Importeren", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "Bestandsindeling" diff --git a/apps/browser/src/_locales/nn/messages.json b/apps/browser/src/_locales/nn/messages.json index 02945c6ff9b..04386b72930 100644 --- a/apps/browser/src/_locales/nn/messages.json +++ b/apps/browser/src/_locales/nn/messages.json @@ -1322,11 +1322,21 @@ "exportFrom": { "message": "Export from" }, - "export": { - "message": "Export" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, - "import": { - "message": "Import" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "File format" diff --git a/apps/browser/src/_locales/or/messages.json b/apps/browser/src/_locales/or/messages.json index 02945c6ff9b..04386b72930 100644 --- a/apps/browser/src/_locales/or/messages.json +++ b/apps/browser/src/_locales/or/messages.json @@ -1322,11 +1322,21 @@ "exportFrom": { "message": "Export from" }, - "export": { - "message": "Export" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, - "import": { - "message": "Import" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "File format" diff --git a/apps/browser/src/_locales/pl/messages.json b/apps/browser/src/_locales/pl/messages.json index c8e452d7190..67e7a2a2a00 100644 --- a/apps/browser/src/_locales/pl/messages.json +++ b/apps/browser/src/_locales/pl/messages.json @@ -1322,11 +1322,21 @@ "exportFrom": { "message": "Eksportuj z" }, - "export": { - "message": "Export" + "exportVerb": { + "message": "Eksportuj", + "description": "The verb form of the word Export" }, - "import": { - "message": "Import" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "importNoun": { + "message": "Importuj", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "Format pliku" @@ -1422,7 +1432,7 @@ "message": "Enter your master password" }, "updateSettings": { - "message": "Update settings" + "message": "Zaktualizuj ustawienia" }, "later": { "message": "Później" diff --git a/apps/browser/src/_locales/pt_BR/messages.json b/apps/browser/src/_locales/pt_BR/messages.json index 20c1a7e098d..c7ecfe3f81d 100644 --- a/apps/browser/src/_locales/pt_BR/messages.json +++ b/apps/browser/src/_locales/pt_BR/messages.json @@ -1322,11 +1322,21 @@ "exportFrom": { "message": "Exportar de" }, - "export": { - "message": "Exportar" + "exportVerb": { + "message": "Exportar", + "description": "The verb form of the word Export" }, - "import": { - "message": "Importar" + "exportNoun": { + "message": "Exportação", + "description": "The noun form of the word Export" + }, + "importNoun": { + "message": "Importação", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Importar", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "Formato do arquivo" diff --git a/apps/browser/src/_locales/pt_PT/messages.json b/apps/browser/src/_locales/pt_PT/messages.json index e0cbf43a813..d766e8f95fb 100644 --- a/apps/browser/src/_locales/pt_PT/messages.json +++ b/apps/browser/src/_locales/pt_PT/messages.json @@ -1322,11 +1322,21 @@ "exportFrom": { "message": "Exportar de" }, - "export": { - "message": "Exportar" + "exportVerb": { + "message": "Exportar", + "description": "The verb form of the word Export" }, - "import": { - "message": "Importar" + "exportNoun": { + "message": "Exportação", + "description": "The noun form of the word Export" + }, + "importNoun": { + "message": "Importar", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Importação", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "Formato do ficheiro" diff --git a/apps/browser/src/_locales/ro/messages.json b/apps/browser/src/_locales/ro/messages.json index d6d32804dcb..f0f71168074 100644 --- a/apps/browser/src/_locales/ro/messages.json +++ b/apps/browser/src/_locales/ro/messages.json @@ -1322,11 +1322,21 @@ "exportFrom": { "message": "Export from" }, - "export": { - "message": "Export" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, - "import": { - "message": "Import" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "Format fișier" diff --git a/apps/browser/src/_locales/ru/messages.json b/apps/browser/src/_locales/ru/messages.json index 575b29350fd..3185edea5d5 100644 --- a/apps/browser/src/_locales/ru/messages.json +++ b/apps/browser/src/_locales/ru/messages.json @@ -1322,11 +1322,21 @@ "exportFrom": { "message": "Экспорт из" }, - "export": { - "message": "Экспорт" + "exportVerb": { + "message": "Экспортировать", + "description": "The verb form of the word Export" }, - "import": { - "message": "Импорт" + "exportNoun": { + "message": "Экспорт", + "description": "The noun form of the word Export" + }, + "importNoun": { + "message": "Импорт", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Импортировать", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "Формат файла" diff --git a/apps/browser/src/_locales/si/messages.json b/apps/browser/src/_locales/si/messages.json index d4fb646f471..7ef837967c9 100644 --- a/apps/browser/src/_locales/si/messages.json +++ b/apps/browser/src/_locales/si/messages.json @@ -1322,11 +1322,21 @@ "exportFrom": { "message": "Export from" }, - "export": { - "message": "Export" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, - "import": { - "message": "Import" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "ගොනු ආකෘතිය" diff --git a/apps/browser/src/_locales/sk/messages.json b/apps/browser/src/_locales/sk/messages.json index c32da5e7adb..343f16a921b 100644 --- a/apps/browser/src/_locales/sk/messages.json +++ b/apps/browser/src/_locales/sk/messages.json @@ -1322,11 +1322,21 @@ "exportFrom": { "message": "Exportovať z" }, - "export": { - "message": "Exportovať" + "exportVerb": { + "message": "Exportovať", + "description": "The verb form of the word Export" }, - "import": { - "message": "Importovať" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Importovať", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "Formát súboru" diff --git a/apps/browser/src/_locales/sl/messages.json b/apps/browser/src/_locales/sl/messages.json index fcfa6857588..2806020d5f0 100644 --- a/apps/browser/src/_locales/sl/messages.json +++ b/apps/browser/src/_locales/sl/messages.json @@ -1322,11 +1322,21 @@ "exportFrom": { "message": "Export from" }, - "export": { - "message": "Export" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, - "import": { - "message": "Import" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "Format datoteke" diff --git a/apps/browser/src/_locales/sr/messages.json b/apps/browser/src/_locales/sr/messages.json index fa3e918bc01..4a23b7e7141 100644 --- a/apps/browser/src/_locales/sr/messages.json +++ b/apps/browser/src/_locales/sr/messages.json @@ -1322,11 +1322,21 @@ "exportFrom": { "message": "Извоз од" }, - "export": { - "message": "Извези" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, - "import": { - "message": "Увоз" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "Формат датотеке" diff --git a/apps/browser/src/_locales/sv/messages.json b/apps/browser/src/_locales/sv/messages.json index bb1e65f82fa..d04202f5c0b 100644 --- a/apps/browser/src/_locales/sv/messages.json +++ b/apps/browser/src/_locales/sv/messages.json @@ -1322,11 +1322,21 @@ "exportFrom": { "message": "Exportera från" }, - "export": { - "message": "Exportera" + "exportVerb": { + "message": "Exportera", + "description": "The verb form of the word Export" }, - "import": { - "message": "Importera" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Importera", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "Filformat" @@ -4802,13 +4812,13 @@ "message": "Kontosäkerhet" }, "phishingBlocker": { - "message": "Phishing Blocker" + "message": "Nätfiskeblockerare" }, "enablePhishingDetection": { - "message": "Phishing detection" + "message": "Nätfiskedetektering" }, "enablePhishingDetectionDesc": { - "message": "Display warning before accessing suspected phishing sites" + "message": "Visa varning innan du öppnar misstänkta nätfiskeplatser" }, "notifications": { "message": "Aviseringar" diff --git a/apps/browser/src/_locales/ta/messages.json b/apps/browser/src/_locales/ta/messages.json index 9856e53591d..1e25b5157ef 100644 --- a/apps/browser/src/_locales/ta/messages.json +++ b/apps/browser/src/_locales/ta/messages.json @@ -1322,11 +1322,21 @@ "exportFrom": { "message": "இதிலிருந்து ஏற்றுமதிசெய்" }, - "export": { - "message": "Export" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, - "import": { - "message": "Import" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "கோப்பு வடிவம்" diff --git a/apps/browser/src/_locales/te/messages.json b/apps/browser/src/_locales/te/messages.json index 02945c6ff9b..04386b72930 100644 --- a/apps/browser/src/_locales/te/messages.json +++ b/apps/browser/src/_locales/te/messages.json @@ -1322,11 +1322,21 @@ "exportFrom": { "message": "Export from" }, - "export": { - "message": "Export" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, - "import": { - "message": "Import" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "File format" diff --git a/apps/browser/src/_locales/th/messages.json b/apps/browser/src/_locales/th/messages.json index a07b23793b7..50bac4d6a44 100644 --- a/apps/browser/src/_locales/th/messages.json +++ b/apps/browser/src/_locales/th/messages.json @@ -6,7 +6,7 @@ "message": "โลโก้ Bitwarden" }, "extName": { - "message": "Bitwarden - จัดการรหัสผ่าน", + "message": "Bitwarden Password Manager", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { @@ -14,7 +14,7 @@ "description": "Extension description, MUST be less than 112 characters (Safari restriction)" }, "loginOrCreateNewAccount": { - "message": "ล็อกอิน หรือ สร้างบัญชีใหม่ เพื่อใช้งานตู้นิรภัยของคุณ" + "message": "เข้าสู่ระบบหรือสร้างบัญชีใหม่เพื่อเข้าถึงตู้นิรภัยของคุณ" }, "inviteAccepted": { "message": "ตอบรับคำเชิญแล้ว" @@ -23,28 +23,28 @@ "message": "สร้างบัญชี" }, "newToBitwarden": { - "message": "เพิ่งเริ่มใช้ Bitwarden ใช่ไหม?" + "message": "เพิ่งเคยใช้งาน Bitwarden ใช่หรือไม่" }, "logInWithPasskey": { "message": "เข้าสู่ระบบด้วยพาสคีย์" }, "useSingleSignOn": { - "message": "ใช้การลงชื่อเพียงครั้งเดียว" + "message": "ใช้การลงชื่อเข้าใช้แบบ SSO" }, "yourOrganizationRequiresSingleSignOn": { - "message": "Your organization requires single sign-on." + "message": "องค์กรของคุณบังคับใช้ Single Sign-On" }, "welcomeBack": { - "message": "ยินดีต้อนรับกลับมา" + "message": "ยินดีต้อนรับกลับ" }, "setAStrongPassword": { "message": "ตั้งรหัสผ่านที่รัดกุม" }, "finishCreatingYourAccountBySettingAPassword": { - "message": "ดำเนินการสร้างบัญชีของคุณให้เสร็จสมบูรณ์โดยการตั้งรหัสผ่าน" + "message": "ตั้งรหัสผ่านเพื่อเสร็จสิ้นการสร้างบัญชี" }, "enterpriseSingleSignOn": { - "message": "Enterprise Single Sign-On" + "message": "SSO สำหรับองค์กร" }, "cancel": { "message": "ยกเลิก" @@ -53,22 +53,22 @@ "message": "ปิด" }, "submit": { - "message": "ส่งข้อมูล" + "message": "ส่ง" }, "emailAddress": { "message": "ที่อยู่อีเมล" }, "masterPass": { - "message": "Master Password" + "message": "รหัสผ่านหลัก" }, "masterPassDesc": { - "message": "รหัสผ่านหลัก คือ รหัสผ่านที่ใช้เข้าถึงตู้นิรภัยของคุณ สิ่งสำคัญมาก คือ คุณจะต้องไม่ลืมรหัสผ่านหลักโดยเด็ดขาด เพราะหากคุณลืมแล้วล่ะก็ จะไม่มีวิธีที่สามารถกู้รหัสผ่านของคุณได้เลย" + "message": "รหัสผ่านหลักคือรหัสที่คุณใช้เข้าถึงตู้นิรภัย สิ่งสำคัญคือห้ามลืมรหัสผ่านหลักเด็ดขาด เนื่องจากไม่มีวิธีกู้คืนรหัสผ่านหากคุณลืม" }, "masterPassHintDesc": { - "message": "คำใบ้เกี่ยวกับรหัสผ่านหลักสามารถช่วยให้คุณนึกรหัสผ่านหลักออกได้หากลืม" + "message": "คำใบ้รหัสผ่านหลักช่วยเตือนความจำหากคุณลืมรหัสผ่าน" }, "masterPassHintText": { - "message": "หากคุณลืมรหัสผ่าน ระบบสามารถส่งคำใบ้รหัสผ่านไปยังอีเมลของคุณได้ จำกัด $CURRENT$/$MAXIMUM$ ตัวอักษร", + "message": "หากลืมรหัสผ่าน ระบบจะส่งคำใบ้ไปที่อีเมลของคุณ สูงสุด $CURRENT$/$MAXIMUM$ ตัวอักษร", "placeholders": { "current": { "content": "$1", @@ -81,13 +81,13 @@ } }, "reTypeMasterPass": { - "message": "Re-type Master Password" + "message": "ป้อนรหัสผ่านหลักอีกครั้ง" }, "masterPassHint": { - "message": "Master Password Hint (optional)" + "message": "คำใบ้รหัสผ่านหลัก (ไม่บังคับ)" }, "passwordStrengthScore": { - "message": "คะแนนความรัดกุมของรหัสผ่าน $SCORE$", + "message": "ระดับความรัดกุมของรหัสผ่าน $SCORE$", "placeholders": { "score": { "content": "$1", @@ -96,10 +96,10 @@ } }, "joinOrganization": { - "message": "Join organization" + "message": "เข้าร่วมองค์กร" }, "joinOrganizationName": { - "message": "Join $ORGANIZATIONNAME$", + "message": "เข้าร่วม $ORGANIZATIONNAME$", "placeholders": { "organizationName": { "content": "$1", @@ -108,7 +108,7 @@ } }, "finishJoiningThisOrganizationBySettingAMasterPassword": { - "message": "Finish joining this organization by setting a master password." + "message": "ตั้งรหัสผ่านหลักเพื่อเสร็จสิ้นการเข้าร่วมองค์กรนี้" }, "tab": { "message": "แท็บ" @@ -117,7 +117,7 @@ "message": "ตู้นิรภัย" }, "myVault": { - "message": "My Vault" + "message": "ตู้นิรภัยของฉัน" }, "allVaults": { "message": "ตู้นิรภัยทั้งหมด" @@ -135,10 +135,10 @@ "message": "คัดลอกรหัสผ่าน" }, "copyPassphrase": { - "message": "Copy passphrase" + "message": "คัดลอกวลีรหัสผ่าน" }, "copyNote": { - "message": "Copy Note" + "message": "คัดลอกโน้ต" }, "copyUri": { "message": "คัดลอก URI" @@ -150,34 +150,34 @@ "message": "คัดลอกหมายเลข" }, "copySecurityCode": { - "message": "คัดลอกรหัสรักษาความปลอดภัย" + "message": "คัดลอกรหัสความปลอดภัย" }, "copyName": { - "message": "Copy name" + "message": "คัดลอกชื่อ" }, "copyCompany": { - "message": "Copy company" + "message": "คัดลอกบริษัท" }, "copySSN": { - "message": "Copy Social Security number" + "message": "คัดลอกหมายเลขประกันสังคม" }, "copyPassportNumber": { - "message": "Copy passport number" + "message": "คัดลอกเลขหนังสือเดินทาง" }, "copyLicenseNumber": { - "message": "Copy license number" + "message": "คัดลอกเลขใบขับขี่" }, "copyPrivateKey": { - "message": "Copy private key" + "message": "คัดลอกกุญแจส่วนตัว" }, "copyPublicKey": { - "message": "Copy public key" + "message": "คัดลอกกุญแจสาธารณะ" }, "copyFingerprint": { - "message": "Copy fingerprint" + "message": "คัดลอกลายนิ้วมือ" }, "copyCustomField": { - "message": "Copy $FIELD$", + "message": "คัดลอก $FIELD$", "placeholders": { "field": { "content": "$1", @@ -186,186 +186,186 @@ } }, "copyWebsite": { - "message": "Copy website" + "message": "คัดลอกเว็บไซต์" }, "copyNotes": { - "message": "Copy notes" + "message": "คัดลอกโน้ต" }, "copy": { - "message": "Copy", + "message": "คัดลอก", "description": "Copy to clipboard" }, "fill": { - "message": "Fill", + "message": "ป้อน", "description": "This string is used on the vault page to indicate autofilling. Horizontal space is limited in the interface here so try and keep translations as concise as possible." }, "autoFill": { - "message": "กรอกข้อมูลอัตโนมัติ" + "message": "ป้อนอัตโนมัติ" }, "autoFillLogin": { - "message": "Autofill login" + "message": "ป้อนข้อมูลเข้าสู่ระบบอัตโนมัติ" }, "autoFillCard": { - "message": "Autofill card" + "message": "ป้อนข้อมูลบัตรอัตโนมัติ" }, "autoFillIdentity": { - "message": "Autofill identity" + "message": "ป้อนข้อมูลระบุตัวตนอัตโนมัติ" }, "fillVerificationCode": { - "message": "Fill verification code" + "message": "ป้อนรหัสยืนยัน" }, "fillVerificationCodeAria": { - "message": "Fill Verification Code", + "message": "ป้อนรหัสยืนยัน", "description": "Aria label for the heading displayed the inline menu for totp code autofill" }, "generatePasswordCopied": { - "message": "Generate Password (copied)" + "message": "สร้างรหัสผ่าน (คัดลอกแล้ว)" }, "copyElementIdentifier": { - "message": "คัดลอกชื่อของช่องที่กำหนดเอง" + "message": "คัดลอกชื่อฟิลด์ที่กำหนดเอง" }, "noMatchingLogins": { - "message": "ไม่พบข้อมูลล็อกอินที่ตรงกัน" + "message": "ไม่พบข้อมูลเข้าสู่ระบบที่ตรงกัน" }, "noCards": { - "message": "No cards" + "message": "ไม่มีบัตร" }, "noIdentities": { - "message": "No identities" + "message": "ไม่มีข้อมูลระบุตัวตน" }, "addLoginMenu": { - "message": "Add login" + "message": "เพิ่มข้อมูลเข้าสู่ระบบ" }, "addCardMenu": { - "message": "Add card" + "message": "เพิ่มบัตร" }, "addIdentityMenu": { - "message": "Add identity" + "message": "เพิ่มข้อมูลระบุตัวตน" }, "unlockVaultMenu": { - "message": "ปลดล็อกกตู้นิรภัยของคุณ" + "message": "ปลดล็อกตู้นิรภัย" }, "loginToVaultMenu": { - "message": "ลงชื่อเข้าใช้ตู้นิรภัยของคุณ" + "message": "เข้าสู่ระบบตู้นิรภัย" }, "autoFillInfo": { - "message": "ไม่พบข้อมูลล็อกอินเพื่อใช้กรอกข้อมูลอัตโนมัติ สำหรับแท็บปัจจุบันของเบราว์เซอร์" + "message": "ไม่มีข้อมูลเข้าสู่ระบบสำหรับป้อนในแท็บเบราว์เซอร์ปัจจุบัน" }, "addLogin": { - "message": "Add a Login" + "message": "เพิ่มข้อมูลเข้าสู่ระบบ" }, "addItem": { "message": "เพิ่มรายการ" }, "accountEmail": { - "message": "Account email" + "message": "อีเมลบัญชี" }, "requestHint": { - "message": "Request hint" + "message": "ขอคำใบ้" }, "requestPasswordHint": { - "message": "Request password hint" + "message": "ขอคำใบ้รหัสผ่าน" }, "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { - "message": "กรอกที่อยู่อีเมลบัญชีของคุณ แล้วระบบจะส่งคำใบ้รหัสผ่านไปให้คุณ" + "message": "กรอกอีเมลบัญชี แล้วระบบจะส่งคำใบ้รหัสผ่านไปให้" }, "getMasterPasswordHint": { - "message": "รับคำใบ้เกี่ยวกับรหัสผ่านหลักของคุณ" + "message": "รับคำใบ้รหัสผ่านหลัก" }, "continue": { - "message": "ดำเนินการต่อไป" + "message": "ดำเนินการต่อ" }, "sendVerificationCode": { - "message": "ส่งโค้ดยืนยันไปยังอีเมลของคุณ" + "message": "ส่งรหัสยืนยันไปที่อีเมล" }, "sendCode": { - "message": "ส่งโค้ด" + "message": "ส่งรหัส" }, "codeSent": { - "message": "ส่งโค้ดแล้ว" + "message": "ส่งรหัสแล้ว" }, "verificationCode": { - "message": "Verification Code" + "message": "รหัสยืนยัน" }, "confirmIdentity": { - "message": "ยืนยันตัวตนของคุณเพื่อดำเนินการต่อ" + "message": "ยืนยันตัวตนเพื่อดำเนินการต่อ" }, "changeMasterPassword": { - "message": "Change Master Password" + "message": "เปลี่ยนรหัสผ่านหลัก" }, "continueToWebApp": { - "message": "Continue to web app?" + "message": "ไปที่เว็บแอปหรือไม่" }, "continueToWebAppDesc": { - "message": "Explore more features of your Bitwarden account on the web app." + "message": "สำรวจฟีเจอร์เพิ่มเติมของบัญชี Bitwarden บนเว็บแอป" }, "continueToHelpCenter": { - "message": "Continue to Help Center?" + "message": "ไปที่ศูนย์ช่วยเหลือหรือไม่" }, "continueToHelpCenterDesc": { - "message": "Learn more about how to use Bitwarden on the Help Center." + "message": "เรียนรู้วิธีการใช้งาน Bitwarden เพิ่มเติมได้ที่ศูนย์ช่วยเหลือ" }, "continueToBrowserExtensionStore": { - "message": "Continue to browser extension store?" + "message": "ไปที่ร้านค้าส่วนขยายหรือไม่" }, "continueToBrowserExtensionStoreDesc": { - "message": "Help others find out if Bitwarden is right for them. Visit your browser's extension store and leave a rating now." + "message": "ช่วยบอกต่อว่า Bitwarden ดีอย่างไร แวะไปที่ร้านค้าส่วนขยายของเบราว์เซอร์และให้คะแนนเลย" }, "changeMasterPasswordOnWebConfirmation": { - "message": "You can change your master password on the Bitwarden web app." + "message": "คุณสามารถเปลี่ยนรหัสผ่านหลักได้ที่เว็บแอป Bitwarden" }, "fingerprintPhrase": { - "message": "Fingerprint Phrase", + "message": "วลีลายนิ้วมือ", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, "yourAccountsFingerprint": { - "message": "ข้อความลายนิ้วมือของบัญชีของคุณ", + "message": "วลีลายนิ้วมือของบัญชี", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, "twoStepLogin": { - "message": "Two-step Login" + "message": "การยืนยันตัวตนสองขั้นตอน" }, "logOut": { "message": "ออกจากระบบ" }, "aboutBitwarden": { - "message": "About Bitwarden" + "message": "เกี่ยวกับ Bitwarden" }, "about": { "message": "เกี่ยวกับ" }, "moreFromBitwarden": { - "message": "More from Bitwarden" + "message": "เพิ่มเติมจาก Bitwarden" }, "continueToBitwardenDotCom": { - "message": "Continue to bitwarden.com?" + "message": "ไปที่ bitwarden.com หรือไม่" }, "bitwardenForBusiness": { - "message": "Bitwarden for Business" + "message": "Bitwarden สำหรับธุรกิจ" }, "bitwardenAuthenticator": { "message": "Bitwarden Authenticator" }, "continueToAuthenticatorPageDesc": { - "message": "Bitwarden Authenticator allows you to store authenticator keys and generate TOTP codes for 2-step verification flows. Learn more on the bitwarden.com website" + "message": "Bitwarden Authenticator ช่วยจัดเก็บคีย์และสร้างรหัส TOTP สำหรับการยืนยันตัวตนสองขั้นตอน เรียนรู้เพิ่มเติมที่เว็บไซต์ bitwarden.com" }, "bitwardenSecretsManager": { "message": "Bitwarden Secrets Manager" }, "continueToSecretsManagerPageDesc": { - "message": "Securely store, manage, and share developer secrets with Bitwarden Secrets Manager. Learn more on the bitwarden.com website." + "message": "จัดเก็บ จัดการ และแชร์ความลับสำหรับนักพัฒนาอย่างปลอดภัยด้วย Bitwarden Secrets Manager เรียนรู้เพิ่มเติมที่เว็บไซต์ bitwarden.com" }, "passwordlessDotDev": { "message": "Passwordless.dev" }, "continueToPasswordlessDotDevPageDesc": { - "message": "Create smooth and secure login experiences free from traditional passwords with Passwordless.dev. Learn more on the bitwarden.com website." + "message": "สร้างประสบการณ์การเข้าสู่ระบบที่ลื่นไหลและปลอดภัย ปราศจากรหัสผ่านแบบเดิม ๆ ด้วย Passwordless.dev เรียนรู้เพิ่มเติมที่เว็บไซต์ bitwarden.com" }, "freeBitwardenFamilies": { - "message": "Free Bitwarden Families" + "message": "Bitwarden Families ฟรี" }, "freeBitwardenFamiliesPageDesc": { - "message": "You are eligible for Free Bitwarden Families. Redeem this offer today in the web app." + "message": "คุณได้รับสิทธิ์ใช้งาน Bitwarden Families ฟรี รับสิทธิ์ข้อเสนอนี้ได้ทันทีในเว็บแอป" }, "version": { "message": "เวอร์ชัน" @@ -386,7 +386,7 @@ "message": "แก้ไขโฟลเดอร์" }, "editFolderWithName": { - "message": "Edit folder: $FOLDERNAME$", + "message": "แก้ไขโฟลเดอร์: $FOLDERNAME$", "placeholders": { "foldername": { "content": "$1", @@ -395,22 +395,22 @@ } }, "newFolder": { - "message": "New folder" + "message": "โฟลเดอร์ใหม่" }, "folderName": { - "message": "Folder name" + "message": "ชื่อโฟลเดอร์" }, "folderHintText": { - "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + "message": "ซ้อนโฟลเดอร์โดยการใส่ชื่อโฟลเดอร์หลักตามด้วยเครื่องหมาย “/” ตัวอย่าง: โซเชียล/ฟอรัม" }, "noFoldersAdded": { - "message": "No folders added" + "message": "ยังไม่ได้เพิ่มโฟลเดอร์" }, "createFoldersToOrganize": { - "message": "Create folders to organize your vault items" + "message": "สร้างโฟลเดอร์เพื่อจัดระเบียบรายการในตู้นิรภัย" }, "deleteFolderPermanently": { - "message": "Are you sure you want to permanently delete this folder?" + "message": "ยืนยันที่จะลบโฟลเดอร์นี้ถาวรหรือไม่" }, "deleteFolder": { "message": "ลบโฟลเดอร์" @@ -419,65 +419,65 @@ "message": "โฟลเดอร์" }, "noFolders": { - "message": "ไม่มีโฟลเดอร์" + "message": "ไม่มีโฟลเดอร์ที่จะแสดง" }, "helpFeedback": { - "message": "Help & Feedback" + "message": "ความช่วยเหลือและข้อเสนอแนะ" }, "helpCenter": { - "message": "Bitwarden Help center" + "message": "ศูนย์ช่วยเหลือ Bitwarden" }, "communityForums": { - "message": "Explore Bitwarden community forums" + "message": "สำรวจฟอรัมชุมชน Bitwarden" }, "contactSupport": { - "message": "Contact Bitwarden support" + "message": "ติดต่อฝ่ายสนับสนุน Bitwarden" }, "sync": { "message": "ซิงค์" }, "syncNow": { - "message": "Sync now" + "message": "ซิงค์ทันที" }, "lastSync": { - "message": "Last Sync:" + "message": "ซิงค์ล่าสุด:" }, "passGen": { - "message": "Password Generator" + "message": "ตัวสร้างรหัสผ่าน" }, "generator": { - "message": "สุ่มรหัส", + "message": "ตัวสร้าง", "description": "Short for 'credential generator'." }, "passGenInfo": { - "message": "สร้างรหัสผ่านที่รัดกุมและไม่ซ้ำใครโดยอัตโนมัติสำหรับการเข้าสู่ระบบของคุณ" + "message": "สร้างรหัสผ่านที่รัดกุมและไม่ซ้ำกันสำหรับข้อมูลเข้าสู่ระบบของคุณโดยอัตโนมัติ" }, "bitWebVaultApp": { - "message": "Bitwarden web app" + "message": "เว็บแอป Bitwarden" }, "select": { "message": "เลือก" }, "generatePassword": { - "message": "Generate Password" + "message": "สร้างรหัสผ่าน" }, "generatePassphrase": { - "message": "Generate passphrase" + "message": "สร้างวลีรหัสผ่าน" }, "passwordGenerated": { - "message": "Password generated" + "message": "สร้างรหัสผ่านแล้ว" }, "passphraseGenerated": { - "message": "Passphrase generated" + "message": "สร้างวลีรหัสผ่านแล้ว" }, "usernameGenerated": { - "message": "Username generated" + "message": "สร้างชื่อผู้ใช้แล้ว" }, "emailGenerated": { - "message": "Email generated" + "message": "สร้างอีเมลแล้ว" }, "regeneratePassword": { - "message": "Regenerate Password" + "message": "สร้างรหัสผ่านใหม่" }, "options": { "message": "ตัวเลือก" @@ -486,11 +486,11 @@ "message": "ความยาว" }, "include": { - "message": "Include", + "message": "รวม", "description": "Card header for password generator include block" }, "uppercaseDescription": { - "message": "Include uppercase characters", + "message": "รวมตัวอักษรพิมพ์ใหญ่", "description": "Tooltip for the password generator uppercase character checkbox" }, "uppercaseLabel": { @@ -498,7 +498,7 @@ "description": "Label for the password generator uppercase character checkbox" }, "lowercaseDescription": { - "message": "Include lowercase characters", + "message": "รวมตัวอักษรพิมพ์เล็ก", "description": "Full description for the password generator lowercase character checkbox" }, "lowercaseLabel": { @@ -506,7 +506,7 @@ "description": "Label for the password generator lowercase character checkbox" }, "numbersDescription": { - "message": "Include numbers", + "message": "รวมตัวเลข", "description": "Full description for the password generator numbers checkbox" }, "numbersLabel": { @@ -514,100 +514,100 @@ "description": "Label for the password generator numbers checkbox" }, "specialCharactersDescription": { - "message": "Include special characters", + "message": "รวมอักขระพิเศษ", "description": "Full description for the password generator special characters checkbox" }, "numWords": { - "message": "Number of Words" + "message": "จำนวนคำ" }, "wordSeparator": { - "message": "Word Separator" + "message": "ตัวคั่นคำ" }, "capitalize": { "message": "ขึ้นต้นด้วยตัวพิมพ์ใหญ่", "description": "Make the first letter of a work uppercase." }, "includeNumber": { - "message": "ต่อท้ายด้วยตัวเลข" + "message": "รวมตัวเลข" }, "minNumbers": { - "message": "Minimum Numbers" + "message": "จำนวนตัวเลขขั้นต่ำ" }, "minSpecial": { - "message": "Minimum Special" + "message": "จำนวนอักขระพิเศษขั้นต่ำ" }, "avoidAmbiguous": { - "message": "Avoid ambiguous characters", + "message": "หลีกเลี่ยงอักขระที่กำกวม", "description": "Label for the avoid ambiguous characters checkbox." }, "generatorPolicyInEffect": { - "message": "Enterprise policy requirements have been applied to your generator options.", + "message": "มีการใช้นโยบายองค์กรกับตัวเลือกของตัวสร้างรหัสผ่าน", "description": "Indicates that a policy limits the credential generator screen." }, "searchVault": { "message": "ค้นหาในตู้นิรภัย" }, "resetSearch": { - "message": "Reset search" + "message": "รีเซ็ตการค้นหา" }, "archiveNoun": { - "message": "Archive", + "message": "รายการจัดเก็บถาวร", "description": "Noun" }, "archiveVerb": { - "message": "Archive", + "message": "จัดเก็บถาวร", "description": "Verb" }, "unArchive": { - "message": "Unarchive" + "message": "เลิกจัดเก็บถาวร" }, "itemsInArchive": { - "message": "Items in archive" + "message": "รายการในที่จัดเก็บถาวร" }, "noItemsInArchive": { - "message": "No items in archive" + "message": "ไม่มีรายการจัดเก็บถาวร" }, "noItemsInArchiveDesc": { - "message": "Archived items will appear here and will be excluded from general search results and autofill suggestions." + "message": "รายการที่จัดเก็บถาวรจะปรากฏที่นี่ และจะไม่ถูกรวมในผลการค้นหาทั่วไปหรือคำแนะนำการป้อนอัตโนมัติ" }, "itemWasSentToArchive": { - "message": "Item was sent to archive" + "message": "ย้ายรายการไปที่จัดเก็บถาวรแล้ว" }, "itemUnarchived": { - "message": "Item was unarchived" + "message": "เลิกจัดเก็บถาวรรายการแล้ว" }, "archiveItem": { - "message": "Archive item" + "message": "จัดเก็บรายการถาวร" }, "archiveItemConfirmDesc": { - "message": "Archived items are excluded from general search results and autofill suggestions. Are you sure you want to archive this item?" + "message": "รายการที่จัดเก็บถาวรจะไม่ถูกรวมในผลการค้นหาทั่วไปและคำแนะนำการป้อนอัตโนมัติ ยืนยันที่จะจัดเก็บรายการนี้ถาวรหรือไม่" }, "upgradeToUseArchive": { - "message": "A premium membership is required to use Archive." + "message": "ต้องเป็นสมาชิกพรีเมียมจึงจะใช้งานฟีเจอร์จัดเก็บถาวรได้" }, "edit": { "message": "แก้ไข" }, "view": { - "message": "แสดง" + "message": "ดู" }, "viewAll": { - "message": "View all" + "message": "ดูทั้งหมด" }, "showAll": { - "message": "Show all" + "message": "แสดงทั้งหมด" }, "viewLess": { - "message": "View less" + "message": "ดูน้อยลง" }, "viewLogin": { - "message": "View login" + "message": "ดูข้อมูลเข้าสู่ระบบ" }, "noItemsInList": { - "message": "ไม่มีรายการ" + "message": "ไม่มีรายการที่จะแสดง" }, "itemInformation": { - "message": "Item Information" + "message": "ข้อมูลรายการ" }, "username": { "message": "ชื่อผู้ใช้" @@ -616,28 +616,28 @@ "message": "รหัสผ่าน" }, "totp": { - "message": "Authenticator secret" + "message": "รหัสลับยืนยันตัวตน" }, "passphrase": { - "message": "ข้อความรหัสผ่าน" + "message": "วลีรหัสผ่าน" }, "favorite": { "message": "รายการโปรด" }, "unfavorite": { - "message": "Unfavorite" + "message": "เลิกเป็นรายการโปรด" }, "itemAddedToFavorites": { - "message": "Item added to favorites" + "message": "เพิ่มรายการในรายการโปรดแล้ว" }, "itemRemovedFromFavorites": { - "message": "Item removed from favorites" + "message": "ลบรายการออกจากรายการโปรดแล้ว" }, "notes": { "message": "โน้ต" }, "privateNote": { - "message": "Private note" + "message": "โน้ตส่วนตัว" }, "note": { "message": "โน้ต" @@ -655,13 +655,13 @@ "message": "ดูรายการ" }, "launch": { - "message": "เริ่ม" + "message": "เปิด" }, "launchWebsite": { - "message": "Launch website" + "message": "เปิดเว็บไซต์" }, "launchWebsiteName": { - "message": "Launch website $ITEMNAME$", + "message": "เปิดเว็บไซต์ $ITEMNAME$", "placeholders": { "itemname": { "content": "$1", @@ -673,7 +673,7 @@ "message": "เว็บไซต์" }, "toggleVisibility": { - "message": "Toggle Visibility" + "message": "สลับการแสดงผล" }, "manage": { "message": "จัดการ" @@ -682,55 +682,55 @@ "message": "อื่น ๆ" }, "unlockMethods": { - "message": "Unlock options" + "message": "ตัวเลือกการปลดล็อก" }, "unlockMethodNeededToChangeTimeoutActionDesc": { - "message": "Set up an unlock method to change your vault timeout action." + "message": "ตั้งค่าวิธีการปลดล็อกเพื่อเปลี่ยนการดำเนินการเมื่อตู้นิรภัยหมดเวลา" }, "unlockMethodNeeded": { - "message": "Set up an unlock method in Settings" + "message": "ตั้งค่าวิธีการปลดล็อกในการตั้งค่า" }, "sessionTimeoutHeader": { - "message": "Session timeout" + "message": "เซสชันหมดเวลา" }, "vaultTimeoutHeader": { - "message": "Vault timeout" + "message": "ตู้นิรภัยหมดเวลา" }, "otherOptions": { - "message": "Other options" + "message": "ตัวเลือกอื่น ๆ" }, "rateExtension": { - "message": "Rate the Extension" + "message": "ให้คะแนนส่วนขยาย" }, "browserNotSupportClipboard": { - "message": "เว็บเบราว์เซอร์ของคุณไม่รองรับการคัดลอกคลิปบอร์ดอย่างง่าย คัดลอกด้วยตนเองแทน" + "message": "เว็บเบราว์เซอร์ของคุณไม่รองรับการคัดลอกไปยังคลิปบอร์ดแบบง่าย โปรดคัดลอกด้วยตนเองแทน" }, "verifyYourIdentity": { - "message": "Verify your identity" + "message": "ยืนยันตัวตนของคุณ" }, "weDontRecognizeThisDevice": { - "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + "message": "เราไม่รู้จักอุปกรณ์นี้ ป้อนรหัสที่ส่งไปยังอีเมลของคุณเพื่อยืนยันตัวตน" }, "continueLoggingIn": { - "message": "Continue logging in" + "message": "ดำเนินการเข้าสู่ระบบต่อ" }, "yourVaultIsLocked": { - "message": "ตู้เซฟของคุณถูกล็อก ยืนยันตัวตนของคุณเพื่อดำเนินการต่อ" + "message": "ตู้นิรภัยล็อกอยู่ ยืนยันตัวตนเพื่อดำเนินการต่อ" }, "yourVaultIsLockedV2": { - "message": "ห้องนิรภัยของคุณถูกล็อก" + "message": "ตู้นิรภัยล็อกอยู่" }, "yourAccountIsLocked": { - "message": "Your account is locked" + "message": "บัญชีของคุณถูกล็อก" }, "or": { - "message": "or" + "message": "หรือ" }, "unlock": { - "message": "ปลดล็อค" + "message": "ปลดล็อก" }, "loggedInAsOn": { - "message": "ล็อกอินด้วย $EMAIL$ บน $HOSTNAME$", + "message": "เข้าสู่ระบบในชื่อ $EMAIL$ บน $HOSTNAME$", "placeholders": { "email": { "content": "$1", @@ -746,7 +746,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", @@ -755,16 +755,16 @@ } }, "vaultTimeout": { - "message": "ระยะเวลาล็อกตู้เซฟ" + "message": "ระยะเวลาหมดเวลาตู้นิรภัย" }, "vaultTimeout1": { - "message": "Timeout" + "message": "หมดเวลา" }, "lockNow": { - "message": "Lock Now" + "message": "ล็อกทันที" }, "lockAll": { - "message": "Lock all" + "message": "ล็อกทั้งหมด" }, "immediately": { "message": "ทันที" @@ -800,52 +800,52 @@ "message": "4 ชั่วโมง" }, "onLocked": { - "message": "On Locked" + "message": "เมื่อล็อกระบบ" }, "onIdle": { - "message": "On system idle" + "message": "เมื่อระบบไม่ได้ใช้งาน" }, "onSleep": { - "message": "On system sleep" + "message": "เมื่อระบบสลีป" }, "onRestart": { - "message": "On Restart" + "message": "เมื่อรีสตาร์ตเบราว์เซอร์" }, "never": { - "message": "ไม่อีกเลย" + "message": "ไม่เลย" }, "security": { "message": "ความปลอดภัย" }, "confirmMasterPassword": { - "message": "Confirm master password" + "message": "ยืนยันรหัสผ่านหลัก" }, "masterPassword": { - "message": "Master password" + "message": "รหัสผ่านหลัก" }, "masterPassImportant": { - "message": "Your master password cannot be recovered if you forget it!" + "message": "รหัสผ่านหลักไม่สามารถกู้คืนได้หากคุณลืม!" }, "masterPassHintLabel": { - "message": "Master password hint" + "message": "คำใบ้รหัสผ่านหลัก" }, "errorOccurred": { - "message": "พบข้อผิดพลาด" + "message": "เกิดข้อผิดพลาด" }, "emailRequired": { - "message": "ต้องระบุอีเมล" + "message": "จำเป็นต้องระบุที่อยู่อีเมล" }, "invalidEmail": { "message": "ที่อยู่อีเมลไม่ถูกต้อง" }, "masterPasswordRequired": { - "message": "ต้องใช้รหัสผ่านหลัก" + "message": "จำเป็นต้องระบุรหัสผ่านหลัก" }, "confirmMasterPasswordRequired": { - "message": "ต้องพิมพ์รหัสผ่านหลักอีกครั้ง" + "message": "จำเป็นต้องป้อนรหัสผ่านหลักซ้ำ" }, "masterPasswordMinlength": { - "message": "Master password must be at least $VALUE$ characters long.", + "message": "รหัสผ่านหลักต้องมีความยาวอย่างน้อย $VALUE$ ตัวอักษร", "description": "The Master Password must be at least a specific number of characters long.", "placeholders": { "value": { @@ -855,37 +855,37 @@ } }, "masterPassDoesntMatch": { - "message": "การยืนยันรหัสผ่านหลักไม่ตรงกัน" + "message": "ยืนยันรหัสผ่านหลักไม่ตรงกัน" }, "newAccountCreated": { - "message": "บัญชีใหม่ของคุณถูกสร้างขึ้นแล้ว! ตอนนี้คุณสามารถเข้าสู่ระบบ" + "message": "สร้างบัญชีใหม่แล้ว! คุณสามารถเข้าสู่ระบบได้ทันที" }, "newAccountCreated2": { - "message": "Your new account has been created!" + "message": "สร้างบัญชีใหม่แล้ว!" }, "youHaveBeenLoggedIn": { - "message": "You have been logged in!" + "message": "เข้าสู่ระบบแล้ว!" }, "youSuccessfullyLoggedIn": { - "message": "You successfully logged in" + "message": "คุณเข้าสู่ระบบสำเร็จ" }, "youMayCloseThisWindow": { - "message": "You may close this window" + "message": "คุณสามารถปิดหน้าต่างนี้ได้" }, "masterPassSent": { - "message": "เราได้ส่งอีเมลพร้อมคำใบ้รหัสผ่านหลักของคุณออกไปแล้ว" + "message": "ส่งอีเมลแจ้งคำใบ้รหัสผ่านหลักให้คุณแล้ว" }, "verificationCodeRequired": { - "message": "ต้องระบุโค้ดยืนยัน" + "message": "จำเป็นต้องระบุรหัสยืนยัน" }, "webauthnCancelOrTimeout": { - "message": "The authentication was cancelled or took too long. Please try again." + "message": "การยืนยันตัวตนถูกยกเลิกหรือใช้เวลานานเกินไป โปรดลองอีกครั้ง" }, "invalidVerificationCode": { - "message": "โค้ดยืนยันไม่ถูกต้อง" + "message": "รหัสยืนยันไม่ถูกต้อง" }, "valueCopied": { - "message": "$VALUE$ copied", + "message": "คัดลอก $VALUE$ แล้ว", "description": "Value has been copied to the clipboard.", "placeholders": { "value": { @@ -895,127 +895,127 @@ } }, "autofillError": { - "message": "Unable to auto-fill the selected login on this page. Copy/paste your username and/or password instead." + "message": "ไม่สามารถป้อนข้อมูลรายการที่เลือกบนหน้านี้ได้อัตโนมัติ โปรดคัดลอกและวางข้อมูลแทน" }, "totpCaptureError": { - "message": "Unable to scan QR code from the current webpage" + "message": "ไม่สามารถสแกน QR Code จากหน้าเว็บปัจจุบัน" }, "totpCaptureSuccess": { - "message": "Authenticator key added" + "message": "เพิ่มคีย์ยืนยันตัวตนแล้ว" }, "totpCapture": { - "message": "Scan authenticator QR code from current webpage" + "message": "สแกน QR Code สำหรับแอปยืนยันตัวตนจากหน้าเว็บปัจจุบัน" }, "totpHelperTitle": { - "message": "Make 2-step verification seamless" + "message": "ทำให้การยืนยันตัวตน 2 ขั้นตอนราบรื่นยิ่งขึ้น" }, "totpHelper": { - "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + "message": "Bitwarden สามารถจัดเก็บและป้อนรหัสยืนยัน 2 ขั้นตอนได้ คัดลอกและวางคีย์ลงในช่องนี้" }, "totpHelperWithCapture": { - "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + "message": "Bitwarden สามารถจัดเก็บและป้อนรหัสยืนยัน 2 ขั้นตอนได้ เลือกไอคอนกล้องเพื่อถ่ายภาพหน้าจอ QR Code ของแอปยืนยันตัวตนจากเว็บไซต์นี้ หรือคัดลอกและวางคีย์ลงในช่องนี้" }, "learnMoreAboutAuthenticators": { - "message": "Learn more about authenticators" + "message": "เรียนรู้เพิ่มเติมเกี่ยวกับแอปยืนยันตัวตน" }, "copyTOTP": { - "message": "Copy Authenticator key (TOTP)" + "message": "คัดลอกคีย์ยืนยันตัวตน (TOTP)" }, "loggedOut": { - "message": "ออกจากระบบ" + "message": "ออกจากระบบแล้ว" }, "loggedOutDesc": { - "message": "You have been logged out of your account." + "message": "คุณออกจากระบบบัญชีแล้ว" }, "loginExpired": { - "message": "เซสชันของคุณหมดอายุแล้ว" + "message": "เซสชันการเข้าสู่ระบบหมดอายุ" }, "logIn": { - "message": "Log in" + "message": "เข้าสู่ระบบ" }, "logInToBitwarden": { - "message": "Log in to Bitwarden" + "message": "เข้าสู่ระบบ Bitwarden" }, "enterTheCodeSentToYourEmail": { - "message": "Enter the code sent to your email" + "message": "ป้อนรหัสที่ส่งไปยังอีเมลของคุณ" }, "enterTheCodeFromYourAuthenticatorApp": { - "message": "Enter the code from your authenticator app" + "message": "ป้อนรหัสจากแอปยืนยันตัวตน" }, "pressYourYubiKeyToAuthenticate": { - "message": "Press your YubiKey to authenticate" + "message": "กด YubiKey ของคุณเพื่อยืนยันตัวตน" }, "duoTwoFactorRequiredPageSubtitle": { - "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + "message": "บัญชีของคุณจำเป็นต้องเข้าสู่ระบบ 2 ขั้นตอนผ่าน Duo ทำตามขั้นตอนด้านล่างเพื่อเสร็จสิ้นการเข้าสู่ระบบ" }, "followTheStepsBelowToFinishLoggingIn": { - "message": "Follow the steps below to finish logging in." + "message": "ทำตามขั้นตอนด้านล่างเพื่อเสร็จสิ้นการเข้าสู่ระบบ" }, "followTheStepsBelowToFinishLoggingInWithSecurityKey": { - "message": "Follow the steps below to finish logging in with your security key." + "message": "ทำตามขั้นตอนด้านล่างเพื่อเสร็จสิ้นการเข้าสู่ระบบด้วยคีย์ความปลอดภัย" }, "restartRegistration": { - "message": "Restart registration" + "message": "เริ่มการลงทะเบียนใหม่" }, "expiredLink": { - "message": "Expired link" + "message": "ลิงก์หมดอายุ" }, "pleaseRestartRegistrationOrTryLoggingIn": { - "message": "Please restart registration or try logging in." + "message": "โปรดเริ่มการลงทะเบียนใหม่หรือลองเข้าสู่ระบบ" }, "youMayAlreadyHaveAnAccount": { - "message": "You may already have an account" + "message": "คุณอาจมีบัญชีอยู่แล้ว" }, "logOutConfirmation": { - "message": "คุณต้องการล็อกเอาต์ใช่หรือไม่?" + "message": "ยืนยันที่จะออกจากระบบหรือไม่" }, "yes": { "message": "ใช่" }, "no": { - "message": "ไม่ใช่" + "message": "ไม่" }, "location": { - "message": "Location" + "message": "ตำแหน่งที่ตั้ง" }, "unexpectedError": { - "message": "An unexpected error has occured." + "message": "เกิดข้อผิดพลาดที่ไม่คาดคิด" }, "nameRequired": { - "message": "ต้องระบุชื่อ" + "message": "จำเป็นต้องระบุชื่อ" }, "addedFolder": { "message": "เพิ่มโฟลเดอร์แล้ว" }, "twoStepLoginConfirmation": { - "message": "Two-step login makes your account more secure by requiring you to enter a security code from an authenticator app whenever you log in. Two-step login can be enabled on the bitwarden.com web vault. Do you want to visit the website now?" + "message": "การเข้าสู่ระบบ 2 ขั้นตอนช่วยให้บัญชีปลอดภัยยิ่งขึ้น โดยกำหนดให้คุณยืนยันการเข้าสู่ระบบด้วยอุปกรณ์อื่น เช่น คีย์ความปลอดภัย แอปยืนยันตัวตน SMS โทรศัพท์ หรืออีเมล สามารถตั้งค่าได้ที่เว็บตู้นิรภัย bitwarden.com ต้องการไปที่เว็บไซต์ตอนนี้หรือไม่" }, "twoStepLoginConfirmationContent": { - "message": "Make your account more secure by setting up two-step login in the Bitwarden web app." + "message": "ทำให้บัญชีปลอดภัยยิ่งขึ้นด้วยการตั้งค่าการเข้าสู่ระบบ 2 ขั้นตอนในเว็บแอป Bitwarden" }, "twoStepLoginConfirmationTitle": { - "message": "Continue to web app?" + "message": "ไปที่เว็บแอปหรือไม่" }, "editedFolder": { - "message": "Edited Folder" + "message": "บันทึกโฟลเดอร์แล้ว" }, "deleteFolderConfirmation": { - "message": "คุณแน่ใจหรือไม่ว่าต้องการลบโฟลเดอร์นี้" + "message": "ยืนยันที่จะลบโฟลเดอร์นี้หรือไม่" }, "deletedFolder": { "message": "ลบโฟลเดอร์แล้ว" }, "gettingStartedTutorial": { - "message": "Getting Started Tutorial" + "message": "บทแนะนำการเริ่มต้นใช้งาน" }, "gettingStartedTutorialVideo": { - "message": "ดูบทช่วยสอนการเริ่มต้นของเราเพื่อเรียนรู้วิธีใช้ประโยชน์สูงสุดจากส่วนขยายเบราว์เซอร์" + "message": "ดูวิดีโอแนะนำการเริ่มต้นใช้งานเพื่อเรียนรู้วิธีใช้งานส่วนขยายเบราว์เซอร์ให้คุ้มค่าที่สุด" }, "syncingComplete": { - "message": "การซิงก์เสร็จสมบูรณ์" + "message": "ซิงค์เสร็จสมบูรณ์" }, "syncingFailed": { - "message": "การซิงก์ล้มเหลว" + "message": "การซิงค์ล้มเหลว" }, "passwordCopied": { "message": "คัดลอกรหัสผ่านแล้ว" @@ -1034,23 +1034,23 @@ } }, "newUri": { - "message": "เพิ่ม URI ใหม่" + "message": "URI ใหม่" }, "addDomain": { - "message": "Add domain", + "message": "เพิ่มโดเมน", "description": "'Domain' here refers to an internet domain name (e.g. 'bitwarden.com') and the message in whole described the act of putting a domain value into the context." }, "addedItem": { "message": "เพิ่มรายการแล้ว" }, "editedItem": { - "message": "แก้ไขรายการแล้ว" + "message": "บันทึกรายการแล้ว" }, "savedWebsite": { - "message": "Saved website" + "message": "เว็บไซต์ที่บันทึก" }, "savedWebsites": { - "message": "Saved websites ( $COUNT$ )", + "message": "เว็บไซต์ที่บันทึก ( $COUNT$ )", "placeholders": { "count": { "content": "$1", @@ -1059,88 +1059,88 @@ } }, "deleteItemConfirmation": { - "message": "คุณต้องการส่งไปยังถังขยะใช่หรือไม่?" + "message": "ยืนยันที่จะย้ายไปถังขยะหรือไม่" }, "deletedItem": { - "message": "ส่งรายการไปยังถังขยะแล้ว" + "message": "ย้ายรายการไปถังขยะแล้ว" }, "overwritePassword": { - "message": "Overwrite Password" + "message": "เขียนทับรหัสผ่าน" }, "overwritePasswordConfirmation": { - "message": "คุณต้องการเขียนทับรหัสผ่านปัจจุบันใช่หรือไม่?" + "message": "ยืนยันที่จะเขียนทับรหัสผ่านปัจจุบันหรือไม่" }, "overwriteUsername": { "message": "เขียนทับชื่อผู้ใช้" }, "overwriteUsernameConfirmation": { - "message": "คุณแน่ใจหรือไม่ว่าต้องการเขียนทับชื่อผู้ใช้ปัจจุบัน" + "message": "ยืนยันที่จะเขียนทับชื่อผู้ใช้ปัจจุบันหรือไม่" }, "searchFolder": { - "message": "ค้นหาในโพลเดอร์" + "message": "ค้นหาโฟลเดอร์" }, "searchCollection": { - "message": "คอลเลกชันการค้นหา" + "message": "ค้นหาคอลเลกชัน" }, "searchType": { "message": "ประเภทการค้นหา" }, "noneFolder": { - "message": "No Folder", + "message": "ไม่มีโฟลเดอร์", "description": "This is the folder for uncategorized items" }, "enableAddLoginNotification": { - "message": "ถามเพื่อให้เพิ่มการเข้าสู่ระบบ" + "message": "ถามเพื่อเพิ่มข้อมูลเข้าสู่ระบบ" }, "vaultSaveOptionsTitle": { - "message": "Save to vault options" + "message": "ตัวเลือกการบันทึกลงตู้นิรภัย" }, "addLoginNotificationDesc": { - "message": "The \"Add Login Notification\" automatically prompts you to save new logins to your vault whenever you log into them for the first time." + "message": "ถามเพื่อเพิ่มรายการหากไม่พบข้อมูลในตู้นิรภัย" }, "addLoginNotificationDescAlt": { - "message": "หากไม่พบรายการในห้องนิรภัยของคุณ ระบบจะถามเพื่อเพิ่มรายการ มีผลกับทุกบัญชีที่ลงชื่อเข้าใช้" + "message": "ถามเพื่อเพิ่มรายการหากไม่พบข้อมูลในตู้นิรภัย (สำหรับทุกบัญชีที่เข้าสู่ระบบ)" }, "showCardsInVaultViewV2": { - "message": "Always show cards as Autofill suggestions on Vault view" + "message": "แสดงบัตรเป็นคำแนะนำการป้อนอัตโนมัติในมุมมองตู้นิรภัยเสมอ" }, "showCardsCurrentTab": { - "message": "แสดงการ์ดบนหน้าแท็บ" + "message": "แสดงบัตรในหน้าแท็บ" }, "showCardsCurrentTabDesc": { - "message": "บัตรรายการในหน้าแท็บเพื่อให้ป้อนอัตโนมัติได้ง่าย" + "message": "แสดงรายการบัตรในหน้าแท็บเพื่อให้ป้อนอัตโนมัติได้ง่าย" }, "showIdentitiesInVaultViewV2": { - "message": "Always show identities as Autofill suggestions on Vault view" + "message": "แสดงข้อมูลระบุตัวตนเป็นคำแนะนำการป้อนอัตโนมัติในมุมมองตู้นิรภัยเสมอ" }, "showIdentitiesCurrentTab": { - "message": "แสดงตัวตนบนหน้าแท็บ" + "message": "แสดงข้อมูลระบุตัวตนในหน้าแท็บ" }, "showIdentitiesCurrentTabDesc": { - "message": "แสดงรายการข้อมูลประจำตัวในหน้าแท็บเพื่อให้ป้อนอัตโนมัติได้ง่าย" + "message": "แสดงรายการข้อมูลระบุตัวตนในหน้าแท็บเพื่อให้ป้อนอัตโนมัติได้ง่าย" }, "clickToAutofillOnVault": { - "message": "Click items to autofill on Vault view" + "message": "คลิกรายการเพื่อป้อนข้อมูลอัตโนมัติในมุมมองตู้นิรภัย" }, "clickToAutofill": { - "message": "Click items in autofill suggestion to fill" + "message": "คลิกรายการในคำแนะนำเพื่อป้อนข้อมูล" }, "clearClipboard": { "message": "ล้างคลิปบอร์ด", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." }, "clearClipboardDesc": { - "message": "ล้างค่าที่คัดลอกโดยอัตโนมัติจากคลิปบอร์ดของคุณ", + "message": "ล้างค่าที่คัดลอกออกจากคลิปบอร์ดโดยอัตโนมัติ", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." }, "notificationAddDesc": { - "message": "Should bitwarden remember this password for you?" + "message": "ต้องการให้ Bitwarden จำรหัสผ่านนี้หรือไม่" }, "notificationAddSave": { - "message": "Yes, Save Now" + "message": "บันทึก" }, "notificationViewAria": { - "message": "View $ITEMNAME$, opens in new window", + "message": "ดู $ITEMNAME$ เปิดในหน้าต่างใหม่", "placeholders": { "itemName": { "content": "$1" @@ -1149,18 +1149,18 @@ "description": "Aria label for the view button in notification bar confirmation message" }, "notificationNewItemAria": { - "message": "New Item, opens in new window", + "message": "รายการใหม่ เปิดในหน้าต่างใหม่", "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" }, "notificationEditTooltip": { - "message": "Edit before saving", + "message": "แก้ไขก่อนบันทึก", "description": "Tooltip and Aria label for edit button on cipher item" }, "newNotification": { - "message": "New notification" + "message": "การแจ้งเตือนใหม่" }, "labelWithNotification": { - "message": "$LABEL$: New notification", + "message": "$LABEL$: การแจ้งเตือนใหม่", "description": "Label for the notification with a new login suggestion.", "placeholders": { "label": { @@ -1170,15 +1170,15 @@ } }, "notificationLoginSaveConfirmation": { - "message": "saved to Bitwarden.", + "message": "บันทึกลง Bitwarden แล้ว", "description": "Shown to user after item is saved." }, "notificationLoginUpdatedConfirmation": { - "message": "updated in Bitwarden.", + "message": "อัปเดตใน Bitwarden แล้ว", "description": "Shown to user after item is updated." }, "selectItemAriaLabel": { - "message": "Select $ITEMTYPE$, $ITEMNAME$", + "message": "เลือก $ITEMTYPE$, $ITEMNAME$", "description": "Used by screen readers. $1 is the item type (like vault or folder), $2 is the selected item name.", "placeholders": { "itemType": { @@ -1190,35 +1190,35 @@ } }, "saveAsNewLoginAction": { - "message": "Save as new login", + "message": "บันทึกเป็นข้อมูลเข้าสู่ระบบใหม่", "description": "Button text for saving login details as a new entry." }, "updateLoginAction": { - "message": "Update login", + "message": "อัปเดตข้อมูลเข้าสู่ระบบ", "description": "Button text for updating an existing login entry." }, "unlockToSave": { - "message": "Unlock to save this login", + "message": "ปลดล็อกเพื่อบันทึกข้อมูลเข้าสู่ระบบนี้", "description": "User prompt to take action in order to save the login they just entered." }, "saveLogin": { - "message": "Save login", + "message": "บันทึกข้อมูลเข้าสู่ระบบ", "description": "Prompt asking the user if they want to save their login details." }, "updateLogin": { - "message": "Update existing login", + "message": "อัปเดตข้อมูลเข้าสู่ระบบเดิม", "description": "Prompt asking the user if they want to update an existing login entry." }, "loginSaveSuccess": { - "message": "Login saved", + "message": "บันทึกข้อมูลเข้าสู่ระบบแล้ว", "description": "Message displayed when login details are successfully saved." }, "loginUpdateSuccess": { - "message": "Login updated", + "message": "อัปเดตข้อมูลเข้าสู่ระบบแล้ว", "description": "Message displayed when login details are successfully updated." }, "loginUpdateTaskSuccess": { - "message": "Great job! You took the steps to make you and $ORGANIZATION$ more secure.", + "message": "เยี่ยมมาก! คุณได้ดำเนินการเพื่อทำให้คุณและ $ORGANIZATION$ ปลอดภัยยิ่งขึ้น", "placeholders": { "organization": { "content": "$1" @@ -1227,7 +1227,7 @@ "description": "Shown to user after login is updated." }, "loginUpdateTaskSuccessAdditional": { - "message": "Thank you for making $ORGANIZATION$ more secure. You have $TASK_COUNT$ more passwords to update.", + "message": "ขอบคุณที่ช่วยให้ $ORGANIZATION$ ปลอดภัยยิ่งขึ้น คุณยังมีรหัสผ่านที่ต้องอัปเดตอีก $TASK_COUNT$ รายการ", "placeholders": { "organization": { "content": "$1" @@ -1239,77 +1239,77 @@ "description": "Shown to user after login is updated." }, "nextSecurityTaskAction": { - "message": "Change next password", + "message": "เปลี่ยนรหัสผ่านถัดไป", "description": "Message prompting user to undertake completion of another security task." }, "saveFailure": { - "message": "Error saving", + "message": "เกิดข้อผิดพลาดในการบันทึก", "description": "Error message shown when the system fails to save login details." }, "saveFailureDetails": { - "message": "Oh no! We couldn't save this. Try entering the details manually.", + "message": "แย่แล้ว! เราไม่สามารถบันทึกรายการนี้ได้ ลองป้อนรายละเอียดด้วยตนเอง", "description": "Detailed error message shown when saving login details fails." }, "changePasswordWarning": { - "message": "After changing your password, you will need to log in with your new password. Active sessions on other devices will be logged out within one hour." + "message": "หลังจากเปลี่ยนรหัสผ่าน คุณจะต้องเข้าสู่ระบบด้วยรหัสผ่านใหม่ เซสชันที่ใช้งานอยู่บนอุปกรณ์อื่นจะถูกออกจากระบบภายใน 1 ชั่วโมง" }, "accountRecoveryUpdateMasterPasswordSubtitle": { - "message": "Change your master password to complete account recovery." + "message": "เปลี่ยนรหัสผ่านหลักเพื่อเสร็จสิ้นการกู้คืนบัญชี" }, "enableChangedPasswordNotification": { - "message": "ขอให้ปรับปรุงการเข้าสู่ระบบที่มีอยู่" + "message": "ถามเพื่ออัปเดตข้อมูลเข้าสู่ระบบที่มีอยู่" }, "changedPasswordNotificationDesc": { - "message": "Ask to update a login's password when a change is detected on a website." + "message": "ถามเพื่ออัปเดตรหัสผ่านของข้อมูลเข้าสู่ระบบเมื่อตรวจพบการเปลี่ยนแปลงบนเว็บไซต์" }, "changedPasswordNotificationDescAlt": { - "message": "Ask to update a login's password when a change is detected on a website. Applies to all logged in accounts." + "message": "ถามเพื่ออัปเดตรหัสผ่านของข้อมูลเข้าสู่ระบบเมื่อตรวจพบการเปลี่ยนแปลงบนเว็บไซต์ (สำหรับทุกบัญชีที่เข้าสู่ระบบ)" }, "enableUsePasskeys": { - "message": "Ask to save and use passkeys" + "message": "ถามเพื่อบันทึกและใช้พาสคีย์" }, "usePasskeysDesc": { - "message": "Ask to save new passkeys or log in with passkeys stored in your vault. Applies to all logged in accounts." + "message": "ถามเพื่อบันทึกพาสคีย์ใหม่หรือเข้าสู่ระบบด้วยพาสคีย์ที่เก็บในตู้นิรภัย (สำหรับทุกบัญชีที่เข้าสู่ระบบ)" }, "notificationChangeDesc": { - "message": "คุณต้องการอัปเดตรหัสผ่านนี้ใน Bitwarden หรือไม่?" + "message": "คุณต้องการอัปเดตรหัสผ่านนี้ใน Bitwarden หรือไม่" }, "notificationChangeSave": { - "message": "Yes, Update Now" + "message": "อัปเดต" }, "notificationUnlockDesc": { - "message": "Unlock your Bitwarden vault to complete the autofill request." + "message": "ปลดล็อกตู้นิรภัย Bitwarden เพื่อดำเนินการตามคำขอกรอกข้อมูลอัตโนมัติ" }, "notificationUnlock": { - "message": "Unlock" + "message": "ปลดล็อก" }, "additionalOptions": { - "message": "Additional options" + "message": "ตัวเลือกเพิ่มเติม" }, "enableContextMenuItem": { "message": "แสดงตัวเลือกเมนูบริบท" }, "contextMenuItemDesc": { - "message": "ใช้การคลิกสำรองเพื่อเข้าถึงการสร้างรหัสผ่านและการเข้าสู่ระบบที่ตรงกันสำหรับเว็บไซต์ " + "message": "ใช้การคลิกขวาเพื่อเข้าถึงการสร้างรหัสผ่านและข้อมูลเข้าสู่ระบบที่ตรงกันสำหรับเว็บไซต์" }, "contextMenuItemDescAlt": { - "message": "Use a secondary click to access password generation and matching logins for the website. Applies to all logged in accounts." + "message": "ใช้การคลิกขวาเพื่อเข้าถึงการสร้างรหัสผ่านและข้อมูลเข้าสู่ระบบที่ตรงกันสำหรับเว็บไซต์ (สำหรับทุกบัญชีที่เข้าสู่ระบบ)" }, "defaultUriMatchDetection": { - "message": "การตรวจจับการจับคู่ URI เริ่มต้น", + "message": "การตรวจสอบการจับคู่ URI เริ่มต้น", "description": "Default URI match detection for autofill." }, "defaultUriMatchDetectionDesc": { - "message": "เลือกวิธีเริ่มต้นในการจัดการการตรวจหาการจับคู่ URI สำหรับการเข้าสู่ระบบเมื่อดำเนินการต่างๆ เช่น การป้อนอัตโนมัติ" + "message": "เลือกวิธีเริ่มต้นในการจัดการการจับคู่ URI สำหรับข้อมูลเข้าสู่ระบบเมื่อดำเนินการ เช่น การป้อนอัตโนมัติ" }, "theme": { "message": "ธีม" }, "themeDesc": { - "message": "Change the application's color theme." + "message": "เปลี่ยนธีมสีของแอปพลิเคชัน" }, "themeDescAlt": { - "message": "Change the application's color theme. Applies to all logged in accounts." + "message": "เปลี่ยนธีมสีของแอปพลิเคชัน (สำหรับทุกบัญชีที่เข้าสู่ระบบ)" }, "dark": { "message": "มืด", @@ -1320,75 +1320,85 @@ "description": "Light color" }, "exportFrom": { - "message": "Export from" + "message": "ส่งออกจาก" }, - "export": { - "message": "Export" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, - "import": { - "message": "Import" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { - "message": "File Format" + "message": "รูปแบบไฟล์" }, "fileEncryptedExportWarningDesc": { - "message": "This file export will be password protected and require the file password to decrypt." + "message": "ไฟล์ส่งออกนี้จะได้รับการป้องกันด้วยรหัสผ่าน และต้องใช้รหัสผ่านไฟล์เพื่อถอดรหัส" }, "filePassword": { - "message": "File password" + "message": "รหัสผ่านไฟล์" }, "exportPasswordDescription": { - "message": "This password will be used to export and import this file" + "message": "รหัสผ่านนี้จะถูกใช้เพื่อส่งออกและนำเข้าไฟล์นี้" }, "accountRestrictedOptionDescription": { - "message": "Use your account encryption key, derived from your account's username and Master Password, to encrypt the export and restrict import to only the current Bitwarden account." + "message": "ใช้กุญแจเข้ารหัสบัญชีของคุณ ซึ่งได้มาจากชื่อผู้ใช้และรหัสผ่านหลัก เพื่อเข้ารหัสไฟล์ส่งออก และจำกัดการนำเข้าเฉพาะบัญชี Bitwarden ปัจจุบันเท่านั้น" }, "passwordProtectedOptionDescription": { - "message": "Set a file password to encrypt the export and import it to any Bitwarden account using the password for decryption." + "message": "ตั้งรหัสผ่านไฟล์เพื่อเข้ารหัสไฟล์ส่งออก และสามารถนำเข้าสู่บัญชี Bitwarden ใดก็ได้โดยใช้รหัสผ่านเพื่อถอดรหัส" }, "exportTypeHeading": { - "message": "Export type" + "message": "ประเภทการส่งออก" }, "accountRestricted": { - "message": "Account restricted" + "message": "จำกัดเฉพาะบัญชี" }, "filePasswordAndConfirmFilePasswordDoNotMatch": { - "message": "“File password” and “Confirm file password“ do not match." + "message": "“รหัสผ่านไฟล์” และ “ยืนยันรหัสผ่านไฟล์” ไม่ตรงกัน" }, "warning": { "message": "คำเตือน", "description": "WARNING (should stay in capitalized letters if the language permits)" }, "warningCapitalized": { - "message": "Warning", + "message": "คำเตือน", "description": "Warning (should maintain locale-relevant capitalization)" }, "confirmVaultExport": { "message": "ยืนยันการส่งออกตู้นิรภัย" }, "exportWarningDesc": { - "message": "This export contains your vault data in an unencrypted format. You should not store or send the exported file over unsecure channels (such as email). Delete it immediately after you are done using it." + "message": "การส่งออกนี้มีข้อมูลตู้นิรภัยในรูปแบบที่ไม่ได้เข้ารหัส คุณไม่ควรจัดเก็บหรือส่งไฟล์ที่ส่งออกผ่านช่องทางที่ไม่ปลอดภัย (เช่น อีเมล) ลบไฟล์ทันทีหลังจากใช้งานเสร็จ" }, "encExportKeyWarningDesc": { - "message": "การส่งออกนี้เข้ารหัสข้อมูลของคุณโดยใช้คีย์เข้ารหัสของบัญชีของคุณ หากคุณเคยหมุนเวียนคีย์เข้ารหัสของบัญชี คุณควรส่งออกอีกครั้ง เนื่องจากคุณจะไม่สามารถถอดรหัสไฟล์ส่งออกนี้ได้" + "message": "การส่งออกนี้เข้ารหัสข้อมูลของคุณโดยใช้กุญแจเข้ารหัสบัญชีของคุณ หากคุณหมุนเวียนกุญแจเข้ารหัสบัญชี คุณควรส่งออกใหม่อีกครั้ง เนื่องจากคุณจะไม่สามารถถอดรหัสไฟล์ส่งออกนี้ได้" }, "encExportAccountWarningDesc": { - "message": "คีย์การเข้ารหัสบัญชีจะไม่ซ้ำกันสำหรับบัญชีผู้ใช้ Bitwarden แต่ละบัญชี ดังนั้นคุณจึงไม่สามารถนำเข้าการส่งออกที่เข้ารหัสไปยังบัญชีอื่นได้" + "message": "กุญแจเข้ารหัสบัญชีเป็นกุญแจเฉพาะสำหรับผู้ใช้ Bitwarden แต่ละบัญชี ดังนั้นคุณไม่สามารถนำเข้าไฟล์ส่งออกที่เข้ารหัสไปยังบัญชีอื่นได้" }, "exportMasterPassword": { - "message": "ป้อนรหัสผ่านหลักของคุณเพื่อส่งออกข้อมูลตู้นิรภัยของคุณ" + "message": "ป้อนรหัสผ่านหลักเพื่อส่งออกข้อมูลตู้นิรภัย" }, "shared": { "message": "แชร์แล้ว" }, "bitwardenForBusinessPageDesc": { - "message": "Bitwarden for Business allows you to share your vault items with others by using an organization. Learn more on the bitwarden.com website." + "message": "Bitwarden สำหรับธุรกิจช่วยให้คุณแชร์รายการในตู้นิรภัยกับผู้อื่นโดยใช้องค์กร เรียนรู้เพิ่มเติมที่เว็บไซต์ bitwarden.com" }, "moveToOrganization": { - "message": "ย้ายไปยังแบบองค์กร" + "message": "ย้ายไปที่องค์กร" }, "movedItemToOrg": { - "message": "ย้าย $ITEMNAME$ ไปยัง $ORGNAME$ แล้ว", + "message": "ย้าย $ITEMNAME$ ไปที่ $ORGNAME$ แล้ว", "placeholders": { "itemname": { "content": "$1", @@ -1401,40 +1411,40 @@ } }, "moveToOrgDesc": { - "message": "เลือกองค์กรที่คุณต้องการย้ายรายการนี้ไป การย้ายไปยังองค์กรจะโอนความเป็นเจ้าของรายการไปยังองค์กรนั้น คุณจะไม่ได้เป็นเจ้าของโดยตรงของรายการนี้อีกต่อไปเมื่อมีการย้ายแล้ว" + "message": "เลือกองค์กรที่คุณต้องการย้ายรายการนี้ไป การย้ายไปที่องค์กรจะโอนกรรมสิทธิ์ของรายการนั้นไปยังองค์กร คุณจะไม่เป็นเจ้าของโดยตรงของรายการนี้อีกต่อไปหลังจากย้ายแล้ว" }, "learnMore": { "message": "เรียนรู้เพิ่มเติม" }, "migrationsFailed": { - "message": "An error occurred updating the encryption settings." + "message": "เกิดข้อผิดพลาดในการอัปเดตการตั้งค่าการเข้ารหัส" }, "updateEncryptionSettingsTitle": { - "message": "Update your encryption settings" + "message": "อัปเดตการตั้งค่าการเข้ารหัส" }, "updateEncryptionSettingsDesc": { - "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + "message": "การตั้งค่าการเข้ารหัสใหม่ที่แนะนำจะช่วยปรับปรุงความปลอดภัยของบัญชี ป้อนรหัสผ่านหลักเพื่ออัปเดตทันที" }, "confirmIdentityToContinue": { - "message": "Confirm your identity to continue" + "message": "ยืนยันตัวตนเพื่อดำเนินการต่อ" }, "enterYourMasterPassword": { - "message": "Enter your master password" + "message": "ป้อนรหัสผ่านหลัก" }, "updateSettings": { - "message": "Update settings" + "message": "อัปเดตการตั้งค่า" }, "later": { - "message": "Later" + "message": "ไว้ทีหลัง" }, "authenticatorKeyTotp": { - "message": "Authenticator Key (TOTP)" + "message": "คีย์ยืนยันตัวตน (TOTP)" }, "verificationCodeTotp": { - "message": "Verification Code (TOTP)" + "message": "รหัสยืนยัน (TOTP)" }, "copyVerificationCode": { - "message": "Copy Verification Code" + "message": "คัดลอกรหัสยืนยัน" }, "attachments": { "message": "ไฟล์แนบ" @@ -1443,7 +1453,7 @@ "message": "ลบไฟล์แนบ" }, "deleteAttachmentConfirmation": { - "message": "คุณต้องการลบไฟล์แนบนี้ใช่หรือไม่?" + "message": "ยืนยันที่จะลบไฟล์แนบนี้หรือไม่" }, "deletedAttachment": { "message": "ลบไฟล์แนบแล้ว" @@ -1458,58 +1468,58 @@ "message": "บันทึกไฟล์แนบแล้ว" }, "fixEncryption": { - "message": "Fix encryption" + "message": "ซ่อมแซมการเข้ารหัส" }, "fixEncryptionTooltip": { - "message": "This file is using an outdated encryption method." + "message": "ไฟล์นี้ใช้วิธีการเข้ารหัสที่ล้าสมัย" }, "attachmentUpdated": { - "message": "Attachment updated" + "message": "อัปเดตไฟล์แนบแล้ว" }, "file": { "message": "ไฟล์" }, "fileToShare": { - "message": "File to share" + "message": "ไฟล์ที่จะแชร์" }, "selectFile": { "message": "เลือกไฟล์" }, "itemsTransferred": { - "message": "Items transferred" + "message": "รายการที่โอนย้าย" }, "maxFileSize": { - "message": "ขนาดไฟล์สูงสุด คือ 500 MB" + "message": "ขนาดไฟล์สูงสุดคือ 500 MB" }, "featureUnavailable": { - "message": "Feature Unavailable" + "message": "ฟีเจอร์ไม่พร้อมใช้งาน" }, "legacyEncryptionUnsupported": { - "message": "Legacy encryption is no longer supported. Please contact support to recover your account." + "message": "ไม่รองรับการเข้ารหัสแบบเก่าอีกต่อไป โปรดติดต่อฝ่ายสนับสนุนเพื่อกู้คืนบัญชีของคุณ" }, "premiumMembership": { - "message": "Premium Membership" + "message": "สมาชิกพรีเมียม" }, "premiumManage": { - "message": "Manage Membership" + "message": "จัดการการเป็นสมาชิก" }, "premiumManageAlert": { - "message": "คุณสามารถจัดการการเป็นสมาชิกของคุณได้ที่ bitwarden.com web vault คุณต้องการเข้าชมเว็บไซต์ตอนนี้หรือไม่?" + "message": "คุณสามารถจัดการการเป็นสมาชิกได้ที่เว็บตู้นิรภัย bitwarden.com ต้องการไปที่เว็บไซต์ตอนนี้หรือไม่" }, "premiumRefresh": { - "message": "Refresh Membership" + "message": "รีเฟรชสถานะสมาชิก" }, "premiumNotCurrentMember": { - "message": "คุณยังไม่ได้เป็นสมาชิกพรีเมียม" + "message": "ปัจจุบันคุณไม่ได้เป็นสมาชิกพรีเมียม" }, "premiumSignUpAndGet": { - "message": "สมัครสมาชิกพรีเมี่ยมและรับ:" + "message": "สมัครสมาชิกพรีเมียมแล้วรับ:" }, "ppremiumSignUpStorage": { - "message": "1 GB of encrypted file storage." + "message": "พื้นที่จัดเก็บไฟล์แนบเข้ารหัสขนาด 1 GB" }, "premiumSignUpStorageV2": { - "message": "$SIZE$ encrypted storage for file attachments.", + "message": "พื้นที่จัดเก็บไฟล์แนบเข้ารหัสขนาด $SIZE$", "placeholders": { "size": { "content": "$1", @@ -1518,40 +1528,40 @@ } }, "premiumSignUpEmergency": { - "message": "Emergency access." + "message": "การเข้าถึงฉุกเฉิน" }, "premiumSignUpTwoStepOptions": { - "message": "Proprietary two-step login options such as YubiKey and Duo." + "message": "ตัวเลือกการเข้าสู่ระบบ 2 ขั้นตอนแบบพิเศษ เช่น YubiKey และ Duo" }, "ppremiumSignUpReports": { - "message": "สุขอนามัยของรหัสผ่าน ความสมบูรณ์ของบัญชี และรายงานการละเมิดข้อมูลเพื่อให้ตู้นิรภัยของคุณปลอดภัย" + "message": "รายงานความปลอดภัยของรหัสผ่าน สุขภาพบัญชี และข้อมูลรั่วไหล เพื่อรักษาตู้นิรภัยให้ปลอดภัย" }, "ppremiumSignUpTotp": { - "message": "ตัวสร้างรหัสยืนยัน TOTP (2FA) สำหรับการเข้าสู่ระบบในตู้นิรภัยของคุณ" + "message": "ตัวสร้างรหัสยืนยัน TOTP (2FA) สำหรับข้อมูลเข้าสู่ระบบในตู้นิรภัย" }, "ppremiumSignUpSupport": { - "message": "Priority customer support." + "message": "บริการลูกค้าสัมพันธ์ระดับพรีเมียม" }, "ppremiumSignUpFuture": { - "message": "All future Premium features. More coming soon!" + "message": "ฟีเจอร์พรีเมียมในอนาคตทั้งหมด และอื่น ๆ ที่กำลังจะมาเร็ว ๆ นี้!" }, "premiumPurchase": { - "message": "Purchase Premium" + "message": "ซื้อพรีเมียม" }, "premiumPurchaseAlertV2": { - "message": "You can purchase Premium from your account settings on the Bitwarden web app." + "message": "คุณสามารถซื้อพรีเมียมได้จากการตั้งค่าบัญชีในเว็บแอป Bitwarden" }, "premiumCurrentMember": { - "message": "You are a Premium member!" + "message": "คุณเป็นสมาชิกพรีเมียมแล้ว!" }, "premiumCurrentMemberThanks": { - "message": "Thank you for supporting bitwarden." + "message": "ขอบคุณที่สนับสนุน Bitwarden" }, "premiumFeatures": { - "message": "Upgrade to Premium and receive:" + "message": "อัปเกรดเป็นพรีเมียมเพื่อรับ:" }, "premiumPrice": { - "message": "All for just $PRICE$ /year!", + "message": "ทั้งหมดนี้เพียง $PRICE$ /ปี!", "placeholders": { "price": { "content": "$1", @@ -1560,7 +1570,7 @@ } }, "premiumPriceV2": { - "message": "All for just $PRICE$ per year!", + "message": "ทั้งหมดนี้เพียง $PRICE$ ต่อปี!", "placeholders": { "price": { "content": "$1", @@ -1569,25 +1579,25 @@ } }, "refreshComplete": { - "message": "Refresh complete" + "message": "รีเฟรชเสร็จสมบูรณ์" }, "enableAutoTotpCopy": { - "message": "Copy TOTP automatically" + "message": "คัดลอก TOTP อัตโนมัติ" }, "disableAutoTotpCopyDesc": { - "message": "If a login has an authenticator key, copy the TOTP verification code to your clip-board when you autofill the login." + "message": "หากข้อมูลเข้าสู่ระบบมีคีย์ยืนยันตัวตน ให้คัดลอกรหัสยืนยัน TOTP ไปยังคลิปบอร์ดเมื่อคุณป้อนข้อมูลเข้าสู่ระบบอัตโนมัติ" }, "enableAutoBiometricsPrompt": { - "message": "Ask for biometrics on launch" + "message": "ถามหาไบโอเมตริกเมื่อเปิดแอป" }, "authenticationTimeout": { - "message": "Authentication timeout" + "message": "การยืนยันตัวตนหมดเวลา" }, "authenticationSessionTimedOut": { - "message": "The authentication session timed out. Please restart the login process." + "message": "เซสชันการยืนยันตัวตนหมดเวลา โปรดเริ่มกระบวนการเข้าสู่ระบบใหม่" }, "verificationCodeEmailSent": { - "message": "ส่งโค้ดยืนยันไปยังอีเมล $EMAIL$ แล้ว", + "message": "ส่งอีเมลยืนยันไปที่ $EMAIL$ แล้ว", "placeholders": { "email": { "content": "$1", @@ -1596,148 +1606,148 @@ } }, "dontAskAgainOnThisDeviceFor30Days": { - "message": "Don't ask again on this device for 30 days" + "message": "ไม่ต้องถามอีกบนอุปกรณ์นี้เป็นเวลา 30 วัน" }, "selectAnotherMethod": { - "message": "Select another method", + "message": "เลือกวิธีอื่น", "description": "Select another two-step login method" }, "useYourRecoveryCode": { - "message": "Use your recovery code" + "message": "ใช้รหัสกู้คืนของคุณ" }, "insertU2f": { - "message": "Insert your security key into your computer's USB port. If it has a button, touch it." + "message": "เสียบคีย์ความปลอดภัยเข้ากับพอร์ต USB ของคอมพิวเตอร์ หากมีปุ่มให้แตะที่ปุ่ม" }, "openInNewTab": { - "message": "Open in new tab" + "message": "เปิดในแท็บใหม่" }, "webAuthnAuthenticate": { - "message": "Authenticate WebAuthn" + "message": "ยืนยันตัวตน WebAuthn" }, "readSecurityKey": { - "message": "Read security key" + "message": "อ่านคีย์ความปลอดภัย" }, "readingPasskeyLoading": { - "message": "Reading passkey..." + "message": "กำลังอ่านพาสคีย์..." }, "passkeyAuthenticationFailed": { - "message": "Passkey authentication failed" + "message": "การยืนยันตัวตนด้วยพาสคีย์ล้มเหลว" }, "useADifferentLogInMethod": { - "message": "Use a different log in method" + "message": "ใช้วิธีเข้าสู่ระบบอื่น" }, "awaitingSecurityKeyInteraction": { - "message": "Awaiting security key interaction..." + "message": "กำลังรอการตอบสนองจากคีย์ความปลอดภัย..." }, "loginUnavailable": { - "message": "Login Unavailable" + "message": "ไม่สามารถเข้าสู่ระบบได้" }, "noTwoStepProviders": { - "message": "This account has two-step login enabled, however, none of the configured two-step providers are supported by this web browser." + "message": "บัญชีนี้ตั้งค่าการเข้าสู่ระบบ 2 ขั้นตอนไว้ แต่เว็บเบราว์เซอร์นี้ไม่รองรับผู้ให้บริการ 2 ขั้นตอนใด ๆ ที่กำหนดค่าไว้" }, "noTwoStepProviders2": { - "message": "Please use a supported web browser (such as Chrome) and/or add additional providers that are better supported across web browsers (such as an authenticator app)." + "message": "โปรดใช้เว็บเบราว์เซอร์ที่รองรับ (เช่น Chrome) และ/หรือเพิ่มผู้ให้บริการอื่นที่รองรับบนเว็บเบราว์เซอร์ได้ดีกว่า (เช่น แอปยืนยันตัวตน)" }, "twoStepOptions": { - "message": "Two-step Login Options" + "message": "ตัวเลือกการเข้าสู่ระบบ 2 ขั้นตอน" }, "selectTwoStepLoginMethod": { - "message": "Select two-step login method" + "message": "เลือกวิธีการเข้าสู่ระบบ 2 ขั้นตอน" }, "recoveryCodeTitle": { - "message": "Recovery Code" + "message": "รหัสกู้คืน" }, "authenticatorAppTitle": { - "message": "Authenticator App" + "message": "แอปยืนยันตัวตน" }, "authenticatorAppDescV2": { - "message": "Enter a code generated by an authenticator app like Bitwarden Authenticator.", + "message": "ป้อนรหัสที่สร้างโดยแอปยืนยันตัวตน เช่น Bitwarden Authenticator", "description": "'Bitwarden Authenticator' is a product name and should not be translated." }, "yubiKeyTitleV2": { - "message": "Yubico OTP Security Key" + "message": "คีย์ความปลอดภัย Yubico OTP" }, "yubiKeyDesc": { - "message": "Use a YubiKey to access your account. Works with YubiKey 4, 4 Nano, 4C, and NEO devices." + "message": "ใช้ YubiKey เพื่อเข้าถึงบัญชีของคุณ ใช้งานได้กับอุปกรณ์ YubiKey 4, 4 Nano, 4C และ NEO" }, "duoDescV2": { - "message": "Enter a code generated by Duo Security.", + "message": "ป้อนรหัสที่สร้างโดย Duo Security", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, "duoOrganizationDesc": { - "message": "Verify with Duo Security for your organization using the Duo Mobile app, SMS, phone call, or U2F security key.", + "message": "ยืนยันตัวตนกับ Duo Security สำหรับองค์กรของคุณโดยใช้แอป Duo Mobile, SMS, โทรศัพท์ หรือคีย์ความปลอดภัย U2F", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, "webAuthnTitle": { "message": "FIDO2 WebAuthn" }, "webAuthnDesc": { - "message": "ใช้กุญแจความปลอดภัยที่รองรับ WebAuthn ใดก็ได้เพื่อเข้าถึงบัญชีของคุณ" + "message": "ใช้คีย์ความปลอดภัยที่รองรับ WebAuthn เพื่อเข้าถึงบัญชีของคุณ" }, "emailTitle": { "message": "อีเมล" }, "emailDescV2": { - "message": "Enter a code sent to your email." + "message": "ป้อนรหัสที่ส่งไปยังอีเมลของคุณ" }, "selfHostedEnvironment": { - "message": "Self-hosted Environment" + "message": "สภาพแวดล้อมโฮสต์เอง" }, "selfHostedBaseUrlHint": { - "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" + "message": "ระบุ URL เซิร์ฟเวอร์ของการติดตั้ง Bitwarden ที่คุณโฮสต์เอง ตัวอย่าง: https://bitwarden.company.com" }, "selfHostedCustomEnvHeader": { - "message": "For advanced configuration, you can specify the base URL of each service independently." + "message": "สำหรับการกำหนดค่าขั้นสูง คุณสามารถระบุ URL ของแต่ละบริการได้อย่างอิสระ" }, "selfHostedEnvFormInvalid": { - "message": "You must add either the base Server URL or at least one custom environment." + "message": "คุณต้องเพิ่ม URL เซิร์ฟเวอร์หลัก หรือสภาพแวดล้อมที่กำหนดเองอย่างน้อยหนึ่งรายการ" }, "selfHostedEnvMustUseHttps": { - "message": "URLs must use HTTPS." + "message": "URL ต้องใช้ HTTPS" }, "customEnvironment": { - "message": "Custom Environment" + "message": "สภาพแวดล้อมที่กำหนดเอง" }, "baseUrl": { - "message": "URL ของเซิร์ฟเวอร์" + "message": "URL เซิร์ฟเวอร์" }, "selfHostBaseUrl": { - "message": "Self-host server URL", + "message": "URL เซิร์ฟเวอร์โฮสต์เอง", "description": "Label for field requesting a self-hosted integration service URL" }, "apiUrl": { - "message": "API Server URL" + "message": "URL เซิร์ฟเวอร์ API" }, "webVaultUrl": { - "message": "Web Vault Server URL" + "message": "URL เซิร์ฟเวอร์เว็บตู้นิรภัย" }, "identityUrl": { - "message": "Identity Server URL" + "message": "URL เซิร์ฟเวอร์ข้อมูลระบุตัวตน" }, "notificationsUrl": { - "message": "Notifications Server URL" + "message": "URL เซิร์ฟเวอร์การแจ้งเตือน" }, "iconsUrl": { - "message": "Icons Server URL" + "message": "URL เซิร์ฟเวอร์ไอคอน" }, "environmentSaved": { - "message": "Environment URLs saved" + "message": "บันทึก URL สภาพแวดล้อมแล้ว" }, "showAutoFillMenuOnFormFields": { - "message": "Show autofill menu on form fields", + "message": "แสดงเมนูป้อนอัตโนมัติบนช่องกรอกข้อมูล", "description": "Represents the message for allowing the user to enable the autofill overlay" }, "autofillSuggestionsSectionTitle": { - "message": "คำแนะนำการกรอกข้อมูลอัตโนมัติ" + "message": "คำแนะนำการป้อนอัตโนมัติ" }, "autofillSpotlightTitle": { - "message": "Easily find autofill suggestions" + "message": "ค้นหาคำแนะนำการป้อนอัตโนมัติได้ง่ายขึ้น" }, "autofillSpotlightDesc": { - "message": "Turn off your browser's autofill settings, so they don't conflict with Bitwarden." + "message": "ปิดการตั้งค่าป้อนอัตโนมัติของเบราว์เซอร์ เพื่อไม่ให้ขัดแย้งกับ Bitwarden" }, "turnOffBrowserAutofill": { - "message": "Turn off $BROWSER$ autofill", + "message": "ปิดการป้อนอัตโนมัติของ $BROWSER$", "placeholders": { "browser": { "content": "$1", @@ -1746,162 +1756,162 @@ } }, "turnOffAutofill": { - "message": "Turn off autofill" + "message": "ปิดการป้อนอัตโนมัติ" }, "confirmAutofill": { - "message": "Confirm autofill" + "message": "ยืนยันการป้อนอัตโนมัติ" }, "confirmAutofillDesc": { - "message": "This site doesn't match your saved login details. Before you fill in your login credentials, make sure it's a trusted site." + "message": "เว็บไซต์นี้ไม่ตรงกับรายละเอียดการเข้าสู่ระบบที่คุณบันทึกไว้ ก่อนป้อนข้อมูล โปรดตรวจสอบให้แน่ใจว่าเป็นเว็บไซต์ที่เชื่อถือได้" }, "showInlineMenuLabel": { - "message": "Show autofill suggestions on form fields" + "message": "แสดงคำแนะนำการป้อนอัตโนมัติบนช่องกรอกข้อมูล" }, "howDoesBitwardenProtectFromPhishing": { - "message": "How does Bitwarden protect your data from phishing?" + "message": "Bitwarden ปกป้องข้อมูลของคุณจากการฟิชชิงได้อย่างไร" }, "currentWebsite": { - "message": "Current website" + "message": "เว็บไซต์ปัจจุบัน" }, "autofillAndAddWebsite": { - "message": "Autofill and add this website" + "message": "ป้อนอัตโนมัติและเพิ่มเว็บไซต์นี้" }, "autofillWithoutAdding": { - "message": "Autofill without adding" + "message": "ป้อนอัตโนมัติโดยไม่เพิ่ม" }, "doNotAutofill": { - "message": "Do not autofill" + "message": "ไม่ต้องป้อนอัตโนมัติ" }, "showInlineMenuIdentitiesLabel": { - "message": "Display identities as suggestions" + "message": "แสดงข้อมูลระบุตัวตนเป็นคำแนะนำ" }, "showInlineMenuCardsLabel": { - "message": "Display cards as suggestions" + "message": "แสดงบัตรเป็นคำแนะนำ" }, "showInlineMenuOnIconSelectionLabel": { - "message": "Display suggestions when icon is selected" + "message": "แสดงคำแนะนำเมื่อเลือกไอคอน" }, "showInlineMenuOnFormFieldsDescAlt": { - "message": "Applies to all logged in accounts." + "message": "สำหรับทุกบัญชีที่เข้าสู่ระบบ" }, "turnOffBrowserBuiltInPasswordManagerSettings": { - "message": "Turn off your browser's built in password manager settings to avoid conflicts." + "message": "ปิดการตั้งค่าตัวจัดการรหัสผ่านในตัวของเบราว์เซอร์เพื่อหลีกเลี่ยงความขัดแย้ง" }, "turnOffBrowserBuiltInPasswordManagerSettingsLink": { - "message": "Edit browser settings." + "message": "แก้ไขการตั้งค่าเบราว์เซอร์" }, "autofillOverlayVisibilityOff": { - "message": "Off", + "message": "ปิด", "description": "Overlay setting select option for disabling autofill overlay" }, "autofillOverlayVisibilityOnFieldFocus": { - "message": "When field is selected (on focus)", + "message": "เมื่อเลือกช่อง (โฟกัส)", "description": "Overlay appearance select option for showing the field on focus of the input element" }, "autofillOverlayVisibilityOnButtonClick": { - "message": "When autofill icon is selected", + "message": "เมื่อเลือกไอคอนป้อนอัตโนมัติ", "description": "Overlay appearance select option for showing the field on click of the overlay icon" }, "enableAutoFillOnPageLoadSectionTitle": { - "message": "Autofill on page load" + "message": "ป้อนอัตโนมัติเมื่อโหลดหน้าเว็บ" }, "enableAutoFillOnPageLoad": { - "message": "Enable Auto-fill On Page Load." + "message": "ป้อนอัตโนมัติเมื่อโหลดหน้าเว็บ" }, "enableAutoFillOnPageLoadDesc": { - "message": "If a login form is detected, autofill when the web page loads." + "message": "หากตรวจพบแบบฟอร์มเข้าสู่ระบบ ให้ป้อนข้อมูลอัตโนมัติเมื่อหน้าเว็บโหลดเสร็จ" }, "experimentalFeature": { - "message": "Compromised or untrusted websites can exploit autofill on page load." + "message": "เว็บไซต์ที่ไม่น่าเชื่อถือหรือถูกแฮ็กอาจใช้ประโยชน์จากการป้อนอัตโนมัติเมื่อโหลดหน้าเว็บได้" }, "learnMoreAboutAutofillOnPageLoadLinkText": { - "message": "Learn more about risks" + "message": "เรียนรู้เพิ่มเติมเกี่ยวกับความเสี่ยง" }, "learnMoreAboutAutofill": { - "message": "Learn more about autofill" + "message": "เรียนรู้เพิ่มเติมเกี่ยวกับการป้อนอัตโนมัติ" }, "defaultAutoFillOnPageLoad": { - "message": "Default autofill setting for login items" + "message": "การตั้งค่าเริ่มต้นสำหรับการป้อนข้อมูลเข้าสู่ระบบอัตโนมัติ" }, "defaultAutoFillOnPageLoadDesc": { - "message": "You can turn off autofill on page load for individual login items from the item's Edit view." + "message": "คุณสามารถปิดการป้อนอัตโนมัติเมื่อโหลดหน้าเว็บสำหรับแต่ละรายการได้จากหน้าแก้ไขรายการ" }, "autoFillOnPageLoadUseDefault": { - "message": "Use default setting" + "message": "ใช้การตั้งค่าเริ่มต้น" }, "autoFillOnPageLoadYes": { - "message": "Autofill on page load" + "message": "ป้อนอัตโนมัติเมื่อโหลดหน้าเว็บ" }, "autoFillOnPageLoadNo": { - "message": "Do not autofill on page load" + "message": "ไม่ป้อนอัตโนมัติเมื่อโหลดหน้าเว็บ" }, "commandOpenPopup": { - "message": "Open vault popup" + "message": "เปิดป๊อปอัปตู้นิรภัย" }, "commandOpenSidebar": { - "message": "Open vault in sidebar" + "message": "เปิดตู้นิรภัยในแถบด้านข้าง" }, "commandAutofillLoginDesc": { - "message": "Autofill the last used login for the current website" + "message": "ป้อนข้อมูลเข้าสู่ระบบที่ใช้ล่าสุดสำหรับเว็บไซต์ปัจจุบันโดยอัตโนมัติ" }, "commandAutofillCardDesc": { - "message": "Autofill the last used card for the current website" + "message": "ป้อนข้อมูลบัตรที่ใช้ล่าสุดสำหรับเว็บไซต์ปัจจุบันโดยอัตโนมัติ" }, "commandAutofillIdentityDesc": { - "message": "Autofill the last used identity for the current website" + "message": "ป้อนข้อมูลระบุตัวตนที่ใช้ล่าสุดสำหรับเว็บไซต์ปัจจุบันโดยอัตโนมัติ" }, "commandGeneratePasswordDesc": { - "message": "Generate and copy a new random password to the clipboard." + "message": "สร้างและคัดลอกรหัสผ่านแบบสุ่มใหม่ไปยังคลิปบอร์ด" }, "commandLockVaultDesc": { - "message": "ล็อกตู้เซฟ" + "message": "ล็อกตู้นิรภัย" }, "customFields": { - "message": "Custom Fields" + "message": "ฟิลด์ที่กำหนดเอง" }, "copyValue": { - "message": "Copy Value" + "message": "คัดลอกค่า" }, "value": { "message": "ค่า" }, "newCustomField": { - "message": "New Custom Field" + "message": "ฟิลด์ที่กำหนดเองใหม่" }, "dragToSort": { - "message": "Drag to sort" + "message": "ลากเพื่อเรียงลำดับ" }, "dragToReorder": { - "message": "Drag to reorder" + "message": "ลากเพื่อจัดลำดับใหม่" }, "cfTypeText": { "message": "ข้อความ" }, "cfTypeHidden": { - "message": "Hidden" + "message": "ซ่อน" }, "cfTypeBoolean": { - "message": "Boolean" + "message": "บูลีน" }, "cfTypeCheckbox": { - "message": "Checkbox" + "message": "ช่องทำเครื่องหมาย" }, "cfTypeLinked": { - "message": "Linked", + "message": "เชื่อมโยง", "description": "This describes a field that is 'linked' (tied) to another field." }, "linkedValue": { - "message": "Linked value", + "message": "ค่าที่เชื่อมโยง", "description": "This describes a value that is 'linked' (tied) to another value." }, "popup2faCloseMessage": { - "message": "Clicking outside the popup window to check your email for your verification code will cause this popup to close. Do you want to open this popup in a new window so that it does not close?" + "message": "การคลิกนอกหน้าต่างป๊อปอัปเพื่อตรวจสอบรหัสยืนยันในอีเมลจะทำให้ป๊อปอัปปิดลง คุณต้องการเปิดป๊อปอัปนี้ในหน้าต่างใหม่เพื่อไม่ให้ปิดหรือไม่" }, "showIconsChangePasswordUrls": { - "message": "Show website icons and retrieve change password URLs" + "message": "แสดงไอคอนเว็บไซต์และดึง URL เปลี่ยนรหัสผ่าน" }, "cardholderName": { - "message": "Cardholder Name" + "message": "ชื่อผู้ถือบัตร" }, "number": { "message": "หมายเลข" @@ -1910,13 +1920,13 @@ "message": "แบรนด์" }, "expirationMonth": { - "message": "Expiration Month" + "message": "เดือนที่หมดอายุ" }, "expirationYear": { - "message": "Expiration Year" + "message": "ปีที่หมดอายุ" }, "monthly": { - "message": "month" + "message": "เดือน" }, "expiration": { "message": "วันหมดอายุ" @@ -1958,13 +1968,13 @@ "message": "ธันวาคม" }, "securityCode": { - "message": "Security Code" + "message": "รหัสความปลอดภัย" }, "cardNumber": { - "message": "card number" + "message": "หมายเลขบัตร" }, "ex": { - "message": "ex." + "message": "ตัวอย่าง" }, "title": { "message": "คำนำหน้า" @@ -1982,22 +1992,22 @@ "message": "ดร." }, "mx": { - "message": "Mx" + "message": "คุณ" }, "firstName": { - "message": "First Name" + "message": "ชื่อจริง" }, "middleName": { - "message": "Middle Name" + "message": "ชื่อกลาง" }, "lastName": { - "message": "Last Name" + "message": "นามสกุล" }, "fullName": { "message": "ชื่อเต็ม" }, "identityName": { - "message": "Identity Name" + "message": "ชื่อข้อมูลระบุตัวตน" }, "company": { "message": "บริษัท" @@ -2009,7 +2019,7 @@ "message": "หมายเลขหนังสือเดินทาง" }, "licenseNumber": { - "message": "หมายเลขใบอนุญาต" + "message": "หมายเลขใบขับขี่" }, "email": { "message": "อีเมล" @@ -2030,7 +2040,7 @@ "message": "ที่อยู่ 3" }, "cityTown": { - "message": "เมือง" + "message": "เมือง / ตำบล" }, "stateProvince": { "message": "รัฐ / จังหวัด" @@ -2042,116 +2052,116 @@ "message": "ประเทศ" }, "type": { - "message": "ชนิด" + "message": "ประเภท" }, "typeLogin": { - "message": "ล็อกอิน" + "message": "ข้อมูลเข้าสู่ระบบ" }, "typeLogins": { - "message": "ล็อกอิน" + "message": "ข้อมูลเข้าสู่ระบบ" }, "typeSecureNote": { - "message": "Secure Note" + "message": "โน้ตความปลอดภัย" }, "typeCard": { - "message": "บัตรเครดิต" + "message": "บัตร" }, "typeIdentity": { "message": "ข้อมูลระบุตัวตน" }, "typeSshKey": { - "message": "SSH key" + "message": "คีย์ SSH" }, "typeNote": { - "message": "Note" + "message": "โน้ต" }, "newItemHeaderLogin": { - "message": "New Login", + "message": "ข้อมูลเข้าสู่ระบบใหม่", "description": "Header for new login item type" }, "newItemHeaderCard": { - "message": "New Card", + "message": "บัตรใหม่", "description": "Header for new card item type" }, "newItemHeaderIdentity": { - "message": "New Identity", + "message": "ข้อมูลระบุตัวตนใหม่", "description": "Header for new identity item type" }, "newItemHeaderNote": { - "message": "New Note", + "message": "โน้ตใหม่", "description": "Header for new note item type" }, "newItemHeaderSshKey": { - "message": "New SSH key", + "message": "คีย์ SSH ใหม่", "description": "Header for new SSH key item type" }, "newItemHeaderTextSend": { - "message": "New Text Send", + "message": "ข้อความ Send ใหม่", "description": "Header for new text send" }, "newItemHeaderFileSend": { - "message": "New File Send", + "message": "ไฟล์ Send ใหม่", "description": "Header for new file send" }, "editItemHeaderLogin": { - "message": "Edit Login", + "message": "แก้ไขข้อมูลเข้าสู่ระบบ", "description": "Header for edit login item type" }, "editItemHeaderCard": { - "message": "Edit Card", + "message": "แก้ไขบัตร", "description": "Header for edit card item type" }, "editItemHeaderIdentity": { - "message": "Edit Identity", + "message": "แก้ไขข้อมูลระบุตัวตน", "description": "Header for edit identity item type" }, "editItemHeaderNote": { - "message": "Edit Note", + "message": "แก้ไขโน้ต", "description": "Header for edit note item type" }, "editItemHeaderSshKey": { - "message": "Edit SSH key", + "message": "แก้ไขคีย์ SSH", "description": "Header for edit SSH key item type" }, "editItemHeaderTextSend": { - "message": "Edit Text Send", + "message": "แก้ไขข้อความ Send", "description": "Header for edit text send" }, "editItemHeaderFileSend": { - "message": "Edit File Send", + "message": "แก้ไขไฟล์ Send", "description": "Header for edit file send" }, "viewItemHeaderLogin": { - "message": "View Login", + "message": "ดูข้อมูลเข้าสู่ระบบ", "description": "Header for view login item type" }, "viewItemHeaderCard": { - "message": "View Card", + "message": "ดูบัตร", "description": "Header for view card item type" }, "viewItemHeaderIdentity": { - "message": "View Identity", + "message": "ดูข้อมูลระบุตัวตน", "description": "Header for view identity item type" }, "viewItemHeaderNote": { - "message": "View Note", + "message": "ดูโน้ต", "description": "Header for view note item type" }, "viewItemHeaderSshKey": { - "message": "View SSH key", + "message": "ดูคีย์ SSH", "description": "Header for view SSH key item type" }, "passwordHistory": { - "message": "ประวัติของรหัสผ่าน" + "message": "ประวัติรหัสผ่าน" }, "generatorHistory": { - "message": "Generator history" + "message": "ประวัติตัวสร้างรหัสผ่าน" }, "clearGeneratorHistoryTitle": { - "message": "Clear generator history" + "message": "ล้างประวัติตัวสร้างรหัสผ่าน" }, "cleargGeneratorHistoryDescription": { - "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + "message": "หากดำเนินการต่อ รายการทั้งหมดจะถูกลบออกจากประวัติของตัวสร้างอย่างถาวร ยืนยันที่จะดำเนินการต่อหรือไม่" }, "back": { "message": "ย้อนกลับ" @@ -2160,7 +2170,7 @@ "message": "คอลเลกชัน" }, "nCollections": { - "message": "$COUNT$ collections", + "message": "$COUNT$ คอลเลกชัน", "placeholders": { "count": { "content": "$1", @@ -2172,7 +2182,7 @@ "message": "รายการโปรด" }, "popOutNewWindow": { - "message": "เปิดหน้าต่างใหม่" + "message": "แยกหน้าต่างใหม่" }, "refresh": { "message": "รีเฟรช" @@ -2184,23 +2194,23 @@ "message": "ข้อมูลระบุตัวตน" }, "logins": { - "message": "เข้าสู่ระบบ" + "message": "ข้อมูลเข้าสู่ระบบ" }, "secureNotes": { - "message": "Secure Notes" + "message": "โน้ตความปลอดภัย" }, "sshKeys": { - "message": "SSH Keys" + "message": "คีย์ SSH" }, "clear": { - "message": "ลบทิ้ง", + "message": "ล้าง", "description": "To clear something out. example: To clear browser history." }, "checkPassword": { - "message": "ตรวจสอบว่ารหัสผ่านถูกเปิดเผยหรือไม่" + "message": "ตรวจสอบว่ารหัสผ่านรั่วไหลหรือไม่" }, "passwordExposed": { - "message": "This password has been exposed $VALUE$ time(s) in data breaches. You should change it.", + "message": "รหัสผ่านนี้รั่วไหล $VALUE$ ครั้งในเหตุการณ์ข้อมูลรั่วไหล คุณควรเปลี่ยนรหัสผ่านนี้", "placeholders": { "value": { "content": "$1", @@ -2209,14 +2219,14 @@ } }, "passwordSafe": { - "message": "ไม่พบรหัสผ่านนี้ในการละเมิดข้อมูลที่มี ควรใช้อย่างปลอดภัย" + "message": "ไม่พบรหัสผ่านนี้ในเหตุการณ์ข้อมูลรั่วไหลที่รู้จัก ควรจะปลอดภัยต่อการใช้งาน" }, "baseDomain": { - "message": "โดเมนพื้นฐาน", + "message": "โดเมนฐาน", "description": "Domain name. Ex. website.com" }, "baseDomainOptionRecommended": { - "message": "Base domain (recommended)", + "message": "โดเมนฐาน (แนะนำ)", "description": "Domain name. Ex. website.com" }, "domainName": { @@ -2228,25 +2238,25 @@ "description": "A URL's host value. For example, the host of https://sub.domain.com:443 is 'sub.domain.com:443'." }, "exact": { - "message": "ถูกต้อง" + "message": "ตรงกันทุกตัวอักษร" }, "startsWith": { - "message": "เริ่มต้นด้วย" + "message": "ขึ้นต้นด้วย" }, "regEx": { - "message": "นิพจน์ทั่วไป", + "message": "Regular expression", "description": "A programming term, also known as 'RegEx'." }, "matchDetection": { - "message": "Match Detection", + "message": "การตรวจสอบการจับคู่", "description": "URI match detection for autofill." }, "defaultMatchDetection": { - "message": "การตรวจจับการจับคู่เริ่มต้น", + "message": "การตรวจสอบการจับคู่เริ่มต้น", "description": "Default URI match detection for autofill." }, "toggleOptions": { - "message": "Toggle Options" + "message": "สลับตัวเลือก" }, "toggleCurrentUris": { "message": "สลับ URI ปัจจุบัน", @@ -2261,7 +2271,7 @@ "description": "An entity of multiple related people (ex. a team or business organization)." }, "types": { - "message": "ชนิด" + "message": "ประเภท" }, "allItems": { "message": "รายการทั้งหมด" @@ -2270,64 +2280,64 @@ "message": "ไม่มีรหัสผ่านที่จะแสดง" }, "clearHistory": { - "message": "Clear history" + "message": "ล้างประวัติ" }, "nothingToShow": { - "message": "Nothing to show" + "message": "ไม่มีข้อมูลที่จะแสดง" }, "nothingGeneratedRecently": { - "message": "You haven't generated anything recently" + "message": "คุณไม่ได้สร้างรหัสผ่านใด ๆ ในช่วงนี้" }, "remove": { - "message": "ลบ" + "message": "เอาออก" }, "default": { "message": "ค่าเริ่มต้น" }, "dateUpdated": { - "message": "อัปเดตแล้ว", + "message": "อัปเดต", "description": "ex. Date this item was updated" }, "dateCreated": { - "message": "สร้างเมื่อ", + "message": "สร้าง", "description": "ex. Date this item was created" }, "datePasswordUpdated": { - "message": "อัปเดต Password แล้ว", + "message": "รหัสผ่านอัปเดต", "description": "ex. Date this password was updated" }, "neverLockWarning": { - "message": "คุณแน่ใจหรือไม่ว่าต้องการใช้ตัวเลือก \"ไม่เคย\" การตั้งค่าตัวเลือกการล็อกเป็น \"ไม่\" จะเก็บคีย์เข้ารหัสของห้องนิรภัยไว้ในอุปกรณ์ของคุณ หากคุณใช้ตัวเลือกนี้ คุณควรตรวจสอบให้แน่ใจว่าคุณปกป้องอุปกรณ์ของคุณอย่างเหมาะสม" + "message": "ยืนยันที่จะใช้ตัวเลือก “ไม่เลย” หรือไม่ การตั้งค่าตัวเลือกการล็อกเป็น “ไม่เลย” จะจัดเก็บกุญแจเข้ารหัสตู้นิรภัยไว้บนอุปกรณ์ หากใช้ตัวเลือกนี้ คุณควรตรวจสอบให้แน่ใจว่าอุปกรณ์ได้รับการปกป้องอย่างเหมาะสม" }, "noOrganizationsList": { - "message": "You do not belong to any organizations. Organizations allow you to securely share items with other users." + "message": "คุณไม่ได้เป็นสมาชิกขององค์กรใด ๆ องค์กรช่วยให้คุณแชร์รายการกับผู้ใช้อื่นได้อย่างปลอดภัย" }, "noCollectionsInList": { "message": "ไม่มีคอลเลกชันที่จะแสดง" }, "ownership": { - "message": "เจ้าของ" + "message": "ความเป็นเจ้าของ" }, "whoOwnsThisItem": { - "message": "ใครเป็นเจ้าของรายการนี้?" + "message": "ใครเป็นเจ้าของรายการนี้" }, "strong": { - "message": "แข็งแรง", + "message": "รัดกุม", "description": "ex. A strong password. Scale: Weak -> Good -> Strong" }, "good": { - "message": "ไม่เลว", + "message": "ดี", "description": "ex. A good password. Scale: Weak -> Good -> Strong" }, "weak": { - "message": "ง่ายเกินไป", + "message": "อ่อน", "description": "ex. A weak password. Scale: Weak -> Good -> Strong" }, "weakMasterPassword": { - "message": "Weak Master Password" + "message": "รหัสผ่านหลักไม่ปลอดภัย" }, "weakMasterPasswordDesc": { - "message": "รหัสผ่านหลักที่คุณเลือกนั้นไม่รัดกุม คุณควรใช้รหัสผ่านหลักที่รัดกุม (หรือวลีรหัสผ่าน) เพื่อปกป้องบัญชี Bitwarden ของคุณอย่างเหมาะสม คุณแน่ใจหรือไม่ว่าต้องการใช้รหัสผ่านหลักนี้" + "message": "รหัสผ่านหลักที่คุณเลือกไม่ปลอดภัย คุณควรใช้รหัสผ่านหลักที่รัดกุม (หรือวลีรหัสผ่าน) เพื่อปกป้องบัญชี Bitwarden ของคุณอย่างเหมาะสม ยืนยันที่จะใช้รหัสผ่านหลักนี้หรือไม่" }, "pin": { "message": "PIN", @@ -2337,43 +2347,43 @@ "message": "ปลดล็อกด้วย PIN" }, "setYourPinTitle": { - "message": "ตั้ง PIN" + "message": "ตั้งค่า PIN" }, "setYourPinButton": { - "message": "ตั้ง PIN" + "message": "ตั้งค่า PIN" }, "setYourPinCode": { - "message": "ตั้ง PIN เพื่อใช้ปลดล็อก Bitwarden ทั้งนี้ หากคุณล็อกเอาต์ออกจากแอปโดยสมบูรณ์จะเป็นการลบการตั้งค่า PIN ของคุณด้วย" + "message": "ตั้งรหัส PIN สำหรับปลดล็อก Bitwarden การตั้งค่า PIN จะถูกรีเซ็ตหากคุณออกจากระบบแอปพลิเคชันโดยสมบูรณ์" }, "setPinCode": { - "message": "ตั้ง PIN เพื่อใช้ปลดล็อก Bitwarden และหากคุณล็อกเอาต์ออกจากแอปโดยสมบูรณ์จะเป็นการลบการตั้งค่า PIN ของคุณ" + "message": "คุณสามารถใช้ PIN นี้เพื่อปลดล็อก Bitwarden PIN จะถูกรีเซ็ตหากคุณออกจากระบบแอปพลิเคชันโดยสมบูรณ์" }, "pinRequired": { - "message": "ต้องระบุ PIN" + "message": "จำเป็นต้องระบุรหัส PIN" }, "invalidPin": { - "message": "PIN ไม่ถูกต้อง" + "message": "รหัส PIN ไม่ถูกต้อง" }, "tooManyInvalidPinEntryAttemptsLoggingOut": { - "message": "Too many invalid PIN entry attempts. Logging out." + "message": "พยายามป้อน PIN ผิดหลายครั้งเกินไป กำลังออกจากระบบ" }, "unlockWithBiometrics": { "message": "ปลดล็อกด้วยไบโอเมตริก" }, "unlockWithMasterPassword": { - "message": "เข้าสู่ระบบด้วยรหัสผ่านหลัก" + "message": "ปลดล็อกด้วยรหัสผ่านหลัก" }, "awaitDesktop": { - "message": "Awaiting confirmation from desktop" + "message": "กำลังรอการยืนยันจากเดสก์ท็อป" }, "awaitDesktopDesc": { - "message": "โปรดยืนยันการใช้ไบโอเมตริกในแอปพลิเคชันเดสก์ท็อป Bitwarden เพื่อตั้งค่าไบโอเมตริกสำหรับเบราว์เซอร์" + "message": "โปรดยืนยันโดยใช้ไบโอเมตริกในแอปพลิเคชัน Bitwarden บนเดสก์ท็อปเพื่อตั้งค่าไบโอเมตริกสำหรับเบราว์เซอร์" }, "lockWithMasterPassOnRestart": { - "message": "ล็อคด้วยรหัสผ่านหลักเมื่อรีสตาร์ทเบราว์เซอร์" + "message": "ล็อกด้วยรหัสผ่านหลักเมื่อรีสตาร์ตเบราว์เซอร์" }, "lockWithMasterPassOnRestart1": { - "message": "กำหนดให้ป้อนรหัสผ่านหลักเมื่อรีสตาร์ทเบราว์เซอร์" + "message": "ต้องใช้รหัสผ่านหลักเมื่อรีสตาร์ตเบราว์เซอร์" }, "selectOneCollection": { "message": "คุณต้องเลือกอย่างน้อยหนึ่งคอลเลกชัน" @@ -2385,42 +2395,42 @@ "message": "โคลน" }, "passwordGenerator": { - "message": "Password generator" + "message": "ตัวสร้างรหัสผ่าน" }, "usernameGenerator": { - "message": "Username generator" + "message": "ตัวสร้างชื่อผู้ใช้" }, "useThisEmail": { - "message": "Use this email" + "message": "ใช้อีเมลนี้" }, "useThisPassword": { - "message": "Use this password" + "message": "ใช้รหัสผ่านนี้" }, "useThisPassphrase": { - "message": "Use this passphrase" + "message": "ใช้วลีรหัสผ่านนี้" }, "useThisUsername": { - "message": "Use this username" + "message": "ใช้ชื่อผู้ใช้นี้" }, "securePasswordGenerated": { - "message": "Secure password generated! Don't forget to also update your password on the website." + "message": "สร้างรหัสผ่านที่รัดกุมแล้ว! อย่าลืมอัปเดตรหัสผ่านบนเว็บไซต์ด้วย" }, "useGeneratorHelpTextPartOne": { - "message": "Use the generator", + "message": "ใช้ตัวสร้าง", "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, "useGeneratorHelpTextPartTwo": { - "message": "to create a strong unique password", + "message": "เพื่อสร้างรหัสผ่านที่รัดกุมและไม่ซ้ำกัน", "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, "vaultCustomization": { - "message": "Vault customization" + "message": "การปรับแต่งตู้นิรภัย" }, "vaultTimeoutAction": { - "message": "การดำเนินการหลังหมดเวลาล็อคตู้เซฟ" + "message": "การดำเนินการเมื่อตู้นิรภัยหมดเวลา" }, "vaultTimeoutAction1": { - "message": "Timeout action" + "message": "การดำเนินการเมื่อหมดเวลา" }, "lock": { "message": "ล็อก", @@ -2434,52 +2444,52 @@ "message": "ค้นหาในถังขยะ" }, "permanentlyDeleteItem": { - "message": "ลบรายการอย่างถาวร" + "message": "ลบรายการถาวร" }, "permanentlyDeleteItemConfirmation": { - "message": "คุณแน่ใจหรือไม่ว่าต้องการลบรายการนี้อย่างถาวร?" + "message": "ยืนยันที่จะลบรายการนี้ถาวรหรือไม่" }, "permanentlyDeletedItem": { - "message": "ลบรายการอย่างถาวรแล้ว" + "message": "ลบรายการถาวรแล้ว" }, "restoreItem": { "message": "กู้คืนรายการ" }, "restoredItem": { - "message": "คืนค่ารายการแล้ว" + "message": "กู้คืนรายการแล้ว" }, "alreadyHaveAccount": { - "message": "Already have an account?" + "message": "มีบัญชีอยู่แล้วใช่หรือไม่" }, "vaultTimeoutLogOutConfirmation": { - "message": "การออกจากระบบจะลบการเข้าถึงตู้นิรภัยของคุณทั้งหมด และต้องมีการตรวจสอบสิทธิ์ออนไลน์หลังจากหมดเวลา คุณแน่ใจหรือไม่ว่าต้องการใช้การตั้งค่านี้" + "message": "การออกจากระบบจะทำให้สิทธิ์เข้าถึงตู้นิรภัยทั้งหมดถูกยกเลิก และต้องยืนยันตัวตนออนไลน์ใหม่หลังจากหมดเวลา ยืนยันที่จะใช้การตั้งค่านี้หรือไม่" }, "vaultTimeoutLogOutConfirmationTitle": { - "message": "การยืนยันการดำเนินการหมดเวลา" + "message": "ยืนยันการดำเนินการเมื่อหมดเวลา" }, "autoFillAndSave": { - "message": "กรอกอัตโนมัติและบันทึก" + "message": "ป้อนอัตโนมัติและบันทึก" }, "fillAndSave": { - "message": "Fill and save" + "message": "ป้อนและบันทึก" }, "autoFillSuccessAndSavedUri": { - "message": "เติมรายการอัตโนมัติและบันทึก URI แล้ว" + "message": "ป้อนข้อมูลรายการอัตโนมัติและบันทึก URI แล้ว" }, "autoFillSuccess": { - "message": "รายการเติมอัตโนมัติ " + "message": "ป้อนข้อมูลรายการอัตโนมัติแล้ว" }, "insecurePageWarning": { - "message": "Warning: This is an unsecured HTTP page, and any information you submit can potentially be seen and changed by others. This Login was originally saved on a secure (HTTPS) page." + "message": "คำเตือน: หน้านี้เป็นหน้า HTTP ที่ไม่ปลอดภัย ข้อมูลที่คุณส่งอาจถูกดักจับหรือแก้ไขโดยผู้อื่นได้ ข้อมูลเข้าสู่ระบบนี้ถูกบันทึกไว้บนหน้าเว็บที่ปลอดภัย (HTTPS)" }, "insecurePageWarningFillPrompt": { - "message": "Do you still wish to fill this login?" + "message": "ยังต้องการป้อนข้อมูลเข้าสู่ระบบนี้หรือไม่" }, "autofillIframeWarning": { - "message": "The form is hosted by a different domain than the URI of your saved login. Choose OK to autofill anyway, or Cancel to stop." + "message": "แบบฟอร์มนี้โฮสต์โดยโดเมนที่ต่างจาก URI ของข้อมูลเข้าสู่ระบบที่คุณบันทึกไว้ เลือก ตกลง เพื่อป้อนข้อมูลอัตโนมัติ หรือ ยกเลิก เพื่อหยุด" }, "autofillIframeWarningTip": { - "message": "To prevent this warning in the future, save this URI, $HOSTNAME$, to your Bitwarden login item for this site.", + "message": "เพื่อป้องกันการแจ้งเตือนนี้ในอนาคต ให้บันทึก URI $HOSTNAME$ ลงในรายการข้อมูลเข้าสู่ระบบ Bitwarden สำหรับไซต์นี้", "placeholders": { "hostname": { "content": "$1", @@ -2488,22 +2498,22 @@ } }, "topLayerHijackWarning": { - "message": "This page is interfering with the Bitwarden experience. The Bitwarden inline menu has been temporarily disabled as a safety measure." + "message": "หน้านี้รบกวนการทำงานของ Bitwarden เมนูในบรรทัดของ Bitwarden ถูกปิดใช้งานชั่วคราวเพื่อความปลอดภัย" }, "setMasterPassword": { "message": "ตั้งรหัสผ่านหลัก" }, "currentMasterPass": { - "message": "Current master password" + "message": "รหัสผ่านหลักปัจจุบัน" }, "newMasterPass": { - "message": "New master password" + "message": "รหัสผ่านหลักใหม่" }, "confirmNewMasterPass": { - "message": "Confirm new master password" + "message": "ยืนยันรหัสผ่านหลักใหม่" }, "masterPasswordPolicyInEffect": { - "message": "นโยบายองค์กรอย่างน้อยหนึ่งนโยบายกำหนดให้รหัสผ่านหลักของคุณเป็นไปตามข้อกำหนดต่อไปนี้:" + "message": "นโยบายองค์กรอย่างน้อยหนึ่งรายการกำหนดให้รหัสผ่านหลักของคุณต้องเป็นไปตามข้อกำหนดดังนี้:" }, "policyInEffectMinComplexity": { "message": "คะแนนความซับซ้อนขั้นต่ำ $SCORE$", @@ -2515,7 +2525,7 @@ } }, "policyInEffectMinLength": { - "message": "ความยาวอย่างน้อย $LENGTH$ อักขระ", + "message": "ความยาวขั้นต่ำ $LENGTH$ ตัวอักษร", "placeholders": { "length": { "content": "$1", @@ -2524,16 +2534,16 @@ } }, "policyInEffectUppercase": { - "message": "มีตัวพิมพ์ใหญ่อย่างน้อย 1 ตัว" + "message": "มีตัวอักษรพิมพ์ใหญ่อย่างน้อยหนึ่งตัว" }, "policyInEffectLowercase": { - "message": "มีตัวพิมพ์เล็กอย่างน้อย 1 ตัว" + "message": "มีตัวอักษรพิมพ์เล็กอย่างน้อยหนึ่งตัว" }, "policyInEffectNumbers": { - "message": "มีตัวเลขอย่างน้อย 1 ตัว" + "message": "มีตัวเลขอย่างน้อยหนึ่งตัว" }, "policyInEffectSpecial": { - "message": "มีอักขระพิเศษต่อไปนี้อย่างน้อย 1 อักขระ: $CHARS$ ", + "message": "มีอักขระพิเศษต่อไปนี้อย่างน้อยหนึ่งตัว $CHARS$", "placeholders": { "chars": { "content": "$1", @@ -2542,186 +2552,186 @@ } }, "masterPasswordPolicyRequirementsNotMet": { - "message": "รหัสผ่านหลักใหม่ของคุณไม่เป็นไปตามข้อกำหนดของนโยบาย" + "message": "รหัสผ่านหลักใหม่ของคุณไม่ตรงตามข้อกำหนดของนโยบาย" }, "receiveMarketingEmailsV2": { - "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." + "message": "รับคำแนะนำ ประกาศ และโอกาสในการทำวิจัยจาก Bitwarden ทางอีเมล" }, "unsubscribe": { - "message": "Unsubscribe" + "message": "ยกเลิกการรับข่าวสาร" }, "atAnyTime": { - "message": "at any time." + "message": "ได้ทุกเมื่อ" }, "byContinuingYouAgreeToThe": { - "message": "By continuing, you agree to the" + "message": "เมื่อดำเนินการต่อ ถือว่าคุณยอมรับ" }, "and": { - "message": "and" + "message": "และ" }, "acceptPolicies": { - "message": "By checking this box you agree to the following:" + "message": "การเลือกช่องนี้หมายความว่าคุณยอมรับสิ่งต่อไปนี้:" }, "acceptPoliciesRequired": { - "message": "Terms of Service and Privacy Policy have not been acknowledged." + "message": "ยังไม่ได้ยอมรับข้อกำหนดในการให้บริการและนโยบายความเป็นส่วนตัว" }, "termsOfService": { - "message": "Terms of Service" + "message": "ข้อกำหนดในการให้บริการ" }, "privacyPolicy": { - "message": "Privacy Policy" + "message": "นโยบายความเป็นส่วนตัว" }, "yourNewPasswordCannotBeTheSameAsYourCurrentPassword": { - "message": "Your new password cannot be the same as your current password." + "message": "รหัสผ่านใหม่ต้องไม่ซ้ำกับรหัสผ่านปัจจุบัน" }, "hintEqualsPassword": { - "message": "Your password hint cannot be the same as your password." + "message": "คำใบ้รหัสผ่านต้องไม่เหมือนกับรหัสผ่าน" }, "ok": { "message": "ตกลง" }, "errorRefreshingAccessToken": { - "message": "Access Token Refresh Error" + "message": "เกิดข้อผิดพลาดในการรีเฟรชโทเค็นการเข้าถึง" }, "errorRefreshingAccessTokenDesc": { - "message": "No refresh token or API keys found. Please try logging out and logging back in." + "message": "ไม่พบโทเค็นรีเฟรชหรือคีย์ API โปรดลองออกจากระบบแล้วเข้าสู่ระบบใหม่" }, "desktopSyncVerificationTitle": { - "message": "Desktop sync verification" + "message": "การยืนยันการซิงค์เดสก์ท็อป" }, "desktopIntegrationVerificationText": { - "message": "Please verify that the desktop application shows this fingerprint: " + "message": "โปรดยืนยันว่าแอปพลิเคชันเดสก์ท็อปแสดงลายนิ้วมือนี้: " }, "desktopIntegrationDisabledTitle": { - "message": "Browser integration is not set up" + "message": "ยังไม่ได้ตั้งค่าการรวมเบราว์เซอร์" }, "desktopIntegrationDisabledDesc": { - "message": "Browser integration is not set up in the Bitwarden desktop application. Please set it up in the settings within the desktop application." + "message": "ยังไม่ได้ตั้งค่าการรวมเบราว์เซอร์ในแอปพลิเคชัน Bitwarden บนเดสก์ท็อป โปรดตั้งค่าในการตั้งค่าภายในแอปพลิเคชันเดสก์ท็อป" }, "startDesktopTitle": { - "message": "Start the Bitwarden desktop application" + "message": "เริ่มแอปพลิเคชัน Bitwarden บนเดสก์ท็อป" }, "startDesktopDesc": { - "message": "The Bitwarden desktop application needs to be started before unlock with biometrics can be used." + "message": "จำเป็นต้องเปิดแอปพลิเคชัน Bitwarden บนเดสก์ท็อปก่อนจึงจะสามารถใช้การปลดล็อกด้วยไบโอเมตริกได้" }, "errorEnableBiometricTitle": { - "message": "Unable to set up biometrics" + "message": "ไม่สามารถตั้งค่าไบโอเมตริกได้" }, "errorEnableBiometricDesc": { - "message": "Action was canceled by the desktop application" + "message": "การดำเนินการถูกยกเลิกโดยแอปพลิเคชันเดสก์ท็อป" }, "nativeMessagingInvalidEncryptionDesc": { - "message": "Desktop application invalidated the secure communication channel. Please retry this operation" + "message": "แอปพลิเคชันเดสก์ท็อปทำให้ช่องทางการสื่อสารที่ปลอดภัยเป็นโมฆะ โปรดลองดำเนินการนี้อีกครั้ง" }, "nativeMessagingInvalidEncryptionTitle": { - "message": "Desktop communication interrupted" + "message": "การสื่อสารกับเดสก์ท็อปถูกขัดจังหวะ" }, "nativeMessagingWrongUserDesc": { - "message": "The desktop application is logged into a different account. Please ensure both applications are logged into the same account." + "message": "แอปพลิเคชันเดสก์ท็อปเข้าสู่ระบบด้วยบัญชีอื่น โปรดตรวจสอบให้แน่ใจว่าทั้งสองแอปพลิเคชันเข้าสู่ระบบด้วยบัญชีเดียวกัน" }, "nativeMessagingWrongUserTitle": { - "message": "Account missmatch" + "message": "บัญชีไม่ตรงกัน" }, "nativeMessagingWrongUserKeyTitle": { - "message": "Biometric key missmatch" + "message": "คีย์ไบโอเมตริกไม่ตรงกัน" }, "nativeMessagingWrongUserKeyDesc": { - "message": "Biometric unlock failed. The biometric secret key failed to unlock the vault. Please try to set up biometrics again." + "message": "การปลดล็อกด้วยไบโอเมตริกล้มเหลว คีย์ลับไบโอเมตริกไม่สามารถปลดล็อกตู้นิรภัยได้ โปรดลองตั้งค่าไบโอเมตริกใหม่อีกครั้ง" }, "biometricsNotEnabledTitle": { - "message": "Biometrics not set up" + "message": "ยังไม่ได้ตั้งค่าไบโอเมตริก" }, "biometricsNotEnabledDesc": { - "message": "Browser biometrics requires desktop biometric to be set up in the settings first." + "message": "ไบโอเมตริกสำหรับเบราว์เซอร์จำเป็นต้องตั้งค่าไบโอเมตริกในแอปเดสก์ท็อปก่อน" }, "biometricsNotSupportedTitle": { - "message": "Biometrics not supported" + "message": "ไม่รองรับไบโอเมตริก" }, "biometricsNotSupportedDesc": { - "message": "Browser biometrics is not supported on this device." + "message": "อุปกรณ์นี้ไม่รองรับไบโอเมตริกสำหรับเบราว์เซอร์" }, "biometricsNotUnlockedTitle": { - "message": "User locked or logged out" + "message": "ผู้ใช้ถูกล็อกหรือออกจากระบบ" }, "biometricsNotUnlockedDesc": { - "message": "Please unlock this user in the desktop application and try again." + "message": "โปรดปลดล็อกผู้ใช้นี้ในแอปพลิเคชันเดสก์ท็อปแล้วลองอีกครั้ง" }, "biometricsNotAvailableTitle": { - "message": "Biometric unlock unavailable" + "message": "การปลดล็อกด้วยไบโอเมตริกไม่พร้อมใช้งาน" }, "biometricsNotAvailableDesc": { - "message": "Biometric unlock is currently unavailable. Please try again later." + "message": "การปลดล็อกด้วยไบโอเมตริกไม่พร้อมใช้งานในขณะนี้ โปรดลองใหม่อีกครั้งในภายหลัง" }, "biometricsFailedTitle": { - "message": "Biometrics failed" + "message": "ไบโอเมตริกล้มเหลว" }, "biometricsFailedDesc": { - "message": "Biometrics cannot be completed, consider using a master password or logging out. If this persists, please contact Bitwarden support." + "message": "ไม่สามารถดำเนินการไบโอเมตริกให้เสร็จสิ้นได้ โปรดพิจารณาใช้รหัสผ่านหลักหรือออกจากระบบ หากปัญหายังคงอยู่ โปรดติดต่อฝ่ายสนับสนุน Bitwarden" }, "nativeMessaginPermissionErrorTitle": { - "message": "Permission not provided" + "message": "ไม่ได้รับอนุญาต" }, "nativeMessaginPermissionErrorDesc": { - "message": "Without permission to communicate with the Bitwarden Desktop Application we cannot provide biometrics in the browser extension. Please try again." + "message": "เราไม่สามารถให้บริการไบโอเมตริกในส่วนขยายเบราว์เซอร์ได้หากไม่ได้รับอนุญาตให้สื่อสารกับแอปพลิเคชัน Bitwarden บนเดสก์ท็อป โปรดลองอีกครั้ง" }, "nativeMessaginPermissionSidebarTitle": { - "message": "Permission request error" + "message": "ข้อผิดพลาดในการขอสิทธิ์" }, "nativeMessaginPermissionSidebarDesc": { - "message": "This action cannot be done in the sidebar, please retry the action in the popup or popout." + "message": "ไม่สามารถดำเนินการนี้ในแถบด้านข้างได้ โปรดลองดำเนินการอีกครั้งในป๊อปอัปหรือหน้าต่างแยก" }, "personalOwnershipSubmitError": { - "message": "Due to an Enterprise Policy, you are restricted from saving items to your personal vault. Change the Ownership option to an organization and choose from available collections." + "message": "เนื่องจากนโยบายองค์กร คุณถูกจำกัดไม่ให้บันทึกรายการลงในตู้นิรภัยส่วนตัว เปลี่ยนตัวเลือกความเป็นเจ้าของเป็นองค์กรและเลือกคอลเลกชันที่มีอยู่" }, "personalOwnershipPolicyInEffect": { - "message": "An organization policy is affecting your ownership options." + "message": "นโยบายองค์กรมีผลต่อตัวเลือกความเป็นเจ้าของของคุณ" }, "personalOwnershipPolicyInEffectImports": { - "message": "An organization policy has blocked importing items into your individual vault." + "message": "นโยบายองค์กรระงับการนำเข้ารายการไปยังตู้นิรภัยส่วนตัวของคุณ" }, "restrictCardTypeImport": { - "message": "Cannot import card item types" + "message": "ไม่สามารถนำเข้ารายการประเภทบัตรได้" }, "restrictCardTypeImportDesc": { - "message": "A policy set by 1 or more organizations prevents you from importing cards to your vaults." + "message": "นโยบายที่กำหนดโดยองค์กรอย่างน้อย 1 แห่งป้องกันไม่ให้คุณนำเข้าบัตรไปยังตู้นิรภัย" }, "domainsTitle": { - "message": "Domains", + "message": "โดเมน", "description": "A category title describing the concept of web domains" }, "blockedDomains": { - "message": "Blocked domains" + "message": "โดเมนที่ถูกบล็อก" }, "learnMoreAboutBlockedDomains": { - "message": "Learn more about blocked domains" + "message": "เรียนรู้เพิ่มเติมเกี่ยวกับโดเมนที่ถูกบล็อก" }, "excludedDomains": { - "message": "Excluded domains" + "message": "โดเมนที่ยกเว้น" }, "excludedDomainsDesc": { - "message": "Bitwarden will not ask to save login details for these domains. You must refresh the page for changes to take effect." + "message": "Bitwarden จะไม่ถามให้บันทึกรายละเอียดการเข้าสู่ระบบสำหรับโดเมนเหล่านี้ คุณต้องรีเฟรชหน้าเว็บเพื่อให้การเปลี่ยนแปลงมีผล" }, "excludedDomainsDescAlt": { - "message": "Bitwarden will not ask to save login details for these domains for all logged in accounts. You must refresh the page for changes to take effect." + "message": "Bitwarden จะไม่ถามให้บันทึกรายละเอียดการเข้าสู่ระบบสำหรับโดเมนเหล่านี้สำหรับทุกบัญชีที่เข้าสู่ระบบ คุณต้องรีเฟรชหน้าเว็บเพื่อให้การเปลี่ยนแปลงมีผล" }, "blockedDomainsDesc": { - "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + "message": "การป้อนอัตโนมัติและฟีเจอร์อื่น ๆ ที่เกี่ยวข้องจะไม่พร้อมใช้งานสำหรับเว็บไซต์เหล่านี้ คุณต้องรีเฟรชหน้าเว็บเพื่อให้การเปลี่ยนแปลงมีผล" }, "autofillBlockedNoticeV2": { - "message": "Autofill is blocked for this website." + "message": "การป้อนอัตโนมัติถูกบล็อกสำหรับเว็บไซต์นี้" }, "autofillBlockedNoticeGuidance": { - "message": "Change this in settings" + "message": "เปลี่ยนค่านี้ในการตั้งค่า" }, "change": { - "message": "Change" + "message": "เปลี่ยน" }, "changePassword": { - "message": "Change password", + "message": "เปลี่ยนรหัสผ่าน", "description": "Change password button for browser at risk notification on login." }, "changeButtonTitle": { - "message": "Change password - $ITEMNAME$", + "message": "เปลี่ยนรหัสผ่าน - $ITEMNAME$", "placeholders": { "itemname": { "content": "$1", @@ -2730,13 +2740,13 @@ } }, "atRiskPassword": { - "message": "At-risk password" + "message": "รหัสผ่านที่มีความเสี่ยง" }, "atRiskPasswords": { - "message": "At-risk passwords" + "message": "รหัสผ่านที่มีความเสี่ยง" }, "atRiskPasswordDescSingleOrg": { - "message": "$ORGANIZATION$ is requesting you change one password because it is at-risk.", + "message": "$ORGANIZATION$ ร้องขอให้คุณเปลี่ยนรหัสผ่าน 1 รายการเนื่องจากมีความเสี่ยง", "placeholders": { "organization": { "content": "$1", @@ -2745,7 +2755,7 @@ } }, "atRiskPasswordsDescSingleOrgPlural": { - "message": "$ORGANIZATION$ is requesting you change the $COUNT$ passwords because they are at-risk.", + "message": "$ORGANIZATION$ ร้องขอให้คุณเปลี่ยนรหัสผ่าน $COUNT$ รายการเนื่องจากมีความเสี่ยง", "placeholders": { "organization": { "content": "$1", @@ -2758,7 +2768,7 @@ } }, "atRiskPasswordsDescMultiOrgPlural": { - "message": "Your organizations are requesting you change the $COUNT$ passwords because they are at-risk.", + "message": "องค์กรของคุณร้องขอให้คุณเปลี่ยนรหัสผ่าน $COUNT$ รายการเนื่องจากมีความเสี่ยง", "placeholders": { "count": { "content": "$1", @@ -2767,7 +2777,7 @@ } }, "atRiskChangePrompt": { - "message": "Your password for this site is at-risk. $ORGANIZATION$ has requested that you change it.", + "message": "รหัสผ่านของคุณสำหรับเว็บไซต์นี้มีความเสี่ยง $ORGANIZATION$ ได้ร้องขอให้คุณเปลี่ยนรหัสผ่าน", "placeholders": { "organization": { "content": "$1", @@ -2777,7 +2787,7 @@ "description": "Notification body when a login triggers an at-risk password change request and the change password domain is known." }, "atRiskNavigatePrompt": { - "message": "$ORGANIZATION$ wants you to change this password because it is at-risk. Navigate to your account settings to change the password.", + "message": "$ORGANIZATION$ ต้องการให้คุณเปลี่ยนรหัสผ่านนี้เนื่องจากมีความเสี่ยง ไปที่การตั้งค่าบัญชีของคุณเพื่อเปลี่ยนรหัสผ่าน", "placeholders": { "organization": { "content": "$1", @@ -2787,10 +2797,10 @@ "description": "Notification body when a login triggers an at-risk password change request and no change password domain is provided." }, "reviewAndChangeAtRiskPassword": { - "message": "Review and change one at-risk password" + "message": "ตรวจสอบและเปลี่ยนรหัสผ่านที่มีความเสี่ยง 1 รายการ" }, "reviewAndChangeAtRiskPasswordsPlural": { - "message": "Review and change $COUNT$ at-risk passwords", + "message": "ตรวจสอบและเปลี่ยนรหัสผ่านที่มีความเสี่ยง $COUNT$ รายการ", "placeholders": { "count": { "content": "$1", @@ -2799,52 +2809,52 @@ } }, "changeAtRiskPasswordsFaster": { - "message": "Change at-risk passwords faster" + "message": "เปลี่ยนรหัสผ่านที่มีความเสี่ยงได้เร็วยิ่งขึ้น" }, "changeAtRiskPasswordsFasterDesc": { - "message": "Update your settings so you can quickly autofill your passwords and generate new ones" + "message": "อัปเดตการตั้งค่าของคุณเพื่อให้สามารถป้อนรหัสผ่านอัตโนมัติและสร้างรหัสผ่านใหม่ได้อย่างรวดเร็ว" }, "reviewAtRiskLogins": { - "message": "Review at-risk logins" + "message": "ตรวจสอบข้อมูลเข้าสู่ระบบที่มีความเสี่ยง" }, "reviewAtRiskPasswords": { - "message": "Review at-risk passwords" + "message": "ตรวจสอบรหัสผ่านที่มีความเสี่ยง" }, "reviewAtRiskLoginsSlideDesc": { - "message": "Your organization passwords are at-risk because they are weak, reused, and/or exposed.", + "message": "รหัสผ่านองค์กรของคุณมีความเสี่ยงเนื่องจากไม่ปลอดภัย ใช้ซ้ำ และ/หรือรั่วไหล", "description": "Description of the review at-risk login slide on the at-risk password page carousel" }, "reviewAtRiskLoginSlideImgAltPeriod": { - "message": "Illustration of a list of logins that are at-risk." + "message": "ภาพประกอบรายการข้อมูลเข้าสู่ระบบที่มีความเสี่ยง" }, "generatePasswordSlideDesc": { - "message": "Quickly generate a strong, unique password with the Bitwarden autofill menu on the at-risk site.", + "message": "สร้างรหัสผ่านที่รัดกุมและไม่ซ้ำกันอย่างรวดเร็วด้วยเมนูป้อนอัตโนมัติของ Bitwarden บนเว็บไซต์ที่มีความเสี่ยง", "description": "Description of the generate password slide on the at-risk password page carousel" }, "generatePasswordSlideImgAltPeriod": { - "message": "Illustration of the Bitwarden autofill menu displaying a generated password." + "message": "ภาพประกอบเมนูป้อนอัตโนมัติของ Bitwarden แสดงรหัสผ่านที่ถูกสร้าง" }, "updateInBitwarden": { - "message": "Update in Bitwarden" + "message": "อัปเดตใน Bitwarden" }, "updateInBitwardenSlideDesc": { - "message": "Bitwarden will then prompt you to update the password in the password manager.", + "message": "จากนั้น Bitwarden จะแจ้งให้คุณอัปเดตรหัสผ่านในตัวจัดการรหัสผ่าน", "description": "Description of the update in Bitwarden slide on the at-risk password page carousel" }, "updateInBitwardenSlideImgAltPeriod": { - "message": "Illustration of a Bitwarden’s notification prompting the user to update the login." + "message": "ภาพประกอบการแจ้งเตือนของ Bitwarden ที่ให้ผู้ใช้อัปเดตข้อมูลเข้าสู่ระบบ" }, "turnOnAutofill": { - "message": "Turn on autofill" + "message": "เปิดการป้อนอัตโนมัติ" }, "turnedOnAutofill": { - "message": "Turned on autofill" + "message": "เปิดการป้อนอัตโนมัติแล้ว" }, "dismiss": { - "message": "Dismiss" + "message": "ปิด" }, "websiteItemLabel": { - "message": "Website $number$ (URI)", + "message": "เว็บไซต์ $number$ (URI)", "placeholders": { "number": { "content": "$1", @@ -2853,7 +2863,7 @@ } }, "excludedDomainsInvalidDomain": { - "message": "$DOMAIN$ is not a valid domain", + "message": "$DOMAIN$ ไม่ใช่โดเมนที่ถูกต้อง", "placeholders": { "domain": { "content": "$1", @@ -2862,20 +2872,20 @@ } }, "blockedDomainsSavedSuccess": { - "message": "Blocked domain changes saved" + "message": "บันทึกการเปลี่ยนแปลงโดเมนที่ถูกบล็อกแล้ว" }, "excludedDomainsSavedSuccess": { - "message": "Excluded domain changes saved" + "message": "บันทึกการเปลี่ยนแปลงโดเมนที่ยกเว้นแล้ว" }, "limitSendViews": { - "message": "Limit views" + "message": "จำกัดจำนวนการดู" }, "limitSendViewsHint": { - "message": "No one can view this Send after the limit is reached.", + "message": "จะไม่มีใครสามารถดู Send นี้ได้หลังจากครบกำหนด", "description": "Displayed under the limit views field on Send" }, "limitSendViewsCount": { - "message": "$ACCESSCOUNT$ views left", + "message": "ดูได้อีก $ACCESSCOUNT$ ครั้ง", "description": "Displayed under the limit views field on Send", "placeholders": { "accessCount": { @@ -2889,14 +2899,14 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendDetails": { - "message": "Send details", + "message": "รายละเอียด Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendTypeText": { "message": "ข้อความ" }, "sendTypeTextToShare": { - "message": "Text to share" + "message": "ข้อความที่จะแชร์" }, "sendTypeFile": { "message": "ไฟล์" @@ -2906,36 +2916,36 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "maxAccessCountReached": { - "message": "Max access count reached", + "message": "ถึงจำนวนการเข้าถึงสูงสุดแล้ว", "description": "This text will be displayed after a Send has been accessed the maximum amount of times." }, "hideTextByDefault": { - "message": "Hide text by default" + "message": "ซ่อนข้อความโดยค่าเริ่มต้น" }, "expired": { - "message": "Expired" + "message": "หมดอายุ" }, "passwordProtected": { - "message": "Password protected" + "message": "มีการป้องกันด้วยรหัสผ่าน" }, "copyLink": { - "message": "Copy link" + "message": "คัดลอกลิงก์" }, "copySendLink": { - "message": "Copy Send link", + "message": "คัดลอกลิงก์ Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "removePassword": { - "message": "ลบรหัสผ่าน" + "message": "เอารหัสผ่านออก" }, "delete": { "message": "ลบ" }, "removedPassword": { - "message": "รหัสผ่านถูกลบแล้ว" + "message": "เอารหัสผ่านออกแล้ว" }, "deletedSend": { - "message": "Send ถูกลบแล้ว", + "message": "ลบ Send แล้ว", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendLink": { @@ -2943,21 +2953,21 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "disabled": { - "message": "Disabled" + "message": "ปิดใช้งาน" }, "removePasswordConfirmation": { - "message": "คุณต้องการลบรหัสผ่านนี้ใช่หรือไม่" + "message": "ยืนยันที่จะเอารหัสผ่านออกหรือไม่" }, "deleteSend": { "message": "ลบ Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deleteSendConfirmation": { - "message": "คุณต้องการลบ Send นี้ใช่หรือไม่?", + "message": "ยืนยันที่จะลบ Send นี้หรือไม่", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deleteSendPermanentConfirmation": { - "message": "Are you sure you want to permanently delete this Send?", + "message": "ยืนยันที่จะลบ Send นี้ถาวรหรือไม่", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "editSend": { @@ -2965,14 +2975,14 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deletionDate": { - "message": "Deletion date" + "message": "วันที่ลบ" }, "deletionDateDescV2": { - "message": "The Send will be permanently deleted on this date.", + "message": "Send จะถูกลบถาวรในวันนี้", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "expirationDate": { - "message": "Expiration date" + "message": "วันที่หมดอายุ" }, "oneDay": { "message": "1 วัน" @@ -2987,41 +2997,41 @@ } }, "custom": { - "message": "Custom" + "message": "กำหนดเอง" }, "sendPasswordDescV3": { - "message": "Add an optional password for recipients to access this Send.", + "message": "เพิ่มรหัสผ่าน (ไม่บังคับ) เพื่อให้ผู้รับใช้เข้าถึง Send นี้", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "createSend": { - "message": "New Send", + "message": "Send ใหม่", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "newPassword": { - "message": "New password" + "message": "รหัสผ่านใหม่" }, "sendDisabled": { - "message": "Send removed", + "message": "เอา Send ออกแล้ว", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendDisabledWarning": { - "message": "Due to an enterprise policy, you are only able to delete an existing Send.", + "message": "เนื่องจากนโยบายองค์กร คุณสามารถลบ Send ที่มีอยู่ได้เท่านั้น", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "createdSend": { - "message": "Send created", + "message": "สร้าง Send แล้ว", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "createdSendSuccessfully": { - "message": "Send created successfully!", + "message": "สร้าง Send สำเร็จแล้ว!", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendExpiresInHoursSingle": { - "message": "The Send will be available to anyone with the link for the next 1 hour.", + "message": "Send จะพร้อมใช้งานสำหรับทุกคนที่มีลิงก์ในอีก 1 ชั่วโมงข้างหน้า", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendExpiresInHours": { - "message": "The Send will be available to anyone with the link for the next $HOURS$ hours.", + "message": "Send จะพร้อมใช้งานสำหรับทุกคนที่มีลิงก์ในอีก $HOURS$ ชั่วโมงข้างหน้า", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", "placeholders": { "hours": { @@ -3031,11 +3041,11 @@ } }, "sendExpiresInDaysSingle": { - "message": "The Send will be available to anyone with the link for the next 1 day.", + "message": "Send จะพร้อมใช้งานสำหรับทุกคนที่มีลิงก์ในอีก 1 วันข้างหน้า", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendExpiresInDays": { - "message": "The Send will be available to anyone with the link for the next $DAYS$ days.", + "message": "Send จะพร้อมใช้งานสำหรับทุกคนที่มีลิงก์ในอีก $DAYS$ วันข้างหน้า", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", "placeholders": { "days": { @@ -3045,113 +3055,113 @@ } }, "sendLinkCopied": { - "message": "Send link copied", + "message": "คัดลอกลิงก์ Send แล้ว", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "editedSend": { - "message": "Send saved", + "message": "บันทึก Send แล้ว", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendFilePopoutDialogText": { - "message": "Pop out extension?", + "message": "แยกหน้าต่างส่วนขยายหรือไม่", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendFilePopoutDialogDesc": { - "message": "To create a file Send, you need to pop out the extension to a new window.", + "message": "หากต้องการสร้างไฟล์ Send คุณต้องแยกส่วนขยายออกมาเป็นหน้าต่างใหม่", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendLinuxChromiumFileWarning": { - "message": "In order to choose a file, open the extension in the sidebar (if possible) or pop out to a new window by clicking this banner." + "message": "หากต้องการเลือกไฟล์ ให้เปิดส่วนขยายในแถบด้านข้าง (ถ้าทำได้) หรือแยกเป็นหน้าต่างใหม่โดยคลิกที่แบนเนอร์นี้" }, "sendFirefoxFileWarning": { - "message": "In order to choose a file using Firefox, open the extension in the sidebar or pop out to a new window by clicking this banner." + "message": "หากต้องการเลือกไฟล์โดยใช้ Firefox ให้เปิดส่วนขยายในแถบด้านข้างหรือแยกเป็นหน้าต่างใหม่โดยคลิกที่แบนเนอร์นี้" }, "sendSafariFileWarning": { - "message": "In order to choose a file using Safari, pop out to a new window by clicking this banner." + "message": "หากต้องการเลือกไฟล์โดยใช้ Safari ให้แยกเป็นหน้าต่างใหม่โดยคลิกที่แบนเนอร์นี้" }, "popOut": { - "message": "Pop out" + "message": "แยกหน้าต่าง" }, "sendFileCalloutHeader": { - "message": "Before you start" + "message": "ก่อนที่คุณจะเริ่ม" }, "expirationDateIsInvalid": { - "message": "The expiration date provided is not valid." + "message": "วันที่หมดอายุที่ระบุไม่ถูกต้อง" }, "deletionDateIsInvalid": { - "message": "The deletion date provided is not valid." + "message": "วันที่ลบที่ระบุไม่ถูกต้อง" }, "expirationDateAndTimeRequired": { - "message": "An expiration date and time are required." + "message": "จำเป็นต้องระบุวันที่และเวลาหมดอายุ" }, "deletionDateAndTimeRequired": { - "message": "A deletion date and time are required." + "message": "จำเป็นต้องระบุวันที่และเวลาที่จะลบ" }, "dateParsingError": { - "message": "There was an error saving your deletion and expiration dates." + "message": "เกิดข้อผิดพลาดในการบันทึกวันที่ลบและวันที่หมดอายุ" }, "hideYourEmail": { - "message": "Hide your email address from viewers." + "message": "ซ่อนที่อยู่อีเมลของคุณจากผู้ชม" }, "passwordPrompt": { - "message": "การยืนยันให้ป้อนรหัสผ่านหลักอีกครั้ง" + "message": "แจ้งเตือนให้ป้อนรหัสผ่านหลักซ้ำ" }, "passwordConfirmation": { - "message": "Master password confirmation" + "message": "การยืนยันรหัสผ่านหลัก" }, "passwordConfirmationDesc": { - "message": "This action is protected. To continue, please re-enter your master password to verify your identity." + "message": "การดำเนินการนี้ได้รับการป้องกัน หากต้องการดำเนินการต่อ โปรดป้อนรหัสผ่านหลักอีกครั้งเพื่อยืนยันตัวตน" }, "emailVerificationRequired": { - "message": "Email verification required" + "message": "จำเป็นต้องยืนยันอีเมล" }, "emailVerifiedV2": { - "message": "Email verified" + "message": "ยืนยันอีเมลแล้ว" }, "emailVerificationRequiredDesc": { - "message": "You must verify your email to use this feature. You can verify your email in the web vault." + "message": "คุณต้องยืนยันอีเมลเพื่อใช้งานฟีเจอร์นี้ คุณสามารถยืนยันอีเมลได้ในเว็บตู้นิรภัย" }, "masterPasswordSuccessfullySet": { - "message": "Master password successfully set" + "message": "ตั้งรหัสผ่านหลักสำเร็จแล้ว" }, "updatedMasterPassword": { - "message": "Updated master password" + "message": "อัปเดตรหัสผ่านหลักแล้ว" }, "updateMasterPassword": { - "message": "Update master password" + "message": "อัปเดตรหัสผ่านหลัก" }, "updateMasterPasswordWarning": { - "message": "Your master password was recently changed by an administrator in your organization. In order to access the vault, you must update it now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." + "message": "รหัสผ่านหลักของคุณเพิ่งถูกเปลี่ยนโดยผู้ดูแลระบบในองค์กร หากต้องการเข้าถึงตู้นิรภัย คุณต้องอัปเดตรหัสผ่านทันที การดำเนินการนี้จะทำให้คุณออกจากระบบเซสชันปัจจุบัน และต้องเข้าสู่ระบบใหม่ เซสชันที่ใช้งานอยู่บนอุปกรณ์อื่นอาจยังคงใช้งานได้นานสูงสุด 1 ชั่วโมง" }, "updateWeakMasterPasswordWarning": { - "message": "Your master password does not meet one or more of your organization policies. In order to access the vault, you must update your master password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." + "message": "รหัสผ่านหลักของคุณไม่ตรงตามนโยบายองค์กรอย่างน้อยหนึ่งข้อ หากต้องการเข้าถึงตู้นิรภัย คุณต้องอัปเดตรหัสผ่านหลักทันที การดำเนินการนี้จะทำให้คุณออกจากระบบเซสชันปัจจุบัน และต้องเข้าสู่ระบบใหม่ เซสชันที่ใช้งานอยู่บนอุปกรณ์อื่นอาจยังคงใช้งานได้นานสูงสุด 1 ชั่วโมง" }, "tdeDisabledMasterPasswordRequired": { - "message": "Your organization has disabled trusted device encryption. Please set a master password to access your vault." + "message": "องค์กรของคุณปิดใช้งานการเข้ารหัสอุปกรณ์ที่เชื่อถือได้ โปรดตั้งรหัสผ่านหลักเพื่อเข้าถึงตู้นิรภัยของคุณ" }, "resetPasswordPolicyAutoEnroll": { - "message": "Automatic enrollment" + "message": "การลงทะเบียนอัตโนมัติ" }, "resetPasswordAutoEnrollInviteWarning": { - "message": "This organization has an enterprise policy that will automatically enroll you in password reset. Enrollment will allow organization administrators to change your master password." + "message": "องค์กรนี้มีนโยบายองค์กรที่จะลงทะเบียนคุณในการรีเซ็ตรหัสผ่านโดยอัตโนมัติ การลงทะเบียนจะอนุญาตให้ผู้ดูแลระบบขององค์กรเปลี่ยนรหัสผ่านหลักของคุณได้" }, "selectFolder": { - "message": "Select folder..." + "message": "เลือกโฟลเดอร์..." }, "noFoldersFound": { - "message": "No folders found", + "message": "ไม่พบโฟลเดอร์", "description": "Used as a message within the notification bar when no folders are found" }, "orgPermissionsUpdatedMustSetPassword": { - "message": "Your organization permissions were updated, requiring you to set a master password.", + "message": "สิทธิ์ในองค์กรของคุณได้รับการอัปเดต ซึ่งกำหนดให้คุณต้องตั้งรหัสผ่านหลัก", "description": "Used as a card title description on the set password page to explain why the user is there" }, "orgRequiresYouToSetPassword": { - "message": "Your organization requires you to set a master password.", + "message": "องค์กรของคุณกำหนดให้คุณต้องตั้งรหัสผ่านหลัก", "description": "Used as a card title description on the set password page to explain why the user is there" }, "cardMetrics": { - "message": "out of $TOTAL$", + "message": "จาก $TOTAL$", "placeholders": { "total": { "content": "$1", @@ -3160,20 +3170,20 @@ } }, "verificationRequired": { - "message": "Verification required", + "message": "จำเป็นต้องยืนยันตัวตน", "description": "Default title for the user verification dialog." }, "hours": { - "message": "Hours" + "message": "ชั่วโมง" }, "minutes": { - "message": "Minutes" + "message": "นาที" }, "vaultTimeoutPolicyAffectingOptions": { - "message": "Enterprise policy requirements have been applied to your timeout options" + "message": "ข้อกำหนดนโยบายองค์กรถูกนำไปใช้กับตัวเลือกเวลาหมดเวลาของคุณแล้ว" }, "vaultTimeoutPolicyInEffect": { - "message": "Your organization policies have set your maximum allowed vault timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "message": "นโยบายองค์กรกำหนดระยะเวลาหมดเวลาของตู้นิรภัยสูงสุดไว้ที่ $HOURS$ ชั่วโมง $MINUTES$ นาที", "placeholders": { "hours": { "content": "$1", @@ -3186,7 +3196,7 @@ } }, "vaultTimeoutPolicyInEffect1": { - "message": "$HOURS$ hour(s) and $MINUTES$ minute(s) maximum.", + "message": "สูงสุด $HOURS$ ชั่วโมง $MINUTES$ นาที", "placeholders": { "hours": { "content": "$1", @@ -3199,7 +3209,7 @@ } }, "vaultTimeoutPolicyMaximumError": { - "message": "Timeout exceeds the restriction set by your organization: $HOURS$ hour(s) and $MINUTES$ minute(s) maximum", + "message": "เวลาหมดเวลาเกินข้อจำกัดที่องค์กรกำหนด: สูงสุด $HOURS$ ชั่วโมง $MINUTES$ นาที", "placeholders": { "hours": { "content": "$1", @@ -3212,7 +3222,7 @@ } }, "vaultTimeoutPolicyWithActionInEffect": { - "message": "Your organization policies are affecting your vault timeout. Maximum allowed vault timeout is $HOURS$ hour(s) and $MINUTES$ minute(s). Your vault timeout action is set to $ACTION$.", + "message": "นโยบายองค์กรมีผลต่อเวลาหมดเวลาของตู้นิรภัย อนุญาตให้หมดเวลาสูงสุด $HOURS$ ชั่วโมง $MINUTES$ นาที การดำเนินการเมื่อตู้นิรภัยหมดเวลาตั้งค่าไว้ที่ $ACTION$", "placeholders": { "hours": { "content": "$1", @@ -3229,7 +3239,7 @@ } }, "vaultTimeoutActionPolicyInEffect": { - "message": "Your organization policies have set your vault timeout action to $ACTION$.", + "message": "นโยบายองค์กรกำหนดการดำเนินการเมื่อตู้นิรภัยหมดเวลาเป็น $ACTION$", "placeholders": { "action": { "content": "$1", @@ -3238,52 +3248,52 @@ } }, "vaultTimeoutTooLarge": { - "message": "Your vault timeout exceeds the restrictions set by your organization." + "message": "เวลาหมดเวลาของตู้นิรภัยเกินข้อจำกัดที่องค์กรกำหนด" }, "vaultExportDisabled": { - "message": "Vault export unavailable" + "message": "ไม่สามารถส่งออกตู้นิรภัยได้" }, "personalVaultExportPolicyInEffect": { - "message": "One or more organization policies prevents you from exporting your individual vault." + "message": "นโยบายองค์กรอย่างน้อยหนึ่งรายการป้องกันไม่ให้คุณส่งออกตู้นิรภัยส่วนตัว" }, "copyCustomFieldNameInvalidElement": { - "message": "Unable to identify a valid form element. Try inspecting the HTML instead." + "message": "ไม่สามารถระบุองค์ประกอบแบบฟอร์มที่ถูกต้องได้ ลองตรวจสอบ HTML แทน" }, "copyCustomFieldNameNotUnique": { - "message": "No unique identifier found." + "message": "ไม่พบตัวระบุที่ไม่ซ้ำกัน" }, "organizationName": { - "message": "Organization name" + "message": "ชื่อองค์กร" }, "keyConnectorDomain": { - "message": "Key Connector domain" + "message": "โดเมน Key Connector" }, "leaveOrganization": { - "message": "Leave organization" + "message": "ออกจากองค์กร" }, "removeMasterPassword": { - "message": "Remove master password" + "message": "เอารหัสผ่านหลักออก" }, "removedMasterPassword": { - "message": "Master password removed" + "message": "เอารหัสผ่านหลักออกแล้ว" }, "leaveOrganizationConfirmation": { - "message": "Are you sure you want to leave this organization?" + "message": "ยืนยันที่จะออกจากองค์กรนี้หรือไม่" }, "leftOrganization": { - "message": "You have left the organization." + "message": "คุณออกจากองค์กรแล้ว" }, "toggleCharacterCount": { - "message": "Toggle character count" + "message": "สลับการนับตัวอักษร" }, "sessionTimeout": { - "message": "Your session has timed out. Please go back and try logging in again." + "message": "เซสชันของคุณหมดเวลาแล้ว โปรดย้อนกลับและลองเข้าสู่ระบบอีกครั้ง" }, "exportingPersonalVaultTitle": { - "message": "Exporting individual vault" + "message": "กำลังส่งออกตู้นิรภัยส่วนตัว" }, "exportingIndividualVaultDescription": { - "message": "Only the individual vault items associated with $EMAIL$ will be exported. Organization vault items will not be included. Only vault item information will be exported and will not include associated attachments.", + "message": "เฉพาะรายการในตู้นิรภัยส่วนตัวที่เชื่อมโยงกับ $EMAIL$ เท่านั้นที่จะถูกส่งออก รายการในตู้นิรภัยองค์กรจะไม่รวมอยู่ด้วย ระบบจะส่งออกเฉพาะข้อมูลรายการในตู้นิรภัยและไม่รวมไฟล์แนบที่เกี่ยวข้อง", "placeholders": { "email": { "content": "$1", @@ -3292,7 +3302,7 @@ } }, "exportingIndividualVaultWithAttachmentsDescription": { - "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included", + "message": "เฉพาะรายการในตู้นิรภัยส่วนตัวและไฟล์แนบที่เชื่อมโยงกับ $EMAIL$ เท่านั้นที่จะถูกส่งออก รายการในตู้นิรภัยองค์กรจะไม่รวมอยู่ด้วย", "placeholders": { "email": { "content": "$1", @@ -3301,10 +3311,10 @@ } }, "exportingOrganizationVaultTitle": { - "message": "Exporting organization vault" + "message": "กำลังส่งออกตู้นิรภัยองค์กร" }, "exportingOrganizationVaultDesc": { - "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "message": "เฉพาะตู้นิรภัยองค์กรที่เชื่อมโยงกับ $ORGANIZATION$ เท่านั้นที่จะถูกส่งออก รายการในตู้นิรภัยส่วนตัวหรือองค์กรอื่นจะไม่รวมอยู่ด้วย", "placeholders": { "organization": { "content": "$1", @@ -3313,7 +3323,7 @@ } }, "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { - "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "message": "เฉพาะตู้นิรภัยองค์กรที่เชื่อมโยงกับ $ORGANIZATION$ เท่านั้นที่จะถูกส่งออก", "placeholders": { "organization": { "content": "$1", @@ -3322,7 +3332,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", @@ -3331,33 +3341,33 @@ } }, "error": { - "message": "Error" + "message": "ข้อผิดพลาด" }, "decryptionError": { - "message": "Decryption error" + "message": "ข้อผิดพลาดในการถอดรหัส" }, "errorGettingAutoFillData": { - "message": "Error getting autofill data" + "message": "เกิดข้อผิดพลาดในการดึงข้อมูลป้อนอัตโนมัติ" }, "couldNotDecryptVaultItemsBelow": { - "message": "Bitwarden could not decrypt the vault item(s) listed below." + "message": "Bitwarden ไม่สามารถถอดรหัสรายการตู้นิรภัยที่ระบุไว้ด้านล่าง" }, "contactCSToAvoidDataLossPart1": { - "message": "Contact customer success", + "message": "ติดต่อทีม Customer Success", "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "contactCSToAvoidDataLossPart2": { - "message": "to avoid additional data loss.", + "message": "เพื่อหลีกเลี่ยงการสูญหายของข้อมูลเพิ่มเติม", "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "generateUsername": { - "message": "Generate username" + "message": "สร้างชื่อผู้ใช้" }, "generateEmail": { - "message": "Generate email" + "message": "สร้างอีเมล" }, "spinboxBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$.", + "message": "ค่าต้องอยู่ระหว่าง $MIN$ ถึง $MAX$", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -3371,7 +3381,7 @@ } }, "passwordLengthRecommendationHint": { - "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "message": " ใช้ $RECOMMENDED$ ตัวอักษรขึ้นไปเพื่อสร้างรหัสผ่านที่รัดกุม", "description": "Appended to `spinboxBoundariesHint` to recommend a length to the user. This must include any language-specific 'sentence' separator characters (e.g. a space in english).", "placeholders": { "recommended": { @@ -3381,7 +3391,7 @@ } }, "passphraseNumWordsRecommendationHint": { - "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "message": " ใช้ $RECOMMENDED$ คำขึ้นไปเพื่อสร้างวลีรหัสผ่านที่รัดกุม", "description": "Appended to `spinboxBoundariesHint` to recommend a number of words to the user. This must include any language-specific 'sentence' separator characters (e.g. a space in english).", "placeholders": { "recommended": { @@ -3391,46 +3401,46 @@ } }, "plusAddressedEmail": { - "message": "Plus addressed email", + "message": "อีเมลแบบ Plus addressing", "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" }, "plusAddressedEmailDesc": { - "message": "Use your email provider's sub-addressing capabilities." + "message": "ใช้ความสามารถ sub-addressing ของผู้ให้บริการอีเมลของคุณ" }, "catchallEmail": { - "message": "Catch-all email" + "message": "อีเมลแบบ Catch-all" }, "catchallEmailDesc": { - "message": "Use your domain's configured catch-all inbox." + "message": "ใช้อินบ็อกซ์แบบ Catch-all ที่กำหนดค่าไว้สำหรับโดเมนของคุณ" }, "random": { - "message": "Random" + "message": "สุ่ม" }, "randomWord": { - "message": "Random word" + "message": "คำสุ่ม" }, "websiteName": { - "message": "Website name" + "message": "ชื่อเว็บไซต์" }, "service": { - "message": "Service" + "message": "บริการ" }, "forwardedEmail": { - "message": "Forwarded email alias" + "message": "นามแฝงอีเมลแบบส่งต่อ" }, "forwardedEmailDesc": { - "message": "Generate an email alias with an external forwarding service." + "message": "สร้างนามแฝงอีเมลด้วยบริการส่งต่อภายนอก" }, "forwarderDomainName": { - "message": "Email domain", + "message": "โดเมนอีเมล", "description": "Labels the domain name email forwarder service option" }, "forwarderDomainNameHint": { - "message": "Choose a domain that is supported by the selected service", + "message": "เลือกโดเมนที่บริการที่เลือกไว้รองรับ", "description": "Guidance provided for email forwarding services that support multiple email domains." }, "forwarderError": { - "message": "$SERVICENAME$ error: $ERRORMESSAGE$", + "message": "ข้อผิดพลาด $SERVICENAME$: $ERRORMESSAGE$", "description": "Reports an error returned by a forwarding service to the user.", "placeholders": { "servicename": { @@ -3444,11 +3454,11 @@ } }, "forwarderGeneratedBy": { - "message": "Generated by Bitwarden.", + "message": "สร้างโดย Bitwarden", "description": "Displayed with the address on the forwarding service's configuration screen." }, "forwarderGeneratedByWithWebsite": { - "message": "Website: $WEBSITE$. Generated by Bitwarden.", + "message": "เว็บไซต์: $WEBSITE$ สร้างโดย Bitwarden", "description": "Displayed with the address on the forwarding service's configuration screen.", "placeholders": { "WEBSITE": { @@ -3458,7 +3468,7 @@ } }, "forwaderInvalidToken": { - "message": "Invalid $SERVICENAME$ API token", + "message": "โทเค็น API ของ $SERVICENAME$ ไม่ถูกต้อง", "description": "Displayed when the user's API token is empty or rejected by the forwarding service.", "placeholders": { "servicename": { @@ -3468,7 +3478,7 @@ } }, "forwaderInvalidTokenWithMessage": { - "message": "Invalid $SERVICENAME$ API token: $ERRORMESSAGE$", + "message": "โทเค็น API ของ $SERVICENAME$ ไม่ถูกต้อง: $ERRORMESSAGE$", "description": "Displayed when the user's API token is rejected by the forwarding service with an error message.", "placeholders": { "servicename": { @@ -3482,7 +3492,7 @@ } }, "forwaderInvalidOperation": { - "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "message": "$SERVICENAME$ ปฏิเสธคำขอของคุณ โปรดติดต่อผู้ให้บริการเพื่อขอความช่วยเหลือ", "description": "Displayed when the user is forbidden from using the API by the forwarding service.", "placeholders": { "servicename": { @@ -3492,7 +3502,7 @@ } }, "forwaderInvalidOperationWithMessage": { - "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "message": "$SERVICENAME$ ปฏิเสธคำขอของคุณ: $ERRORMESSAGE$", "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", "placeholders": { "servicename": { @@ -3506,7 +3516,7 @@ } }, "forwarderNoAccountId": { - "message": "Unable to obtain $SERVICENAME$ masked email account ID.", + "message": "ไม่สามารถรับ ID บัญชีอีเมลแบบปิดบังตัวตนของ $SERVICENAME$", "description": "Displayed when the forwarding service fails to return an account ID.", "placeholders": { "servicename": { @@ -3516,7 +3526,7 @@ } }, "forwarderNoDomain": { - "message": "Invalid $SERVICENAME$ domain.", + "message": "โดเมน $SERVICENAME$ ไม่ถูกต้อง", "description": "Displayed when the domain is empty or domain authorization failed at the forwarding service.", "placeholders": { "servicename": { @@ -3526,7 +3536,7 @@ } }, "forwarderNoUrl": { - "message": "Invalid $SERVICENAME$ url.", + "message": "URL ของ $SERVICENAME$ ไม่ถูกต้อง", "description": "Displayed when the url of the forwarding service wasn't supplied.", "placeholders": { "servicename": { @@ -3536,7 +3546,7 @@ } }, "forwarderUnknownError": { - "message": "Unknown $SERVICENAME$ error occurred.", + "message": "เกิดข้อผิดพลาด $SERVICENAME$ ที่ไม่รู้จัก", "description": "Displayed when the forwarding service failed due to an unknown error.", "placeholders": { "servicename": { @@ -3546,7 +3556,7 @@ } }, "forwarderUnknownForwarder": { - "message": "Unknown forwarder: '$SERVICENAME$'.", + "message": "ไม่รู้จักบริการส่งต่อ: '$SERVICENAME$'", "description": "Displayed when the forwarding service is not supported.", "placeholders": { "servicename": { @@ -3556,29 +3566,29 @@ } }, "hostname": { - "message": "Hostname", + "message": "ชื่อโฮสต์", "description": "Part of a URL." }, "apiAccessToken": { - "message": "API Access Token" + "message": "โทเค็นการเข้าถึง API" }, "apiKey": { - "message": "API Key" + "message": "คีย์ API" }, "ssoKeyConnectorError": { - "message": "Key connector error: make sure key connector is available and working correctly." + "message": "ข้อผิดพลาด Key Connector: ตรวจสอบให้แน่ใจว่า Key Connector พร้อมใช้งานและทำงานอย่างถูกต้อง" }, "premiumSubcriptionRequired": { - "message": "Premium subscription required" + "message": "จำเป็นต้องสมัครสมาชิกพรีเมียม" }, "organizationIsDisabled": { - "message": "Organization suspended." + "message": "องค์กรถูกระงับ" }, "disabledOrganizationFilterError": { - "message": "Items in suspended Organizations cannot be accessed. Contact your Organization owner for assistance." + "message": "ไม่สามารถเข้าถึงรายการในองค์กรที่ถูกระงับได้ ติดต่อเจ้าขององค์กรเพื่อขอความช่วยเหลือ" }, "loggingInTo": { - "message": "Logging in to $DOMAIN$", + "message": "กำลังเข้าสู่ระบบ $DOMAIN$", "placeholders": { "domain": { "content": "$1", @@ -3587,16 +3597,16 @@ } }, "serverVersion": { - "message": "Server version" + "message": "เวอร์ชันเซิร์ฟเวอร์" }, "selfHostedServer": { - "message": "self-hosted" + "message": "โฮสต์เอง" }, "thirdParty": { - "message": "Third-party" + "message": "บุคคลที่สาม" }, "thirdPartyServerMessage": { - "message": "Connected to third-party server implementation, $SERVERNAME$. Please verify bugs using the official server, or report them to the third-party server.", + "message": "เชื่อมต่อกับเซิร์ฟเวอร์บุคคลที่สาม $SERVERNAME$ แล้ว โปรดตรวจสอบข้อผิดพลาดโดยใช้เซิร์ฟเวอร์อย่างเป็นทางการ หรือรายงานข้อผิดพลาดไปยังเซิร์ฟเวอร์บุคคลที่สาม", "placeholders": { "servername": { "content": "$1", @@ -3605,7 +3615,7 @@ } }, "lastSeenOn": { - "message": "last seen on: $DATE$", + "message": "ใช้งานล่าสุดเมื่อ: $DATE$", "placeholders": { "date": { "content": "$1", @@ -3614,58 +3624,58 @@ } }, "loginWithMasterPassword": { - "message": "Log in with master password" + "message": "เข้าสู่ระบบด้วยรหัสผ่านหลัก" }, "newAroundHere": { - "message": "New around here?" + "message": "เพิ่งเคยใช้งานใช่หรือไม่" }, "rememberEmail": { - "message": "Remember email" + "message": "จดจำอีเมล" }, "loginWithDevice": { - "message": "Log in with device" + "message": "เข้าสู่ระบบด้วยอุปกรณ์" }, "fingerprintPhraseHeader": { - "message": "Fingerprint phrase" + "message": "วลีลายนิ้วมือ" }, "fingerprintMatchInfo": { - "message": "โปรดตรวจสอบให้แน่ใจว่าห้องนิรภัยของคุณปลดล็อกอยู่ และลายนิ้วมือตรงกันบนอุปกรณ์อื่น" + "message": "โปรดตรวจสอบให้แน่ใจว่าตู้นิรภัยปลดล็อกอยู่ และวลีลายนิ้วมือตรงกับอุปกรณ์อีกเครื่อง" }, "resendNotification": { - "message": "Resend notification" + "message": "ส่งการแจ้งเตือนอีกครั้ง" }, "viewAllLogInOptions": { - "message": "View all log in options" + "message": "ดูตัวเลือกการเข้าสู่ระบบทั้งหมด" }, "notificationSentDevice": { - "message": "A notification has been sent to your device." + "message": "ส่งการแจ้งเตือนไปยังอุปกรณ์ของคุณแล้ว" }, "notificationSentDevicePart1": { - "message": "Unlock Bitwarden on your device or on the" + "message": "ปลดล็อก Bitwarden บนอุปกรณ์ของคุณหรือบน" }, "notificationSentDeviceAnchor": { - "message": "web app" + "message": "เว็บแอป" }, "notificationSentDevicePart2": { - "message": "Make sure the Fingerprint phrase matches the one below before approving." + "message": "ตรวจสอบให้แน่ใจว่าวลีลายนิ้วมือตรงกับด้านล่างก่อนอนุมัติ" }, "aNotificationWasSentToYourDevice": { - "message": "A notification was sent to your device" + "message": "ส่งการแจ้งเตือนไปยังอุปกรณ์ของคุณแล้ว" }, "youWillBeNotifiedOnceTheRequestIsApproved": { - "message": "You will be notified once the request is approved" + "message": "คุณจะได้รับแจ้งเมื่อคำขอได้รับการอนุมัติ" }, "needAnotherOptionV1": { - "message": "Need another option?" + "message": "ต้องการตัวเลือกอื่นหรือไม่" }, "loginInitiated": { - "message": "Login initiated" + "message": "เริ่มการเข้าสู่ระบบแล้ว" }, "logInRequestSent": { - "message": "Request sent" + "message": "ส่งคำขอแล้ว" }, "loginRequestApprovedForEmailOnDevice": { - "message": "Login request approved for $EMAIL$ on $DEVICE$", + "message": "อนุมัติคำขอเข้าสู่ระบบสำหรับ $EMAIL$ บน $DEVICE$ แล้ว", "placeholders": { "email": { "content": "$1", @@ -3678,40 +3688,40 @@ } }, "youDeniedLoginAttemptFromAnotherDevice": { - "message": "You denied a login attempt from another device. If this was you, try to log in with the device again." + "message": "คุณปฏิเสธความพยายามเข้าสู่ระบบจากอุปกรณ์อื่น หากนี่คือคุณ ให้ลองเข้าสู่ระบบด้วยอุปกรณ์อีกครั้ง" }, "device": { - "message": "Device" + "message": "อุปกรณ์" }, "loginStatus": { - "message": "Login status" + "message": "สถานะการเข้าสู่ระบบ" }, "masterPasswordChanged": { - "message": "Master password saved" + "message": "บันทึกรหัสผ่านหลักแล้ว" }, "exposedMasterPassword": { - "message": "Exposed Master Password" + "message": "รหัสผ่านหลักรั่วไหล" }, "exposedMasterPasswordDesc": { - "message": "Password found in a data breach. Use a unique password to protect your account. Are you sure you want to use an exposed password?" + "message": "พบรหัสผ่านในเหตุการณ์ข้อมูลรั่วไหล ใช้รหัสผ่านที่ไม่ซ้ำกันเพื่อปกป้องบัญชีของคุณ ยืนยันที่จะใช้รหัสผ่านที่รั่วไหลหรือไม่" }, "weakAndExposedMasterPassword": { - "message": "Weak and Exposed Master Password" + "message": "รหัสผ่านหลักไม่ปลอดภัยและรั่วไหล" }, "weakAndBreachedMasterPasswordDesc": { - "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" + "message": "พบรหัสผ่านที่ไม่ปลอดภัยและอยู่ในเหตุการณ์ข้อมูลรั่วไหล ใช้รหัสผ่านที่รัดกุมและไม่ซ้ำกันเพื่อปกป้องบัญชีของคุณ ยืนยันที่จะใช้รหัสผ่านนี้หรือไม่" }, "checkForBreaches": { - "message": "Check known data breaches for this password" + "message": "ตรวจสอบเหตุการณ์ข้อมูลรั่วไหลสำหรับรหัสผ่านนี้" }, "important": { - "message": "Important:" + "message": "สำคัญ:" }, "masterPasswordHint": { - "message": "Your master password cannot be recovered if you forget it!" + "message": "รหัสผ่านหลักไม่สามารถกู้คืนได้หากคุณลืม!" }, "characterMinimum": { - "message": "$LENGTH$ character minimum", + "message": "ขั้นต่ำ $LENGTH$ ตัวอักษร", "placeholders": { "length": { "content": "$1", @@ -3720,10 +3730,10 @@ } }, "autofillPageLoadPolicyActivated": { - "message": "Your organization policies have turned on autofill on page load." + "message": "นโยบายองค์กรของคุณเปิดใช้งานการป้อนอัตโนมัติเมื่อโหลดหน้าเว็บ" }, "autofillSelectInfoWithCommand": { - "message": "Select an item from this screen, use the shortcut $COMMAND$, or explore other options in settings.", + "message": "เลือกรายการจากหน้านี้ ใช้ทางลัด $COMMAND$ หรือสำรวจตัวเลือกอื่น ๆ ในการตั้งค่า", "placeholders": { "command": { "content": "$1", @@ -3732,31 +3742,31 @@ } }, "autofillSelectInfoWithoutCommand": { - "message": "Select an item from this screen, or explore other options in settings." + "message": "เลือกรายการจากหน้านี้ หรือสำรวจตัวเลือกอื่น ๆ ในการตั้งค่า" }, "gotIt": { - "message": "Got it" + "message": "เข้าใจแล้ว" }, "autofillSettings": { - "message": "Autofill settings" + "message": "การตั้งค่าการป้อนอัตโนมัติ" }, "autofillKeyboardShortcutSectionTitle": { - "message": "Autofill shortcut" + "message": "ทางลัดการป้อนอัตโนมัติ" }, "autofillKeyboardShortcutUpdateLabel": { - "message": "Change shortcut" + "message": "เปลี่ยนทางลัด" }, "autofillKeyboardManagerShortcutsLabel": { - "message": "Manage shortcuts" + "message": "จัดการทางลัด" }, "autofillShortcut": { - "message": "Autofill keyboard shortcut" + "message": "คีย์ลัดการป้อนอัตโนมัติ" }, "autofillLoginShortcutNotSet": { - "message": "The autofill login shortcut is not set. Change this in the browser's settings." + "message": "ยังไม่ได้ตั้งค่าทางลัดการป้อนข้อมูลเข้าสู่ระบบ เปลี่ยนค่านี้ได้ในการตั้งค่าของเบราว์เซอร์" }, "autofillLoginShortcutText": { - "message": "The autofill login shortcut is $COMMAND$. Manage all shortcuts in the browser's settings.", + "message": "ทางลัดการป้อนข้อมูลเข้าสู่ระบบคือ $COMMAND$ จัดการทางลัดทั้งหมดได้ในการตั้งค่าของเบราว์เซอร์", "placeholders": { "command": { "content": "$1", @@ -3765,7 +3775,7 @@ } }, "autofillShortcutTextSafari": { - "message": "Default autofill shortcut: $COMMAND$.", + "message": "ทางลัดการป้อนอัตโนมัติเริ่มต้น: $COMMAND$", "placeholders": { "command": { "content": "$1", @@ -3774,34 +3784,34 @@ } }, "opensInANewWindow": { - "message": "Opens in a new window" + "message": "เปิดในหน้าต่างใหม่" }, "rememberThisDeviceToMakeFutureLoginsSeamless": { - "message": "Remember this device to make future logins seamless" + "message": "จดจำอุปกรณ์นี้เพื่อให้การเข้าสู่ระบบในอนาคตราบรื่นยิ่งขึ้น" }, "manageDevices": { - "message": "Manage devices" + "message": "จัดการอุปกรณ์" }, "currentSession": { - "message": "Current session" + "message": "เซสชันปัจจุบัน" }, "mobile": { - "message": "Mobile", + "message": "มือถือ", "description": "Mobile app" }, "extension": { - "message": "Extension", + "message": "ส่วนขยาย", "description": "Browser extension/addon" }, "desktop": { - "message": "Desktop", + "message": "เดสก์ท็อป", "description": "Desktop app" }, "webVault": { - "message": "Web vault" + "message": "เว็บตู้นิรภัย" }, "webApp": { - "message": "Web app" + "message": "เว็บแอป" }, "cli": { "message": "CLI" @@ -3811,22 +3821,22 @@ "description": "Software Development Kit" }, "requestPending": { - "message": "Request pending" + "message": "คำขอรอดำเนินการ" }, "firstLogin": { - "message": "First login" + "message": "เข้าสู่ระบบครั้งแรก" }, "trusted": { - "message": "Trusted" + "message": "เชื่อถือได้" }, "needsApproval": { - "message": "Needs approval" + "message": "รอการอนุมัติ" }, "devices": { - "message": "Devices" + "message": "อุปกรณ์" }, "accessAttemptBy": { - "message": "Access attempt by $EMAIL$", + "message": "ความพยายามเข้าถึงโดย $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -3835,31 +3845,31 @@ } }, "confirmAccess": { - "message": "Confirm access" + "message": "ยืนยันการเข้าถึง" }, "denyAccess": { - "message": "Deny access" + "message": "ปฏิเสธการเข้าถึง" }, "time": { - "message": "Time" + "message": "เวลา" }, "deviceType": { - "message": "Device Type" + "message": "ประเภทอุปกรณ์" }, "loginRequest": { - "message": "Login request" + "message": "คำขอเข้าสู่ระบบ" }, "thisRequestIsNoLongerValid": { - "message": "This request is no longer valid." + "message": "คำขอนี้ใช้ไม่ได้อีกต่อไป" }, "loginRequestHasAlreadyExpired": { - "message": "Login request has already expired." + "message": "คำขอเข้าสู่ระบบหมดอายุแล้ว" }, "justNow": { - "message": "Just now" + "message": "เมื่อสักครู่" }, "requestedXMinutesAgo": { - "message": "Requested $MINUTES$ minutes ago", + "message": "ร้องขอเมื่อ $MINUTES$ นาทีที่แล้ว", "placeholders": { "minutes": { "content": "$1", @@ -3868,136 +3878,136 @@ } }, "deviceApprovalRequired": { - "message": "Device approval required. Select an approval option below:" + "message": "จำเป็นต้องอนุมัติอุปกรณ์ เลือกตัวเลือกการอนุมัติด้านล่าง:" }, "deviceApprovalRequiredV2": { - "message": "Device approval required" + "message": "จำเป็นต้องอนุมัติอุปกรณ์" }, "selectAnApprovalOptionBelow": { - "message": "Select an approval option below" + "message": "เลือกตัวเลือกการอนุมัติด้านล่าง" }, "rememberThisDevice": { - "message": "Remember this device" + "message": "จดจำอุปกรณ์นี้" }, "uncheckIfPublicDevice": { - "message": "Uncheck if using a public device" + "message": "ไม่ต้องเลือกหากใช้อุปกรณ์สาธารณะ" }, "approveFromYourOtherDevice": { - "message": "Approve from your other device" + "message": "อนุมัติจากอุปกรณ์อื่นของคุณ" }, "requestAdminApproval": { - "message": "Request admin approval" + "message": "ขอการอนุมัติจากผู้ดูแลระบบ" }, "unableToCompleteLogin": { - "message": "Unable to complete login" + "message": "ไม่สามารถเข้าสู่ระบบให้เสร็จสมบูรณ์ได้" }, "loginOnTrustedDeviceOrAskAdminToAssignPassword": { - "message": "You need to log in on a trusted device or ask your administrator to assign you a password." + "message": "คุณต้องเข้าสู่ระบบบนอุปกรณ์ที่เชื่อถือได้ หรือขอให้ผู้ดูแลระบบกำหนดรหัสผ่านให้คุณ" }, "ssoIdentifierRequired": { - "message": "Organization SSO identifier is required." + "message": "จำเป็นต้องระบุตัวระบุ SSO ขององค์กร" }, "creatingAccountOn": { - "message": "Creating account on" + "message": "กำลังสร้างบัญชีบน" }, "checkYourEmail": { - "message": "Check your email" + "message": "ตรวจสอบอีเมลของคุณ" }, "followTheLinkInTheEmailSentTo": { - "message": "Follow the link in the email sent to" + "message": "คลิกลิงก์ในอีเมลที่ส่งไปที่" }, "andContinueCreatingYourAccount": { - "message": "and continue creating your account." + "message": "และดำเนินการสร้างบัญชีต่อ" }, "noEmail": { - "message": "No email?" + "message": "ไม่ได้รับอีเมลใช่ไหม" }, "goBack": { - "message": "Go back" + "message": "ย้อนกลับ" }, "toEditYourEmailAddress": { - "message": "to edit your email address." + "message": "เพื่อแก้ไขที่อยู่อีเมลของคุณ" }, "eu": { "message": "EU", "description": "European Union" }, "accessDenied": { - "message": "Access denied. You do not have permission to view this page." + "message": "การเข้าถึงถูกปฏิเสธ คุณไม่มีสิทธิ์ดูหน้านี้" }, "general": { - "message": "General" + "message": "ทั่วไป" }, "display": { - "message": "Display" + "message": "การแสดงผล" }, "accountSuccessfullyCreated": { - "message": "Account successfully created!" + "message": "สร้างบัญชีสำเร็จแล้ว!" }, "adminApprovalRequested": { - "message": "Admin approval requested" + "message": "ส่งคำขออนุมัติจากผู้ดูแลระบบแล้ว" }, "adminApprovalRequestSentToAdmins": { - "message": "Your request has been sent to your admin." + "message": "ส่งคำขอของคุณไปยังผู้ดูแลระบบแล้ว" }, "troubleLoggingIn": { - "message": "Trouble logging in?" + "message": "มีปัญหาในการเข้าสู่ระบบใช่หรือไม่" }, "loginApproved": { - "message": "Login approved" + "message": "อนุมัติการเข้าสู่ระบบแล้ว" }, "userEmailMissing": { - "message": "User email missing" + "message": "ไม่พบอีเมลผู้ใช้" }, "activeUserEmailNotFoundLoggingYouOut": { - "message": "Active user email not found. Logging you out." + "message": "ไม่พบอีเมลผู้ใช้ที่ใช้งานอยู่ กำลังออกจากระบบ" }, "deviceTrusted": { - "message": "Device trusted" + "message": "อุปกรณ์ที่เชื่อถือได้" }, "trustOrganization": { - "message": "Trust organization" + "message": "เชื่อถือองค์กร" }, "trust": { - "message": "Trust" + "message": "เชื่อถือ" }, "doNotTrust": { - "message": "Do not trust" + "message": "ไม่เชื่อถือ" }, "organizationNotTrusted": { - "message": "Organization is not trusted" + "message": "องค์กรไม่น่าเชื่อถือ" }, "emergencyAccessTrustWarning": { - "message": "เพื่อความปลอดภัยของบัญชีของคุณ โปรดยืนยันว่าคุณได้ให้สิทธิ์การเข้าถึงในกรณีฉุกเฉินแก่ผู้ใช้นี้ และลายนิ้วมือของผู้ใช้ตรงกับที่แสดงในบัญชีของพวกเขาเท่านั้น" + "message": "เพื่อความปลอดภัยของบัญชี โปรดยืนยันเฉพาะเมื่อคุณได้ให้สิทธิ์การเข้าถึงฉุกเฉินแก่ผู้ใช้นี้ และลายนิ้วมือของพวกเขาตรงกับที่แสดงในบัญชีของพวกเขา" }, "orgTrustWarning": { - "message": "เพื่อความปลอดภัยของบัญชีของคุณ ให้ดำเนินการต่อเมื่อคุณเป็นสมาชิกขององค์กรนี้, ได้เปิดใช้งานการกู้คืนบัญชี, และลายนิ้วมือที่แสดงด้านล่างตรงกับลายนิ้วมือขององค์กรเท่านั้น" + "message": "เพื่อความปลอดภัยของบัญชี โปรดดำเนินการต่อเฉพาะเมื่อคุณเป็นสมาชิกขององค์กรนี้ เปิดใช้งานการกู้คืนบัญชี และลายนิ้วมือที่แสดงด้านล่างตรงกับลายนิ้วมือขององค์กร" }, "orgTrustWarning1": { - "message": "This organization has an Enterprise policy that will enroll you in account recovery. Enrollment will allow organization administrators to change your password. Only proceed if you recognize this organization and the fingerprint phrase displayed below matches the organization's fingerprint." + "message": "องค์กรนี้มีนโยบายองค์กรที่จะลงทะเบียนคุณในการกู้คืนบัญชี การลงทะเบียนจะอนุญาตให้ผู้ดูแลระบบองค์กรเปลี่ยนรหัสผ่านของคุณได้ โปรดดำเนินการต่อเฉพาะเมื่อคุณรู้จักองค์กรนี้ และวลีลายนิ้วมือที่แสดงด้านล่างตรงกับลายนิ้วมือขององค์กร" }, "trustUser": { - "message": "Trust user" + "message": "เชื่อถือผู้ใช้" }, "sendsTitleNoItems": { - "message": "Send sensitive information safely", + "message": "ส่งข้อมูลสำคัญอย่างปลอดภัยด้วย Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendsBodyNoItems": { - "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "message": "แชร์ไฟล์และข้อมูลอย่างปลอดภัยกับทุกคน บนทุกแพลตฟอร์ม ข้อมูลของคุณจะได้รับการเข้ารหัสแบบ End-to-end และจำกัดการเข้าถึง", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "inputRequired": { - "message": "Input is required." + "message": "จำเป็นต้องระบุข้อมูล" }, "required": { - "message": "required" + "message": "จำเป็น" }, "search": { - "message": "Search" + "message": "ค้นหา" }, "inputMinLength": { - "message": "Input must be at least $COUNT$ characters long.", + "message": "ข้อมูลต้องมีความยาวอย่างน้อย $COUNT$ ตัวอักษร", "placeholders": { "count": { "content": "$1", @@ -4006,7 +4016,7 @@ } }, "inputMaxLength": { - "message": "Input must not exceed $COUNT$ characters in length.", + "message": "ข้อมูลต้องมีความยาวไม่เกิน $COUNT$ ตัวอักษร", "placeholders": { "count": { "content": "$1", @@ -4015,7 +4025,7 @@ } }, "inputForbiddenCharacters": { - "message": "The following characters are not allowed: $CHARACTERS$", + "message": "ไม่อนุญาตให้ใช้อักขระต่อไปนี้: $CHARACTERS$", "placeholders": { "characters": { "content": "$1", @@ -4024,7 +4034,7 @@ } }, "inputMinValue": { - "message": "Input value must be at least $MIN$.", + "message": "ค่าที่ป้อนต้องมีค่าอย่างน้อย $MIN$", "placeholders": { "min": { "content": "$1", @@ -4033,7 +4043,7 @@ } }, "inputMaxValue": { - "message": "Input value must not exceed $MAX$.", + "message": "ค่าที่ป้อนต้องมีค่าไม่เกิน $MAX$", "placeholders": { "max": { "content": "$1", @@ -4042,17 +4052,17 @@ } }, "multipleInputEmails": { - "message": "1 or more emails are invalid" + "message": "อีเมลอย่างน้อย 1 รายการไม่ถูกต้อง" }, "inputTrimValidator": { - "message": "Input must not contain only whitespace.", + "message": "ข้อมูลต้องไม่ประกอบด้วยช่องว่างเพียงอย่างเดียว", "description": "Notification to inform the user that a form's input can't contain only whitespace." }, "inputEmail": { - "message": "Input is not an email address." + "message": "ข้อมูลไม่ใช่ที่อยู่อีเมล" }, "fieldsNeedAttention": { - "message": "$COUNT$ field(s) above need your attention.", + "message": "ฟิลด์ด้านบน $COUNT$ รายการต้องการการตรวจสอบ", "placeholders": { "count": { "content": "$1", @@ -4061,10 +4071,10 @@ } }, "singleFieldNeedsAttention": { - "message": "1 field needs your attention." + "message": "1 ฟิลด์ต้องการการตรวจสอบ" }, "multipleFieldsNeedAttention": { - "message": "$COUNT$ fields need your attention.", + "message": "$COUNT$ ฟิลด์ต้องการการตรวจสอบ", "placeholders": { "count": { "content": "$1", @@ -4073,22 +4083,22 @@ } }, "selectPlaceholder": { - "message": "-- Select --" + "message": "-- เลือก --" }, "multiSelectPlaceholder": { - "message": "-- Type to filter --" + "message": "-- พิมพ์เพื่อกรอง --" }, "multiSelectLoading": { - "message": "Retrieving options..." + "message": "กำลังดึงตัวเลือก..." }, "multiSelectNotFound": { - "message": "No items found" + "message": "ไม่พบรายการ" }, "multiSelectClearAll": { - "message": "Clear all" + "message": "ล้างทั้งหมด" }, "plusNMore": { - "message": "+ $QUANTITY$ more", + "message": "+ อีก $QUANTITY$ รายการ", "placeholders": { "quantity": { "content": "$1", @@ -4097,141 +4107,141 @@ } }, "submenu": { - "message": "Submenu" + "message": "เมนูย่อย" }, "toggleCollapse": { - "message": "Toggle collapse", + "message": "ย่อ/ขยาย", "description": "Toggling an expand/collapse state." }, "aliasDomain": { - "message": "Alias domain" + "message": "โดเมนนามแฝง" }, "autofillOnPageLoadSetToDefault": { - "message": "Autofill on page load set to use default setting.", + "message": "ตั้งค่าการป้อนอัตโนมัติเมื่อโหลดหน้าเว็บเป็นค่าเริ่มต้นแล้ว", "description": "Toast message for informing the user that autofill on page load has been set to the default setting." }, "cannotAutofill": { - "message": "Cannot autofill" + "message": "ไม่สามารถป้อนอัตโนมัติได้" }, "cannotAutofillExactMatch": { - "message": "Default matching is set to 'Exact Match'. The current website does not exactly match the saved login details for this item." + "message": "การจับคู่เริ่มต้นตั้งค่าไว้ที่ 'ตรงกันทุกตัวอักษร' เว็บไซต์ปัจจุบันไม่ตรงกับรายละเอียดข้อมูลเข้าสู่ระบบที่บันทึกไว้สำหรับรายการนี้อย่างถูกต้อง" }, "okay": { - "message": "Okay" + "message": "ตกลง" }, "toggleSideNavigation": { - "message": "Toggle side navigation" + "message": "สลับแถบนำทางด้านข้าง" }, "skipToContent": { - "message": "Skip to content" + "message": "ข้ามไปที่เนื้อหา" }, "bitwardenOverlayButton": { - "message": "Bitwarden autofill menu button", + "message": "ปุ่มเมนูป้อนอัตโนมัติ Bitwarden", "description": "Page title for the iframe containing the overlay button" }, "toggleBitwardenVaultOverlay": { - "message": "Toggle Bitwarden autofill menu", + "message": "สลับเมนูป้อนอัตโนมัติ Bitwarden", "description": "Screen reader and tool tip label for the overlay button" }, "bitwardenVault": { - "message": "Bitwarden autofill menu", + "message": "เมนูป้อนอัตโนมัติ Bitwarden", "description": "Page title in overlay" }, "unlockYourAccountToViewMatchingLogins": { - "message": "Unlock your account to view matching logins", + "message": "ปลดล็อกบัญชีเพื่อดูข้อมูลเข้าสู่ระบบที่ตรงกัน", "description": "Text to display in overlay when the account is locked." }, "unlockYourAccountToViewAutofillSuggestions": { - "message": "Unlock your account to view autofill suggestions", + "message": "ปลดล็อกบัญชีเพื่อดูคำแนะนำการป้อนอัตโนมัติ", "description": "Text to display in overlay when the account is locked." }, "unlockAccount": { - "message": "Unlock account", + "message": "ปลดล็อกบัญชี", "description": "Button text to display in overlay when the account is locked." }, "unlockAccountAria": { - "message": "Unlock your account, opens in a new window", + "message": "ปลดล็อกบัญชี เปิดในหน้าต่างใหม่", "description": "Screen reader text (aria-label) for unlock account button in overlay" }, "totpCodeAria": { - "message": "Time-based One-Time Password Verification Code", + "message": "รหัสยืนยันรหัสผ่านใช้ครั้งเดียวตามเวลา", "description": "Aria label for the totp code displayed in the inline menu for autofill" }, "totpSecondsSpanAria": { - "message": "Time remaining before current TOTP expires", + "message": "เวลาที่เหลือก่อน TOTP ปัจจุบันหมดอายุ", "description": "Aria label for the totp seconds displayed in the inline menu for autofill" }, "fillCredentialsFor": { - "message": "Fill credentials for", + "message": "ป้อนข้อมูลประจำตัวสำหรับ", "description": "Screen reader text for when overlay item is in focused" }, "partialUsername": { - "message": "Partial username", + "message": "ชื่อผู้ใช้บางส่วน", "description": "Screen reader text for when a login item is focused where a partial username is displayed. SR will announce this phrase before reading the text of the partial username" }, "noItemsToShow": { - "message": "No items to show", + "message": "ไม่มีรายการที่จะแสดง", "description": "Text to show in overlay if there are no matching items" }, "newItem": { - "message": "New item", + "message": "รายการใหม่", "description": "Button text to display in overlay when there are no matching items" }, "addNewVaultItem": { - "message": "Add new vault item", + "message": "เพิ่มรายการตู้นิรภัยใหม่", "description": "Screen reader text (aria-label) for new item button in overlay" }, "newLogin": { - "message": "New login", + "message": "ข้อมูลเข้าสู่ระบบใหม่", "description": "Button text to display within inline menu when there are no matching items on a login field" }, "addNewLoginItemAria": { - "message": "Add new vault login item, opens in a new window", + "message": "เพิ่มข้อมูลเข้าสู่ระบบตู้นิรภัยใหม่ เปิดในหน้าต่างใหม่", "description": "Screen reader text (aria-label) for new login button within inline menu" }, "newCard": { - "message": "New card", + "message": "บัตรใหม่", "description": "Button text to display within inline menu when there are no matching items on a credit card field" }, "addNewCardItemAria": { - "message": "Add new vault card item, opens in a new window", + "message": "เพิ่มรายการบัตรตู้นิรภัยใหม่ เปิดในหน้าต่างใหม่", "description": "Screen reader text (aria-label) for new card button within inline menu" }, "newIdentity": { - "message": "New identity", + "message": "ข้อมูลระบุตัวตนใหม่", "description": "Button text to display within inline menu when there are no matching items on an identity field" }, "addNewIdentityItemAria": { - "message": "Add new vault identity item, opens in a new window", + "message": "เพิ่มข้อมูลระบุตัวตนตู้นิรภัยใหม่ เปิดในหน้าต่างใหม่", "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 พร้อมใช้งาน กดปุ่มลูกศรลงเพื่อเลือก", "description": "Screen reader text for announcing when the overlay opens on the page" }, "turnOn": { - "message": "Turn on" + "message": "เปิด" }, "ignore": { - "message": "Ignore" + "message": "เพิกเฉย" }, "importError": { - "message": "Import error" + "message": "ข้อผิดพลาดในการนำเข้า" }, "importErrorDesc": { - "message": "There was a problem with the data you tried to import. Please resolve the errors listed below in your source file and try again." + "message": "เกิดปัญหากับข้อมูลที่คุณพยายามนำเข้า โปรดแก้ไขข้อผิดพลาดที่ระบุด้านล่างในไฟล์ต้นฉบับแล้วลองอีกครั้ง" }, "resolveTheErrorsBelowAndTryAgain": { - "message": "Resolve the errors below and try again." + "message": "แก้ไขข้อผิดพลาดด้านล่างแล้วลองอีกครั้ง" }, "description": { - "message": "Description" + "message": "คำอธิบาย" }, "importSuccess": { - "message": "Data successfully imported" + "message": "นำเข้าข้อมูลสำเร็จแล้ว" }, "importSuccessNumberOfItems": { - "message": "A total of $AMOUNT$ items were imported.", + "message": "นำเข้าข้อมูลทั้งหมด $AMOUNT$ รายการ", "placeholders": { "amount": { "content": "$1", @@ -4240,46 +4250,46 @@ } }, "tryAgain": { - "message": "Try again" + "message": "ลองอีกครั้ง" }, "verificationRequiredForActionSetPinToContinue": { - "message": "Verification required for this action. Set a PIN to continue." + "message": "จำเป็นต้องยืนยันตัวตนสำหรับการดำเนินการนี้ ตั้งค่า PIN เพื่อดำเนินการต่อ" }, "setPin": { - "message": "ตั้ง PIN" + "message": "ตั้งค่า PIN" }, "verifyWithBiometrics": { - "message": "Verify with biometrics" + "message": "ยืนยันด้วยไบโอเมตริก" }, "awaitingConfirmation": { - "message": "Awaiting confirmation" + "message": "รอการยืนยัน" }, "couldNotCompleteBiometrics": { - "message": "Could not complete biometrics." + "message": "ไม่สามารถดำเนินการไบโอเมตริกให้เสร็จสิ้นได้" }, "needADifferentMethod": { - "message": "Need a different method?" + "message": "ต้องการวิธีอื่นหรือไม่" }, "useMasterPassword": { - "message": "Use master password" + "message": "ใช้รหัสผ่านหลัก" }, "usePin": { - "message": "Use PIN" + "message": "ใช้ PIN" }, "useBiometrics": { - "message": "Use biometrics" + "message": "ใช้ไบโอเมตริก" }, "enterVerificationCodeSentToEmail": { - "message": "Enter the verification code that was sent to your email." + "message": "ป้อนรหัสยืนยันที่ส่งไปที่อีเมลของคุณ" }, "resendCode": { - "message": "Resend code" + "message": "ส่งรหัสอีกครั้ง" }, "total": { - "message": "Total" + "message": "รวม" }, "importWarning": { - "message": "You are importing data to $ORGANIZATION$. Your data may be shared with members of this organization. Do you want to proceed?", + "message": "คุณกำลังนำเข้าข้อมูลไปยัง $ORGANIZATION$ ข้อมูลของคุณอาจถูกแชร์กับสมาชิกขององค์กรนี้ ต้องการดำเนินการต่อหรือไม่", "placeholders": { "organization": { "content": "$1", @@ -4288,67 +4298,67 @@ } }, "duoHealthCheckResultsInNullAuthUrlError": { - "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + "message": "เกิดข้อผิดพลาดในการเชื่อมต่อกับบริการ Duo ใช้วิธีการเข้าสู่ระบบ 2 ขั้นตอนอื่น หรือติดต่อ Duo เพื่อขอความช่วยเหลือ" }, "duoRequiredForAccount": { - "message": "Duo two-step login is required for your account." + "message": "บัญชีของคุณจำเป็นต้องใช้การเข้าสู่ระบบ 2 ขั้นตอนผ่าน Duo" }, "popoutExtension": { - "message": "Popout extension" + "message": "แยกหน้าต่างส่วนขยาย" }, "launchDuo": { - "message": "Launch Duo" + "message": "เปิดใช้งาน Duo" }, "importFormatError": { - "message": "Data is not formatted correctly. Please check your import file and try again." + "message": "รูปแบบข้อมูลไม่ถูกต้อง โปรดตรวจสอบไฟล์นำเข้าแล้วลองอีกครั้ง" }, "importNothingError": { - "message": "Nothing was imported." + "message": "ไม่มีการนำเข้าข้อมูล" }, "importEncKeyError": { - "message": "Error decrypting the exported file. Your encryption key does not match the encryption key used export the data." + "message": "เกิดข้อผิดพลาดในการถอดรหัสไฟล์ที่ส่งออก กุญแจเข้ารหัสของคุณไม่ตรงกับกุญแจเข้ารหัสที่ใช้ส่งออกข้อมูล" }, "invalidFilePassword": { - "message": "Invalid file password, please use the password you entered when you created the export file." + "message": "รหัสผ่านไฟล์ไม่ถูกต้อง โปรดใช้รหัสผ่านที่คุณป้อนตอนสร้างไฟล์ส่งออก" }, "destination": { - "message": "Destination" + "message": "ปลายทาง" }, "learnAboutImportOptions": { - "message": "Learn about your import options" + "message": "เรียนรู้เกี่ยวกับตัวเลือกการนำเข้า" }, "selectImportFolder": { - "message": "Select a folder" + "message": "เลือกโฟลเดอร์" }, "selectImportCollection": { - "message": "Select a collection" + "message": "เลือกคอลเลกชัน" }, "importTargetHintCollection": { - "message": "Select this option if you want the imported file contents moved to a collection" + "message": "เลือกตัวเลือกนี้หากต้องการย้ายเนื้อหาไฟล์ที่นำเข้าไปยังคอลเลกชัน" }, "importTargetHintFolder": { - "message": "Select this option if you want the imported file contents moved to a folder" + "message": "เลือกตัวเลือกนี้หากต้องการย้ายเนื้อหาไฟล์ที่นำเข้าไปยังโฟลเดอร์" }, "importUnassignedItemsError": { - "message": "File contains unassigned items." + "message": "ไฟล์มีรายการที่ไม่ได้มอบหมาย" }, "selectFormat": { - "message": "Select the format of the import file" + "message": "เลือกรูปแบบของไฟล์นำเข้า" }, "selectImportFile": { - "message": "Select the import file" + "message": "เลือกไฟล์นำเข้า" }, "chooseFile": { "message": "เลือกไฟล์" }, "noFileChosen": { - "message": "ไม่มีไฟล์ที่เลือก" + "message": "ไม่ได้เลือกไฟล์" }, "orCopyPasteFileContents": { - "message": "or copy/paste the import file contents" + "message": "หรือคัดลอก/วางเนื้อหาไฟล์นำเข้า" }, "instructionsFor": { - "message": "$NAME$ Instructions", + "message": "คำแนะนำสำหรับ $NAME$", "description": "The title for the import tool instructions.", "placeholders": { "name": { @@ -4358,200 +4368,200 @@ } }, "confirmVaultImport": { - "message": "Confirm vault import" + "message": "ยืนยันการนำเข้าตู้นิรภัย" }, "confirmVaultImportDesc": { - "message": "This file is password-protected. Please enter the file password to import data." + "message": "ไฟล์นี้มีการป้องกันด้วยรหัสผ่าน โปรดป้อนรหัสผ่านไฟล์เพื่อนำเข้าข้อมูล" }, "confirmFilePassword": { - "message": "Confirm file password" + "message": "ยืนยันรหัสผ่านไฟล์" }, "exportSuccess": { - "message": "Vault data exported" + "message": "ส่งออกข้อมูลตู้นิรภัยแล้ว" }, "typePasskey": { - "message": "Passkey" + "message": "พาสคีย์" }, "accessing": { "message": "กำลังเข้าถึง" }, "loggedInExclamation": { - "message": "Logged in!" + "message": "เข้าสู่ระบบแล้ว!" }, "passkeyNotCopied": { - "message": "Passkey will not be copied" + "message": "จะไม่คัดลอกพาสคีย์" }, "passkeyNotCopiedAlert": { - "message": "The passkey will not be copied to the cloned item. Do you want to continue cloning this item?" + "message": "พาสคีย์จะไม่ถูกคัดลอกไปยังรายการที่โคลน ต้องการโคลนรายการนี้ต่อหรือไม่" }, "logInWithPasskeyQuestion": { - "message": "Log in with passkey?" + "message": "เข้าสู่ระบบด้วยพาสคีย์หรือไม่" }, "passkeyAlreadyExists": { - "message": "A passkey already exists for this application." + "message": "มีพาสคีย์สำหรับแอปพลิเคชันนี้อยู่แล้ว" }, "noPasskeysFoundForThisApplication": { - "message": "No passkeys found for this application." + "message": "ไม่พบพาสคีย์สำหรับแอปพลิเคชันนี้" }, "noMatchingPasskeyLogin": { - "message": "You do not have a matching login for this site." + "message": "คุณไม่มีข้อมูลเข้าสู่ระบบที่ตรงกันสำหรับไซต์นี้" }, "noMatchingLoginsForSite": { - "message": "No matching logins for this site" + "message": "ไม่มีข้อมูลเข้าสู่ระบบที่ตรงกันสำหรับไซต์นี้" }, "searchSavePasskeyNewLogin": { - "message": "Search or save passkey as new login" + "message": "ค้นหาหรือบันทึกพาสคีย์เป็นข้อมูลเข้าสู่ระบบใหม่" }, "confirm": { - "message": "Confirm" + "message": "ยืนยัน" }, "savePasskey": { - "message": "Save passkey" + "message": "บันทึกพาสคีย์" }, "savePasskeyNewLogin": { - "message": "Save passkey as new login" + "message": "บันทึกพาสคีย์เป็นข้อมูลเข้าสู่ระบบใหม่" }, "chooseCipherForPasskeySave": { - "message": "Choose a login to save this passkey to" + "message": "เลือกข้อมูลเข้าสู่ระบบเพื่อบันทึกพาสคีย์นี้" }, "chooseCipherForPasskeyAuth": { - "message": "Choose a passkey to log in with" + "message": "เลือกพาสคีย์เพื่อเข้าสู่ระบบ" }, "passkeyItem": { - "message": "Passkey Item" + "message": "รายการพาสคีย์" }, "overwritePasskey": { - "message": "Overwrite passkey?" + "message": "เขียนทับพาสคีย์หรือไม่" }, "overwritePasskeyAlert": { - "message": "This item already contains a passkey. Are you sure you want to overwrite the current passkey?" + "message": "รายการนี้มีพาสคีย์อยู่แล้ว ยืนยันที่จะเขียนทับพาสคีย์ปัจจุบันหรือไม่" }, "featureNotSupported": { - "message": "Feature not yet supported" + "message": "ยังไม่รองรับฟีเจอร์นี้" }, "yourPasskeyIsLocked": { - "message": "Authentication required to use passkey. Verify your identity to continue." + "message": "จำเป็นต้องยืนยันตัวตนเพื่อใช้พาสคีย์ ยืนยันตัวตนของคุณเพื่อดำเนินการต่อ" }, "multifactorAuthenticationCancelled": { - "message": "Multifactor authentication cancelled" + "message": "ยกเลิกการยืนยันตัวตนแบบหลายปัจจัยแล้ว" }, "noLastPassDataFound": { - "message": "No LastPass data found" + "message": "ไม่พบข้อมูล LastPass" }, "incorrectUsernameOrPassword": { - "message": "Incorrect username or password" + "message": "ชื่อผู้ใช้หรือรหัสผ่านไม่ถูกต้อง" }, "incorrectPassword": { - "message": "Incorrect password" + "message": "รหัสผ่านไม่ถูกต้อง" }, "incorrectCode": { - "message": "Incorrect code" + "message": "รหัสไม่ถูกต้อง" }, "incorrectPin": { - "message": "Incorrect PIN" + "message": "รหัส PIN ไม่ถูกต้อง" }, "multifactorAuthenticationFailed": { - "message": "Multifactor authentication failed" + "message": "การยืนยันตัวตนแบบหลายปัจจัยล้มเหลว" }, "includeSharedFolders": { - "message": "Include shared folders" + "message": "รวมโฟลเดอร์ที่แชร์" }, "lastPassEmail": { - "message": "LastPass Email" + "message": "อีเมล LastPass" }, "importingYourAccount": { - "message": "Importing your account..." + "message": "กำลังนำเข้าบัญชีของคุณ..." }, "lastPassMFARequired": { - "message": "LastPass multifactor authentication required" + "message": "จำเป็นต้องยืนยันตัวตนแบบหลายปัจจัยของ LastPass" }, "lastPassMFADesc": { - "message": "Enter your one-time passcode from your authentication app" + "message": "ป้อนรหัสผ่านใช้ครั้งเดียวจากแอปยืนยันตัวตนของคุณ" }, "lastPassOOBDesc": { - "message": "Approve the login request in your authentication app or enter a one-time passcode." + "message": "อนุมัติคำขอเข้าสู่ระบบในแอปยืนยันตัวตนของคุณ หรือป้อนรหัสผ่านใช้ครั้งเดียว" }, "passcode": { - "message": "Passcode" + "message": "รหัสผ่าน" }, "lastPassMasterPassword": { - "message": "LastPass master password" + "message": "รหัสผ่านหลัก LastPass" }, "lastPassAuthRequired": { - "message": "LastPass authentication required" + "message": "จำเป็นต้องยืนยันตัวตน LastPass" }, "awaitingSSO": { - "message": "Awaiting SSO authentication" + "message": "กำลังรอการยืนยันตัวตน SSO" }, "awaitingSSODesc": { - "message": "Please continue to log in using your company credentials." + "message": "โปรดดำเนินการเข้าสู่ระบบโดยใช้ข้อมูลประจำตัวของบริษัทของคุณ" }, "seeDetailedInstructions": { - "message": "See detailed instructions on our help site at", + "message": "ดูคำแนะนำโดยละเอียดบนเว็บไซต์ช่วยเหลือของเราที่", "description": "This is followed a by a hyperlink to the help website." }, "importDirectlyFromLastPass": { - "message": "Import directly from LastPass" + "message": "นำเข้าจาก LastPass โดยตรง" }, "importFromCSV": { - "message": "Import from CSV" + "message": "นำเข้าจาก CSV" }, "lastPassTryAgainCheckEmail": { - "message": "Try again or look for an email from LastPass to verify it's you." + "message": "ลองอีกครั้งหรือตรวจสอบอีเมลจาก LastPass เพื่อยืนยันว่าเป็นคุณ" }, "collection": { - "message": "Collection" + "message": "คอลเลกชัน" }, "lastPassYubikeyDesc": { - "message": "Insert the YubiKey associated with your LastPass account into your computer's USB port, then touch its button." + "message": "เสียบ YubiKey ที่เชื่อมโยงกับบัญชี LastPass เข้ากับพอร์ต USB ของคอมพิวเตอร์ แล้วแตะที่ปุ่ม" }, "switchAccount": { - "message": "Switch account" + "message": "สลับบัญชี" }, "switchAccounts": { - "message": "Switch accounts" + "message": "สลับบัญชี" }, "switchToAccount": { - "message": "Switch to account" + "message": "สลับไปที่บัญชี" }, "activeAccount": { - "message": "Active account" + "message": "บัญชีที่ใช้งานอยู่" }, "bitwardenAccount": { - "message": "Bitwarden account" + "message": "บัญชี Bitwarden" }, "availableAccounts": { - "message": "Available accounts" + "message": "บัญชีที่มีอยู่" }, "accountLimitReached": { - "message": "ถึงขีดจำกัดของบัญชีแล้ว กรุณาออกจากระบบบัญชีอื่นเพื่อเพิ่มบัญชีใหม่" + "message": "ถึงขีดจำกัดจำนวนบัญชีแล้ว ออกจากระบบบัญชีหนึ่งเพื่อเพิ่มบัญชีอื่น" }, "active": { - "message": "active" + "message": "ใช้งานอยู่" }, "locked": { - "message": "locked" + "message": "ล็อกอยู่" }, "unlocked": { - "message": "unlocked" + "message": "ปลดล็อกแล้ว" }, "server": { - "message": "server" + "message": "เซิร์ฟเวอร์" }, "hostedAt": { - "message": "hosted at" + "message": "โฮสต์ที่" }, "useDeviceOrHardwareKey": { - "message": "Use your device or hardware key" + "message": "ใช้อุปกรณ์หรือคีย์ฮาร์ดแวร์ของคุณ" }, "justOnce": { - "message": "Just once" + "message": "เพียงครั้งเดียว" }, "alwaysForThisSite": { - "message": "Always for this site" + "message": "เสมอสำหรับไซต์นี้" }, "domainAddedToExcludedDomains": { - "message": "$DOMAIN$ added to excluded domains.", + "message": "เพิ่ม $DOMAIN$ ในโดเมนที่ยกเว้นแล้ว", "placeholders": { "domain": { "content": "$1", @@ -4560,126 +4570,126 @@ } }, "commonImportFormats": { - "message": "Common formats", + "message": "รูปแบบทั่วไป", "description": "Label indicating the most common import formats" }, "uriMatchDefaultStrategyHint": { - "message": "URI match detection is how Bitwarden identifies autofill suggestions.", + "message": "การตรวจสอบการจับคู่ URI คือวิธีที่ Bitwarden ระบุคำแนะนำการป้อนอัตโนมัติ", "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": { - "message": "\"Regular expression\" is an advanced option with increased risk of exposing credentials.", + "message": "\"Regular expression\" เป็นตัวเลือกขั้นสูงที่มีความเสี่ยงสูงในการเปิดเผยข้อมูลเข้าสู่ระบบ", "description": "Content for dialog which warns a user when selecting 'regular expression' matching strategy as a cipher match strategy" }, "startsWithAdvancedOptionWarning": { - "message": "\"Starts with\" is an advanced option with increased risk of exposing credentials.", + "message": "\"ขึ้นต้นด้วย\" เป็นตัวเลือกขั้นสูงที่มีความเสี่ยงสูงในการเปิดเผยข้อมูลเข้าสู่ระบบ", "description": "Content for dialog which warns a user when selecting 'starts with' matching strategy as a cipher match strategy" }, "uriMatchWarningDialogLink": { - "message": "More about match detection", + "message": "เพิ่มเติมเกี่ยวกับการตรวจสอบการจับคู่", "description": "Link to match detection docs on warning dialog for advance match strategy" }, "uriAdvancedOption": { - "message": "Advanced options", + "message": "ตัวเลือกขั้นสูง", "description": "Advanced option placeholder for uri option component" }, "confirmContinueToBrowserSettingsTitle": { - "message": "Continue to browser settings?", + "message": "ไปที่การตั้งค่าเบราว์เซอร์หรือไม่", "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": "ไปที่ศูนย์ช่วยเหลือหรือไม่", "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": "เปลี่ยนการตั้งค่าการป้อนอัตโนมัติและการจัดการรหัสผ่านของเบราว์เซอร์", "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": "คุณสามารถดูและตั้งค่าทางลัดส่วนขยายได้ในการตั้งค่าของเบราว์เซอร์", "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": "เปลี่ยนการตั้งค่าการป้อนอัตโนมัติและการจัดการรหัสผ่านของเบราว์เซอร์", "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": "คุณสามารถดูและตั้งค่าทางลัดส่วนขยายได้ในการตั้งค่าของเบราว์เซอร์", "description": "Body content for dialog which asks if the user wants to proceed to the browser's keyboard shortcut settings page" }, "overrideDefaultBrowserAutofillTitle": { - "message": "Make Bitwarden your default password manager?", + "message": "ตั้งให้ Bitwarden เป็นตัวจัดการรหัสผ่านเริ่มต้นหรือไม่", "description": "Dialog title facilitating the ability to override a chrome browser's default autofill behavior" }, "overrideDefaultBrowserAutofillDescription": { - "message": "Ignoring this option may cause conflicts between Bitwarden autofill suggestions and your browser's.", + "message": "การเพิกเฉยตัวเลือกนี้อาจทำให้เกิดความขัดแย้งระหว่างคำแนะนำการป้อนอัตโนมัติของ Bitwarden กับของเบราว์เซอร์", "description": "Dialog message facilitating the ability to override a chrome browser's default autofill behavior" }, "overrideDefaultBrowserAutoFillSettings": { - "message": "Make Bitwarden your default password manager", + "message": "ตั้งให้ Bitwarden เป็นตัวจัดการรหัสผ่านเริ่มต้น", "description": "Label for the setting that allows overriding the default browser autofill settings" }, "privacyPermissionAdditionNotGrantedTitle": { - "message": "Unable to set Bitwarden as the default password manager", + "message": "ไม่สามารถตั้งค่า Bitwarden เป็นตัวจัดการรหัสผ่านเริ่มต้นได้", "description": "Title for the dialog that appears when the user has not granted the extension permission to set privacy settings" }, "privacyPermissionAdditionNotGrantedDescription": { - "message": "You must grant browser privacy permissions to Bitwarden to set it as the default password manager.", + "message": "คุณต้องอนุญาตสิทธิ์ความเป็นส่วนตัวของเบราว์เซอร์ให้ Bitwarden เพื่อตั้งค่าเป็นตัวจัดการรหัสผ่านเริ่มต้น", "description": "Description for the dialog that appears when the user has not granted the extension permission to set privacy settings" }, "makeDefault": { - "message": "Make default", + "message": "ตั้งเป็นค่าเริ่มต้น", "description": "Button text for the setting that allows overriding the default browser autofill settings" }, "saveCipherAttemptSuccess": { - "message": "Credentials saved successfully!", + "message": "บันทึกข้อมูลเข้าสู่ระบบสำเร็จแล้ว!", "description": "Notification message for when saving credentials has succeeded." }, "passwordSaved": { - "message": "Password saved!", + "message": "บันทึกรหัสผ่านแล้ว!", "description": "Notification message for when saving credentials has succeeded." }, "updateCipherAttemptSuccess": { - "message": "Credentials updated successfully!", + "message": "อัปเดตข้อมูลเข้าสู่ระบบสำเร็จแล้ว!", "description": "Notification message for when updating credentials has succeeded." }, "passwordUpdated": { - "message": "Password updated!", + "message": "อัปเดตรหัสผ่านแล้ว!", "description": "Notification message for when updating credentials has succeeded." }, "saveCipherAttemptFailed": { - "message": "Error saving credentials. Check console for details.", + "message": "เกิดข้อผิดพลาดในการบันทึกข้อมูลเข้าสู่ระบบ ตรวจสอบรายละเอียดในคอนโซล", "description": "Notification message for when saving credentials has failed." }, "success": { - "message": "Success" + "message": "สำเร็จ" }, "removePasskey": { - "message": "Remove passkey" + "message": "เอาพาสคีย์ออก" }, "passkeyRemoved": { - "message": "Passkey removed" + "message": "เอาพาสคีย์ออกแล้ว" }, "autofillSuggestions": { - "message": "Autofill suggestions" + "message": "คำแนะนำการป้อนอัตโนมัติ" }, "itemSuggestions": { - "message": "Suggested items" + "message": "รายการที่แนะนำ" }, "autofillSuggestionsTip": { - "message": "Save a login item for this site to autofill" + "message": "บันทึกข้อมูลเข้าสู่ระบบสำหรับไซต์นี้เพื่อป้อนอัตโนมัติ" }, "yourVaultIsEmpty": { - "message": "Your vault is empty" + "message": "ตู้นิรภัยของคุณว่างเปล่า" }, "noItemsMatchSearch": { - "message": "No items match your search" + "message": "ไม่พบรายการที่ตรงกับการค้นหา" }, "clearFiltersOrTryAnother": { - "message": "Clear filters or try another search term" + "message": "ล้างตัวกรองหรือลองค้นหาด้วยคำอื่น" }, "copyInfoTitle": { - "message": "Copy info - $ITEMNAME$", + "message": "คัดลอกข้อมูล - $ITEMNAME$", "description": "Title for a button that opens a menu with options to copy information from an item.", "placeholders": { "itemname": { @@ -4689,7 +4699,7 @@ } }, "copyNoteTitle": { - "message": "Copy Note - $ITEMNAME$", + "message": "คัดลอกโน้ต - $ITEMNAME$", "description": "Title for a button copies a note to the clipboard.", "placeholders": { "itemname": { @@ -4699,7 +4709,7 @@ } }, "moreOptionsLabel": { - "message": "More options, $ITEMNAME$", + "message": "ตัวเลือกเพิ่มเติม $ITEMNAME$", "description": "Aria label for a button that opens a menu with more options for an item.", "placeholders": { "itemname": { @@ -4709,7 +4719,7 @@ } }, "moreOptionsTitle": { - "message": "More options - $ITEMNAME$", + "message": "ตัวเลือกเพิ่มเติม - $ITEMNAME$", "description": "Title for a button that opens a menu with more options for an item.", "placeholders": { "itemname": { @@ -4719,7 +4729,7 @@ } }, "viewItemTitle": { - "message": "View item - $ITEMNAME$", + "message": "ดูรายการ - $ITEMNAME$", "description": "Title for a link that opens a view for an item.", "placeholders": { "itemname": { @@ -4729,7 +4739,7 @@ } }, "viewItemTitleWithField": { - "message": "View item - $ITEMNAME$ - $FIELD$", + "message": "ดูรายการ - $ITEMNAME$ - $FIELD$", "description": "Title for a link that opens a view for an item.", "placeholders": { "itemname": { @@ -4743,7 +4753,7 @@ } }, "autofillTitle": { - "message": "Autofill - $ITEMNAME$", + "message": "ป้อนอัตโนมัติ - $ITEMNAME$", "description": "Title for a button that autofills a login item.", "placeholders": { "itemname": { @@ -4753,7 +4763,7 @@ } }, "autofillTitleWithField": { - "message": "Autofill - $ITEMNAME$ - $FIELD$", + "message": "ป้อนอัตโนมัติ - $ITEMNAME$ - $FIELD$", "description": "Title for a button that autofills a login item.", "placeholders": { "itemname": { @@ -4767,7 +4777,7 @@ } }, "copyFieldCipherName": { - "message": "Copy $FIELD$, $CIPHERNAME$", + "message": "คัดลอก $FIELD$, $CIPHERNAME$", "description": "Title for a button that copies a field value to the clipboard.", "placeholders": { "field": { @@ -4781,49 +4791,49 @@ } }, "noValuesToCopy": { - "message": "No values to copy" + "message": "ไม่มีค่าให้คัดลอก" }, "assignToCollections": { - "message": "Assign to collections" + "message": "มอบหมายให้คอลเลกชัน" }, "copyEmail": { - "message": "Copy email" + "message": "คัดลอกอีเมล" }, "copyPhone": { - "message": "Copy phone" + "message": "คัดลอกเบอร์โทรศัพท์" }, "copyAddress": { - "message": "Copy address" + "message": "คัดลอกที่อยู่" }, "adminConsole": { - "message": "Admin Console" + "message": "คอนโซลผู้ดูแลระบบ" }, "accountSecurity": { "message": "ความปลอดภัยของบัญชี" }, "phishingBlocker": { - "message": "Phishing Blocker" + "message": "ตัวบล็อกฟิชชิง" }, "enablePhishingDetection": { - "message": "Phishing detection" + "message": "การตรวจจับฟิชชิง" }, "enablePhishingDetectionDesc": { - "message": "Display warning before accessing suspected phishing sites" + "message": "แสดงคำเตือนก่อนเข้าถึงไซต์ที่ต้องสงสัยว่าเป็นฟิชชิง" }, "notifications": { - "message": "Notifications" + "message": "การแจ้งเตือน" }, "appearance": { - "message": "Appearance" + "message": "รูปลักษณ์" }, "errorAssigningTargetCollection": { - "message": "Error assigning target collection." + "message": "เกิดข้อผิดพลาดในการมอบหมายคอลเลกชันปลายทาง" }, "errorAssigningTargetFolder": { - "message": "Error assigning target folder." + "message": "เกิดข้อผิดพลาดในการมอบหมายโฟลเดอร์ปลายทาง" }, "viewItemsIn": { - "message": "View items in $NAME$", + "message": "ดูรายการใน $NAME$", "description": "Button to view the contents of a folder or collection", "placeholders": { "name": { @@ -4833,7 +4843,7 @@ } }, "backTo": { - "message": "Back to $NAME$", + "message": "กลับไปที่ $NAME$", "description": "Navigate back to a previous folder or collection", "placeholders": { "name": { @@ -4843,10 +4853,10 @@ } }, "new": { - "message": "New" + "message": "ใหม่" }, "removeItem": { - "message": "Remove $NAME$", + "message": "เอา $NAME$ ออก", "description": "Remove a selected option, such as a folder or collection", "placeholders": { "name": { @@ -4856,44 +4866,44 @@ } }, "itemsWithNoFolder": { - "message": "Items with no folder" + "message": "รายการที่ไม่มีโฟลเดอร์" }, "itemDetails": { - "message": "Item details" + "message": "รายละเอียดรายการ" }, "itemName": { - "message": "Item name" + "message": "ชื่อรายการ" }, "organizationIsDeactivated": { - "message": "Organization is deactivated" + "message": "องค์กรถูกปิดใช้งาน" }, "owner": { - "message": "Owner" + "message": "เจ้าของ" }, "selfOwnershipLabel": { - "message": "You", + "message": "คุณ", "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": "ไม่สามารถเข้าถึงรายการในองค์กรที่ถูกปิดใช้งาน ติดต่อเจ้าขององค์กรเพื่อขอความช่วยเหลือ" }, "additionalInformation": { - "message": "Additional information" + "message": "ข้อมูลเพิ่มเติม" }, "itemHistory": { - "message": "ประวัติการแก้ไขรายการ" + "message": "ประวัติรายการ" }, "lastEdited": { - "message": "แก้ไขล่าสุดเมื่อ" + "message": "แก้ไขล่าสุด" }, "ownerYou": { - "message": "Owner: You" + "message": "เจ้าของ: คุณ" }, "linked": { - "message": "Linked" + "message": "เชื่อมโยง" }, "copySuccessful": { - "message": "Copy Successful" + "message": "คัดลอกสำเร็จ" }, "upload": { "message": "อัปโหลด" @@ -4902,10 +4912,10 @@ "message": "เพิ่มไฟล์แนบ" }, "maxFileSizeSansPunctuation": { - "message": "ขนาดไฟล์สูงสุด คือ 500 MB" + "message": "ขนาดไฟล์สูงสุดคือ 500 MB" }, "deleteAttachmentName": { - "message": "Delete attachment $NAME$", + "message": "ลบไฟล์แนบ $NAME$", "placeholders": { "name": { "content": "$1", @@ -4914,7 +4924,7 @@ } }, "downloadAttachmentName": { - "message": "Download $NAME$", + "message": "ดาวน์โหลด $NAME$", "placeholders": { "name": { "content": "$1", @@ -4923,55 +4933,55 @@ } }, "downloadBitwarden": { - "message": "Download Bitwarden" + "message": "ดาวน์โหลด Bitwarden" }, "downloadBitwardenOnAllDevices": { - "message": "Download Bitwarden on all devices" + "message": "ดาวน์โหลด Bitwarden บนทุกอุปกรณ์" }, "getTheMobileApp": { - "message": "Get the mobile app" + "message": "รับแอปมือถือ" }, "getTheMobileAppDesc": { - "message": "Access your passwords on the go with the Bitwarden mobile app." + "message": "เข้าถึงรหัสผ่านของคุณได้ทุกที่ด้วยแอปมือถือ Bitwarden" }, "getTheDesktopApp": { - "message": "Get the desktop app" + "message": "รับแอปเดสก์ท็อป" }, "getTheDesktopAppDesc": { - "message": "Access your vault without a browser, then set up unlock with biometrics to expedite unlocking in both the desktop app and browser extension." + "message": "เข้าถึงตู้นิรภัยโดยไม่ต้องใช้เบราว์เซอร์ จากนั้นตั้งค่าการปลดล็อกด้วยไบโอเมตริกเพื่อเร่งการปลดล็อกทั้งในแอปเดสก์ท็อปและส่วนขยายเบราว์เซอร์" }, "downloadFromBitwardenNow": { - "message": "Download from bitwarden.com now" + "message": "ดาวน์โหลดจาก bitwarden.com เลย" }, "getItOnGooglePlay": { - "message": "Get it on Google Play" + "message": "ดาวน์โหลดได้จาก Google Play" }, "downloadOnTheAppStore": { - "message": "Download on the App Store" + "message": "ดาวน์โหลดได้จาก App Store" }, "permanentlyDeleteAttachmentConfirmation": { - "message": "Are you sure you want to permanently delete this attachment?" + "message": "ยืนยันที่จะลบไฟล์แนบนี้ถาวรหรือไม่" }, "premium": { - "message": "Premium" + "message": "พรีเมียม" }, "unlockFeaturesWithPremium": { - "message": "Unlock reporting, emergency access, and more security features with Premium." + "message": "ปลดล็อกการรายงาน การเข้าถึงฉุกเฉิน และฟีเจอร์ความปลอดภัยเพิ่มเติมด้วยพรีเมียม" }, "freeOrgsCannotUseAttachments": { - "message": "Free organizations cannot use attachments" + "message": "องค์กรฟรีไม่สามารถใช้ไฟล์แนบได้" }, "filters": { - "message": "Filters" + "message": "ตัวกรอง" }, "filterVault": { - "message": "Filter vault" + "message": "กรองตู้นิรภัย" }, "filterApplied": { - "message": "One filter applied" + "message": "ใช้ตัวกรอง 1 รายการ" }, "filterAppliedPlural": { - "message": "$COUNT$ filters applied", + "message": "ใช้ตัวกรอง $COUNT$ รายการ", "placeholders": { "count": { "content": "$1", @@ -4980,16 +4990,16 @@ } }, "personalDetails": { - "message": "Personal details" + "message": "ข้อมูลส่วนตัว" }, "identification": { - "message": "Identification" + "message": "เอกสารระบุตัวตน" }, "contactInfo": { - "message": "Contact info" + "message": "ข้อมูลติดต่อ" }, "downloadAttachment": { - "message": "Download - $ITEMNAME$", + "message": "ดาวน์โหลด - $ITEMNAME$", "placeholders": { "itemname": { "content": "$1", @@ -4998,23 +5008,23 @@ } }, "cardNumberEndsWith": { - "message": "card number ends with", + "message": "หมายเลขบัตรลงท้ายด้วย", "description": "Used within the inline menu to provide an aria description when users are attempting to fill a card cipher." }, "loginCredentials": { - "message": "Login credentials" + "message": "ข้อมูลเข้าสู่ระบบ" }, "authenticatorKey": { - "message": "Authenticator key" + "message": "คีย์ยืนยันตัวตน" }, "autofillOptions": { - "message": "ตัวเลือกในการป้อนอัตโนมัติ" + "message": "ตัวเลือกการป้อนอัตโนมัติ" }, "websiteUri": { - "message": "Website (URI)" + "message": "เว็บไซต์ (URI)" }, "websiteUriCount": { - "message": "Website (URI) $COUNT$", + "message": "เว็บไซต์ (URI) $COUNT$", "description": "Label for an input field that contains a website URI. The input field is part of a list of fields, and the count indicates the position of the field in the list.", "placeholders": { "count": { @@ -5024,16 +5034,16 @@ } }, "websiteAdded": { - "message": "Website added" + "message": "เพิ่มเว็บไซต์แล้ว" }, "addWebsite": { - "message": "Add website" + "message": "เพิ่มเว็บไซต์" }, "deleteWebsite": { - "message": "Delete website" + "message": "ลบเว็บไซต์" }, "defaultLabel": { - "message": "Default ($VALUE$)", + "message": "ค่าเริ่มต้น ($VALUE$)", "description": "A label that indicates the default value for a field with the current default value in parentheses.", "placeholders": { "value": { @@ -5043,7 +5053,7 @@ } }, "defaultLabelWithValue": { - "message": "Default ( $VALUE$ )", + "message": "ค่าเริ่มต้น ( $VALUE$ )", "description": "A label that indicates the default value for a field with the current default value in parentheses.", "placeholders": { "value": { @@ -5053,7 +5063,7 @@ } }, "showMatchDetection": { - "message": "Show match detection $WEBSITE$", + "message": "แสดงการตรวจสอบการจับคู่ $WEBSITE$", "placeholders": { "website": { "content": "$1", @@ -5062,7 +5072,7 @@ } }, "hideMatchDetection": { - "message": "Hide match detection $WEBSITE$", + "message": "ซ่อนการตรวจสอบการจับคู่ $WEBSITE$", "placeholders": { "website": { "content": "$1", @@ -5071,19 +5081,19 @@ } }, "autoFillOnPageLoad": { - "message": "Autofill on page load?" + "message": "ป้อนอัตโนมัติเมื่อโหลดหน้าเว็บหรือไม่" }, "cardExpiredTitle": { - "message": "Expired card" + "message": "บัตรหมดอายุ" }, "cardExpiredMessage": { - "message": "If you've renewed it, update the card's information" + "message": "หากคุณต่ออายุแล้ว ให้อัปเดตข้อมูลบัตร" }, "cardDetails": { - "message": "Card details" + "message": "รายละเอียดบัตร" }, "cardBrandDetails": { - "message": "$BRAND$ details", + "message": "รายละเอียด $BRAND$", "placeholders": { "brand": { "content": "$1", @@ -5092,40 +5102,40 @@ } }, "showAnimations": { - "message": "Show animations" + "message": "แสดงภาพเคลื่อนไหว" }, "addAccount": { "message": "เพิ่มบัญชี" }, "loading": { - "message": "Loading" + "message": "กำลังโหลด" }, "data": { - "message": "Data" + "message": "ข้อมูล" }, "passkeys": { - "message": "Passkeys", + "message": "พาสคีย์", "description": "A section header for a list of passkeys." }, "passwords": { - "message": "Passwords", + "message": "รหัสผ่าน", "description": "A section header for a list of passwords." }, "logInWithPasskeyAriaLabel": { - "message": "Log in with passkey", + "message": "เข้าสู่ระบบด้วยพาสคีย์", "description": "ARIA label for the inline menu button that logs in with a passkey." }, "assign": { - "message": "Assign" + "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": "เฉพาะสมาชิกองค์กรที่มีสิทธิ์เข้าถึงคอลเลกชันเหล่านี้เท่านั้นที่จะเห็นรายการเหล่านี้" }, "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$ รายการ คุณไม่สามารถอัปเดต $READONLY_COUNT$ รายการได้เนื่องจากคุณไม่มีสิทธิ์แก้ไข", "placeholders": { "total_count": { "content": "$1", @@ -5140,34 +5150,34 @@ "message": "เพิ่มฟิลด์" }, "add": { - "message": "Add" + "message": "เพิ่ม" }, "fieldType": { - "message": "Field type" + "message": "ประเภทฟิลด์" }, "fieldLabel": { - "message": "Field label" + "message": "ป้ายกำกับฟิลด์" }, "textHelpText": { - "message": "ใช้ช่องข้อความสำหรับเก็บข้อมูล เช่น คำถามเพื่อความปลอดภัย" + "message": "ใช้ฟิลด์ข้อความสำหรับข้อมูลเช่นคำถามความปลอดภัย" }, "hiddenHelpText": { - "message": "Use hidden fields for sensitive data like a password" + "message": "ใช้ฟิลด์ซ่อนสำหรับข้อมูลที่ละเอียดอ่อนเช่นรหัสผ่าน" }, "checkBoxHelpText": { - "message": "Use checkboxes if you'd like to autofill a form's checkbox, like a remember email" + "message": "ใช้ช่องทำเครื่องหมายหากคุณต้องการป้อนข้อมูลลงในช่องทำเครื่องหมายของแบบฟอร์มโดยอัตโนมัติ เช่น การจดจำอีเมล" }, "linkedHelpText": { - "message": "Use a linked field when you are experiencing autofill issues for a specific website." + "message": "ใช้ฟิลด์เชื่อมโยงเมื่อคุณประสบปัญหาการป้อนอัตโนมัติสำหรับเว็บไซต์ใดเว็บไซต์หนึ่งโดยเฉพาะ" }, "linkedLabelHelpText": { - "message": "Enter the the field's html id, name, aria-label, or placeholder." + "message": "ป้อน html id, name, aria-label หรือ placeholder ของฟิลด์" }, "editField": { - "message": "Edit field" + "message": "แก้ไขฟิลด์" }, "editFieldLabel": { - "message": "Edit $LABEL$", + "message": "แก้ไข $LABEL$", "placeholders": { "label": { "content": "$1", @@ -5176,7 +5186,7 @@ } }, "deleteCustomField": { - "message": "Delete $LABEL$", + "message": "ลบ $LABEL$", "placeholders": { "label": { "content": "$1", @@ -5194,7 +5204,7 @@ } }, "reorderToggleButton": { - "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "message": "จัดลำดับ $LABEL$ ใหม่ ใช้ปุ่มลูกศรเพื่อเลื่อนรายการขึ้นหรือลง", "placeholders": { "label": { "content": "$1", @@ -5203,10 +5213,10 @@ } }, "reorderWebsiteUriButton": { - "message": "Reorder website URI. Use arrow key to move item up or down." + "message": "จัดลำดับ URI เว็บไซต์ใหม่ ใช้ปุ่มลูกศรเพื่อเลื่อนรายการขึ้นหรือลง" }, "reorderFieldUp": { - "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "message": "เลื่อน $LABEL$ ขึ้น ตำแหน่งที่ $INDEX$ จาก $LENGTH$", "placeholders": { "label": { "content": "$1", @@ -5223,13 +5233,13 @@ } }, "selectCollectionsToAssign": { - "message": "Select collections to assign" + "message": "เลือกคอลเลกชันที่จะมอบหมาย" }, "personalItemTransferWarningSingular": { - "message": "1 item will be permanently transferred to the selected organization. You will no longer own this item." + "message": "1 รายการจะถูกโอนย้ายไปยังองค์กรที่เลือกอย่างถาวร คุณจะไม่ได้เป็นเจ้าของรายการนี้อีกต่อไป" }, "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$ รายการจะถูกโอนย้ายไปยังองค์กรที่เลือกอย่างถาวร คุณจะไม่ได้เป็นเจ้าของรายการเหล่านี้อีกต่อไป", "placeholders": { "personal_items_count": { "content": "$1", @@ -5238,7 +5248,7 @@ } }, "personalItemWithOrgTransferWarningSingular": { - "message": "1 item will be permanently transferred to $ORG$. You will no longer own this item.", + "message": "1 รายการจะถูกโอนย้ายไปยัง $ORG$ อย่างถาวร คุณจะไม่ได้เป็นเจ้าของรายการนี้อีกต่อไป", "placeholders": { "org": { "content": "$1", @@ -5247,7 +5257,7 @@ } }, "personalItemsWithOrgTransferWarningPlural": { - "message": "$PERSONAL_ITEMS_COUNT$ items will be permanently transferred to $ORG$. You will no longer own these items.", + "message": "$PERSONAL_ITEMS_COUNT$ รายการจะถูกโอนย้ายไปยัง $ORG$ อย่างถาวร คุณจะไม่ได้เป็นเจ้าของรายการเหล่านี้อีกต่อไป", "placeholders": { "personal_items_count": { "content": "$1", @@ -5260,13 +5270,13 @@ } }, "successfullyAssignedCollections": { - "message": "Successfully assigned collections" + "message": "มอบหมายคอลเลกชันสำเร็จแล้ว" }, "nothingSelected": { - "message": "You have not selected anything." + "message": "คุณไม่ได้เลือกสิ่งใดเลย" }, "itemsMovedToOrg": { - "message": "Items moved to $ORGNAME$", + "message": "ย้ายรายการไปที่ $ORGNAME$ แล้ว", "placeholders": { "orgname": { "content": "$1", @@ -5275,7 +5285,7 @@ } }, "itemMovedToOrg": { - "message": "Item moved to $ORGNAME$", + "message": "ย้ายรายการไปที่ $ORGNAME$ แล้ว", "placeholders": { "orgname": { "content": "$1", @@ -5284,7 +5294,7 @@ } }, "reorderFieldDown": { - "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "message": "เลื่อน $LABEL$ ลง ตำแหน่งที่ $INDEX$ จาก $LENGTH$", "placeholders": { "label": { "content": "$1", @@ -5301,25 +5311,25 @@ } }, "itemLocation": { - "message": "Item Location" + "message": "ตำแหน่งรายการ" }, "fileSends": { - "message": "File Sends" + "message": "ไฟล์ Send" }, "textSends": { - "message": "Text Sends" + "message": "ข้อความ Send" }, "accountActions": { - "message": "การจัดการบัญชี" + "message": "การดำเนินการกับบัญชี" }, "showNumberOfAutofillSuggestions": { - "message": "Show number of login autofill suggestions on extension icon" + "message": "แสดงจำนวนคำแนะนำการป้อนข้อมูลเข้าสู่ระบบอัตโนมัติบนไอคอนส่วนขยาย" }, "accountAccessRequested": { - "message": "Account access requested" + "message": "ร้องขอการเข้าถึงบัญชีแล้ว" }, "confirmAccessAttempt": { - "message": "Confirm access attempt for $EMAIL$", + "message": "ยืนยันความพยายามเข้าถึงสำหรับ $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -5328,25 +5338,25 @@ } }, "showQuickCopyActions": { - "message": "Show quick copy actions on Vault" + "message": "แสดงการดำเนินการคัดลอกด่วนบนตู้นิรภัย" }, "systemDefault": { - "message": "System default" + "message": "ค่าเริ่มต้นของระบบ" }, "enterprisePolicyRequirementsApplied": { - "message": "Enterprise policy requirements have been applied to this setting" + "message": "ข้อกำหนดนโยบายองค์กรถูกนำมาใช้กับการตั้งค่านี้นี้" }, "sshPrivateKey": { - "message": "Private key" + "message": "กุญแจส่วนตัว" }, "sshPublicKey": { - "message": "Public key" + "message": "กุญแจสาธารณะ" }, "sshFingerprint": { - "message": "Fingerprint" + "message": "ลายนิ้วมือ" }, "sshKeyAlgorithm": { - "message": "Key type" + "message": "ประเภทคีย์" }, "sshKeyAlgorithmED25519": { "message": "ED25519" @@ -5361,58 +5371,58 @@ "message": "RSA 4096-Bit" }, "retry": { - "message": "Retry" + "message": "ลองใหม่" }, "vaultCustomTimeoutMinimum": { - "message": "Minimum custom timeout is 1 minute." + "message": "เวลาหมดเวลาที่กำหนดเองขั้นต่ำคือ 1 นาที" }, "fileSavedToDevice": { - "message": "File saved to device. Manage from your device downloads." + "message": "บันทึกไฟล์ลงในอุปกรณ์แล้ว จัดการได้จากการดาวน์โหลดของอุปกรณ์" }, "showCharacterCount": { - "message": "Show character count" + "message": "แสดงจำนวนตัวอักษร" }, "hideCharacterCount": { - "message": "Hide character count" + "message": "ซ่อนจำนวนตัวอักษร" }, "itemsInTrash": { - "message": "Items in trash" + "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 วัน" }, "trashWarning": { - "message": "Items that have been in trash more than 30 days will automatically be deleted" + "message": "รายการที่อยู่ในถังขยะนานกว่า 30 วันจะถูกลบโดยอัตโนมัติ" }, "restore": { - "message": "Restore" + "message": "กู้คืน" }, "deleteForever": { - "message": "Delete forever" + "message": "ลบถาวร" }, "noEditPermissions": { - "message": "You don't have permission to edit this item" + "message": "คุณไม่มีสิทธิ์แก้ไขรายการนี้" }, "biometricsStatusHelptextUnlockNeeded": { - "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + "message": "การปลดล็อกด้วยไบโอเมตริกไม่พร้อมใช้งาน เนื่องจากต้องปลดล็อกด้วย PIN หรือรหัสผ่านก่อน" }, "biometricsStatusHelptextHardwareUnavailable": { - "message": "Biometric unlock is currently unavailable." + "message": "การปลดล็อกด้วยไบโอเมตริกไม่พร้อมใช้งานในขณะนี้" }, "biometricsStatusHelptextAutoSetupNeeded": { - "message": "Biometric unlock is unavailable due to misconfigured system files." + "message": "การปลดล็อกด้วยไบโอเมตริกไม่พร้อมใช้งานเนื่องจากการกำหนดค่าไฟล์ระบบไม่ถูกต้อง" }, "biometricsStatusHelptextManualSetupNeeded": { - "message": "Biometric unlock is unavailable due to misconfigured system files." + "message": "การปลดล็อกด้วยไบโอเมตริกไม่พร้อมใช้งานเนื่องจากการกำหนดค่าไฟล์ระบบไม่ถูกต้อง" }, "biometricsStatusHelptextDesktopDisconnected": { - "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + "message": "การปลดล็อกด้วยไบโอเมตริกไม่พร้อมใช้งาน เนื่องจากแอป Bitwarden บนเดสก์ท็อปปิดอยู่" }, "biometricsStatusHelptextNotEnabledInDesktop": { - "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "message": "การปลดล็อกด้วยไบโอเมตริกไม่พร้อมใช้งาน เนื่องจากยังไม่ได้เปิดใช้งานสำหรับ $EMAIL$ ในแอป Bitwarden บนเดสก์ท็อป", "placeholders": { "email": { "content": "$1", @@ -5421,41 +5431,41 @@ } }, "biometricsStatusHelptextUnavailableReasonUnknown": { - "message": "Biometric unlock is currently unavailable for an unknown reason." + "message": "การปลดล็อกด้วยไบโอเมตริกไม่พร้อมใช้งานในขณะนี้โดยไม่ทราบสาเหตุ" }, "unlockVault": { - "message": "Unlock your vault in seconds" + "message": "ปลดล็อกตู้นิรภัยของคุณในไม่กี่วินาที" }, "unlockVaultDesc": { - "message": "You can customize your unlock and timeout settings to more quickly access your vault." + "message": "คุณสามารถปรับแต่งการตั้งค่าการปลดล็อกและเวลาหมดเวลาเพื่อเข้าถึงตู้นิรภัยได้รวดเร็วยิ่งขึ้น" }, "unlockPinSet": { - "message": "ตั้งค่า PIN สำหรับปลดล็อกแล้ว" + "message": "ตั้งค่า PIN ปลดล็อกแล้ว" }, "unlockWithBiometricSet": { - "message": "Unlock with biometrics set" + "message": "ตั้งค่าการปลดล็อกด้วยไบโอเมตริกแล้ว" }, "authenticating": { - "message": "Authenticating" + "message": "กำลังยืนยันตัวตน" }, "fillGeneratedPassword": { - "message": "Fill generated password", + "message": "ป้อนรหัสผ่านที่สร้างขึ้น", "description": "Heading for the password generator within the inline menu" }, "passwordRegenerated": { - "message": "Password regenerated", + "message": "สร้างรหัสผ่านใหม่แล้ว", "description": "Notification message for when a password has been regenerated" }, "saveToBitwarden": { - "message": "Save to Bitwarden", + "message": "บันทึกลง Bitwarden", "description": "Confirmation message for saving a login to Bitwarden" }, "spaceCharacterDescriptor": { - "message": "Space", + "message": "เว้นวรรค", "description": "Represents the space key in screen reader content as a readable word" }, "tildeCharacterDescriptor": { - "message": "Tilde", + "message": "ตัวหนอน", "description": "Represents the ~ key in screen reader content as a readable word" }, "backtickCharacterDescriptor": { @@ -5463,23 +5473,23 @@ "description": "Represents the ` key in screen reader content as a readable word" }, "exclamationCharacterDescriptor": { - "message": "Exclamation mark", + "message": "เครื่องหมายตกใจ", "description": "Represents the ! key in screen reader content as a readable word" }, "atSignCharacterDescriptor": { - "message": "At sign", + "message": "เครื่องหมาย @", "description": "Represents the @ key in screen reader content as a readable word" }, "hashSignCharacterDescriptor": { - "message": "Hash sign", + "message": "เครื่องหมาย #", "description": "Represents the # key in screen reader content as a readable word" }, "dollarSignCharacterDescriptor": { - "message": "Dollar sign", + "message": "เครื่องหมาย $", "description": "Represents the $ key in screen reader content as a readable word" }, "percentSignCharacterDescriptor": { - "message": "Percent sign", + "message": "เครื่องหมาย %", "description": "Represents the % key in screen reader content as a readable word" }, "caretCharacterDescriptor": { @@ -5487,154 +5497,154 @@ "description": "Represents the ^ key in screen reader content as a readable word" }, "ampersandCharacterDescriptor": { - "message": "Ampersand", + "message": "เครื่องหมาย &", "description": "Represents the & key in screen reader content as a readable word" }, "asteriskCharacterDescriptor": { - "message": "Asterisk", + "message": "ดอกจัน", "description": "Represents the * key in screen reader content as a readable word" }, "parenLeftCharacterDescriptor": { - "message": "Left parenthesis", + "message": "วงเล็บเปิด", "description": "Represents the ( key in screen reader content as a readable word" }, "parenRightCharacterDescriptor": { - "message": "Right parenthesis", + "message": "วงเล็บปิด", "description": "Represents the ) key in screen reader content as a readable word" }, "hyphenCharacterDescriptor": { - "message": "Underscore", + "message": "ขีดล่าง", "description": "Represents the _ key in screen reader content as a readable word" }, "underscoreCharacterDescriptor": { - "message": "Hyphen", + "message": "ขีดกลาง", "description": "Represents the - key in screen reader content as a readable word" }, "plusCharacterDescriptor": { - "message": "Plus", + "message": "เครื่องหมาย +", "description": "Represents the + key in screen reader content as a readable word" }, "equalsCharacterDescriptor": { - "message": "Equals", + "message": "เครื่องหมาย =", "description": "Represents the = key in screen reader content as a readable word" }, "braceLeftCharacterDescriptor": { - "message": "Left brace", + "message": "ปีกกาเปิด", "description": "Represents the { key in screen reader content as a readable word" }, "braceRightCharacterDescriptor": { - "message": "Right brace", + "message": "ปีกกาปิด", "description": "Represents the } key in screen reader content as a readable word" }, "bracketLeftCharacterDescriptor": { - "message": "Left bracket", + "message": "วงเล็บเหลี่ยมเปิด", "description": "Represents the [ key in screen reader content as a readable word" }, "bracketRightCharacterDescriptor": { - "message": "Right bracket", + "message": "วงเล็บเหลี่ยมปิด", "description": "Represents the ] key in screen reader content as a readable word" }, "pipeCharacterDescriptor": { - "message": "Pipe", + "message": "ขีดตั้ง", "description": "Represents the | key in screen reader content as a readable word" }, "backSlashCharacterDescriptor": { - "message": "Back slash", + "message": "แบคสแลช", "description": "Represents the back slash key in screen reader content as a readable word" }, "colonCharacterDescriptor": { - "message": "Colon", + "message": "โคลอน", "description": "Represents the : key in screen reader content as a readable word" }, "semicolonCharacterDescriptor": { - "message": "Semicolon", + "message": "เซมิโคลอน", "description": "Represents the ; key in screen reader content as a readable word" }, "doubleQuoteCharacterDescriptor": { - "message": "Double quote", + "message": "ฟันหนู", "description": "Represents the double quote key in screen reader content as a readable word" }, "singleQuoteCharacterDescriptor": { - "message": "Single quote", + "message": "ฝนทอง", "description": "Represents the ' key in screen reader content as a readable word" }, "lessThanCharacterDescriptor": { - "message": "Less than", + "message": "น้อยกว่า", "description": "Represents the < key in screen reader content as a readable word" }, "greaterThanCharacterDescriptor": { - "message": "Greater than", + "message": "มากกว่า", "description": "Represents the > key in screen reader content as a readable word" }, "commaCharacterDescriptor": { - "message": "Comma", + "message": "จุลภาค", "description": "Represents the , key in screen reader content as a readable word" }, "periodCharacterDescriptor": { - "message": "Period", + "message": "จุด", "description": "Represents the . key in screen reader content as a readable word" }, "questionCharacterDescriptor": { - "message": "Question mark", + "message": "เครื่องหมายคำถาม", "description": "Represents the ? key in screen reader content as a readable word" }, "forwardSlashCharacterDescriptor": { - "message": "Forward slash", + "message": "ทับ", "description": "Represents the / key in screen reader content as a readable word" }, "lowercaseAriaLabel": { - "message": "Lowercase" + "message": "ตัวพิมพ์เล็ก" }, "uppercaseAriaLabel": { - "message": "Uppercase" + "message": "ตัวพิมพ์ใหญ่" }, "generatedPassword": { - "message": "Generated password" + "message": "รหัสผ่านที่สร้างขึ้น" }, "compactMode": { - "message": "Compact mode" + "message": "โหมดกะทัดรัด" }, "beta": { - "message": "Beta" + "message": "เบต้า" }, "extensionWidth": { - "message": "Extension width" + "message": "ความกว้างส่วนขยาย" }, "wide": { - "message": "Wide" + "message": "กว้าง" }, "extraWide": { - "message": "Extra wide" + "message": "กว้างพิเศษ" }, "sshKeyWrongPassword": { - "message": "The password you entered is incorrect." + "message": "รหัสผ่านที่คุณป้อนไม่ถูกต้อง" }, "importSshKey": { - "message": "Import" + "message": "นำเข้า" }, "confirmSshKeyPassword": { - "message": "Confirm password" + "message": "ยืนยันรหัสผ่าน" }, "enterSshKeyPasswordDesc": { - "message": "Enter the password for the SSH key." + "message": "ป้อนรหัสผ่านสำหรับคีย์ SSH" }, "enterSshKeyPassword": { - "message": "Enter password" + "message": "ป้อนรหัสผ่าน" }, "invalidSshKey": { - "message": "The SSH key is invalid" + "message": "คีย์ SSH ไม่ถูกต้อง" }, "sshKeyTypeUnsupported": { - "message": "The SSH key type is not supported" + "message": "ไม่รองรับประเภทคีย์ SSH" }, "importSshKeyFromClipboard": { - "message": "Import key from clipboard" + "message": "นำเข้าคีย์จากคลิปบอร์ด" }, "sshKeyImported": { - "message": "SSH key imported successfully" + "message": "นำเข้าคีย์ SSH สำเร็จแล้ว" }, "cannotRemoveViewOnlyCollections": { - "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "message": "คุณไม่สามารถลบคอลเลกชันที่มีสิทธิ์ดูอย่างเดียวได้: $COLLECTIONS$", "placeholders": { "collections": { "content": "$1", @@ -5643,93 +5653,93 @@ } }, "updateDesktopAppOrDisableFingerprintDialogTitle": { - "message": "Please update your desktop application" + "message": "โปรดอัปเดตแอปพลิเคชันเดสก์ท็อปของคุณ" }, "updateDesktopAppOrDisableFingerprintDialogMessage": { - "message": "To use biometric unlock, please update your desktop application, or disable fingerprint unlock in the desktop settings." + "message": "หากต้องการใช้การปลดล็อกด้วยไบโอเมตริก โปรดอัปเดตแอปพลิเคชันเดสก์ท็อป หรือปิดใช้งานการปลดล็อกด้วยลายนิ้วมือในการตั้งค่าเดสก์ท็อป" }, "changeAtRiskPassword": { - "message": "Change at-risk password" + "message": "เปลี่ยนรหัสผ่านที่มีความเสี่ยง" }, "changeAtRiskPasswordAndAddWebsite": { - "message": "This login is at-risk and missing a website. Add a website and change the password for stronger security." + "message": "ข้อมูลเข้าสู่ระบบนี้มีความเสี่ยงและไม่มีเว็บไซต์ เพิ่มเว็บไซต์และเปลี่ยนรหัสผ่านเพื่อความปลอดภัยที่รัดกุมยิ่งขึ้น" }, "missingWebsite": { - "message": "Missing website" + "message": "ไม่มีเว็บไซต์" }, "settingsVaultOptions": { - "message": "Vault options" + "message": "ตัวเลือกตู้นิรภัย" }, "emptyVaultDescription": { - "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + "message": "ตู้นิรภัยปกป้องมากกว่าแค่รหัสผ่าน จัดเก็บข้อมูลเข้าสู่ระบบ เอกสารระบุตัวตน บัตร และโน้ตอย่างปลอดภัยที่นี่" }, "introCarouselLabel": { - "message": "Welcome to Bitwarden" + "message": "ยินดีต้อนรับสู่ Bitwarden" }, "securityPrioritized": { - "message": "Security, prioritized" + "message": "ให้ความสำคัญกับความปลอดภัย" }, "securityPrioritizedBody": { - "message": "Save logins, cards, and identities to your secure vault. Bitwarden uses zero-knowledge, end-to-end encryption to protect what’s important to you." + "message": "บันทึกข้อมูลเข้าสู่ระบบ บัตร และข้อมูลระบุตัวตนลงในตู้นิรภัยที่ปลอดภัย Bitwarden ใช้การเข้ารหัสแบบ End-to-end แบบ Zero-knowledge เพื่อปกป้องสิ่งที่สำคัญสำหรับคุณ" }, "quickLogin": { - "message": "Quick and easy login" + "message": "เข้าสู่ระบบที่รวดเร็วและง่ายดาย" }, "quickLoginBody": { - "message": "ตั้งค่าการปลดล็อกด้วยไบโอเมตริกซ์และการกรอกข้อมูลอัตโนมัติ เพื่อลงชื่อเข้าใช้บัญชีของคุณโดยไม่ต้องพิมพ์แม้แต่ตัวอักษรเดียว" + "message": "ตั้งค่าการปลดล็อกด้วยไบโอเมตริกและการป้อนอัตโนมัติเพื่อเข้าสู่ระบบบัญชีโดยไม่ต้องพิมพ์แม้แต่ตัวอักษรเดียว" }, "secureUser": { - "message": "Level up your logins" + "message": "ยกระดับการเข้าสู่ระบบของคุณ" }, "secureUserBody": { - "message": "Use the generator to create and save strong, unique passwords for all your accounts." + "message": "ใช้ตัวสร้างเพื่อสร้างและบันทึกรหัสผ่านที่รัดกุมและไม่ซ้ำกันสำหรับทุกบัญชีของคุณ" }, "secureDevices": { - "message": "Your data, when and where you need it" + "message": "ข้อมูลของคุณ ทุกที่ทุกเวลาที่คุณต้องการ" }, "secureDevicesBody": { - "message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps." + "message": "บันทึกรหัสผ่านได้ไม่จำกัดบนอุปกรณ์ไม่จำกัดด้วยแอป Bitwarden บนมือถือ เบราว์เซอร์ และเดสก์ท็อป" }, "nudgeBadgeAria": { - "message": "1 notification" + "message": "1 การแจ้งเตือน" }, "emptyVaultNudgeTitle": { - "message": "Import existing passwords" + "message": "นำเข้ารหัสผ่านที่มีอยู่" }, "emptyVaultNudgeBody": { - "message": "Use the importer to quickly transfer logins to Bitwarden without manually adding them." + "message": "ใช้ตัวนำเข้าเพื่อโอนย้ายข้อมูลเข้าสู่ระบบไปยัง Bitwarden อย่างรวดเร็วโดยไม่ต้องเพิ่มด้วยตนเอง" }, "emptyVaultNudgeButton": { - "message": "Import now" + "message": "นำเข้าเลย" }, "hasItemsVaultNudgeTitle": { - "message": "Welcome to your vault!" + "message": "ยินดีต้อนรับสู่ตู้นิรภัยของคุณ!" }, "phishingPageTitleV2": { - "message": "Phishing attempt detected" + "message": "ตรวจพบความพยายามฟิชชิง" }, "phishingPageSummary": { - "message": "The site you are attempting to visit is a known malicious site and a security risk." + "message": "ไซต์ที่คุณพยายามเข้าชมเป็นไซต์อันตรายที่รู้จักและมีความเสี่ยงด้านความปลอดภัย" }, "phishingPageCloseTabV2": { - "message": "Close this tab" + "message": "ปิดแท็บนี้" }, "phishingPageContinueV2": { - "message": "Continue to this site (not recommended)" + "message": "ไปที่ไซต์นี้ต่อ (ไม่แนะนำ)" }, "phishingPageExplanation1": { - "message": "This site was found in ", + "message": "พบไซต์นี้ใน ", "description": "This is in multiple parts to allow for bold text in the middle of the sentence. A proper name follows this." }, "phishingPageExplanation2": { - "message": ", an open-source list of known phishing sites used for stealing personal and sensitive information.", + "message": " ซึ่งเป็นรายการโอเพนซอร์สของไซต์ฟิชชิงที่รู้จักที่ใช้ขโมยข้อมูลส่วนบุคคลและข้อมูลสำคัญ", "description": "This is in multiple parts to allow for bold text in the middle of the sentence. A proper name precedes this." }, "phishingPageLearnMore": { - "message": "Learn more about phishing detection" + "message": "เรียนรู้เพิ่มเติมเกี่ยวกับการตรวจจับฟิชชิง" }, "protectedBy": { - "message": "Protected by $PRODUCT$", + "message": "ปกป้องโดย $PRODUCT$", "placeholders": { "product": { "content": "$1", @@ -5738,208 +5748,208 @@ } }, "hasItemsVaultNudgeBodyOne": { - "message": "Autofill items for the current page" + "message": "ป้อนข้อมูลรายการอัตโนมัติสำหรับหน้าปัจจุบัน" }, "hasItemsVaultNudgeBodyTwo": { - "message": "Favorite items for easy access" + "message": "ตั้งรายการโปรดเพื่อการเข้าถึงที่ง่ายดาย" }, "hasItemsVaultNudgeBodyThree": { - "message": "Search your vault for something else" + "message": "ค้นหาสิ่งอื่นในตู้นิรภัยของคุณ" }, "newLoginNudgeTitle": { - "message": "Save time with autofill" + "message": "ประหยัดเวลาด้วยการป้อนอัตโนมัติ" }, "newLoginNudgeBodyOne": { - "message": "Include a", + "message": "ระบุ", "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." }, "newLoginNudgeBodyBold": { - "message": "Website", + "message": "เว็บไซต์", "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." }, "newLoginNudgeBodyTwo": { - "message": "so this login appears as an autofill suggestion.", + "message": "เพื่อให้ข้อมูลเข้าสู่ระบบนี้ปรากฏเป็นคำแนะนำการป้อนอัตโนมัติ", "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." }, "newCardNudgeTitle": { - "message": "Seamless online checkout" + "message": "ชำระเงินออนไลน์ได้อย่างราบรื่น" }, "newCardNudgeBody": { - "message": "With cards, easily autofill payment forms securely and accurately." + "message": "ด้วยบัตร คุณสามารถป้อนแบบฟอร์มการชำระเงินอัตโนมัติได้อย่างปลอดภัยและแม่นยำ" }, "newIdentityNudgeTitle": { - "message": "Simplify creating accounts" + "message": "สร้างบัญชีได้ง่ายขึ้น" }, "newIdentityNudgeBody": { - "message": "With identities, quickly autofill long registration or contact forms." + "message": "ด้วยข้อมูลระบุตัวตน คุณสามารถป้อนแบบฟอร์มลงทะเบียนหรือข้อมูลติดต่อยาว ๆ ได้อย่างรวดเร็ว" }, "newNoteNudgeTitle": { - "message": "Keep your sensitive data safe" + "message": "เก็บรักษาข้อมูลสำคัญของคุณให้ปลอดภัย" }, "newNoteNudgeBody": { - "message": "With notes, securely store sensitive data like banking or insurance details." + "message": "ด้วยโน้ต คุณสามารถจัดเก็บข้อมูลสำคัญ เช่น รายละเอียดธนาคารหรือประกันภัยได้อย่างปลอดภัย" }, "newSshNudgeTitle": { - "message": "Developer-friendly SSH access" + "message": "การเข้าถึง SSH ที่เป็นมิตรกับนักพัฒนา" }, "newSshNudgeBodyOne": { - "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication.", + "message": "จัดเก็บคีย์ของคุณและเชื่อมต่อกับ SSH Agent เพื่อการยืนยันตัวตนที่รวดเร็วและเข้ารหัส", "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": "Learn more about SSH agent", + "message": "เรียนรู้เพิ่มเติมเกี่ยวกับ SSH Agent", "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, "generatorNudgeTitle": { - "message": "Quickly create passwords" + "message": "สร้างรหัสผ่านอย่างรวดเร็ว" }, "generatorNudgeBodyOne": { - "message": "Easily create strong and unique passwords by clicking on", + "message": "สร้างรหัสผ่านที่รัดกุมและไม่ซ้ำกันได้ง่าย ๆ โดยคลิกที่", "description": "Two part message", "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." }, "generatorNudgeBodyTwo": { - "message": "to help you keep your logins secure.", + "message": "เพื่อช่วยรักษาความปลอดภัยข้อมูลเข้าสู่ระบบของคุณ", "description": "Two part message", "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." }, "generatorNudgeBodyAria": { - "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "message": "สร้างรหัสผ่านที่รัดกุมและไม่ซ้ำกันได้ง่าย ๆ โดยคลิกที่ปุ่มสร้างรหัสผ่าน เพื่อช่วยรักษาความปลอดภัยข้อมูลเข้าสู่ระบบของคุณ", "description": "Aria label for the body content of the generator nudge" }, "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 เปลี่ยนรหัสผ่านใดเพื่อปรับปรุงประสบการณ์ของคุณ ไม่มีการรวบรวมหรือบันทึกข้อมูลเมื่อคุณใช้บริการนี้" }, "noPermissionsViewPage": { - "message": "You do not have permissions to view this page. Try logging in with a different account." + "message": "คุณไม่มีสิทธิ์ดูหน้านี้ ลองเข้าสู่ระบบด้วยบัญชีอื่น" }, "wasmNotSupported": { - "message": "WebAssembly is not supported on your browser or is not enabled. WebAssembly is required to use the Bitwarden app.", + "message": "เบราว์เซอร์ของคุณไม่รองรับ WebAssembly หรือไม่ได้เปิดใช้งาน จำเป็นต้องมี WebAssembly เพื่อใช้งานแอป Bitwarden", "description": "'WebAssembly' is a technical term and should not be translated." }, "showMore": { - "message": "Show more" + "message": "แสดงเพิ่มเติม" }, "showLess": { - "message": "Show less" + "message": "แสดงน้อยลง" }, "next": { - "message": "Next" + "message": "ถัดไป" }, "moreBreadcrumbs": { - "message": "More breadcrumbs", + "message": "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." }, "confirmKeyConnectorDomain": { - "message": "Confirm Key Connector domain" + "message": "ยืนยันโดเมน Key Connector" }, "atRiskLoginsSecured": { - "message": "Great job securing your at-risk logins!" + "message": "เยี่ยมมาก คุณจัดการความปลอดภัยข้อมูลเข้าสู่ระบบที่มีความเสี่ยงแล้ว!" }, "upgradeNow": { - "message": "Upgrade now" + "message": "อัปเกรดตอนนี้" }, "builtInAuthenticator": { - "message": "Built-in authenticator" + "message": "ตัวยืนยันตัวตนในตัว" }, "secureFileStorage": { - "message": "Secure file storage" + "message": "พื้นที่จัดเก็บไฟล์ที่ปลอดภัย" }, "emergencyAccess": { - "message": "Emergency access" + "message": "การเข้าถึงฉุกเฉิน" }, "breachMonitoring": { - "message": "Breach monitoring" + "message": "การตรวจสอบข้อมูลรั่วไหล" }, "andMoreFeatures": { - "message": "And more!" + "message": "และอื่น ๆ!" }, "advancedOnlineSecurity": { - "message": "Advanced online security" + "message": "ความปลอดภัยออนไลน์ขั้นสูง" }, "upgradeToPremium": { - "message": "Upgrade to Premium" + "message": "อัปเกรดเป็นพรีเมียม" }, "unlockAdvancedSecurity": { - "message": "Unlock advanced security features" + "message": "ปลดล็อกฟีเจอร์ความปลอดภัยขั้นสูง" }, "unlockAdvancedSecurityDesc": { - "message": "A Premium subscription gives you more tools to stay secure and in control" + "message": "การสมัครสมาชิกพรีเมียมมอบเครื่องมือเพิ่มเติมเพื่อให้คุณปลอดภัยและควบคุมได้" }, "explorePremium": { - "message": "Explore Premium" + "message": "สำรวจพรีเมียม" }, "loadingVault": { - "message": "Loading vault" + "message": "กำลังโหลดตู้นิรภัย" }, "vaultLoaded": { - "message": "Vault loaded" + "message": "โหลดตู้นิรภัยแล้ว" }, "settingDisabledByPolicy": { - "message": "This setting is disabled by your organization's policy.", + "message": "การตั้งค่านี้ถูกปิดใช้งานโดยนโยบายองค์กรของคุณ", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." }, "zipPostalCodeLabel": { - "message": "ZIP / Postal code" + "message": "รหัสไปรษณีย์" }, "cardNumberLabel": { - "message": "Card number" + "message": "หมายเลขบัตร" }, "removeMasterPasswordForOrgUserKeyConnector": { - "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + "message": "องค์กรของคุณไม่ใช้รหัสผ่านหลักในการเข้าสู่ระบบ Bitwarden อีกต่อไป หากต้องการดำเนินการต่อ ให้ยืนยันองค์กรและโดเมน" }, "continueWithLogIn": { - "message": "Continue with log in" + "message": "ดำเนินการเข้าสู่ระบบต่อ" }, "doNotContinue": { - "message": "Do not continue" + "message": "ไม่ดำเนินการต่อ" }, "domain": { - "message": "Domain" + "message": "โดเมน" }, "keyConnectorDomainTooltip": { - "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + "message": "โดเมนนี้จะจัดเก็บกุญแจเข้ารหัสบัญชีของคุณ ดังนั้นโปรดตรวจสอบให้แน่ใจว่าคุณเชื่อถือ หากไม่แน่ใจ ให้ตรวจสอบกับผู้ดูแลระบบ" }, "verifyYourOrganization": { - "message": "Verify your organization to log in" + "message": "ยืนยันองค์กรของคุณเพื่อเข้าสู่ระบบ" }, "organizationVerified": { - "message": "Organization verified" + "message": "ยืนยันองค์กรแล้ว" }, "domainVerified": { - "message": "Domain verified" + "message": "ยืนยันโดเมนแล้ว" }, "leaveOrganizationContent": { - "message": "If you don't verify your organization, your access to the organization will be revoked." + "message": "หากคุณไม่ยืนยันองค์กร สิทธิ์การเข้าถึงองค์กรของคุณจะถูกเพิกถอน" }, "leaveNow": { - "message": "Leave now" + "message": "ออกตอนนี้" }, "verifyYourDomainToLogin": { - "message": "Verify your domain to log in" + "message": "ยืนยันโดเมนของคุณเพื่อเข้าสู่ระบบ" }, "verifyYourDomainDescription": { - "message": "To continue with log in, verify this domain." + "message": "หากต้องการดำเนินการเข้าสู่ระบบต่อ ให้ยืนยันโดเมนนี้" }, "confirmKeyConnectorOrganizationUserDescription": { - "message": "To continue with log in, verify the organization and domain." + "message": "หากต้องการดำเนินการเข้าสู่ระบบต่อ ให้ยืนยันองค์กรและโดเมน" }, "sessionTimeoutSettingsAction": { - "message": "Timeout action" + "message": "การดำเนินการเมื่อหมดเวลา" }, "sessionTimeoutSettingsManagedByOrganization": { - "message": "This setting is managed by your organization." + "message": "การตั้งค่านี้ได้รับการจัดการโดยองค์กรของคุณ" }, "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { - "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "message": "องค์กรของคุณกำหนดเวลาหมดเวลาเซสชันสูงสุดไว้ที่ $HOURS$ ชั่วโมง $MINUTES$ นาที", "placeholders": { "hours": { "content": "$1", @@ -5952,16 +5962,16 @@ } }, "sessionTimeoutSettingsPolicySetDefaultTimeoutToImmediately": { - "message": "Your organization has set the default session timeout to Immediately." + "message": "องค์กรของคุณกำหนดเวลาหมดเวลาเซสชันเริ่มต้นเป็น ทันที" }, "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { - "message": "Your organization has set the default session timeout to On system lock." + "message": "องค์กรของคุณกำหนดเวลาหมดเวลาเซสชันเริ่มต้นเป็น เมื่อล็อกระบบ" }, "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { - "message": "Your organization has set the default session timeout to On browser restart." + "message": "องค์กรของคุณกำหนดเวลาหมดเวลาเซสชันเริ่มต้นเป็น เมื่อรีสตาร์ตเบราว์เซอร์" }, "sessionTimeoutSettingsPolicyMaximumError": { - "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "message": "เวลาหมดเวลาสูงสุดต้องไม่เกิน $HOURS$ ชั่วโมง $MINUTES$ นาที", "placeholders": { "hours": { "content": "$1", @@ -5974,25 +5984,25 @@ } }, "sessionTimeoutOnRestart": { - "message": "On browser restart" + "message": "เมื่อรีสตาร์ตเบราว์เซอร์" }, "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { - "message": "Set an unlock method to change your timeout action" + "message": "ตั้งค่าวิธีการปลดล็อกเพื่อเปลี่ยนการดำเนินการเมื่อหมดเวลา" }, "upgrade": { - "message": "Upgrade" + "message": "อัปเกรด" }, "leaveConfirmationDialogTitle": { - "message": "Are you sure you want to leave?" + "message": "ยืนยันที่จะออกหรือไม่" }, "leaveConfirmationDialogContentOne": { - "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + "message": "หากปฏิเสธ รายการส่วนตัวจะยังคงอยู่ในบัญชีของคุณ แต่คุณจะเสียสิทธิ์เข้าถึงรายการที่แชร์และฟีเจอร์ขององค์กร" }, "leaveConfirmationDialogContentTwo": { - "message": "Contact your admin to regain access." + "message": "ติดต่อผู้ดูแลระบบเพื่อขอรับสิทธิ์เข้าถึงอีกครั้ง" }, "leaveConfirmationDialogConfirmButton": { - "message": "Leave $ORGANIZATION$", + "message": "ออกจาก $ORGANIZATION$", "placeholders": { "organization": { "content": "$1", @@ -6001,10 +6011,10 @@ } }, "howToManageMyVault": { - "message": "How do I manage my vault?" + "message": "ฉันจะจัดการตู้นิรภัยได้อย่างไร" }, "transferItemsToOrganizationTitle": { - "message": "Transfer items to $ORGANIZATION$", + "message": "โอนย้ายรายการไปยัง $ORGANIZATION$", "placeholders": { "organization": { "content": "$1", @@ -6013,7 +6023,7 @@ } }, "transferItemsToOrganizationContent": { - "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "message": "$ORGANIZATION$ กำหนดให้รายการทั้งหมดต้องเป็นขององค์กรเพื่อความปลอดภัยและการปฏิบัติตามข้อกำหนด คลิกยอมรับเพื่อโอนกรรมสิทธิ์รายการของคุณ", "placeholders": { "organization": { "content": "$1", @@ -6022,12 +6032,12 @@ } }, "acceptTransfer": { - "message": "Accept transfer" + "message": "ยอมรับการโอนย้าย" }, "declineAndLeave": { - "message": "Decline and leave" + "message": "ปฏิเสธและออก" }, "whyAmISeeingThis": { - "message": "Why am I seeing this?" + "message": "ทำไมฉันจึงเห็นสิ่งนี้" } } diff --git a/apps/browser/src/_locales/tr/messages.json b/apps/browser/src/_locales/tr/messages.json index 70e9a5c0afe..e6a787716a4 100644 --- a/apps/browser/src/_locales/tr/messages.json +++ b/apps/browser/src/_locales/tr/messages.json @@ -1322,11 +1322,21 @@ "exportFrom": { "message": "Dışa aktarılacak konum" }, - "export": { - "message": "Dışa aktar" + "exportVerb": { + "message": "Dışa aktar", + "description": "The verb form of the word Export" }, - "import": { - "message": "İçe aktar" + "exportNoun": { + "message": "Dışa aktar", + "description": "The noun form of the word Export" + }, + "importNoun": { + "message": "İçe aktar", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "İçe aktar", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "Dosya biçimi" diff --git a/apps/browser/src/_locales/uk/messages.json b/apps/browser/src/_locales/uk/messages.json index 8d040513d67..d777dee1472 100644 --- a/apps/browser/src/_locales/uk/messages.json +++ b/apps/browser/src/_locales/uk/messages.json @@ -437,7 +437,7 @@ "message": "Синхронізація" }, "syncNow": { - "message": "Sync now" + "message": "Синхронізувати" }, "lastSync": { "message": "Остання синхронізація:" @@ -583,7 +583,7 @@ "message": "Архівовані записи виключаються з результатів звичайного пошуку та пропозицій автозаповнення. Ви дійсно хочете архівувати цей запис?" }, "upgradeToUseArchive": { - "message": "A premium membership is required to use Archive." + "message": "Для використання архіву необхідна передплата Premium." }, "edit": { "message": "Змінити" @@ -595,10 +595,10 @@ "message": "Переглянути все" }, "showAll": { - "message": "Show all" + "message": "Показати все" }, "viewLess": { - "message": "View less" + "message": "Показати менше" }, "viewLogin": { "message": "Переглянути запис" @@ -803,10 +803,10 @@ "message": "З блокуванням системи" }, "onIdle": { - "message": "On system idle" + "message": "Бездіяльність системи" }, "onSleep": { - "message": "On system sleep" + "message": "Режим сну" }, "onRestart": { "message": "З перезапуском браузера" @@ -1322,11 +1322,21 @@ "exportFrom": { "message": "Експортувати з" }, - "export": { - "message": "Export" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, - "import": { - "message": "Import" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "Формат файлу" @@ -1407,25 +1417,25 @@ "message": "Докладніше" }, "migrationsFailed": { - "message": "An error occurred updating the encryption settings." + "message": "Сталася помилка під час оновлення налаштувань шифрування." }, "updateEncryptionSettingsTitle": { - "message": "Update your encryption settings" + "message": "Оновіть свої налаштування шифрування" }, "updateEncryptionSettingsDesc": { - "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + "message": "Нові рекомендовані налаштування шифрування покращать безпеку вашого облікового запису. Щоб оновити їх, введіть свій головний пароль." }, "confirmIdentityToContinue": { - "message": "Confirm your identity to continue" + "message": "Щоб продовжити, підтвердьте свої облікові дані" }, "enterYourMasterPassword": { - "message": "Enter your master password" + "message": "Введіть головний пароль" }, "updateSettings": { - "message": "Update settings" + "message": "Оновити налаштування" }, "later": { - "message": "Later" + "message": "Пізніше" }, "authenticatorKeyTotp": { "message": "Ключ автентифікації (TOTP)" @@ -1458,13 +1468,13 @@ "message": "Вкладення збережено" }, "fixEncryption": { - "message": "Fix encryption" + "message": "Виправити шифрування" }, "fixEncryptionTooltip": { - "message": "This file is using an outdated encryption method." + "message": "Для цього файлу використовується застарілий метод шифрування." }, "attachmentUpdated": { - "message": "Attachment updated" + "message": "Вкладення оновлено" }, "file": { "message": "Файл" @@ -1476,7 +1486,7 @@ "message": "Оберіть файл" }, "itemsTransferred": { - "message": "Items transferred" + "message": "Записи переміщено" }, "maxFileSize": { "message": "Максимальний розмір файлу 500 МБ." @@ -1509,7 +1519,7 @@ "message": "1 ГБ зашифрованого сховища для файлів." }, "premiumSignUpStorageV2": { - "message": "$SIZE$ encrypted storage for file attachments.", + "message": "$SIZE$ зашифрованого сховища для вкладених файлів.", "placeholders": { "size": { "content": "$1", @@ -1916,7 +1926,7 @@ "message": "Рік завершення" }, "monthly": { - "message": "month" + "message": "місяць" }, "expiration": { "message": "Термін дії" @@ -2488,7 +2498,7 @@ } }, "topLayerHijackWarning": { - "message": "This page is interfering with the Bitwarden experience. The Bitwarden inline menu has been temporarily disabled as a safety measure." + "message": "Ця сторінка заважає роботі Bitwarden. Задля безпеки вбудоване меню Bitwarden тимчасово вимкнено." }, "setMasterPassword": { "message": "Встановити головний пароль" @@ -4802,13 +4812,13 @@ "message": "Безпека облікового запису" }, "phishingBlocker": { - "message": "Phishing Blocker" + "message": "Блокувальник шахрайства" }, "enablePhishingDetection": { - "message": "Phishing detection" + "message": "Виявлення шахрайства" }, "enablePhishingDetectionDesc": { - "message": "Display warning before accessing suspected phishing sites" + "message": "Показувати попередження перед відвідуванням підозрюваних шахрайських сайтів" }, "notifications": { "message": "Сповіщення" @@ -4956,7 +4966,7 @@ "message": "Premium" }, "unlockFeaturesWithPremium": { - "message": "Unlock reporting, emergency access, and more security features with Premium." + "message": "Розблокуйте звіти, екстрений доступ та інші функції безпеки, передплативши Premium." }, "freeOrgsCannotUseAttachments": { "message": "Організації без передплати не можуть використовувати вкладення" @@ -5043,7 +5053,7 @@ } }, "defaultLabelWithValue": { - "message": "Default ( $VALUE$ )", + "message": "Типово ( $VALUE$ )", "description": "A label that indicates the default value for a field with the current default value in parentheses.", "placeholders": { "value": { @@ -5863,25 +5873,25 @@ "message": "Інші можливості!" }, "advancedOnlineSecurity": { - "message": "Advanced online security" + "message": "Розширена онлайн-безпека" }, "upgradeToPremium": { "message": "Покращити до Premium" }, "unlockAdvancedSecurity": { - "message": "Unlock advanced security features" + "message": "Розблокуйте розширені функції безпеки" }, "unlockAdvancedSecurityDesc": { - "message": "A Premium subscription gives you more tools to stay secure and in control" + "message": "З передплатою Premium ви матимете більше інструментів безпеки та контролю" }, "explorePremium": { - "message": "Explore Premium" + "message": "Ознайомитися з Premium" }, "loadingVault": { - "message": "Loading vault" + "message": "Завантаження сховища" }, "vaultLoaded": { - "message": "Vault loaded" + "message": "Сховище завантажено" }, "settingDisabledByPolicy": { "message": "Цей параметр вимкнено політикою вашої організації.", @@ -5894,52 +5904,52 @@ "message": "Номер картки" }, "removeMasterPasswordForOrgUserKeyConnector": { - "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + "message": "Ваша організація більше не використовує головні паролі для входу в Bitwarden. Щоб продовжити, підтвердіть організацію та домен." }, "continueWithLogIn": { - "message": "Continue with log in" + "message": "Перейти до входу" }, "doNotContinue": { - "message": "Do not continue" + "message": "Не продовжувати" }, "domain": { - "message": "Domain" + "message": "Домен" }, "keyConnectorDomainTooltip": { - "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + "message": "Цей домен зберігатиме ключі шифрування вашого облікового запису, тому переконайтеся в його надійності. Якщо ви не впевнені, зверніться до свого адміністратора." }, "verifyYourOrganization": { - "message": "Verify your organization to log in" + "message": "Підтвердьте свою організацію, щоб увійти" }, "organizationVerified": { - "message": "Organization verified" + "message": "Організацію підтверджено" }, "domainVerified": { - "message": "Domain verified" + "message": "Домен підтверджено" }, "leaveOrganizationContent": { - "message": "If you don't verify your organization, your access to the organization will be revoked." + "message": "Якщо ви не підтвердите свою організацію, ваш доступ до неї буде відкликаний." }, "leaveNow": { - "message": "Leave now" + "message": "Покинути" }, "verifyYourDomainToLogin": { - "message": "Verify your domain to log in" + "message": "Підтвердьте свій домен, щоб увійти" }, "verifyYourDomainDescription": { - "message": "To continue with log in, verify this domain." + "message": "Щоб перейти до входу, підтвердьте цей домен." }, "confirmKeyConnectorOrganizationUserDescription": { - "message": "To continue with log in, verify the organization and domain." + "message": "Щоб перейти до входу, підтвердьте організацію і домен." }, "sessionTimeoutSettingsAction": { - "message": "Timeout action" + "message": "Дія після часу очікування" }, "sessionTimeoutSettingsManagedByOrganization": { - "message": "This setting is managed by your organization." + "message": "Цим параметром керує ваша організація." }, "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { - "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "message": "Ваша організація встановила максимальний час очікування сеансу $HOURS$ год і $MINUTES$ хв.", "placeholders": { "hours": { "content": "$1", @@ -5952,16 +5962,16 @@ } }, "sessionTimeoutSettingsPolicySetDefaultTimeoutToImmediately": { - "message": "Your organization has set the default session timeout to Immediately." + "message": "Ваша організація встановила максимальний час очікування сеансу \"Негайно\"." }, "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { - "message": "Your organization has set the default session timeout to On system lock." + "message": "Ваша організація встановила максимальний час очікування сеансу \"Блокування системи\"." }, "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { - "message": "Your organization has set the default session timeout to On browser restart." + "message": "Ваша організація встановила максимальний час очікування сеансу \"З перезапуском браузера\"." }, "sessionTimeoutSettingsPolicyMaximumError": { - "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "message": "Максимальний час очікування не може перевищувати $HOURS$ год і $MINUTES$ хв", "placeholders": { "hours": { "content": "$1", @@ -5974,25 +5984,25 @@ } }, "sessionTimeoutOnRestart": { - "message": "On browser restart" + "message": "З перезапуском браузера" }, "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { - "message": "Set an unlock method to change your timeout action" + "message": "Встановіть спосіб розблокування, щоб змінити дію під час очікування" }, "upgrade": { - "message": "Upgrade" + "message": "Оновити" }, "leaveConfirmationDialogTitle": { - "message": "Are you sure you want to leave?" + "message": "Ви дійсно хочете покинути?" }, "leaveConfirmationDialogContentOne": { - "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + "message": "Відхиливши, ваші особисті записи залишаться у вашому обліковому записі, але ви втратите доступ до спільних записів та функцій організації." }, "leaveConfirmationDialogContentTwo": { - "message": "Contact your admin to regain access." + "message": "Зверніться до свого адміністратора, щоб відновити доступ." }, "leaveConfirmationDialogConfirmButton": { - "message": "Leave $ORGANIZATION$", + "message": "Покинути $ORGANIZATION$", "placeholders": { "organization": { "content": "$1", @@ -6001,10 +6011,10 @@ } }, "howToManageMyVault": { - "message": "How do I manage my vault?" + "message": "Як керувати своїм сховищем?" }, "transferItemsToOrganizationTitle": { - "message": "Transfer items to $ORGANIZATION$", + "message": "Перемістити записи до $ORGANIZATION$", "placeholders": { "organization": { "content": "$1", @@ -6013,7 +6023,7 @@ } }, "transferItemsToOrganizationContent": { - "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "message": "З міркувань безпеки та для забезпечення відповідності $ORGANIZATION$ вимагає, щоб власником всіх елементів була організація. Натисніть кнопку \"прийняти\", щоб передати права власності на ваші елементи.", "placeholders": { "organization": { "content": "$1", @@ -6022,12 +6032,12 @@ } }, "acceptTransfer": { - "message": "Accept transfer" + "message": "Схвалити переміщення" }, "declineAndLeave": { - "message": "Decline and leave" + "message": "Відхилити і покинути" }, "whyAmISeeingThis": { - "message": "Why am I seeing this?" + "message": "Чому я це бачу?" } } diff --git a/apps/browser/src/_locales/vi/messages.json b/apps/browser/src/_locales/vi/messages.json index 4049af21e06..cd8ad7cbc91 100644 --- a/apps/browser/src/_locales/vi/messages.json +++ b/apps/browser/src/_locales/vi/messages.json @@ -1322,11 +1322,21 @@ "exportFrom": { "message": "Xuất từ" }, - "export": { - "message": "Export" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, - "import": { - "message": "Import" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "Định dạng tập tin" diff --git a/apps/browser/src/_locales/zh_CN/messages.json b/apps/browser/src/_locales/zh_CN/messages.json index 4ebfaca7ef8..53d51a8e16f 100644 --- a/apps/browser/src/_locales/zh_CN/messages.json +++ b/apps/browser/src/_locales/zh_CN/messages.json @@ -1322,11 +1322,21 @@ "exportFrom": { "message": "导出自" }, - "export": { - "message": "导出" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, - "import": { - "message": "导入" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "文件格式" @@ -2355,7 +2365,7 @@ "message": "无效 PIN 码。" }, "tooManyInvalidPinEntryAttemptsLoggingOut": { - "message": "无效的 PIN 输入尝试次数过多,正在注销。" + "message": "无效的 PIN 输入尝试次数过多。正在注销。" }, "unlockWithBiometrics": { "message": "使用生物识别解锁" @@ -4802,13 +4812,13 @@ "message": "账户安全" }, "phishingBlocker": { - "message": "Phishing Blocker" + "message": "网络钓鱼拦截器" }, "enablePhishingDetection": { - "message": "Phishing detection" + "message": "网络钓鱼检测" }, "enablePhishingDetectionDesc": { - "message": "Display warning before accessing suspected phishing sites" + "message": "在访问疑似钓鱼网站前显示警告" }, "notifications": { "message": "通知" @@ -5726,7 +5736,7 @@ "description": "This is in multiple parts to allow for bold text in the middle of the sentence. A proper name precedes this." }, "phishingPageLearnMore": { - "message": "进一步了解钓鱼检测" + "message": "进一步了解网络钓鱼检测" }, "protectedBy": { "message": "受 $PRODUCT$ 保护", diff --git a/apps/browser/src/_locales/zh_TW/messages.json b/apps/browser/src/_locales/zh_TW/messages.json index e0b69c212b5..b36ba76f0a1 100644 --- a/apps/browser/src/_locales/zh_TW/messages.json +++ b/apps/browser/src/_locales/zh_TW/messages.json @@ -1322,11 +1322,21 @@ "exportFrom": { "message": "匯出自" }, - "export": { - "message": "Export" + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, - "import": { - "message": "Import" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "fileFormat": { "message": "檔案格式" diff --git a/apps/browser/store/locales/th/copy.resx b/apps/browser/store/locales/th/copy.resx index 1473e24d92e..5ffab9e41fe 100644 --- a/apps/browser/store/locales/th/copy.resx +++ b/apps/browser/store/locales/th/copy.resx @@ -118,58 +118,57 @@ <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 - จัดการรหัสผ่าน</value> + <value>Bitwarden Password Manager</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>ไม่ว่าจะที่บ้าน ที่ทำงาน หรือระหว่างเดินทาง Bitwarden ช่วยปกป้องรหัสผ่าน พาสคีย์ และข้อมูลสำคัญของคุณได้อย่างง่ายดาย</value> </data> <data name="Description" xml:space="preserve"> - <value>Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more! + <value>ได้รับการยอมรับว่าเป็นตัวจัดการรหัสผ่านที่ดีที่สุดโดย PCMag, WIRED, The Verge, CNET, G2 และอีกมากมาย! -SECURE YOUR DIGITAL LIFE -Secure your digital life and protect against data breaches by generating and saving unique, strong passwords for every account. Maintain everything in an end-to-end encrypted password vault that only you can access. +ปกป้องชีวิตดิจิทัลของคุณ +ปกป้องชีวิตดิจิทัลและป้องกันข้อมูลรั่วไหลโดยการสร้างและบันทึกรหัสผ่านที่รัดกุมและไม่ซ้ำกันสำหรับทุกบัญชี ดูแลจัดการทุกอย่างในตู้นิรภัยรหัสผ่านที่เข้ารหัสแบบต้นทางถึงปลายทาง ซึ่งมีเพียงคุณเท่านั้นที่เข้าถึงได้ -ACCESS YOUR DATA, ANYWHERE, ANYTIME, ON ANY DEVICE -Easily manage, store, secure, and share unlimited passwords across unlimited devices without restrictions. +เข้าถึงข้อมูลของคุณได้ทุกที่ ทุกเวลา บนทุกอุปกรณ์ +จัดการ จัดเก็บ ปกป้อง และแชร์รหัสผ่านได้ไม่จำกัดบนอุปกรณ์ไม่จำกัดจำนวนได้อย่างง่ายดายโดยไม่มีข้อจำกัด -EVERYONE SHOULD HAVE THE TOOLS TO STAY SAFE ONLINE -Utilize Bitwarden for free with no ads or selling data. Bitwarden believes everyone should have the ability to stay safe online. Premium plans offer access to advanced features. +ทุกคนควรมีเครื่องมือเพื่อความปลอดภัยบนโลกออนไลน์ +ใช้งาน Bitwarden ได้ฟรีโดยไม่มีโฆษณาหรือการขายข้อมูล Bitwarden เชื่อว่าทุกคนควรมีความสามารถในการอยู่อย่างปลอดภัยบนโลกออนไลน์ แผนพรีเมียมจะมอบการเข้าถึงฟีเจอร์ขั้นสูง -EMPOWER YOUR TEAMS WITH BITWARDEN -Plans for Teams and Enterprise come with professional business features. Some examples include SSO integration, self-hosting, directory integration and SCIM provisioning, global policies, API access, event logs, and more. +เสริมศักยภาพทีมของคุณด้วย BITWARDEN +แผนสำหรับ Teams และ Enterprise มาพร้อมกับฟีเจอร์ทางธุรกิจระดับมืออาชีพ ตัวอย่างเช่น การรวม SSO, การโฮสต์เอง (Self-hosting), การรวมไดเรกทอรีและการจัดเตรียม SCIM, นโยบายระดับองค์กร, การเข้าถึง API, บันทึกเหตุการณ์ และอื่น ๆ -Use Bitwarden to secure your workforce and share sensitive information with colleagues. +ใช้ Bitwarden เพื่อปกป้องบุคลากรของคุณและแชร์ข้อมูลสำคัญกับเพื่อนร่วมงาน +เหตุผลเพิ่มเติมที่ควรเลือก Bitwarden: -More reasons to choose Bitwarden: +การเข้ารหัสระดับโลก +รหัสผ่านได้รับการปกป้องด้วยการเข้ารหัสแบบต้นทางถึงปลายทาง ขั้นสูง (AES-256 bit, salted hashing และ PBKDF2 SHA-256) ข้อมูลของคุณจึงปลอดภัยและเป็นส่วนตัว -World-Class Encryption -Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashing, and PBKDF2 SHA-256) so your data stays secure and private. +การตรวจสอบโดยบุคคลที่สาม +Bitwarden ดำเนินการตรวจสอบความปลอดภัยโดยบุคคลที่สามอย่างครอบคลุมกับบริษัทความปลอดภัยที่มีชื่อเสียงอย่างสม่ำเสมอ การตรวจสอบประจำปีเหล่านี้รวมถึงการประเมินซอร์สโค้ดและการทดสอบเจาะระบบครอบคลุมทั้ง IP, เซิร์ฟเวอร์ และเว็บแอปพลิเคชันของ Bitwarden -3rd-party Audits -Bitwarden regularly conducts comprehensive third-party security audits with notable security firms. These annual audits include source code assessments and penetration testing across Bitwarden IPs, servers, and web applications. - -Advanced 2FA -Secure your login with a third-party authenticator, emailed codes, or FIDO2 WebAuthn credentials such as a hardware security key or passkey. +2FA ขั้นสูง +ปกป้องการเข้าสู่ระบบของคุณด้วยแอปยืนยันตัวตนของบุคคลที่สาม, รหัสทางอีเมล หรือข้อมูลประจำตัว FIDO2 WebAuthn เช่น คีย์ความปลอดภัยฮาร์ดแวร์หรือพาสคีย์ Bitwarden Send -Transmit data directly to others while maintaining end-to-end encrypted security and limiting exposure. +ส่งข้อมูลโดยตรงถึงผู้อื่นในขณะที่ยังคงรักษาความปลอดภัยด้วยการเข้ารหัสแบบต้นทางถึงปลายทาง และจำกัดการเปิดเผยข้อมูล -Built-in Generator -Create long, complex, and distinct passwords and unique usernames for every site you visit. Integrate with email alias providers for additional privacy. +ตัวสร้างในตัว +สร้างรหัสผ่านที่ยาว ซับซ้อน และแตกต่างกัน รวมถึงชื่อผู้ใช้ที่ไม่ซ้ำกันสำหรับทุกเว็บไซต์ที่คุณเยี่ยมชม ผสานรวมกับผู้ให้บริการนามแฝงอีเมลเพื่อความเป็นส่วนตัวเพิ่มเติม -Global Translations -Bitwarden translations exist for more than 60 languages, translated by the global community though Crowdin. +การแปลภาษาทั่วโลก +Bitwarden มีการแปลภาษามากกว่า 60 ภาษา โดยชุมชนทั่วโลกผ่าน Crowdin -Cross-Platform Applications -Secure and share sensitive data within your Bitwarden Vault from any browser, mobile device, or desktop OS, and more. +แอปพลิเคชันข้ามแพลตฟอร์ม +ปกป้องและแชร์ข้อมูลสำคัญภายในตู้นิรภัย Bitwarden ของคุณจากเบราว์เซอร์ อุปกรณ์มือถือ หรือระบบปฏิบัติการเดสก์ท็อปใดก็ได้ และอื่น ๆ -Bitwarden secures more than just passwords -End-to-end encrypted credential management solutions from Bitwarden empower organizations to secure everything, including developer secrets and passkey experiences. Visit Bitwarden.com to learn more about Bitwarden Secrets Manager and Bitwarden Passwordless.dev! +Bitwarden ปกป้องมากกว่าแค่รหัสผ่าน +โซลูชันการจัดการข้อมูลประจำตัวที่เข้ารหัสแบบ End-to-end จาก Bitwarden ช่วยให้องค์กรปกป้องทุกสิ่ง รวมถึงความลับของนักพัฒนาและประสบการณ์พาสคีย์ เยี่ยมชม Bitwarden.com เพื่อเรียนรู้เพิ่มเติมเกี่ยวกับ Bitwarden Secrets Manager และ Bitwarden Passwordless.dev! </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>ไม่ว่าจะที่บ้าน ที่ทำงาน หรือระหว่างเดินทาง Bitwarden ช่วยปกป้องรหัสผ่าน พาสคีย์ และข้อมูลสำคัญของคุณได้อย่างง่ายดาย</value> </data> <data name="ScreenshotSync" xml:space="preserve"> <value>ซิงค์และเข้าถึงตู้นิรภัยของคุณจากหลายอุปกรณ์</value> @@ -178,15 +177,15 @@ End-to-end encrypted credential management solutions from Bitwarden empower orga <value>จัดการล็อกอินและรหัสผ่านทั้งหมดของคุณจากตู้นิรภัยที่ปลอดภัย</value> </data> <data name="ScreenshotAutofill" xml:space="preserve"> - <value>กรอกข้อมูลล็อกอินโดยอัตโนมัติบนทุกเว็บไซต์ที่คุณใช้งานได้อย่างรวดเร็ว</value> + <value>ป้อนข้อมูลเข้าสู่ระบบของคุณลงในเว็บไซต์ที่คุณเยี่ยมชมอย่างรวดเร็วโดยอัตโนมัติ</value> </data> <data name="ScreenshotMenu" xml:space="preserve"> - <value>ตู้เซฟของคุณยังสามารถเข้าถึงได้โดยสะดวกผ่านเมนูคลิกขวา</value> + <value>เข้าถึงตู้นิรภัยของคุณได้อย่างสะดวกจากเมนูคลิกขวา</value> </data> <data name="ScreenshotPassword" xml:space="preserve"> - <value>ส่มสร้างรหัสผ่านที่แข็งแกร่งและปลอดภัยโดยอัตโนมัติ</value> + <value>สร้างรหัสผ่านที่รัดกุม แบบสุ่ม และปลอดภัยโดยอัตโนมัติ</value> </data> <data name="ScreenshotEdit" xml:space="preserve"> - <value>ข้อมูลของคุณได้รับการจัดการอย่างปลอดภัยโดยใช้การเข้ารหัส AES 256 บิต</value> + <value>ข้อมูลของคุณได้รับการจัดการอย่างปลอดภัยโดยใช้การเข้ารหัส AES-256 bit</value> </data> </root> From 21157f71e70e1731659ce1cdfbc447e31c070402 Mon Sep 17 00:00:00 2001 From: "bw-ghapp[bot]" <178206702+bw-ghapp[bot]@users.noreply.github.com> Date: Fri, 19 Dec 2025 12:24:41 +0100 Subject: [PATCH 130/188] Autosync the updated translations (#18046) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/web/src/locales/af/messages.json | 75 +++++++++++++++++-- apps/web/src/locales/ar/messages.json | 75 +++++++++++++++++-- apps/web/src/locales/az/messages.json | 75 +++++++++++++++++-- apps/web/src/locales/be/messages.json | 75 +++++++++++++++++-- apps/web/src/locales/bg/messages.json | 75 +++++++++++++++++-- apps/web/src/locales/bn/messages.json | 75 +++++++++++++++++-- apps/web/src/locales/bs/messages.json | 75 +++++++++++++++++-- apps/web/src/locales/ca/messages.json | 75 +++++++++++++++++-- apps/web/src/locales/cs/messages.json | 75 +++++++++++++++++-- apps/web/src/locales/cy/messages.json | 75 +++++++++++++++++-- apps/web/src/locales/da/messages.json | 75 +++++++++++++++++-- apps/web/src/locales/de/messages.json | 75 +++++++++++++++++-- apps/web/src/locales/el/messages.json | 75 +++++++++++++++++-- apps/web/src/locales/en_GB/messages.json | 75 +++++++++++++++++-- apps/web/src/locales/en_IN/messages.json | 75 +++++++++++++++++-- apps/web/src/locales/eo/messages.json | 75 +++++++++++++++++-- apps/web/src/locales/es/messages.json | 75 +++++++++++++++++-- apps/web/src/locales/et/messages.json | 75 +++++++++++++++++-- apps/web/src/locales/eu/messages.json | 75 +++++++++++++++++-- apps/web/src/locales/fa/messages.json | 75 +++++++++++++++++-- apps/web/src/locales/fi/messages.json | 75 +++++++++++++++++-- apps/web/src/locales/fil/messages.json | 75 +++++++++++++++++-- apps/web/src/locales/fr/messages.json | 75 +++++++++++++++++-- apps/web/src/locales/gl/messages.json | 75 +++++++++++++++++-- apps/web/src/locales/he/messages.json | 75 +++++++++++++++++-- apps/web/src/locales/hi/messages.json | 75 +++++++++++++++++-- apps/web/src/locales/hr/messages.json | 91 ++++++++++++++++++++---- apps/web/src/locales/hu/messages.json | 75 +++++++++++++++++-- apps/web/src/locales/id/messages.json | 75 +++++++++++++++++-- apps/web/src/locales/it/messages.json | 75 +++++++++++++++++-- apps/web/src/locales/ja/messages.json | 75 +++++++++++++++++-- apps/web/src/locales/ka/messages.json | 75 +++++++++++++++++-- apps/web/src/locales/km/messages.json | 75 +++++++++++++++++-- apps/web/src/locales/kn/messages.json | 75 +++++++++++++++++-- apps/web/src/locales/ko/messages.json | 75 +++++++++++++++++-- apps/web/src/locales/lv/messages.json | 75 +++++++++++++++++-- apps/web/src/locales/ml/messages.json | 75 +++++++++++++++++-- apps/web/src/locales/mr/messages.json | 75 +++++++++++++++++-- apps/web/src/locales/my/messages.json | 75 +++++++++++++++++-- apps/web/src/locales/nb/messages.json | 75 +++++++++++++++++-- apps/web/src/locales/ne/messages.json | 75 +++++++++++++++++-- apps/web/src/locales/nl/messages.json | 75 +++++++++++++++++-- apps/web/src/locales/nn/messages.json | 75 +++++++++++++++++-- apps/web/src/locales/or/messages.json | 75 +++++++++++++++++-- apps/web/src/locales/pl/messages.json | 75 +++++++++++++++++-- apps/web/src/locales/pt_BR/messages.json | 75 +++++++++++++++++-- apps/web/src/locales/pt_PT/messages.json | 75 +++++++++++++++++-- apps/web/src/locales/ro/messages.json | 75 +++++++++++++++++-- apps/web/src/locales/ru/messages.json | 75 +++++++++++++++++-- apps/web/src/locales/si/messages.json | 75 +++++++++++++++++-- apps/web/src/locales/sk/messages.json | 75 +++++++++++++++++-- apps/web/src/locales/sl/messages.json | 75 +++++++++++++++++-- apps/web/src/locales/sr_CS/messages.json | 75 +++++++++++++++++-- apps/web/src/locales/sr_CY/messages.json | 75 +++++++++++++++++-- apps/web/src/locales/sv/messages.json | 75 +++++++++++++++++-- apps/web/src/locales/ta/messages.json | 75 +++++++++++++++++-- apps/web/src/locales/te/messages.json | 75 +++++++++++++++++-- apps/web/src/locales/th/messages.json | 75 +++++++++++++++++-- apps/web/src/locales/tr/messages.json | 75 +++++++++++++++++-- apps/web/src/locales/uk/messages.json | 75 +++++++++++++++++-- apps/web/src/locales/vi/messages.json | 75 +++++++++++++++++-- apps/web/src/locales/zh_CN/messages.json | 75 +++++++++++++++++-- apps/web/src/locales/zh_TW/messages.json | 75 +++++++++++++++++-- 63 files changed, 4481 insertions(+), 260 deletions(-) diff --git a/apps/web/src/locales/af/messages.json b/apps/web/src/locales/af/messages.json index 0961a8dd5e2..76f130bc4d5 100644 --- a/apps/web/src/locales/af/messages.json +++ b/apps/web/src/locales/af/messages.json @@ -1969,8 +1969,13 @@ "encExportAccountWarningDesc": { "message": "Rekeningenkripsiesleutels is uniek tot elke Bitwarden-gebruikersrekening, daarom kan u nie ’n geënkripteerde uitstuur in ’n ander rekening invoer nie." }, - "export": { - "message": "Uitstuur" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, "exportFrom": { "message": "Export from" @@ -2297,8 +2302,13 @@ "tools": { "message": "Nutsmiddels" }, - "import": { - "message": "Import" + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "importData": { "message": "Voer data in" @@ -3283,6 +3293,9 @@ "launchCloudSubscription": { "message": "Launch Cloud Subscription" }, + "launchCloudSubscriptionSentenceCase": { + "message": "Launch cloud subscription" + }, "storage": { "message": "Berging" }, @@ -4198,6 +4211,12 @@ } } }, + "userAcceptedTransfer": { + "message": "Accepted transfer to organization ownership." + }, + "userDeclinedTransfer": { + "message": "Revoked for declining transfer to organization ownership." + }, "invitedUserId": { "message": "Gebruiker $ID$ genooi.", "placeholders": { @@ -12412,5 +12431,53 @@ }, "whyAmISeeingThis": { "message": "Why am I seeing this?" + }, + "youHaveBitwardenPremium": { + "message": "You have Bitwarden Premium" + }, + "viewAndManagePremiumSubscription": { + "message": "View and manage your Premium subscription" + }, + "youNeedToUpdateLicenseFile": { + "message": "You'll need to update your license file" + }, + "youNeedToUpdateLicenseFileDate": { + "message": "$DATE$.", + "placeholders": { + "date": { + "content": "$1", + "example": "June 12, 2026" + } + } + }, + "uploadLicenseFile": { + "message": "Upload license file" + }, + "uploadYourLicenseFile": { + "message": "Upload your license file" + }, + "uploadYourPremiumLicenseFile": { + "message": "Upload your Premium license file" + }, + "uploadLicenseFileDesc": { + "message": "Your license file name will be similar to: $FILE_NAME$", + "placeholders": { + "file_name": { + "content": "$1", + "example": "bitwarden_license.json" + } + } + }, + "alreadyHaveSubscriptionQuestion": { + "message": "Already have a subscription?" + }, + "alreadyHaveSubscriptionSelfHostedMessage": { + "message": "Open the subscription page on your Bitwarden cloud account and download your license file. Then return to this screen and upload it below." + }, + "viewAllPlans": { + "message": "View all plans" + }, + "planDescPremium": { + "message": "Complete online security" } } diff --git a/apps/web/src/locales/ar/messages.json b/apps/web/src/locales/ar/messages.json index 311490950b6..c794d8f48a0 100644 --- a/apps/web/src/locales/ar/messages.json +++ b/apps/web/src/locales/ar/messages.json @@ -1969,8 +1969,13 @@ "encExportAccountWarningDesc": { "message": "Account encryption keys are unique to each Bitwarden user account, so you can't import an encrypted export into a different account." }, - "export": { - "message": "تصدير" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, "exportFrom": { "message": "التصدير من" @@ -2297,8 +2302,13 @@ "tools": { "message": "الأدوات" }, - "import": { - "message": "Import" + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "importData": { "message": "استيراد البيانات" @@ -3283,6 +3293,9 @@ "launchCloudSubscription": { "message": "Launch Cloud Subscription" }, + "launchCloudSubscriptionSentenceCase": { + "message": "Launch cloud subscription" + }, "storage": { "message": "مساحة التخزين" }, @@ -4198,6 +4211,12 @@ } } }, + "userAcceptedTransfer": { + "message": "Accepted transfer to organization ownership." + }, + "userDeclinedTransfer": { + "message": "Revoked for declining transfer to organization ownership." + }, "invitedUserId": { "message": "Invited user $ID$.", "placeholders": { @@ -12412,5 +12431,53 @@ }, "whyAmISeeingThis": { "message": "Why am I seeing this?" + }, + "youHaveBitwardenPremium": { + "message": "You have Bitwarden Premium" + }, + "viewAndManagePremiumSubscription": { + "message": "View and manage your Premium subscription" + }, + "youNeedToUpdateLicenseFile": { + "message": "You'll need to update your license file" + }, + "youNeedToUpdateLicenseFileDate": { + "message": "$DATE$.", + "placeholders": { + "date": { + "content": "$1", + "example": "June 12, 2026" + } + } + }, + "uploadLicenseFile": { + "message": "Upload license file" + }, + "uploadYourLicenseFile": { + "message": "Upload your license file" + }, + "uploadYourPremiumLicenseFile": { + "message": "Upload your Premium license file" + }, + "uploadLicenseFileDesc": { + "message": "Your license file name will be similar to: $FILE_NAME$", + "placeholders": { + "file_name": { + "content": "$1", + "example": "bitwarden_license.json" + } + } + }, + "alreadyHaveSubscriptionQuestion": { + "message": "Already have a subscription?" + }, + "alreadyHaveSubscriptionSelfHostedMessage": { + "message": "Open the subscription page on your Bitwarden cloud account and download your license file. Then return to this screen and upload it below." + }, + "viewAllPlans": { + "message": "View all plans" + }, + "planDescPremium": { + "message": "Complete online security" } } diff --git a/apps/web/src/locales/az/messages.json b/apps/web/src/locales/az/messages.json index 580bb3beed1..1b6593578a6 100644 --- a/apps/web/src/locales/az/messages.json +++ b/apps/web/src/locales/az/messages.json @@ -1969,8 +1969,13 @@ "encExportAccountWarningDesc": { "message": "Hesab şifrələmə açarları, hər Bitwarden istifadəçi hesabı üçün unikaldır, buna görə də şifrələnmiş bir ixracı, fərqli bir hesaba idxal edə bilməzsiniz." }, - "export": { - "message": "İxrac et" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, "exportFrom": { "message": "Buradan xaricə köçür" @@ -2297,8 +2302,13 @@ "tools": { "message": "Alətlər" }, - "import": { - "message": "Daxilə köçür" + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "importData": { "message": "Veriləri daxilə köçür" @@ -3283,6 +3293,9 @@ "launchCloudSubscription": { "message": "Bulud Abunəliyini Başlat" }, + "launchCloudSubscriptionSentenceCase": { + "message": "Launch cloud subscription" + }, "storage": { "message": "Saxlama" }, @@ -4198,6 +4211,12 @@ } } }, + "userAcceptedTransfer": { + "message": "Accepted transfer to organization ownership." + }, + "userDeclinedTransfer": { + "message": "Revoked for declining transfer to organization ownership." + }, "invitedUserId": { "message": "$ID$ istifadəçisi dəvət edildi.", "placeholders": { @@ -12412,5 +12431,53 @@ }, "whyAmISeeingThis": { "message": "Bunu niyə görürəm?" + }, + "youHaveBitwardenPremium": { + "message": "You have Bitwarden Premium" + }, + "viewAndManagePremiumSubscription": { + "message": "View and manage your Premium subscription" + }, + "youNeedToUpdateLicenseFile": { + "message": "You'll need to update your license file" + }, + "youNeedToUpdateLicenseFileDate": { + "message": "$DATE$.", + "placeholders": { + "date": { + "content": "$1", + "example": "June 12, 2026" + } + } + }, + "uploadLicenseFile": { + "message": "Upload license file" + }, + "uploadYourLicenseFile": { + "message": "Upload your license file" + }, + "uploadYourPremiumLicenseFile": { + "message": "Upload your Premium license file" + }, + "uploadLicenseFileDesc": { + "message": "Your license file name will be similar to: $FILE_NAME$", + "placeholders": { + "file_name": { + "content": "$1", + "example": "bitwarden_license.json" + } + } + }, + "alreadyHaveSubscriptionQuestion": { + "message": "Already have a subscription?" + }, + "alreadyHaveSubscriptionSelfHostedMessage": { + "message": "Open the subscription page on your Bitwarden cloud account and download your license file. Then return to this screen and upload it below." + }, + "viewAllPlans": { + "message": "View all plans" + }, + "planDescPremium": { + "message": "Complete online security" } } diff --git a/apps/web/src/locales/be/messages.json b/apps/web/src/locales/be/messages.json index 3bfaeea871c..f0f595e9b63 100644 --- a/apps/web/src/locales/be/messages.json +++ b/apps/web/src/locales/be/messages.json @@ -1969,8 +1969,13 @@ "encExportAccountWarningDesc": { "message": "Ключы шыфравання з'яўляюцца ўнікальнымі для кожнага ўліковага запісу Bitwarden, таму нельга імпартаваць зашыфраванае сховішча ў іншы ўліковы запіс." }, - "export": { - "message": "Экспартаваць" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, "exportFrom": { "message": "Export from" @@ -2297,8 +2302,13 @@ "tools": { "message": "Інструменты" }, - "import": { - "message": "Import" + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "importData": { "message": "Імпартаванне даных" @@ -3283,6 +3293,9 @@ "launchCloudSubscription": { "message": "Запусціць воблачную падпіску" }, + "launchCloudSubscriptionSentenceCase": { + "message": "Launch cloud subscription" + }, "storage": { "message": "Сховішча" }, @@ -4198,6 +4211,12 @@ } } }, + "userAcceptedTransfer": { + "message": "Accepted transfer to organization ownership." + }, + "userDeclinedTransfer": { + "message": "Revoked for declining transfer to organization ownership." + }, "invitedUserId": { "message": "Запрошаны карыстальнік $ID$.", "placeholders": { @@ -12412,5 +12431,53 @@ }, "whyAmISeeingThis": { "message": "Why am I seeing this?" + }, + "youHaveBitwardenPremium": { + "message": "You have Bitwarden Premium" + }, + "viewAndManagePremiumSubscription": { + "message": "View and manage your Premium subscription" + }, + "youNeedToUpdateLicenseFile": { + "message": "You'll need to update your license file" + }, + "youNeedToUpdateLicenseFileDate": { + "message": "$DATE$.", + "placeholders": { + "date": { + "content": "$1", + "example": "June 12, 2026" + } + } + }, + "uploadLicenseFile": { + "message": "Upload license file" + }, + "uploadYourLicenseFile": { + "message": "Upload your license file" + }, + "uploadYourPremiumLicenseFile": { + "message": "Upload your Premium license file" + }, + "uploadLicenseFileDesc": { + "message": "Your license file name will be similar to: $FILE_NAME$", + "placeholders": { + "file_name": { + "content": "$1", + "example": "bitwarden_license.json" + } + } + }, + "alreadyHaveSubscriptionQuestion": { + "message": "Already have a subscription?" + }, + "alreadyHaveSubscriptionSelfHostedMessage": { + "message": "Open the subscription page on your Bitwarden cloud account and download your license file. Then return to this screen and upload it below." + }, + "viewAllPlans": { + "message": "View all plans" + }, + "planDescPremium": { + "message": "Complete online security" } } diff --git a/apps/web/src/locales/bg/messages.json b/apps/web/src/locales/bg/messages.json index 7ec68a1f962..31a91a195ad 100644 --- a/apps/web/src/locales/bg/messages.json +++ b/apps/web/src/locales/bg/messages.json @@ -1969,8 +1969,13 @@ "encExportAccountWarningDesc": { "message": "Ключовете за шифриране са уникални за всеки потребител, затова не може да внесете шифрирани данни от един потребител в регистрацията на друг." }, - "export": { - "message": "Изнасяне" + "exportNoun": { + "message": "Изнасяне", + "description": "The noun form of the word Export" + }, + "exportVerb": { + "message": "Изнасяне", + "description": "The verb form of the word Export" }, "exportFrom": { "message": "Изнасяне от" @@ -2297,8 +2302,13 @@ "tools": { "message": "Инструменти" }, - "import": { - "message": "Внасяне" + "importNoun": { + "message": "Внасяне", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Внасяне", + "description": "The verb form of the word Import" }, "importData": { "message": "Внасяне на данни" @@ -3283,6 +3293,9 @@ "launchCloudSubscription": { "message": "Launch Cloud Subscription" }, + "launchCloudSubscriptionSentenceCase": { + "message": "Стартиране на облачния абонамент" + }, "storage": { "message": "Съхранение на данни" }, @@ -4198,6 +4211,12 @@ } } }, + "userAcceptedTransfer": { + "message": "Прието прехвърляне на собствеността към организацията." + }, + "userDeclinedTransfer": { + "message": "Премахнато, поради отказ от прехвърляне на собствеността към организацията." + }, "invitedUserId": { "message": "Потребител № $ID$ е поканен.", "placeholders": { @@ -12412,5 +12431,53 @@ }, "whyAmISeeingThis": { "message": "Защо виждам това?" + }, + "youHaveBitwardenPremium": { + "message": "Вие имате платен абонамент за Битуорден" + }, + "viewAndManagePremiumSubscription": { + "message": "Прегледайте и управлявайте своя платен абонамент" + }, + "youNeedToUpdateLicenseFile": { + "message": "Ще трябва да обновите лицензния си файл" + }, + "youNeedToUpdateLicenseFileDate": { + "message": "$DATE$.", + "placeholders": { + "date": { + "content": "$1", + "example": "June 12, 2026" + } + } + }, + "uploadLicenseFile": { + "message": "Качване на лицензен файл" + }, + "uploadYourLicenseFile": { + "message": "Качете своя лицензен файл" + }, + "uploadYourPremiumLicenseFile": { + "message": "Качете своя лицензен файл за платения абонамент" + }, + "uploadLicenseFileDesc": { + "message": "Името на Вашия лицензен файл ще бъде подобно на: $FILE_NAME$", + "placeholders": { + "file_name": { + "content": "$1", + "example": "bitwarden_license.json" + } + } + }, + "alreadyHaveSubscriptionQuestion": { + "message": "Имате ли вече абонамент?" + }, + "alreadyHaveSubscriptionSelfHostedMessage": { + "message": "Отворете страницата за абонаменти в облачния си акаунт за Битуорден и свалете лицензния си файл. След това се върнете на този екран и го качете по-долу." + }, + "viewAllPlans": { + "message": "Преглед на всички планове" + }, + "planDescPremium": { + "message": "Пълна сигурност в Интернет" } } diff --git a/apps/web/src/locales/bn/messages.json b/apps/web/src/locales/bn/messages.json index f6ea3edb275..a7558a78856 100644 --- a/apps/web/src/locales/bn/messages.json +++ b/apps/web/src/locales/bn/messages.json @@ -1969,8 +1969,13 @@ "encExportAccountWarningDesc": { "message": "Account encryption keys are unique to each Bitwarden user account, so you can't import an encrypted export into a different account." }, - "export": { - "message": "Export" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, "exportFrom": { "message": "Export from" @@ -2297,8 +2302,13 @@ "tools": { "message": "Tools" }, - "import": { - "message": "Import" + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "importData": { "message": "Import data" @@ -3283,6 +3293,9 @@ "launchCloudSubscription": { "message": "Launch Cloud Subscription" }, + "launchCloudSubscriptionSentenceCase": { + "message": "Launch cloud subscription" + }, "storage": { "message": "Storage" }, @@ -4198,6 +4211,12 @@ } } }, + "userAcceptedTransfer": { + "message": "Accepted transfer to organization ownership." + }, + "userDeclinedTransfer": { + "message": "Revoked for declining transfer to organization ownership." + }, "invitedUserId": { "message": "Invited user $ID$.", "placeholders": { @@ -12412,5 +12431,53 @@ }, "whyAmISeeingThis": { "message": "Why am I seeing this?" + }, + "youHaveBitwardenPremium": { + "message": "You have Bitwarden Premium" + }, + "viewAndManagePremiumSubscription": { + "message": "View and manage your Premium subscription" + }, + "youNeedToUpdateLicenseFile": { + "message": "You'll need to update your license file" + }, + "youNeedToUpdateLicenseFileDate": { + "message": "$DATE$.", + "placeholders": { + "date": { + "content": "$1", + "example": "June 12, 2026" + } + } + }, + "uploadLicenseFile": { + "message": "Upload license file" + }, + "uploadYourLicenseFile": { + "message": "Upload your license file" + }, + "uploadYourPremiumLicenseFile": { + "message": "Upload your Premium license file" + }, + "uploadLicenseFileDesc": { + "message": "Your license file name will be similar to: $FILE_NAME$", + "placeholders": { + "file_name": { + "content": "$1", + "example": "bitwarden_license.json" + } + } + }, + "alreadyHaveSubscriptionQuestion": { + "message": "Already have a subscription?" + }, + "alreadyHaveSubscriptionSelfHostedMessage": { + "message": "Open the subscription page on your Bitwarden cloud account and download your license file. Then return to this screen and upload it below." + }, + "viewAllPlans": { + "message": "View all plans" + }, + "planDescPremium": { + "message": "Complete online security" } } diff --git a/apps/web/src/locales/bs/messages.json b/apps/web/src/locales/bs/messages.json index 7cd48d3c6ed..f287b526aa2 100644 --- a/apps/web/src/locales/bs/messages.json +++ b/apps/web/src/locales/bs/messages.json @@ -1969,8 +1969,13 @@ "encExportAccountWarningDesc": { "message": "Account encryption keys are unique to each Bitwarden user account, so you can't import an encrypted export into a different account." }, - "export": { - "message": "Export" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, "exportFrom": { "message": "Export from" @@ -2297,8 +2302,13 @@ "tools": { "message": "Tools" }, - "import": { - "message": "Import" + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "importData": { "message": "Import data" @@ -3283,6 +3293,9 @@ "launchCloudSubscription": { "message": "Launch Cloud Subscription" }, + "launchCloudSubscriptionSentenceCase": { + "message": "Launch cloud subscription" + }, "storage": { "message": "Storage" }, @@ -4198,6 +4211,12 @@ } } }, + "userAcceptedTransfer": { + "message": "Accepted transfer to organization ownership." + }, + "userDeclinedTransfer": { + "message": "Revoked for declining transfer to organization ownership." + }, "invitedUserId": { "message": "Invited user $ID$.", "placeholders": { @@ -12412,5 +12431,53 @@ }, "whyAmISeeingThis": { "message": "Why am I seeing this?" + }, + "youHaveBitwardenPremium": { + "message": "You have Bitwarden Premium" + }, + "viewAndManagePremiumSubscription": { + "message": "View and manage your Premium subscription" + }, + "youNeedToUpdateLicenseFile": { + "message": "You'll need to update your license file" + }, + "youNeedToUpdateLicenseFileDate": { + "message": "$DATE$.", + "placeholders": { + "date": { + "content": "$1", + "example": "June 12, 2026" + } + } + }, + "uploadLicenseFile": { + "message": "Upload license file" + }, + "uploadYourLicenseFile": { + "message": "Upload your license file" + }, + "uploadYourPremiumLicenseFile": { + "message": "Upload your Premium license file" + }, + "uploadLicenseFileDesc": { + "message": "Your license file name will be similar to: $FILE_NAME$", + "placeholders": { + "file_name": { + "content": "$1", + "example": "bitwarden_license.json" + } + } + }, + "alreadyHaveSubscriptionQuestion": { + "message": "Already have a subscription?" + }, + "alreadyHaveSubscriptionSelfHostedMessage": { + "message": "Open the subscription page on your Bitwarden cloud account and download your license file. Then return to this screen and upload it below." + }, + "viewAllPlans": { + "message": "View all plans" + }, + "planDescPremium": { + "message": "Complete online security" } } diff --git a/apps/web/src/locales/ca/messages.json b/apps/web/src/locales/ca/messages.json index 4f9d5e635dc..47cd621647a 100644 --- a/apps/web/src/locales/ca/messages.json +++ b/apps/web/src/locales/ca/messages.json @@ -1969,8 +1969,13 @@ "encExportAccountWarningDesc": { "message": "Les claus de xifratge són exclusives de cada compte d'usuari Bitwarden, de manera que no podeu importar una exportació xifrada a un compte diferent." }, - "export": { - "message": "Exporta" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, "exportFrom": { "message": "Exporta des de" @@ -2297,8 +2302,13 @@ "tools": { "message": "Eines" }, - "import": { - "message": "Import" + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "importData": { "message": "Importa dades" @@ -3283,6 +3293,9 @@ "launchCloudSubscription": { "message": "Inicia la subscripció al núvol" }, + "launchCloudSubscriptionSentenceCase": { + "message": "Launch cloud subscription" + }, "storage": { "message": "Emmagatzematge" }, @@ -4198,6 +4211,12 @@ } } }, + "userAcceptedTransfer": { + "message": "Accepted transfer to organization ownership." + }, + "userDeclinedTransfer": { + "message": "Revoked for declining transfer to organization ownership." + }, "invitedUserId": { "message": "S'ha convidat a l'usuari $ID$.", "placeholders": { @@ -12412,5 +12431,53 @@ }, "whyAmISeeingThis": { "message": "Why am I seeing this?" + }, + "youHaveBitwardenPremium": { + "message": "You have Bitwarden Premium" + }, + "viewAndManagePremiumSubscription": { + "message": "View and manage your Premium subscription" + }, + "youNeedToUpdateLicenseFile": { + "message": "You'll need to update your license file" + }, + "youNeedToUpdateLicenseFileDate": { + "message": "$DATE$.", + "placeholders": { + "date": { + "content": "$1", + "example": "June 12, 2026" + } + } + }, + "uploadLicenseFile": { + "message": "Upload license file" + }, + "uploadYourLicenseFile": { + "message": "Upload your license file" + }, + "uploadYourPremiumLicenseFile": { + "message": "Upload your Premium license file" + }, + "uploadLicenseFileDesc": { + "message": "Your license file name will be similar to: $FILE_NAME$", + "placeholders": { + "file_name": { + "content": "$1", + "example": "bitwarden_license.json" + } + } + }, + "alreadyHaveSubscriptionQuestion": { + "message": "Already have a subscription?" + }, + "alreadyHaveSubscriptionSelfHostedMessage": { + "message": "Open the subscription page on your Bitwarden cloud account and download your license file. Then return to this screen and upload it below." + }, + "viewAllPlans": { + "message": "View all plans" + }, + "planDescPremium": { + "message": "Complete online security" } } diff --git a/apps/web/src/locales/cs/messages.json b/apps/web/src/locales/cs/messages.json index 7cc37f70371..2cb6547205d 100644 --- a/apps/web/src/locales/cs/messages.json +++ b/apps/web/src/locales/cs/messages.json @@ -1969,8 +1969,13 @@ "encExportAccountWarningDesc": { "message": "Šifrovací klíče účtu jsou pro každý uživatelský účet Bitwardenu jedinečné, takže nelze importovat šifrovaný export do jiného účtu." }, - "export": { - "message": "Exportovat" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "exportVerb": { + "message": "Exportovat", + "description": "The verb form of the word Export" }, "exportFrom": { "message": "Exportovat z" @@ -2297,8 +2302,13 @@ "tools": { "message": "Nástroje" }, - "import": { - "message": "Importovat" + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Importovat", + "description": "The verb form of the word Import" }, "importData": { "message": "Importovat data" @@ -3283,6 +3293,9 @@ "launchCloudSubscription": { "message": "Spustit předplatné Cloud" }, + "launchCloudSubscriptionSentenceCase": { + "message": "Spustit předplatné Cloud" + }, "storage": { "message": "Úložiště" }, @@ -4198,6 +4211,12 @@ } } }, + "userAcceptedTransfer": { + "message": "Převod do vlastnictví organizace byl přijat." + }, + "userDeclinedTransfer": { + "message": "Zrušeno z důvodu odmítnutí převodu do vlastnictví organizace." + }, "invitedUserId": { "message": "Byl pozván uživatel $ID$.", "placeholders": { @@ -12412,5 +12431,53 @@ }, "whyAmISeeingThis": { "message": "Proč se mi toto zobrazuje?" + }, + "youHaveBitwardenPremium": { + "message": "Máte Bitwarden Premium" + }, + "viewAndManagePremiumSubscription": { + "message": "Zobrazit a spravovat předplatné Premium" + }, + "youNeedToUpdateLicenseFile": { + "message": "Budete muset aktualizovat svůj licenční soubor" + }, + "youNeedToUpdateLicenseFileDate": { + "message": "$DATE$.", + "placeholders": { + "date": { + "content": "$1", + "example": "June 12, 2026" + } + } + }, + "uploadLicenseFile": { + "message": "Nahrát licenční soubor" + }, + "uploadYourLicenseFile": { + "message": "Nahrajte Váš licenční soubor" + }, + "uploadYourPremiumLicenseFile": { + "message": "Nahrajte Váš licenční soubor Premium" + }, + "uploadLicenseFileDesc": { + "message": "Název Vašeho licenčního souboru bude podobný názvu $FILE_NAME$", + "placeholders": { + "file_name": { + "content": "$1", + "example": "bitwarden_license.json" + } + } + }, + "alreadyHaveSubscriptionQuestion": { + "message": "Již máte předplatné?" + }, + "alreadyHaveSubscriptionSelfHostedMessage": { + "message": "Otevřete stránku s předplatným na Vašem účtu Bitwarden a stáhněte si licenční soubor. Poté se vraťte na tuto obrazovku a nahrajte jej níže." + }, + "viewAllPlans": { + "message": "Zobraz všechny plány" + }, + "planDescPremium": { + "message": "Dokončit online zabezpečení" } } diff --git a/apps/web/src/locales/cy/messages.json b/apps/web/src/locales/cy/messages.json index 2fe0598ef20..087d353b3a4 100644 --- a/apps/web/src/locales/cy/messages.json +++ b/apps/web/src/locales/cy/messages.json @@ -1969,8 +1969,13 @@ "encExportAccountWarningDesc": { "message": "Account encryption keys are unique to each Bitwarden user account, so you can't import an encrypted export into a different account." }, - "export": { - "message": "Export" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, "exportFrom": { "message": "Export from" @@ -2297,8 +2302,13 @@ "tools": { "message": "Tools" }, - "import": { - "message": "Import" + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "importData": { "message": "Import data" @@ -3283,6 +3293,9 @@ "launchCloudSubscription": { "message": "Launch Cloud Subscription" }, + "launchCloudSubscriptionSentenceCase": { + "message": "Launch cloud subscription" + }, "storage": { "message": "Storage" }, @@ -4198,6 +4211,12 @@ } } }, + "userAcceptedTransfer": { + "message": "Accepted transfer to organization ownership." + }, + "userDeclinedTransfer": { + "message": "Revoked for declining transfer to organization ownership." + }, "invitedUserId": { "message": "Invited user $ID$.", "placeholders": { @@ -12412,5 +12431,53 @@ }, "whyAmISeeingThis": { "message": "Why am I seeing this?" + }, + "youHaveBitwardenPremium": { + "message": "You have Bitwarden Premium" + }, + "viewAndManagePremiumSubscription": { + "message": "View and manage your Premium subscription" + }, + "youNeedToUpdateLicenseFile": { + "message": "You'll need to update your license file" + }, + "youNeedToUpdateLicenseFileDate": { + "message": "$DATE$.", + "placeholders": { + "date": { + "content": "$1", + "example": "June 12, 2026" + } + } + }, + "uploadLicenseFile": { + "message": "Upload license file" + }, + "uploadYourLicenseFile": { + "message": "Upload your license file" + }, + "uploadYourPremiumLicenseFile": { + "message": "Upload your Premium license file" + }, + "uploadLicenseFileDesc": { + "message": "Your license file name will be similar to: $FILE_NAME$", + "placeholders": { + "file_name": { + "content": "$1", + "example": "bitwarden_license.json" + } + } + }, + "alreadyHaveSubscriptionQuestion": { + "message": "Already have a subscription?" + }, + "alreadyHaveSubscriptionSelfHostedMessage": { + "message": "Open the subscription page on your Bitwarden cloud account and download your license file. Then return to this screen and upload it below." + }, + "viewAllPlans": { + "message": "View all plans" + }, + "planDescPremium": { + "message": "Complete online security" } } diff --git a/apps/web/src/locales/da/messages.json b/apps/web/src/locales/da/messages.json index 1d6b5e8df42..53f20b8a8f7 100644 --- a/apps/web/src/locales/da/messages.json +++ b/apps/web/src/locales/da/messages.json @@ -1969,8 +1969,13 @@ "encExportAccountWarningDesc": { "message": "Kontokrypteringsnøgler er unikke for hver Bitwarden-brugerkonto, så man kan ikke importere en krypteret eksport til en anden konto." }, - "export": { - "message": "Eksportér" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, "exportFrom": { "message": "Eksportér fra" @@ -2297,8 +2302,13 @@ "tools": { "message": "Værktøjer" }, - "import": { - "message": "Import" + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "importData": { "message": "Importér data" @@ -3283,6 +3293,9 @@ "launchCloudSubscription": { "message": "Start Cloud Abonnement" }, + "launchCloudSubscriptionSentenceCase": { + "message": "Launch cloud subscription" + }, "storage": { "message": "Lager" }, @@ -4198,6 +4211,12 @@ } } }, + "userAcceptedTransfer": { + "message": "Accepted transfer to organization ownership." + }, + "userDeclinedTransfer": { + "message": "Revoked for declining transfer to organization ownership." + }, "invitedUserId": { "message": "Inviterede bruger $ID$.", "placeholders": { @@ -12412,5 +12431,53 @@ }, "whyAmISeeingThis": { "message": "Why am I seeing this?" + }, + "youHaveBitwardenPremium": { + "message": "You have Bitwarden Premium" + }, + "viewAndManagePremiumSubscription": { + "message": "View and manage your Premium subscription" + }, + "youNeedToUpdateLicenseFile": { + "message": "You'll need to update your license file" + }, + "youNeedToUpdateLicenseFileDate": { + "message": "$DATE$.", + "placeholders": { + "date": { + "content": "$1", + "example": "June 12, 2026" + } + } + }, + "uploadLicenseFile": { + "message": "Upload license file" + }, + "uploadYourLicenseFile": { + "message": "Upload your license file" + }, + "uploadYourPremiumLicenseFile": { + "message": "Upload your Premium license file" + }, + "uploadLicenseFileDesc": { + "message": "Your license file name will be similar to: $FILE_NAME$", + "placeholders": { + "file_name": { + "content": "$1", + "example": "bitwarden_license.json" + } + } + }, + "alreadyHaveSubscriptionQuestion": { + "message": "Already have a subscription?" + }, + "alreadyHaveSubscriptionSelfHostedMessage": { + "message": "Open the subscription page on your Bitwarden cloud account and download your license file. Then return to this screen and upload it below." + }, + "viewAllPlans": { + "message": "View all plans" + }, + "planDescPremium": { + "message": "Complete online security" } } diff --git a/apps/web/src/locales/de/messages.json b/apps/web/src/locales/de/messages.json index d3dd0f712f8..608421ef132 100644 --- a/apps/web/src/locales/de/messages.json +++ b/apps/web/src/locales/de/messages.json @@ -1969,8 +1969,13 @@ "encExportAccountWarningDesc": { "message": "Die Verschlüsselungscodes eines Kontos sind für jedes Bitwarden-Benutzerkonto einzigartig, deshalb kannst du keinen verschlüsselten Export in ein anderes Konto importieren." }, - "export": { - "message": "Exportieren" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "exportVerb": { + "message": "Exportieren", + "description": "The verb form of the word Export" }, "exportFrom": { "message": "Exportieren ab" @@ -2297,8 +2302,13 @@ "tools": { "message": "Werkzeuge" }, - "import": { - "message": "Import" + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Importieren", + "description": "The verb form of the word Import" }, "importData": { "message": "Daten importieren" @@ -3283,6 +3293,9 @@ "launchCloudSubscription": { "message": "Cloud-Abonnement starten" }, + "launchCloudSubscriptionSentenceCase": { + "message": "Launch cloud subscription" + }, "storage": { "message": "Speicher" }, @@ -4198,6 +4211,12 @@ } } }, + "userAcceptedTransfer": { + "message": "Accepted transfer to organization ownership." + }, + "userDeclinedTransfer": { + "message": "Revoked for declining transfer to organization ownership." + }, "invitedUserId": { "message": "Benutzer $ID$ eingeladen.", "placeholders": { @@ -12412,5 +12431,53 @@ }, "whyAmISeeingThis": { "message": "Warum wird mir das angezeigt?" + }, + "youHaveBitwardenPremium": { + "message": "You have Bitwarden Premium" + }, + "viewAndManagePremiumSubscription": { + "message": "View and manage your Premium subscription" + }, + "youNeedToUpdateLicenseFile": { + "message": "You'll need to update your license file" + }, + "youNeedToUpdateLicenseFileDate": { + "message": "$DATE$.", + "placeholders": { + "date": { + "content": "$1", + "example": "June 12, 2026" + } + } + }, + "uploadLicenseFile": { + "message": "Lizenzdatei hochladen" + }, + "uploadYourLicenseFile": { + "message": "Lade deine Lizenzdatei hoch" + }, + "uploadYourPremiumLicenseFile": { + "message": "Upload your Premium license file" + }, + "uploadLicenseFileDesc": { + "message": "Your license file name will be similar to: $FILE_NAME$", + "placeholders": { + "file_name": { + "content": "$1", + "example": "bitwarden_license.json" + } + } + }, + "alreadyHaveSubscriptionQuestion": { + "message": "Already have a subscription?" + }, + "alreadyHaveSubscriptionSelfHostedMessage": { + "message": "Open the subscription page on your Bitwarden cloud account and download your license file. Then return to this screen and upload it below." + }, + "viewAllPlans": { + "message": "View all plans" + }, + "planDescPremium": { + "message": "Umfassende Online-Sicherheit" } } diff --git a/apps/web/src/locales/el/messages.json b/apps/web/src/locales/el/messages.json index 04d28c866cc..9232935b33c 100644 --- a/apps/web/src/locales/el/messages.json +++ b/apps/web/src/locales/el/messages.json @@ -1969,8 +1969,13 @@ "encExportAccountWarningDesc": { "message": "Τα κλειδιά κρυπτογράφησης λογαριασμού είναι μοναδικά για κάθε λογαριασμό χρήστη Bitwarden, οπότε δεν μπορείτε να εισάγετε μια κρυπτογραφημένη εξαγωγή σε διαφορετικό λογαριασμό." }, - "export": { - "message": "Εξαγωγή" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, "exportFrom": { "message": "Εξαγωγή από" @@ -2297,8 +2302,13 @@ "tools": { "message": "Εργαλεία" }, - "import": { - "message": "Import" + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "importData": { "message": "Εισαγωγή Δεδομένων" @@ -3283,6 +3293,9 @@ "launchCloudSubscription": { "message": "Εκκίνηση Συνδρομής Cloud" }, + "launchCloudSubscriptionSentenceCase": { + "message": "Launch cloud subscription" + }, "storage": { "message": "Αποθήκευση" }, @@ -4198,6 +4211,12 @@ } } }, + "userAcceptedTransfer": { + "message": "Accepted transfer to organization ownership." + }, + "userDeclinedTransfer": { + "message": "Revoked for declining transfer to organization ownership." + }, "invitedUserId": { "message": "Προσκεκλημένος χρήστης $ID$.", "placeholders": { @@ -12412,5 +12431,53 @@ }, "whyAmISeeingThis": { "message": "Why am I seeing this?" + }, + "youHaveBitwardenPremium": { + "message": "You have Bitwarden Premium" + }, + "viewAndManagePremiumSubscription": { + "message": "View and manage your Premium subscription" + }, + "youNeedToUpdateLicenseFile": { + "message": "You'll need to update your license file" + }, + "youNeedToUpdateLicenseFileDate": { + "message": "$DATE$.", + "placeholders": { + "date": { + "content": "$1", + "example": "June 12, 2026" + } + } + }, + "uploadLicenseFile": { + "message": "Upload license file" + }, + "uploadYourLicenseFile": { + "message": "Upload your license file" + }, + "uploadYourPremiumLicenseFile": { + "message": "Upload your Premium license file" + }, + "uploadLicenseFileDesc": { + "message": "Your license file name will be similar to: $FILE_NAME$", + "placeholders": { + "file_name": { + "content": "$1", + "example": "bitwarden_license.json" + } + } + }, + "alreadyHaveSubscriptionQuestion": { + "message": "Already have a subscription?" + }, + "alreadyHaveSubscriptionSelfHostedMessage": { + "message": "Open the subscription page on your Bitwarden cloud account and download your license file. Then return to this screen and upload it below." + }, + "viewAllPlans": { + "message": "View all plans" + }, + "planDescPremium": { + "message": "Complete online security" } } diff --git a/apps/web/src/locales/en_GB/messages.json b/apps/web/src/locales/en_GB/messages.json index 9c77e913e3e..3e25235b9a8 100644 --- a/apps/web/src/locales/en_GB/messages.json +++ b/apps/web/src/locales/en_GB/messages.json @@ -1969,8 +1969,13 @@ "encExportAccountWarningDesc": { "message": "Account encryption keys are unique to each Bitwarden user account, so you can't import an encrypted export into a different account." }, - "export": { - "message": "Export" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, "exportFrom": { "message": "Export from" @@ -2297,8 +2302,13 @@ "tools": { "message": "Tools" }, - "import": { - "message": "Import" + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "importData": { "message": "Import data" @@ -3283,6 +3293,9 @@ "launchCloudSubscription": { "message": "Launch Cloud Subscription" }, + "launchCloudSubscriptionSentenceCase": { + "message": "Launch cloud subscription" + }, "storage": { "message": "Storage" }, @@ -4198,6 +4211,12 @@ } } }, + "userAcceptedTransfer": { + "message": "Accepted transfer to organisation ownership." + }, + "userDeclinedTransfer": { + "message": "Revoked for declining transfer to organisation ownership." + }, "invitedUserId": { "message": "Invited user $ID$.", "placeholders": { @@ -12412,5 +12431,53 @@ }, "whyAmISeeingThis": { "message": "Why am I seeing this?" + }, + "youHaveBitwardenPremium": { + "message": "You have Bitwarden Premium" + }, + "viewAndManagePremiumSubscription": { + "message": "View and manage your Premium subscription" + }, + "youNeedToUpdateLicenseFile": { + "message": "You'll need to update your licence file" + }, + "youNeedToUpdateLicenseFileDate": { + "message": "$DATE$.", + "placeholders": { + "date": { + "content": "$1", + "example": "June 12, 2026" + } + } + }, + "uploadLicenseFile": { + "message": "Upload licence file" + }, + "uploadYourLicenseFile": { + "message": "Upload your licence file" + }, + "uploadYourPremiumLicenseFile": { + "message": "Upload your Premium licence file" + }, + "uploadLicenseFileDesc": { + "message": "Your licence file name will be similar to: $FILE_NAME$", + "placeholders": { + "file_name": { + "content": "$1", + "example": "bitwarden_license.json" + } + } + }, + "alreadyHaveSubscriptionQuestion": { + "message": "Already have a subscription?" + }, + "alreadyHaveSubscriptionSelfHostedMessage": { + "message": "Open the subscription page on your Bitwarden cloud account and download your licence file. Then return to this screen and upload it below." + }, + "viewAllPlans": { + "message": "View all plans" + }, + "planDescPremium": { + "message": "Complete online security" } } diff --git a/apps/web/src/locales/en_IN/messages.json b/apps/web/src/locales/en_IN/messages.json index cdd59507064..b65b8d40622 100644 --- a/apps/web/src/locales/en_IN/messages.json +++ b/apps/web/src/locales/en_IN/messages.json @@ -1969,8 +1969,13 @@ "encExportAccountWarningDesc": { "message": "Account encryption keys are unique to each Bitwarden user account, so you can't import an encrypted export into a different account." }, - "export": { - "message": "Export" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, "exportFrom": { "message": "Export from" @@ -2297,8 +2302,13 @@ "tools": { "message": "Tools" }, - "import": { - "message": "Import" + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "importData": { "message": "Import data" @@ -3283,6 +3293,9 @@ "launchCloudSubscription": { "message": "Launch Cloud Subscription" }, + "launchCloudSubscriptionSentenceCase": { + "message": "Launch cloud subscription" + }, "storage": { "message": "Storage" }, @@ -4198,6 +4211,12 @@ } } }, + "userAcceptedTransfer": { + "message": "Accepted transfer to organisation ownership." + }, + "userDeclinedTransfer": { + "message": "Revoked for declining transfer to organisation ownership." + }, "invitedUserId": { "message": "Invited user $ID$.", "placeholders": { @@ -12412,5 +12431,53 @@ }, "whyAmISeeingThis": { "message": "Why am I seeing this?" + }, + "youHaveBitwardenPremium": { + "message": "You have Bitwarden Premium" + }, + "viewAndManagePremiumSubscription": { + "message": "View and manage your Premium subscription" + }, + "youNeedToUpdateLicenseFile": { + "message": "You'll need to update your licence file" + }, + "youNeedToUpdateLicenseFileDate": { + "message": "$DATE$.", + "placeholders": { + "date": { + "content": "$1", + "example": "June 12, 2026" + } + } + }, + "uploadLicenseFile": { + "message": "Upload licence file" + }, + "uploadYourLicenseFile": { + "message": "Upload your licence file" + }, + "uploadYourPremiumLicenseFile": { + "message": "Upload your Premium licence file" + }, + "uploadLicenseFileDesc": { + "message": "Your licence file name will be similar to: $FILE_NAME$", + "placeholders": { + "file_name": { + "content": "$1", + "example": "bitwarden_license.json" + } + } + }, + "alreadyHaveSubscriptionQuestion": { + "message": "Already have a subscription?" + }, + "alreadyHaveSubscriptionSelfHostedMessage": { + "message": "Open the subscription page on your Bitwarden cloud account and download your licence file. Then return to this screen and upload it below." + }, + "viewAllPlans": { + "message": "View all plans" + }, + "planDescPremium": { + "message": "Complete online security" } } diff --git a/apps/web/src/locales/eo/messages.json b/apps/web/src/locales/eo/messages.json index a9ba4d042f0..7ee4e504035 100644 --- a/apps/web/src/locales/eo/messages.json +++ b/apps/web/src/locales/eo/messages.json @@ -1969,8 +1969,13 @@ "encExportAccountWarningDesc": { "message": "Kontaj ĉifraj ŝlosiloj estas unikaj por ĉiu Bitwarden-uzanto-konto, do vi ne povas importi ĉifritan eksportadon en alian konton." }, - "export": { - "message": "Elporti" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, "exportFrom": { "message": "Elporti el" @@ -2297,8 +2302,13 @@ "tools": { "message": "Iloj" }, - "import": { - "message": "Import" + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "importData": { "message": "Importi Datumojn" @@ -3283,6 +3293,9 @@ "launchCloudSubscription": { "message": "Launch Cloud Subscription" }, + "launchCloudSubscriptionSentenceCase": { + "message": "Launch cloud subscription" + }, "storage": { "message": "Stokado" }, @@ -4198,6 +4211,12 @@ } } }, + "userAcceptedTransfer": { + "message": "Accepted transfer to organization ownership." + }, + "userDeclinedTransfer": { + "message": "Revoked for declining transfer to organization ownership." + }, "invitedUserId": { "message": "Invited user $ID$.", "placeholders": { @@ -12412,5 +12431,53 @@ }, "whyAmISeeingThis": { "message": "Why am I seeing this?" + }, + "youHaveBitwardenPremium": { + "message": "You have Bitwarden Premium" + }, + "viewAndManagePremiumSubscription": { + "message": "View and manage your Premium subscription" + }, + "youNeedToUpdateLicenseFile": { + "message": "You'll need to update your license file" + }, + "youNeedToUpdateLicenseFileDate": { + "message": "$DATE$.", + "placeholders": { + "date": { + "content": "$1", + "example": "June 12, 2026" + } + } + }, + "uploadLicenseFile": { + "message": "Upload license file" + }, + "uploadYourLicenseFile": { + "message": "Upload your license file" + }, + "uploadYourPremiumLicenseFile": { + "message": "Upload your Premium license file" + }, + "uploadLicenseFileDesc": { + "message": "Your license file name will be similar to: $FILE_NAME$", + "placeholders": { + "file_name": { + "content": "$1", + "example": "bitwarden_license.json" + } + } + }, + "alreadyHaveSubscriptionQuestion": { + "message": "Already have a subscription?" + }, + "alreadyHaveSubscriptionSelfHostedMessage": { + "message": "Open the subscription page on your Bitwarden cloud account and download your license file. Then return to this screen and upload it below." + }, + "viewAllPlans": { + "message": "View all plans" + }, + "planDescPremium": { + "message": "Complete online security" } } diff --git a/apps/web/src/locales/es/messages.json b/apps/web/src/locales/es/messages.json index f93bbc02cd4..68141386a0e 100644 --- a/apps/web/src/locales/es/messages.json +++ b/apps/web/src/locales/es/messages.json @@ -1969,8 +1969,13 @@ "encExportAccountWarningDesc": { "message": "Las claves de cifrado de cuenta son únicas para cada cuenta de usuario de Bitwarden, por lo que no puede importar una exportación cifrada a una cuenta diferente." }, - "export": { - "message": "Exportar" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, "exportFrom": { "message": "Exportar desde" @@ -2297,8 +2302,13 @@ "tools": { "message": "Herramientas" }, - "import": { - "message": "Import" + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "importData": { "message": "Importar datos" @@ -3283,6 +3293,9 @@ "launchCloudSubscription": { "message": "Iniciar suscripción en la nube" }, + "launchCloudSubscriptionSentenceCase": { + "message": "Launch cloud subscription" + }, "storage": { "message": "Almacenamiento" }, @@ -4198,6 +4211,12 @@ } } }, + "userAcceptedTransfer": { + "message": "Accepted transfer to organization ownership." + }, + "userDeclinedTransfer": { + "message": "Revoked for declining transfer to organization ownership." + }, "invitedUserId": { "message": "Usuario $ID$ invitado.", "placeholders": { @@ -12412,5 +12431,53 @@ }, "whyAmISeeingThis": { "message": "Why am I seeing this?" + }, + "youHaveBitwardenPremium": { + "message": "You have Bitwarden Premium" + }, + "viewAndManagePremiumSubscription": { + "message": "View and manage your Premium subscription" + }, + "youNeedToUpdateLicenseFile": { + "message": "You'll need to update your license file" + }, + "youNeedToUpdateLicenseFileDate": { + "message": "$DATE$.", + "placeholders": { + "date": { + "content": "$1", + "example": "June 12, 2026" + } + } + }, + "uploadLicenseFile": { + "message": "Upload license file" + }, + "uploadYourLicenseFile": { + "message": "Upload your license file" + }, + "uploadYourPremiumLicenseFile": { + "message": "Upload your Premium license file" + }, + "uploadLicenseFileDesc": { + "message": "Your license file name will be similar to: $FILE_NAME$", + "placeholders": { + "file_name": { + "content": "$1", + "example": "bitwarden_license.json" + } + } + }, + "alreadyHaveSubscriptionQuestion": { + "message": "Already have a subscription?" + }, + "alreadyHaveSubscriptionSelfHostedMessage": { + "message": "Open the subscription page on your Bitwarden cloud account and download your license file. Then return to this screen and upload it below." + }, + "viewAllPlans": { + "message": "View all plans" + }, + "planDescPremium": { + "message": "Complete online security" } } diff --git a/apps/web/src/locales/et/messages.json b/apps/web/src/locales/et/messages.json index 83fa92c6985..d05ba09dfbd 100644 --- a/apps/web/src/locales/et/messages.json +++ b/apps/web/src/locales/et/messages.json @@ -1969,8 +1969,13 @@ "encExportAccountWarningDesc": { "message": "Iga Bitwardeni kasutaja krüpteerimisvõti on unikaalne. Eksporditud andmeid ei saa importida teise Bitwardeni kasutajakontosse." }, - "export": { - "message": "Ekspordi" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, "exportFrom": { "message": "Ekspordi asukohast" @@ -2297,8 +2302,13 @@ "tools": { "message": "Tööriistad" }, - "import": { - "message": "Import" + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "importData": { "message": "Andmete importimine" @@ -3283,6 +3293,9 @@ "launchCloudSubscription": { "message": "Ühenda Tasuline Tellimus" }, + "launchCloudSubscriptionSentenceCase": { + "message": "Launch cloud subscription" + }, "storage": { "message": "Salvestusruum" }, @@ -4198,6 +4211,12 @@ } } }, + "userAcceptedTransfer": { + "message": "Accepted transfer to organization ownership." + }, + "userDeclinedTransfer": { + "message": "Revoked for declining transfer to organization ownership." + }, "invitedUserId": { "message": "Kutsus kasutaja $ID$.", "placeholders": { @@ -12412,5 +12431,53 @@ }, "whyAmISeeingThis": { "message": "Why am I seeing this?" + }, + "youHaveBitwardenPremium": { + "message": "You have Bitwarden Premium" + }, + "viewAndManagePremiumSubscription": { + "message": "View and manage your Premium subscription" + }, + "youNeedToUpdateLicenseFile": { + "message": "You'll need to update your license file" + }, + "youNeedToUpdateLicenseFileDate": { + "message": "$DATE$.", + "placeholders": { + "date": { + "content": "$1", + "example": "June 12, 2026" + } + } + }, + "uploadLicenseFile": { + "message": "Upload license file" + }, + "uploadYourLicenseFile": { + "message": "Upload your license file" + }, + "uploadYourPremiumLicenseFile": { + "message": "Upload your Premium license file" + }, + "uploadLicenseFileDesc": { + "message": "Your license file name will be similar to: $FILE_NAME$", + "placeholders": { + "file_name": { + "content": "$1", + "example": "bitwarden_license.json" + } + } + }, + "alreadyHaveSubscriptionQuestion": { + "message": "Already have a subscription?" + }, + "alreadyHaveSubscriptionSelfHostedMessage": { + "message": "Open the subscription page on your Bitwarden cloud account and download your license file. Then return to this screen and upload it below." + }, + "viewAllPlans": { + "message": "View all plans" + }, + "planDescPremium": { + "message": "Complete online security" } } diff --git a/apps/web/src/locales/eu/messages.json b/apps/web/src/locales/eu/messages.json index 9d3ba92bec9..39473dbc75c 100644 --- a/apps/web/src/locales/eu/messages.json +++ b/apps/web/src/locales/eu/messages.json @@ -1969,8 +1969,13 @@ "encExportAccountWarningDesc": { "message": "Kontua zifratzeko gakoak Bitwarden erabiltzaile bakoitzarentzako bakarrik dira; beraz, ezin da inportatu beste kontu batean zifratutako esportazio bat." }, - "export": { - "message": "Esportatu" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, "exportFrom": { "message": "Export from" @@ -2297,8 +2302,13 @@ "tools": { "message": "Tresnak" }, - "import": { - "message": "Import" + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "importData": { "message": "Inportatu datuak" @@ -3283,6 +3293,9 @@ "launchCloudSubscription": { "message": "Launch Cloud Subscription" }, + "launchCloudSubscriptionSentenceCase": { + "message": "Launch cloud subscription" + }, "storage": { "message": "Biltegia" }, @@ -4198,6 +4211,12 @@ } } }, + "userAcceptedTransfer": { + "message": "Accepted transfer to organization ownership." + }, + "userDeclinedTransfer": { + "message": "Revoked for declining transfer to organization ownership." + }, "invitedUserId": { "message": "$ID$ erabiltzailea gonbidatua.", "placeholders": { @@ -12412,5 +12431,53 @@ }, "whyAmISeeingThis": { "message": "Why am I seeing this?" + }, + "youHaveBitwardenPremium": { + "message": "You have Bitwarden Premium" + }, + "viewAndManagePremiumSubscription": { + "message": "View and manage your Premium subscription" + }, + "youNeedToUpdateLicenseFile": { + "message": "You'll need to update your license file" + }, + "youNeedToUpdateLicenseFileDate": { + "message": "$DATE$.", + "placeholders": { + "date": { + "content": "$1", + "example": "June 12, 2026" + } + } + }, + "uploadLicenseFile": { + "message": "Upload license file" + }, + "uploadYourLicenseFile": { + "message": "Upload your license file" + }, + "uploadYourPremiumLicenseFile": { + "message": "Upload your Premium license file" + }, + "uploadLicenseFileDesc": { + "message": "Your license file name will be similar to: $FILE_NAME$", + "placeholders": { + "file_name": { + "content": "$1", + "example": "bitwarden_license.json" + } + } + }, + "alreadyHaveSubscriptionQuestion": { + "message": "Already have a subscription?" + }, + "alreadyHaveSubscriptionSelfHostedMessage": { + "message": "Open the subscription page on your Bitwarden cloud account and download your license file. Then return to this screen and upload it below." + }, + "viewAllPlans": { + "message": "View all plans" + }, + "planDescPremium": { + "message": "Complete online security" } } diff --git a/apps/web/src/locales/fa/messages.json b/apps/web/src/locales/fa/messages.json index be630d5c9f7..b85c692e1fe 100644 --- a/apps/web/src/locales/fa/messages.json +++ b/apps/web/src/locales/fa/messages.json @@ -1969,8 +1969,13 @@ "encExportAccountWarningDesc": { "message": "کلیدهای رمزگذاری حساب برای هر حساب کاربری Bitwarden منحصر به فرد است، بنابراین نمی‌توانید برون ریزی رمزگذاری شده را به حساب دیگری وارد کنید." }, - "export": { - "message": "برون ریزی" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, "exportFrom": { "message": "برون ریزی از" @@ -2297,8 +2302,13 @@ "tools": { "message": "ابزار" }, - "import": { - "message": "Import" + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "importData": { "message": "درون ریزی داده" @@ -3283,6 +3293,9 @@ "launchCloudSubscription": { "message": "اشتراک Cloud را راه اندازی کنید" }, + "launchCloudSubscriptionSentenceCase": { + "message": "Launch cloud subscription" + }, "storage": { "message": "حافظه" }, @@ -4198,6 +4211,12 @@ } } }, + "userAcceptedTransfer": { + "message": "Accepted transfer to organization ownership." + }, + "userDeclinedTransfer": { + "message": "Revoked for declining transfer to organization ownership." + }, "invitedUserId": { "message": "کاربر $ID$ دعوت شد.", "placeholders": { @@ -12412,5 +12431,53 @@ }, "whyAmISeeingThis": { "message": "Why am I seeing this?" + }, + "youHaveBitwardenPremium": { + "message": "You have Bitwarden Premium" + }, + "viewAndManagePremiumSubscription": { + "message": "View and manage your Premium subscription" + }, + "youNeedToUpdateLicenseFile": { + "message": "You'll need to update your license file" + }, + "youNeedToUpdateLicenseFileDate": { + "message": "$DATE$.", + "placeholders": { + "date": { + "content": "$1", + "example": "June 12, 2026" + } + } + }, + "uploadLicenseFile": { + "message": "Upload license file" + }, + "uploadYourLicenseFile": { + "message": "Upload your license file" + }, + "uploadYourPremiumLicenseFile": { + "message": "Upload your Premium license file" + }, + "uploadLicenseFileDesc": { + "message": "Your license file name will be similar to: $FILE_NAME$", + "placeholders": { + "file_name": { + "content": "$1", + "example": "bitwarden_license.json" + } + } + }, + "alreadyHaveSubscriptionQuestion": { + "message": "Already have a subscription?" + }, + "alreadyHaveSubscriptionSelfHostedMessage": { + "message": "Open the subscription page on your Bitwarden cloud account and download your license file. Then return to this screen and upload it below." + }, + "viewAllPlans": { + "message": "View all plans" + }, + "planDescPremium": { + "message": "Complete online security" } } diff --git a/apps/web/src/locales/fi/messages.json b/apps/web/src/locales/fi/messages.json index f9e6ea2f4c6..871b256877a 100644 --- a/apps/web/src/locales/fi/messages.json +++ b/apps/web/src/locales/fi/messages.json @@ -1969,8 +1969,13 @@ "encExportAccountWarningDesc": { "message": "Tilin salausavaimet ovat ainutlaatuisia jokaiselle Bitwarden-käyttäjätilille, joten et voi tuoda salattua vientitiedostoa toiselle tilille." }, - "export": { - "message": "Vie" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, "exportFrom": { "message": "Vie lähteestä" @@ -2297,8 +2302,13 @@ "tools": { "message": "Työkalut" }, - "import": { - "message": "Import" + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "importData": { "message": "Tuo tietoja" @@ -3283,6 +3293,9 @@ "launchCloudSubscription": { "message": "Käynnistä pilvitilaus" }, + "launchCloudSubscriptionSentenceCase": { + "message": "Launch cloud subscription" + }, "storage": { "message": "Tallennustila" }, @@ -4198,6 +4211,12 @@ } } }, + "userAcceptedTransfer": { + "message": "Accepted transfer to organization ownership." + }, + "userDeclinedTransfer": { + "message": "Revoked for declining transfer to organization ownership." + }, "invitedUserId": { "message": "Kutsui käyttäjän \"$ID$\".", "placeholders": { @@ -12412,5 +12431,53 @@ }, "whyAmISeeingThis": { "message": "Why am I seeing this?" + }, + "youHaveBitwardenPremium": { + "message": "You have Bitwarden Premium" + }, + "viewAndManagePremiumSubscription": { + "message": "View and manage your Premium subscription" + }, + "youNeedToUpdateLicenseFile": { + "message": "You'll need to update your license file" + }, + "youNeedToUpdateLicenseFileDate": { + "message": "$DATE$.", + "placeholders": { + "date": { + "content": "$1", + "example": "June 12, 2026" + } + } + }, + "uploadLicenseFile": { + "message": "Upload license file" + }, + "uploadYourLicenseFile": { + "message": "Upload your license file" + }, + "uploadYourPremiumLicenseFile": { + "message": "Upload your Premium license file" + }, + "uploadLicenseFileDesc": { + "message": "Your license file name will be similar to: $FILE_NAME$", + "placeholders": { + "file_name": { + "content": "$1", + "example": "bitwarden_license.json" + } + } + }, + "alreadyHaveSubscriptionQuestion": { + "message": "Already have a subscription?" + }, + "alreadyHaveSubscriptionSelfHostedMessage": { + "message": "Open the subscription page on your Bitwarden cloud account and download your license file. Then return to this screen and upload it below." + }, + "viewAllPlans": { + "message": "View all plans" + }, + "planDescPremium": { + "message": "Complete online security" } } diff --git a/apps/web/src/locales/fil/messages.json b/apps/web/src/locales/fil/messages.json index 539059d79cf..a12aa97341f 100644 --- a/apps/web/src/locales/fil/messages.json +++ b/apps/web/src/locales/fil/messages.json @@ -1969,8 +1969,13 @@ "encExportAccountWarningDesc": { "message": "Magkaiba ang encryption key ng bawat Bitwarden user account, kaya hindi ka makakapag-import ng encrypted export sa iba pang account." }, - "export": { - "message": "I-export" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, "exportFrom": { "message": "Export from" @@ -2297,8 +2302,13 @@ "tools": { "message": "Mga Kagamitan" }, - "import": { - "message": "Import" + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "importData": { "message": "Mag-import ng data" @@ -3283,6 +3293,9 @@ "launchCloudSubscription": { "message": "Ilunsad ang Cloud Subscription" }, + "launchCloudSubscriptionSentenceCase": { + "message": "Launch cloud subscription" + }, "storage": { "message": "Imbakan" }, @@ -4198,6 +4211,12 @@ } } }, + "userAcceptedTransfer": { + "message": "Accepted transfer to organization ownership." + }, + "userDeclinedTransfer": { + "message": "Revoked for declining transfer to organization ownership." + }, "invitedUserId": { "message": "Inimbitahang gumagamit $ID$.", "placeholders": { @@ -12412,5 +12431,53 @@ }, "whyAmISeeingThis": { "message": "Why am I seeing this?" + }, + "youHaveBitwardenPremium": { + "message": "You have Bitwarden Premium" + }, + "viewAndManagePremiumSubscription": { + "message": "View and manage your Premium subscription" + }, + "youNeedToUpdateLicenseFile": { + "message": "You'll need to update your license file" + }, + "youNeedToUpdateLicenseFileDate": { + "message": "$DATE$.", + "placeholders": { + "date": { + "content": "$1", + "example": "June 12, 2026" + } + } + }, + "uploadLicenseFile": { + "message": "Upload license file" + }, + "uploadYourLicenseFile": { + "message": "Upload your license file" + }, + "uploadYourPremiumLicenseFile": { + "message": "Upload your Premium license file" + }, + "uploadLicenseFileDesc": { + "message": "Your license file name will be similar to: $FILE_NAME$", + "placeholders": { + "file_name": { + "content": "$1", + "example": "bitwarden_license.json" + } + } + }, + "alreadyHaveSubscriptionQuestion": { + "message": "Already have a subscription?" + }, + "alreadyHaveSubscriptionSelfHostedMessage": { + "message": "Open the subscription page on your Bitwarden cloud account and download your license file. Then return to this screen and upload it below." + }, + "viewAllPlans": { + "message": "View all plans" + }, + "planDescPremium": { + "message": "Complete online security" } } diff --git a/apps/web/src/locales/fr/messages.json b/apps/web/src/locales/fr/messages.json index df617a0bff2..582d3492a42 100644 --- a/apps/web/src/locales/fr/messages.json +++ b/apps/web/src/locales/fr/messages.json @@ -1969,8 +1969,13 @@ "encExportAccountWarningDesc": { "message": "Les clés de chiffrement du compte sont spécifiques à chaque utilisateur Bitwarden. Vous ne pouvez donc pas importer d'export chiffré dans un compte différent." }, - "export": { - "message": "Exporter" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "exportVerb": { + "message": "Exporter", + "description": "The verb form of the word Export" }, "exportFrom": { "message": "Exporter à partir de" @@ -2297,8 +2302,13 @@ "tools": { "message": "Outils" }, - "import": { - "message": "Importer" + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Importer", + "description": "The verb form of the word Import" }, "importData": { "message": "Importer des données" @@ -3283,6 +3293,9 @@ "launchCloudSubscription": { "message": "Démarrer l'Abonnement Cloud" }, + "launchCloudSubscriptionSentenceCase": { + "message": "Démarrer l'Abonnement Cloud" + }, "storage": { "message": "Stockage" }, @@ -4198,6 +4211,12 @@ } } }, + "userAcceptedTransfer": { + "message": "Transfert accepté à la propriété de l'organisation." + }, + "userDeclinedTransfer": { + "message": "Révoqué pour le refus du transfert vers la propriété de l'organisation." + }, "invitedUserId": { "message": "Utilisateur $ID$ invité.", "placeholders": { @@ -12412,5 +12431,53 @@ }, "whyAmISeeingThis": { "message": "Pourquoi est-ce que je vois ça ?" + }, + "youHaveBitwardenPremium": { + "message": "Vous avez Bitwarden Premium" + }, + "viewAndManagePremiumSubscription": { + "message": "Afficher et gérer votre abonnement Premium" + }, + "youNeedToUpdateLicenseFile": { + "message": "Vous devrez mettre à jour votre fichier de licence" + }, + "youNeedToUpdateLicenseFileDate": { + "message": "$DATE$.", + "placeholders": { + "date": { + "content": "$1", + "example": "June 12, 2026" + } + } + }, + "uploadLicenseFile": { + "message": "Téléverser le fichier de licence" + }, + "uploadYourLicenseFile": { + "message": "Téléverser votre fichier de licence" + }, + "uploadYourPremiumLicenseFile": { + "message": "Téléversez votre fichier de licence Premium" + }, + "uploadLicenseFileDesc": { + "message": "Votre nom de fichier de licence sera similaire à : $FILE_NAME$", + "placeholders": { + "file_name": { + "content": "$1", + "example": "bitwarden_license.json" + } + } + }, + "alreadyHaveSubscriptionQuestion": { + "message": "Vous avez déjà un abonnement ?" + }, + "alreadyHaveSubscriptionSelfHostedMessage": { + "message": "Ouvrez la page d'abonnement sur votre compte cloud Bitwarden et téléchargez votre fichier de licence. Puis revenez à cet écran et téléversez-le ci-dessous." + }, + "viewAllPlans": { + "message": "Afficher tous les forfaits" + }, + "planDescPremium": { + "message": "Sécurité en ligne complète" } } diff --git a/apps/web/src/locales/gl/messages.json b/apps/web/src/locales/gl/messages.json index 996aa0e899e..7c555a3f142 100644 --- a/apps/web/src/locales/gl/messages.json +++ b/apps/web/src/locales/gl/messages.json @@ -1969,8 +1969,13 @@ "encExportAccountWarningDesc": { "message": "Account encryption keys are unique to each Bitwarden user account, so you can't import an encrypted export into a different account." }, - "export": { - "message": "Export" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, "exportFrom": { "message": "Export from" @@ -2297,8 +2302,13 @@ "tools": { "message": "Tools" }, - "import": { - "message": "Import" + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "importData": { "message": "Import data" @@ -3283,6 +3293,9 @@ "launchCloudSubscription": { "message": "Launch Cloud Subscription" }, + "launchCloudSubscriptionSentenceCase": { + "message": "Launch cloud subscription" + }, "storage": { "message": "Storage" }, @@ -4198,6 +4211,12 @@ } } }, + "userAcceptedTransfer": { + "message": "Accepted transfer to organization ownership." + }, + "userDeclinedTransfer": { + "message": "Revoked for declining transfer to organization ownership." + }, "invitedUserId": { "message": "Invited user $ID$.", "placeholders": { @@ -12412,5 +12431,53 @@ }, "whyAmISeeingThis": { "message": "Why am I seeing this?" + }, + "youHaveBitwardenPremium": { + "message": "You have Bitwarden Premium" + }, + "viewAndManagePremiumSubscription": { + "message": "View and manage your Premium subscription" + }, + "youNeedToUpdateLicenseFile": { + "message": "You'll need to update your license file" + }, + "youNeedToUpdateLicenseFileDate": { + "message": "$DATE$.", + "placeholders": { + "date": { + "content": "$1", + "example": "June 12, 2026" + } + } + }, + "uploadLicenseFile": { + "message": "Upload license file" + }, + "uploadYourLicenseFile": { + "message": "Upload your license file" + }, + "uploadYourPremiumLicenseFile": { + "message": "Upload your Premium license file" + }, + "uploadLicenseFileDesc": { + "message": "Your license file name will be similar to: $FILE_NAME$", + "placeholders": { + "file_name": { + "content": "$1", + "example": "bitwarden_license.json" + } + } + }, + "alreadyHaveSubscriptionQuestion": { + "message": "Already have a subscription?" + }, + "alreadyHaveSubscriptionSelfHostedMessage": { + "message": "Open the subscription page on your Bitwarden cloud account and download your license file. Then return to this screen and upload it below." + }, + "viewAllPlans": { + "message": "View all plans" + }, + "planDescPremium": { + "message": "Complete online security" } } diff --git a/apps/web/src/locales/he/messages.json b/apps/web/src/locales/he/messages.json index 6f3049535f9..c0e1995289e 100644 --- a/apps/web/src/locales/he/messages.json +++ b/apps/web/src/locales/he/messages.json @@ -1969,8 +1969,13 @@ "encExportAccountWarningDesc": { "message": "מפתחות הצפנת חשבון הם ייחודים לכל חשבון משתמש של Bitwarden, לכן אינך יכול לייבא ייצוא מוצפן אל תוך חשבון אחר." }, - "export": { - "message": "ייצוא" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, "exportFrom": { "message": "ייצא מ־" @@ -2297,8 +2302,13 @@ "tools": { "message": "כלים" }, - "import": { - "message": "Import" + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "importData": { "message": "ייבא נתונים" @@ -3283,6 +3293,9 @@ "launchCloudSubscription": { "message": "הפעל מנוי ענן" }, + "launchCloudSubscriptionSentenceCase": { + "message": "Launch cloud subscription" + }, "storage": { "message": "אחסון" }, @@ -4198,6 +4211,12 @@ } } }, + "userAcceptedTransfer": { + "message": "Accepted transfer to organization ownership." + }, + "userDeclinedTransfer": { + "message": "Revoked for declining transfer to organization ownership." + }, "invitedUserId": { "message": "משתמש שהוזמן $ID$.", "placeholders": { @@ -12412,5 +12431,53 @@ }, "whyAmISeeingThis": { "message": "Why am I seeing this?" + }, + "youHaveBitwardenPremium": { + "message": "You have Bitwarden Premium" + }, + "viewAndManagePremiumSubscription": { + "message": "View and manage your Premium subscription" + }, + "youNeedToUpdateLicenseFile": { + "message": "You'll need to update your license file" + }, + "youNeedToUpdateLicenseFileDate": { + "message": "$DATE$.", + "placeholders": { + "date": { + "content": "$1", + "example": "June 12, 2026" + } + } + }, + "uploadLicenseFile": { + "message": "Upload license file" + }, + "uploadYourLicenseFile": { + "message": "Upload your license file" + }, + "uploadYourPremiumLicenseFile": { + "message": "Upload your Premium license file" + }, + "uploadLicenseFileDesc": { + "message": "Your license file name will be similar to: $FILE_NAME$", + "placeholders": { + "file_name": { + "content": "$1", + "example": "bitwarden_license.json" + } + } + }, + "alreadyHaveSubscriptionQuestion": { + "message": "Already have a subscription?" + }, + "alreadyHaveSubscriptionSelfHostedMessage": { + "message": "Open the subscription page on your Bitwarden cloud account and download your license file. Then return to this screen and upload it below." + }, + "viewAllPlans": { + "message": "View all plans" + }, + "planDescPremium": { + "message": "Complete online security" } } diff --git a/apps/web/src/locales/hi/messages.json b/apps/web/src/locales/hi/messages.json index 8c0f47f487c..4b578f69e5c 100644 --- a/apps/web/src/locales/hi/messages.json +++ b/apps/web/src/locales/hi/messages.json @@ -1969,8 +1969,13 @@ "encExportAccountWarningDesc": { "message": "Account encryption keys are unique to each Bitwarden user account, so you can't import an encrypted export into a different account." }, - "export": { - "message": "Export" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, "exportFrom": { "message": "Export from" @@ -2297,8 +2302,13 @@ "tools": { "message": "Tools" }, - "import": { - "message": "Import" + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "importData": { "message": "Import data" @@ -3283,6 +3293,9 @@ "launchCloudSubscription": { "message": "Launch Cloud Subscription" }, + "launchCloudSubscriptionSentenceCase": { + "message": "Launch cloud subscription" + }, "storage": { "message": "Storage" }, @@ -4198,6 +4211,12 @@ } } }, + "userAcceptedTransfer": { + "message": "Accepted transfer to organization ownership." + }, + "userDeclinedTransfer": { + "message": "Revoked for declining transfer to organization ownership." + }, "invitedUserId": { "message": "Invited user $ID$.", "placeholders": { @@ -12412,5 +12431,53 @@ }, "whyAmISeeingThis": { "message": "Why am I seeing this?" + }, + "youHaveBitwardenPremium": { + "message": "You have Bitwarden Premium" + }, + "viewAndManagePremiumSubscription": { + "message": "View and manage your Premium subscription" + }, + "youNeedToUpdateLicenseFile": { + "message": "You'll need to update your license file" + }, + "youNeedToUpdateLicenseFileDate": { + "message": "$DATE$.", + "placeholders": { + "date": { + "content": "$1", + "example": "June 12, 2026" + } + } + }, + "uploadLicenseFile": { + "message": "Upload license file" + }, + "uploadYourLicenseFile": { + "message": "Upload your license file" + }, + "uploadYourPremiumLicenseFile": { + "message": "Upload your Premium license file" + }, + "uploadLicenseFileDesc": { + "message": "Your license file name will be similar to: $FILE_NAME$", + "placeholders": { + "file_name": { + "content": "$1", + "example": "bitwarden_license.json" + } + } + }, + "alreadyHaveSubscriptionQuestion": { + "message": "Already have a subscription?" + }, + "alreadyHaveSubscriptionSelfHostedMessage": { + "message": "Open the subscription page on your Bitwarden cloud account and download your license file. Then return to this screen and upload it below." + }, + "viewAllPlans": { + "message": "View all plans" + }, + "planDescPremium": { + "message": "Complete online security" } } diff --git a/apps/web/src/locales/hr/messages.json b/apps/web/src/locales/hr/messages.json index 09cf0d78a06..92f0d020812 100644 --- a/apps/web/src/locales/hr/messages.json +++ b/apps/web/src/locales/hr/messages.json @@ -1969,8 +1969,13 @@ "encExportAccountWarningDesc": { "message": "Ključ za šifriranje jedinstven je za svakog Bitwarden korisnika, kako bi se šifrirani izvoz mogao uvesti u drugi korisnički račun." }, - "export": { - "message": "Izvoz" + "exportNoun": { + "message": "Izvoz", + "description": "The noun form of the word Export" + }, + "exportVerb": { + "message": "Izvoz", + "description": "The verb form of the word Export" }, "exportFrom": { "message": "Izvezi iz" @@ -2297,8 +2302,13 @@ "tools": { "message": "Alati" }, - "import": { - "message": "Import" + "importNoun": { + "message": "Uvoz", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Uvoz", + "description": "The verb form of the word Import" }, "importData": { "message": "Uvezi podatke" @@ -3057,7 +3067,7 @@ "message": "1 GB šifriranog prostora za pohranu podataka." }, "premiumSignUpStorageV2": { - "message": "$SIZE$ encrypted storage for file attachments.", + "message": "$SIZE$ šifriranog prostora za privitke.", "placeholders": { "size": { "content": "$1", @@ -3128,13 +3138,13 @@ } }, "premiumSubscriptionEnded": { - "message": "Your Premium subscription ended" + "message": "Toja Premium pretplata je završila" }, "premiumSubscriptionEndedDesc": { - "message": "To regain access to your archive, restart your Premium subscription. If you edit details for an archived item before restarting, it'll be moved back into your vault." + "message": "Za ponovni pristup svojoj arhivi, ponovno pokreni Premium pretplatu. Ako urediš detalje arhivirane stavke prije ponovnog pokretanja, ona će biti vraćena u tvoj trezor." }, "restartPremium": { - "message": "Restart Premium" + "message": "Ponovno Pokreni Premium" }, "additionalStorageGb": { "message": "Dodatni prostor za pohranu (GB)" @@ -3283,6 +3293,9 @@ "launchCloudSubscription": { "message": "Pokreni Cloud pretplatu" }, + "launchCloudSubscriptionSentenceCase": { + "message": "Pokreni Cloud pretplatu" + }, "storage": { "message": "Prostor za pohranu" }, @@ -4198,6 +4211,12 @@ } } }, + "userAcceptedTransfer": { + "message": "Accepted transfer to organization ownership." + }, + "userDeclinedTransfer": { + "message": "Revoked for declining transfer to organization ownership." + }, "invitedUserId": { "message": "Pozvan korisnik $ID$.", "placeholders": { @@ -4621,19 +4640,19 @@ "message": "Saznaj više" }, "migrationsFailed": { - "message": "An error occurred updating the encryption settings." + "message": "Dogodila se greška pri ažuriranju postavki šifriranja." }, "updateEncryptionSettingsTitle": { - "message": "Update your encryption settings" + "message": "Ažuriraj svoje postakve šifriranja" }, "updateEncryptionSettingsDesc": { - "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + "message": "Nove preporučene postavke šifriranja poboljšat će sigurnost tvojeg računa. Za ažuriranje, unesi svoju glavnu lozinku." }, "confirmIdentityToContinue": { "message": "Confirm your identity to continue" }, "enterYourMasterPassword": { - "message": "Enter your master password" + "message": "Unesi svoju glavnu lozinku" }, "updateSettings": { "message": "Ažuriraj postavke" @@ -12412,5 +12431,53 @@ }, "whyAmISeeingThis": { "message": "Why am I seeing this?" + }, + "youHaveBitwardenPremium": { + "message": "You have Bitwarden Premium" + }, + "viewAndManagePremiumSubscription": { + "message": "View and manage your Premium subscription" + }, + "youNeedToUpdateLicenseFile": { + "message": "You'll need to update your license file" + }, + "youNeedToUpdateLicenseFileDate": { + "message": "$DATE$.", + "placeholders": { + "date": { + "content": "$1", + "example": "June 12, 2026" + } + } + }, + "uploadLicenseFile": { + "message": "Upload license file" + }, + "uploadYourLicenseFile": { + "message": "Upload your license file" + }, + "uploadYourPremiumLicenseFile": { + "message": "Upload your Premium license file" + }, + "uploadLicenseFileDesc": { + "message": "Your license file name will be similar to: $FILE_NAME$", + "placeholders": { + "file_name": { + "content": "$1", + "example": "bitwarden_license.json" + } + } + }, + "alreadyHaveSubscriptionQuestion": { + "message": "Already have a subscription?" + }, + "alreadyHaveSubscriptionSelfHostedMessage": { + "message": "Open the subscription page on your Bitwarden cloud account and download your license file. Then return to this screen and upload it below." + }, + "viewAllPlans": { + "message": "View all plans" + }, + "planDescPremium": { + "message": "Complete online security" } } diff --git a/apps/web/src/locales/hu/messages.json b/apps/web/src/locales/hu/messages.json index 395a87e4544..7e296cd7092 100644 --- a/apps/web/src/locales/hu/messages.json +++ b/apps/web/src/locales/hu/messages.json @@ -1969,8 +1969,13 @@ "encExportAccountWarningDesc": { "message": "A fiók titkosítási kulcsai minden Bitwarden felhasználói fiókhoz egyediek, ezért nem importálhatunk titkosított exportálást egy másik fiókba." }, - "export": { - "message": "Exportálás" + "exportNoun": { + "message": "Exportálás", + "description": "The noun form of the word Export" + }, + "exportVerb": { + "message": "Exportálás", + "description": "The verb form of the word Export" }, "exportFrom": { "message": "Exportálás innen:" @@ -2297,8 +2302,13 @@ "tools": { "message": "Eszközök" }, - "import": { - "message": "Importálás" + "importNoun": { + "message": "Importálás", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Importálás", + "description": "The verb form of the word Import" }, "importData": { "message": "Adatok importálása" @@ -3283,6 +3293,9 @@ "launchCloudSubscription": { "message": "Felhő előfizetés indítása" }, + "launchCloudSubscriptionSentenceCase": { + "message": "Felhő előfizetés indítása" + }, "storage": { "message": "Tárhely" }, @@ -4198,6 +4211,12 @@ } } }, + "userAcceptedTransfer": { + "message": "Accepted transfer to organization ownership." + }, + "userDeclinedTransfer": { + "message": "Revoked for declining transfer to organization ownership." + }, "invitedUserId": { "message": "$ID$ azonosítójú felhasználó meghívásra került.", "placeholders": { @@ -12412,5 +12431,53 @@ }, "whyAmISeeingThis": { "message": "Miért látható ez?" + }, + "youHaveBitwardenPremium": { + "message": "Bitwarden Prémium verzió van használatban." + }, + "viewAndManagePremiumSubscription": { + "message": "Tekintsük meg és kezeljük a Prémium előfizetést." + }, + "youNeedToUpdateLicenseFile": { + "message": "Frissíteni kell a licensz fájlt." + }, + "youNeedToUpdateLicenseFileDate": { + "message": "$DATE$.", + "placeholders": { + "date": { + "content": "$1", + "example": "June 12, 2026" + } + } + }, + "uploadLicenseFile": { + "message": "Licensz fájl feltöltése" + }, + "uploadYourLicenseFile": { + "message": "Licensz fájl feltöltése" + }, + "uploadYourPremiumLicenseFile": { + "message": "Licensz fájl feltöltése" + }, + "uploadLicenseFileDesc": { + "message": "A licensz fájl neve hasonló lesz: $FILE_NAME$", + "placeholders": { + "file_name": { + "content": "$1", + "example": "bitwarden_license.json" + } + } + }, + "alreadyHaveSubscriptionQuestion": { + "message": "Már van előfizetés?" + }, + "alreadyHaveSubscriptionSelfHostedMessage": { + "message": "Nyissuk meg az előfizetési oldalt Bitwarden felhőfiókjában és töltsük le licensz fájlt. Ezután térjünk vissza erre a képernyőre és töltsük fel lentebb." + }, + "viewAllPlans": { + "message": "Összes csomag megtekintése" + }, + "planDescPremium": { + "message": "Teljes körű online biztonság" } } diff --git a/apps/web/src/locales/id/messages.json b/apps/web/src/locales/id/messages.json index 98bf8edf291..e349f4180b9 100644 --- a/apps/web/src/locales/id/messages.json +++ b/apps/web/src/locales/id/messages.json @@ -1969,8 +1969,13 @@ "encExportAccountWarningDesc": { "message": "Kunci enkripsi akun adalah unik untuk setiap akun pengguna Bitwarden, sehingga Anda tidak dapat mengimpor ekspor terenkripsi ke akun yang berbeda." }, - "export": { - "message": "Ekspor" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, "exportFrom": { "message": "Ekspor dari" @@ -2297,8 +2302,13 @@ "tools": { "message": "Alat" }, - "import": { - "message": "Import" + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "importData": { "message": "Impor Data" @@ -3283,6 +3293,9 @@ "launchCloudSubscription": { "message": "Luncurkan Langganan Cloud" }, + "launchCloudSubscriptionSentenceCase": { + "message": "Launch cloud subscription" + }, "storage": { "message": "Penyimpanan" }, @@ -4198,6 +4211,12 @@ } } }, + "userAcceptedTransfer": { + "message": "Accepted transfer to organization ownership." + }, + "userDeclinedTransfer": { + "message": "Revoked for declining transfer to organization ownership." + }, "invitedUserId": { "message": "$ID$ pengguna yang diundang.", "placeholders": { @@ -12412,5 +12431,53 @@ }, "whyAmISeeingThis": { "message": "Why am I seeing this?" + }, + "youHaveBitwardenPremium": { + "message": "You have Bitwarden Premium" + }, + "viewAndManagePremiumSubscription": { + "message": "View and manage your Premium subscription" + }, + "youNeedToUpdateLicenseFile": { + "message": "You'll need to update your license file" + }, + "youNeedToUpdateLicenseFileDate": { + "message": "$DATE$.", + "placeholders": { + "date": { + "content": "$1", + "example": "June 12, 2026" + } + } + }, + "uploadLicenseFile": { + "message": "Upload license file" + }, + "uploadYourLicenseFile": { + "message": "Upload your license file" + }, + "uploadYourPremiumLicenseFile": { + "message": "Upload your Premium license file" + }, + "uploadLicenseFileDesc": { + "message": "Your license file name will be similar to: $FILE_NAME$", + "placeholders": { + "file_name": { + "content": "$1", + "example": "bitwarden_license.json" + } + } + }, + "alreadyHaveSubscriptionQuestion": { + "message": "Already have a subscription?" + }, + "alreadyHaveSubscriptionSelfHostedMessage": { + "message": "Open the subscription page on your Bitwarden cloud account and download your license file. Then return to this screen and upload it below." + }, + "viewAllPlans": { + "message": "View all plans" + }, + "planDescPremium": { + "message": "Complete online security" } } diff --git a/apps/web/src/locales/it/messages.json b/apps/web/src/locales/it/messages.json index 34d17398c3e..0b98058ae82 100644 --- a/apps/web/src/locales/it/messages.json +++ b/apps/web/src/locales/it/messages.json @@ -1969,8 +1969,13 @@ "encExportAccountWarningDesc": { "message": "Le chiavi di cifratura dell'account sono uniche per ogni account utente Bitwarden, quindi non è possibile importare un'esportazione cifrata in un account diverso." }, - "export": { - "message": "Esporta" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, "exportFrom": { "message": "Esporta da" @@ -2297,8 +2302,13 @@ "tools": { "message": "Strumenti" }, - "import": { - "message": "Import" + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "importData": { "message": "Importa dati" @@ -3283,6 +3293,9 @@ "launchCloudSubscription": { "message": "Avvia abbonamento cloud" }, + "launchCloudSubscriptionSentenceCase": { + "message": "Launch cloud subscription" + }, "storage": { "message": "Spazio di archiviazione" }, @@ -4198,6 +4211,12 @@ } } }, + "userAcceptedTransfer": { + "message": "Accepted transfer to organization ownership." + }, + "userDeclinedTransfer": { + "message": "Revoked for declining transfer to organization ownership." + }, "invitedUserId": { "message": "Utente $ID$ invitato.", "placeholders": { @@ -12412,5 +12431,53 @@ }, "whyAmISeeingThis": { "message": "Perché vedo questo avviso?" + }, + "youHaveBitwardenPremium": { + "message": "You have Bitwarden Premium" + }, + "viewAndManagePremiumSubscription": { + "message": "View and manage your Premium subscription" + }, + "youNeedToUpdateLicenseFile": { + "message": "You'll need to update your license file" + }, + "youNeedToUpdateLicenseFileDate": { + "message": "$DATE$.", + "placeholders": { + "date": { + "content": "$1", + "example": "June 12, 2026" + } + } + }, + "uploadLicenseFile": { + "message": "Upload license file" + }, + "uploadYourLicenseFile": { + "message": "Upload your license file" + }, + "uploadYourPremiumLicenseFile": { + "message": "Upload your Premium license file" + }, + "uploadLicenseFileDesc": { + "message": "Your license file name will be similar to: $FILE_NAME$", + "placeholders": { + "file_name": { + "content": "$1", + "example": "bitwarden_license.json" + } + } + }, + "alreadyHaveSubscriptionQuestion": { + "message": "Already have a subscription?" + }, + "alreadyHaveSubscriptionSelfHostedMessage": { + "message": "Open the subscription page on your Bitwarden cloud account and download your license file. Then return to this screen and upload it below." + }, + "viewAllPlans": { + "message": "View all plans" + }, + "planDescPremium": { + "message": "Complete online security" } } diff --git a/apps/web/src/locales/ja/messages.json b/apps/web/src/locales/ja/messages.json index 0a79c1903e4..20fa1137002 100644 --- a/apps/web/src/locales/ja/messages.json +++ b/apps/web/src/locales/ja/messages.json @@ -1969,8 +1969,13 @@ "encExportAccountWarningDesc": { "message": "アカウント暗号化キーは各 Bitwarden ユーザーアカウントに固有であるため、暗号化されたエクスポートを別のアカウントにインポートすることはできません。" }, - "export": { - "message": "エクスポート" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, "exportFrom": { "message": "エクスポート元" @@ -2297,8 +2302,13 @@ "tools": { "message": "ツール" }, - "import": { - "message": "Import" + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "importData": { "message": "データをインポート" @@ -3283,6 +3293,9 @@ "launchCloudSubscription": { "message": "クラウドサブスクリプションを起動" }, + "launchCloudSubscriptionSentenceCase": { + "message": "Launch cloud subscription" + }, "storage": { "message": "ストレージ" }, @@ -4198,6 +4211,12 @@ } } }, + "userAcceptedTransfer": { + "message": "Accepted transfer to organization ownership." + }, + "userDeclinedTransfer": { + "message": "Revoked for declining transfer to organization ownership." + }, "invitedUserId": { "message": "ユーザー「$ID$」の招待", "placeholders": { @@ -12412,5 +12431,53 @@ }, "whyAmISeeingThis": { "message": "Why am I seeing this?" + }, + "youHaveBitwardenPremium": { + "message": "You have Bitwarden Premium" + }, + "viewAndManagePremiumSubscription": { + "message": "View and manage your Premium subscription" + }, + "youNeedToUpdateLicenseFile": { + "message": "You'll need to update your license file" + }, + "youNeedToUpdateLicenseFileDate": { + "message": "$DATE$.", + "placeholders": { + "date": { + "content": "$1", + "example": "June 12, 2026" + } + } + }, + "uploadLicenseFile": { + "message": "Upload license file" + }, + "uploadYourLicenseFile": { + "message": "Upload your license file" + }, + "uploadYourPremiumLicenseFile": { + "message": "Upload your Premium license file" + }, + "uploadLicenseFileDesc": { + "message": "Your license file name will be similar to: $FILE_NAME$", + "placeholders": { + "file_name": { + "content": "$1", + "example": "bitwarden_license.json" + } + } + }, + "alreadyHaveSubscriptionQuestion": { + "message": "Already have a subscription?" + }, + "alreadyHaveSubscriptionSelfHostedMessage": { + "message": "Open the subscription page on your Bitwarden cloud account and download your license file. Then return to this screen and upload it below." + }, + "viewAllPlans": { + "message": "View all plans" + }, + "planDescPremium": { + "message": "Complete online security" } } diff --git a/apps/web/src/locales/ka/messages.json b/apps/web/src/locales/ka/messages.json index 3fb27024064..4a6927d9f3a 100644 --- a/apps/web/src/locales/ka/messages.json +++ b/apps/web/src/locales/ka/messages.json @@ -1969,8 +1969,13 @@ "encExportAccountWarningDesc": { "message": "Account encryption keys are unique to each Bitwarden user account, so you can't import an encrypted export into a different account." }, - "export": { - "message": "ექსპორტი" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, "exportFrom": { "message": "Export from" @@ -2297,8 +2302,13 @@ "tools": { "message": "Tools" }, - "import": { - "message": "Import" + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "importData": { "message": "Import data" @@ -3283,6 +3293,9 @@ "launchCloudSubscription": { "message": "Launch Cloud Subscription" }, + "launchCloudSubscriptionSentenceCase": { + "message": "Launch cloud subscription" + }, "storage": { "message": "Storage" }, @@ -4198,6 +4211,12 @@ } } }, + "userAcceptedTransfer": { + "message": "Accepted transfer to organization ownership." + }, + "userDeclinedTransfer": { + "message": "Revoked for declining transfer to organization ownership." + }, "invitedUserId": { "message": "Invited user $ID$.", "placeholders": { @@ -12412,5 +12431,53 @@ }, "whyAmISeeingThis": { "message": "Why am I seeing this?" + }, + "youHaveBitwardenPremium": { + "message": "You have Bitwarden Premium" + }, + "viewAndManagePremiumSubscription": { + "message": "View and manage your Premium subscription" + }, + "youNeedToUpdateLicenseFile": { + "message": "You'll need to update your license file" + }, + "youNeedToUpdateLicenseFileDate": { + "message": "$DATE$.", + "placeholders": { + "date": { + "content": "$1", + "example": "June 12, 2026" + } + } + }, + "uploadLicenseFile": { + "message": "Upload license file" + }, + "uploadYourLicenseFile": { + "message": "Upload your license file" + }, + "uploadYourPremiumLicenseFile": { + "message": "Upload your Premium license file" + }, + "uploadLicenseFileDesc": { + "message": "Your license file name will be similar to: $FILE_NAME$", + "placeholders": { + "file_name": { + "content": "$1", + "example": "bitwarden_license.json" + } + } + }, + "alreadyHaveSubscriptionQuestion": { + "message": "Already have a subscription?" + }, + "alreadyHaveSubscriptionSelfHostedMessage": { + "message": "Open the subscription page on your Bitwarden cloud account and download your license file. Then return to this screen and upload it below." + }, + "viewAllPlans": { + "message": "View all plans" + }, + "planDescPremium": { + "message": "Complete online security" } } diff --git a/apps/web/src/locales/km/messages.json b/apps/web/src/locales/km/messages.json index d84c2ae7b30..f570d355369 100644 --- a/apps/web/src/locales/km/messages.json +++ b/apps/web/src/locales/km/messages.json @@ -1969,8 +1969,13 @@ "encExportAccountWarningDesc": { "message": "Account encryption keys are unique to each Bitwarden user account, so you can't import an encrypted export into a different account." }, - "export": { - "message": "Export" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, "exportFrom": { "message": "Export from" @@ -2297,8 +2302,13 @@ "tools": { "message": "Tools" }, - "import": { - "message": "Import" + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "importData": { "message": "Import data" @@ -3283,6 +3293,9 @@ "launchCloudSubscription": { "message": "Launch Cloud Subscription" }, + "launchCloudSubscriptionSentenceCase": { + "message": "Launch cloud subscription" + }, "storage": { "message": "Storage" }, @@ -4198,6 +4211,12 @@ } } }, + "userAcceptedTransfer": { + "message": "Accepted transfer to organization ownership." + }, + "userDeclinedTransfer": { + "message": "Revoked for declining transfer to organization ownership." + }, "invitedUserId": { "message": "Invited user $ID$.", "placeholders": { @@ -12412,5 +12431,53 @@ }, "whyAmISeeingThis": { "message": "Why am I seeing this?" + }, + "youHaveBitwardenPremium": { + "message": "You have Bitwarden Premium" + }, + "viewAndManagePremiumSubscription": { + "message": "View and manage your Premium subscription" + }, + "youNeedToUpdateLicenseFile": { + "message": "You'll need to update your license file" + }, + "youNeedToUpdateLicenseFileDate": { + "message": "$DATE$.", + "placeholders": { + "date": { + "content": "$1", + "example": "June 12, 2026" + } + } + }, + "uploadLicenseFile": { + "message": "Upload license file" + }, + "uploadYourLicenseFile": { + "message": "Upload your license file" + }, + "uploadYourPremiumLicenseFile": { + "message": "Upload your Premium license file" + }, + "uploadLicenseFileDesc": { + "message": "Your license file name will be similar to: $FILE_NAME$", + "placeholders": { + "file_name": { + "content": "$1", + "example": "bitwarden_license.json" + } + } + }, + "alreadyHaveSubscriptionQuestion": { + "message": "Already have a subscription?" + }, + "alreadyHaveSubscriptionSelfHostedMessage": { + "message": "Open the subscription page on your Bitwarden cloud account and download your license file. Then return to this screen and upload it below." + }, + "viewAllPlans": { + "message": "View all plans" + }, + "planDescPremium": { + "message": "Complete online security" } } diff --git a/apps/web/src/locales/kn/messages.json b/apps/web/src/locales/kn/messages.json index a90cde5f540..94237cb1028 100644 --- a/apps/web/src/locales/kn/messages.json +++ b/apps/web/src/locales/kn/messages.json @@ -1969,8 +1969,13 @@ "encExportAccountWarningDesc": { "message": "ಖಾತೆ ಎನ್‌ಕ್ರಿಪ್ಶನ್ ಕೀಗಳು ಪ್ರತಿ ಬಿಟ್‌ವಾರ್ಡೆನ್ ಬಳಕೆದಾರ ಖಾತೆಗೆ ಅನನ್ಯವಾಗಿವೆ, ಆದ್ದರಿಂದ ನೀವು ಎನ್‌ಕ್ರಿಪ್ಟ್ ಮಾಡಿದ ರಫ್ತು ಬೇರೆ ಖಾತೆಗೆ ಆಮದು ಮಾಡಲು ಸಾಧ್ಯವಿಲ್ಲ." }, - "export": { - "message": "ರಫ್ತು" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, "exportFrom": { "message": "Export from" @@ -2297,8 +2302,13 @@ "tools": { "message": "ಉಪಕರಣ" }, - "import": { - "message": "Import" + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "importData": { "message": "ಡೇಟಾವನ್ನು ಆಮದು ಮಾಡಿ" @@ -3283,6 +3293,9 @@ "launchCloudSubscription": { "message": "Launch Cloud Subscription" }, + "launchCloudSubscriptionSentenceCase": { + "message": "Launch cloud subscription" + }, "storage": { "message": "ಸಂಗ್ರಹಣೆ" }, @@ -4198,6 +4211,12 @@ } } }, + "userAcceptedTransfer": { + "message": "Accepted transfer to organization ownership." + }, + "userDeclinedTransfer": { + "message": "Revoked for declining transfer to organization ownership." + }, "invitedUserId": { "message": "ಆಹ್ವಾನಿತ ಬಳಕೆದಾರ $ID$.", "placeholders": { @@ -12412,5 +12431,53 @@ }, "whyAmISeeingThis": { "message": "Why am I seeing this?" + }, + "youHaveBitwardenPremium": { + "message": "You have Bitwarden Premium" + }, + "viewAndManagePremiumSubscription": { + "message": "View and manage your Premium subscription" + }, + "youNeedToUpdateLicenseFile": { + "message": "You'll need to update your license file" + }, + "youNeedToUpdateLicenseFileDate": { + "message": "$DATE$.", + "placeholders": { + "date": { + "content": "$1", + "example": "June 12, 2026" + } + } + }, + "uploadLicenseFile": { + "message": "Upload license file" + }, + "uploadYourLicenseFile": { + "message": "Upload your license file" + }, + "uploadYourPremiumLicenseFile": { + "message": "Upload your Premium license file" + }, + "uploadLicenseFileDesc": { + "message": "Your license file name will be similar to: $FILE_NAME$", + "placeholders": { + "file_name": { + "content": "$1", + "example": "bitwarden_license.json" + } + } + }, + "alreadyHaveSubscriptionQuestion": { + "message": "Already have a subscription?" + }, + "alreadyHaveSubscriptionSelfHostedMessage": { + "message": "Open the subscription page on your Bitwarden cloud account and download your license file. Then return to this screen and upload it below." + }, + "viewAllPlans": { + "message": "View all plans" + }, + "planDescPremium": { + "message": "Complete online security" } } diff --git a/apps/web/src/locales/ko/messages.json b/apps/web/src/locales/ko/messages.json index 3b86738b797..c0471507369 100644 --- a/apps/web/src/locales/ko/messages.json +++ b/apps/web/src/locales/ko/messages.json @@ -1969,8 +1969,13 @@ "encExportAccountWarningDesc": { "message": "모든 Bitwarden 사용자 계정은 고유한 계정 암호화 키를 가지고 있습니다. 따라서, 다른 계정에서는 암호화된 내보내기를 가져올 수 없습니다." }, - "export": { - "message": "내보내기" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, "exportFrom": { "message": "Export from" @@ -2297,8 +2302,13 @@ "tools": { "message": "도구" }, - "import": { - "message": "Import" + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "importData": { "message": "데이터 가져오기" @@ -3283,6 +3293,9 @@ "launchCloudSubscription": { "message": "Launch Cloud Subscription" }, + "launchCloudSubscriptionSentenceCase": { + "message": "Launch cloud subscription" + }, "storage": { "message": "저장소" }, @@ -4198,6 +4211,12 @@ } } }, + "userAcceptedTransfer": { + "message": "Accepted transfer to organization ownership." + }, + "userDeclinedTransfer": { + "message": "Revoked for declining transfer to organization ownership." + }, "invitedUserId": { "message": "$ID$ 사용자를 초대했습니다.", "placeholders": { @@ -12412,5 +12431,53 @@ }, "whyAmISeeingThis": { "message": "Why am I seeing this?" + }, + "youHaveBitwardenPremium": { + "message": "You have Bitwarden Premium" + }, + "viewAndManagePremiumSubscription": { + "message": "View and manage your Premium subscription" + }, + "youNeedToUpdateLicenseFile": { + "message": "You'll need to update your license file" + }, + "youNeedToUpdateLicenseFileDate": { + "message": "$DATE$.", + "placeholders": { + "date": { + "content": "$1", + "example": "June 12, 2026" + } + } + }, + "uploadLicenseFile": { + "message": "Upload license file" + }, + "uploadYourLicenseFile": { + "message": "Upload your license file" + }, + "uploadYourPremiumLicenseFile": { + "message": "Upload your Premium license file" + }, + "uploadLicenseFileDesc": { + "message": "Your license file name will be similar to: $FILE_NAME$", + "placeholders": { + "file_name": { + "content": "$1", + "example": "bitwarden_license.json" + } + } + }, + "alreadyHaveSubscriptionQuestion": { + "message": "Already have a subscription?" + }, + "alreadyHaveSubscriptionSelfHostedMessage": { + "message": "Open the subscription page on your Bitwarden cloud account and download your license file. Then return to this screen and upload it below." + }, + "viewAllPlans": { + "message": "View all plans" + }, + "planDescPremium": { + "message": "Complete online security" } } diff --git a/apps/web/src/locales/lv/messages.json b/apps/web/src/locales/lv/messages.json index 7e232de913f..6cb8e0a4a6b 100644 --- a/apps/web/src/locales/lv/messages.json +++ b/apps/web/src/locales/lv/messages.json @@ -1969,8 +1969,13 @@ "encExportAccountWarningDesc": { "message": "Katram Bitwarden kontam ir neatkārtojamas šifrēšanas atslēgas, tādēļ nav iespējams ievietot šifrētu izguvi citā kontā." }, - "export": { - "message": "Izgūšana" + "exportNoun": { + "message": "Izgūšana", + "description": "The noun form of the word Export" + }, + "exportVerb": { + "message": "Izgūt", + "description": "The verb form of the word Export" }, "exportFrom": { "message": "Izgūt no" @@ -2297,8 +2302,13 @@ "tools": { "message": "Rīki" }, - "import": { - "message": "Ievietot" + "importNoun": { + "message": "Ievietošana", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Ievietot", + "description": "The verb form of the word Import" }, "importData": { "message": "Ievietot datus" @@ -3283,6 +3293,9 @@ "launchCloudSubscription": { "message": "Uzsākt mākoņa abonēšanu" }, + "launchCloudSubscriptionSentenceCase": { + "message": "Uzsākt mākoņa abonēšanu" + }, "storage": { "message": "Krātuve" }, @@ -4198,6 +4211,12 @@ } } }, + "userAcceptedTransfer": { + "message": "Accepted transfer to organization ownership." + }, + "userDeclinedTransfer": { + "message": "Revoked for declining transfer to organization ownership." + }, "invitedUserId": { "message": "Uzaicināts lietotājs $ID$.", "placeholders": { @@ -12412,5 +12431,53 @@ }, "whyAmISeeingThis": { "message": "Why am I seeing this?" + }, + "youHaveBitwardenPremium": { + "message": "Tev ir Bitwarden Premium" + }, + "viewAndManagePremiumSubscription": { + "message": "Apskatīt un pārvaldīt savu Premium abonementu" + }, + "youNeedToUpdateLicenseFile": { + "message": "Būs nepieciešams atjaunināt savu licences datni" + }, + "youNeedToUpdateLicenseFileDate": { + "message": "$DATE$.", + "placeholders": { + "date": { + "content": "$1", + "example": "June 12, 2026" + } + } + }, + "uploadLicenseFile": { + "message": "Augšupielādēt licences datni" + }, + "uploadYourLicenseFile": { + "message": "Jāaugšupielādē sava licences datne" + }, + "uploadYourPremiumLicenseFile": { + "message": "Jāaugšupielādē sava Premium licences datne" + }, + "uploadLicenseFileDesc": { + "message": "Your license file name will be similar to: $FILE_NAME$", + "placeholders": { + "file_name": { + "content": "$1", + "example": "bitwarden_license.json" + } + } + }, + "alreadyHaveSubscriptionQuestion": { + "message": "Already have a subscription?" + }, + "alreadyHaveSubscriptionSelfHostedMessage": { + "message": "Open the subscription page on your Bitwarden cloud account and download your license file. Then return to this screen and upload it below." + }, + "viewAllPlans": { + "message": "View all plans" + }, + "planDescPremium": { + "message": "Complete online security" } } diff --git a/apps/web/src/locales/ml/messages.json b/apps/web/src/locales/ml/messages.json index 01cc78f20e7..1dc40cce28b 100644 --- a/apps/web/src/locales/ml/messages.json +++ b/apps/web/src/locales/ml/messages.json @@ -1969,8 +1969,13 @@ "encExportAccountWarningDesc": { "message": "Account encryption keys are unique to each Bitwarden user account, so you can't import an encrypted export into a different account." }, - "export": { - "message": "Export" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, "exportFrom": { "message": "Export from" @@ -2297,8 +2302,13 @@ "tools": { "message": "ഉപകരണങ്ങൾ" }, - "import": { - "message": "Import" + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "importData": { "message": "Import Data" @@ -3283,6 +3293,9 @@ "launchCloudSubscription": { "message": "Launch Cloud Subscription" }, + "launchCloudSubscriptionSentenceCase": { + "message": "Launch cloud subscription" + }, "storage": { "message": "സ്റ്റോറേജ്" }, @@ -4198,6 +4211,12 @@ } } }, + "userAcceptedTransfer": { + "message": "Accepted transfer to organization ownership." + }, + "userDeclinedTransfer": { + "message": "Revoked for declining transfer to organization ownership." + }, "invitedUserId": { "message": "Invited user $ID$.", "placeholders": { @@ -12412,5 +12431,53 @@ }, "whyAmISeeingThis": { "message": "Why am I seeing this?" + }, + "youHaveBitwardenPremium": { + "message": "You have Bitwarden Premium" + }, + "viewAndManagePremiumSubscription": { + "message": "View and manage your Premium subscription" + }, + "youNeedToUpdateLicenseFile": { + "message": "You'll need to update your license file" + }, + "youNeedToUpdateLicenseFileDate": { + "message": "$DATE$.", + "placeholders": { + "date": { + "content": "$1", + "example": "June 12, 2026" + } + } + }, + "uploadLicenseFile": { + "message": "Upload license file" + }, + "uploadYourLicenseFile": { + "message": "Upload your license file" + }, + "uploadYourPremiumLicenseFile": { + "message": "Upload your Premium license file" + }, + "uploadLicenseFileDesc": { + "message": "Your license file name will be similar to: $FILE_NAME$", + "placeholders": { + "file_name": { + "content": "$1", + "example": "bitwarden_license.json" + } + } + }, + "alreadyHaveSubscriptionQuestion": { + "message": "Already have a subscription?" + }, + "alreadyHaveSubscriptionSelfHostedMessage": { + "message": "Open the subscription page on your Bitwarden cloud account and download your license file. Then return to this screen and upload it below." + }, + "viewAllPlans": { + "message": "View all plans" + }, + "planDescPremium": { + "message": "Complete online security" } } diff --git a/apps/web/src/locales/mr/messages.json b/apps/web/src/locales/mr/messages.json index 2ead65c89bd..b51c66468c3 100644 --- a/apps/web/src/locales/mr/messages.json +++ b/apps/web/src/locales/mr/messages.json @@ -1969,8 +1969,13 @@ "encExportAccountWarningDesc": { "message": "Account encryption keys are unique to each Bitwarden user account, so you can't import an encrypted export into a different account." }, - "export": { - "message": "Export" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, "exportFrom": { "message": "Export from" @@ -2297,8 +2302,13 @@ "tools": { "message": "Tools" }, - "import": { - "message": "Import" + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "importData": { "message": "Import data" @@ -3283,6 +3293,9 @@ "launchCloudSubscription": { "message": "Launch Cloud Subscription" }, + "launchCloudSubscriptionSentenceCase": { + "message": "Launch cloud subscription" + }, "storage": { "message": "Storage" }, @@ -4198,6 +4211,12 @@ } } }, + "userAcceptedTransfer": { + "message": "Accepted transfer to organization ownership." + }, + "userDeclinedTransfer": { + "message": "Revoked for declining transfer to organization ownership." + }, "invitedUserId": { "message": "Invited user $ID$.", "placeholders": { @@ -12412,5 +12431,53 @@ }, "whyAmISeeingThis": { "message": "Why am I seeing this?" + }, + "youHaveBitwardenPremium": { + "message": "You have Bitwarden Premium" + }, + "viewAndManagePremiumSubscription": { + "message": "View and manage your Premium subscription" + }, + "youNeedToUpdateLicenseFile": { + "message": "You'll need to update your license file" + }, + "youNeedToUpdateLicenseFileDate": { + "message": "$DATE$.", + "placeholders": { + "date": { + "content": "$1", + "example": "June 12, 2026" + } + } + }, + "uploadLicenseFile": { + "message": "Upload license file" + }, + "uploadYourLicenseFile": { + "message": "Upload your license file" + }, + "uploadYourPremiumLicenseFile": { + "message": "Upload your Premium license file" + }, + "uploadLicenseFileDesc": { + "message": "Your license file name will be similar to: $FILE_NAME$", + "placeholders": { + "file_name": { + "content": "$1", + "example": "bitwarden_license.json" + } + } + }, + "alreadyHaveSubscriptionQuestion": { + "message": "Already have a subscription?" + }, + "alreadyHaveSubscriptionSelfHostedMessage": { + "message": "Open the subscription page on your Bitwarden cloud account and download your license file. Then return to this screen and upload it below." + }, + "viewAllPlans": { + "message": "View all plans" + }, + "planDescPremium": { + "message": "Complete online security" } } diff --git a/apps/web/src/locales/my/messages.json b/apps/web/src/locales/my/messages.json index d84c2ae7b30..f570d355369 100644 --- a/apps/web/src/locales/my/messages.json +++ b/apps/web/src/locales/my/messages.json @@ -1969,8 +1969,13 @@ "encExportAccountWarningDesc": { "message": "Account encryption keys are unique to each Bitwarden user account, so you can't import an encrypted export into a different account." }, - "export": { - "message": "Export" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, "exportFrom": { "message": "Export from" @@ -2297,8 +2302,13 @@ "tools": { "message": "Tools" }, - "import": { - "message": "Import" + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "importData": { "message": "Import data" @@ -3283,6 +3293,9 @@ "launchCloudSubscription": { "message": "Launch Cloud Subscription" }, + "launchCloudSubscriptionSentenceCase": { + "message": "Launch cloud subscription" + }, "storage": { "message": "Storage" }, @@ -4198,6 +4211,12 @@ } } }, + "userAcceptedTransfer": { + "message": "Accepted transfer to organization ownership." + }, + "userDeclinedTransfer": { + "message": "Revoked for declining transfer to organization ownership." + }, "invitedUserId": { "message": "Invited user $ID$.", "placeholders": { @@ -12412,5 +12431,53 @@ }, "whyAmISeeingThis": { "message": "Why am I seeing this?" + }, + "youHaveBitwardenPremium": { + "message": "You have Bitwarden Premium" + }, + "viewAndManagePremiumSubscription": { + "message": "View and manage your Premium subscription" + }, + "youNeedToUpdateLicenseFile": { + "message": "You'll need to update your license file" + }, + "youNeedToUpdateLicenseFileDate": { + "message": "$DATE$.", + "placeholders": { + "date": { + "content": "$1", + "example": "June 12, 2026" + } + } + }, + "uploadLicenseFile": { + "message": "Upload license file" + }, + "uploadYourLicenseFile": { + "message": "Upload your license file" + }, + "uploadYourPremiumLicenseFile": { + "message": "Upload your Premium license file" + }, + "uploadLicenseFileDesc": { + "message": "Your license file name will be similar to: $FILE_NAME$", + "placeholders": { + "file_name": { + "content": "$1", + "example": "bitwarden_license.json" + } + } + }, + "alreadyHaveSubscriptionQuestion": { + "message": "Already have a subscription?" + }, + "alreadyHaveSubscriptionSelfHostedMessage": { + "message": "Open the subscription page on your Bitwarden cloud account and download your license file. Then return to this screen and upload it below." + }, + "viewAllPlans": { + "message": "View all plans" + }, + "planDescPremium": { + "message": "Complete online security" } } diff --git a/apps/web/src/locales/nb/messages.json b/apps/web/src/locales/nb/messages.json index 34521b2f58c..dd76b11f8be 100644 --- a/apps/web/src/locales/nb/messages.json +++ b/apps/web/src/locales/nb/messages.json @@ -1969,8 +1969,13 @@ "encExportAccountWarningDesc": { "message": "Kontokrypteringsnøkler er unike for hver Bitwarden sin brukerkonto, og du kan ikke importere en kryptert eksport til en annen konto." }, - "export": { - "message": "Eksporter" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, "exportFrom": { "message": "Eksport fra" @@ -2297,8 +2302,13 @@ "tools": { "message": "Verktøy" }, - "import": { - "message": "Import" + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "importData": { "message": "Importer data" @@ -3283,6 +3293,9 @@ "launchCloudSubscription": { "message": "Start Sky-abonnement" }, + "launchCloudSubscriptionSentenceCase": { + "message": "Launch cloud subscription" + }, "storage": { "message": "Lagring" }, @@ -4198,6 +4211,12 @@ } } }, + "userAcceptedTransfer": { + "message": "Accepted transfer to organization ownership." + }, + "userDeclinedTransfer": { + "message": "Revoked for declining transfer to organization ownership." + }, "invitedUserId": { "message": "Inviterte brukeren $ID$.", "placeholders": { @@ -12412,5 +12431,53 @@ }, "whyAmISeeingThis": { "message": "Why am I seeing this?" + }, + "youHaveBitwardenPremium": { + "message": "You have Bitwarden Premium" + }, + "viewAndManagePremiumSubscription": { + "message": "View and manage your Premium subscription" + }, + "youNeedToUpdateLicenseFile": { + "message": "You'll need to update your license file" + }, + "youNeedToUpdateLicenseFileDate": { + "message": "$DATE$.", + "placeholders": { + "date": { + "content": "$1", + "example": "June 12, 2026" + } + } + }, + "uploadLicenseFile": { + "message": "Upload license file" + }, + "uploadYourLicenseFile": { + "message": "Upload your license file" + }, + "uploadYourPremiumLicenseFile": { + "message": "Upload your Premium license file" + }, + "uploadLicenseFileDesc": { + "message": "Your license file name will be similar to: $FILE_NAME$", + "placeholders": { + "file_name": { + "content": "$1", + "example": "bitwarden_license.json" + } + } + }, + "alreadyHaveSubscriptionQuestion": { + "message": "Already have a subscription?" + }, + "alreadyHaveSubscriptionSelfHostedMessage": { + "message": "Open the subscription page on your Bitwarden cloud account and download your license file. Then return to this screen and upload it below." + }, + "viewAllPlans": { + "message": "View all plans" + }, + "planDescPremium": { + "message": "Complete online security" } } diff --git a/apps/web/src/locales/ne/messages.json b/apps/web/src/locales/ne/messages.json index 17118cef29b..cfd5c682a99 100644 --- a/apps/web/src/locales/ne/messages.json +++ b/apps/web/src/locales/ne/messages.json @@ -1969,8 +1969,13 @@ "encExportAccountWarningDesc": { "message": "Account encryption keys are unique to each Bitwarden user account, so you can't import an encrypted export into a different account." }, - "export": { - "message": "Export" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, "exportFrom": { "message": "Export from" @@ -2297,8 +2302,13 @@ "tools": { "message": "Tools" }, - "import": { - "message": "Import" + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "importData": { "message": "Import data" @@ -3283,6 +3293,9 @@ "launchCloudSubscription": { "message": "Launch Cloud Subscription" }, + "launchCloudSubscriptionSentenceCase": { + "message": "Launch cloud subscription" + }, "storage": { "message": "Storage" }, @@ -4198,6 +4211,12 @@ } } }, + "userAcceptedTransfer": { + "message": "Accepted transfer to organization ownership." + }, + "userDeclinedTransfer": { + "message": "Revoked for declining transfer to organization ownership." + }, "invitedUserId": { "message": "Invited user $ID$.", "placeholders": { @@ -12412,5 +12431,53 @@ }, "whyAmISeeingThis": { "message": "Why am I seeing this?" + }, + "youHaveBitwardenPremium": { + "message": "You have Bitwarden Premium" + }, + "viewAndManagePremiumSubscription": { + "message": "View and manage your Premium subscription" + }, + "youNeedToUpdateLicenseFile": { + "message": "You'll need to update your license file" + }, + "youNeedToUpdateLicenseFileDate": { + "message": "$DATE$.", + "placeholders": { + "date": { + "content": "$1", + "example": "June 12, 2026" + } + } + }, + "uploadLicenseFile": { + "message": "Upload license file" + }, + "uploadYourLicenseFile": { + "message": "Upload your license file" + }, + "uploadYourPremiumLicenseFile": { + "message": "Upload your Premium license file" + }, + "uploadLicenseFileDesc": { + "message": "Your license file name will be similar to: $FILE_NAME$", + "placeholders": { + "file_name": { + "content": "$1", + "example": "bitwarden_license.json" + } + } + }, + "alreadyHaveSubscriptionQuestion": { + "message": "Already have a subscription?" + }, + "alreadyHaveSubscriptionSelfHostedMessage": { + "message": "Open the subscription page on your Bitwarden cloud account and download your license file. Then return to this screen and upload it below." + }, + "viewAllPlans": { + "message": "View all plans" + }, + "planDescPremium": { + "message": "Complete online security" } } diff --git a/apps/web/src/locales/nl/messages.json b/apps/web/src/locales/nl/messages.json index d339f1887e7..61538a1bb2a 100644 --- a/apps/web/src/locales/nl/messages.json +++ b/apps/web/src/locales/nl/messages.json @@ -1969,8 +1969,13 @@ "encExportAccountWarningDesc": { "message": "Encryptiesleutels zijn uniek voor elk Bitwarden-gebruikersaccount, je kun kunt een versleutelde niet in een ander account importeren." }, - "export": { - "message": "Exporteren" + "exportNoun": { + "message": "Exporteren", + "description": "The noun form of the word Export" + }, + "exportVerb": { + "message": "Exporteren", + "description": "The verb form of the word Export" }, "exportFrom": { "message": "Exporteren vanuit" @@ -2297,8 +2302,13 @@ "tools": { "message": "Hulpmiddelen" }, - "import": { - "message": "Importeren" + "importNoun": { + "message": "Importeren", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Importeren", + "description": "The verb form of the word Import" }, "importData": { "message": "Gegevens importeren" @@ -3283,6 +3293,9 @@ "launchCloudSubscription": { "message": "Cloudabonnement starten" }, + "launchCloudSubscriptionSentenceCase": { + "message": "Cloudabonnement starten" + }, "storage": { "message": "Opslagruimte" }, @@ -4198,6 +4211,12 @@ } } }, + "userAcceptedTransfer": { + "message": "Overdracht naar organisatie-eigendom geaccepteerd." + }, + "userDeclinedTransfer": { + "message": "Ingetrokken wegens afwijzen van overdracht naar organisatie-eigendom." + }, "invitedUserId": { "message": "Gebruiker uitgenodigd $ID$.", "placeholders": { @@ -12412,5 +12431,53 @@ }, "whyAmISeeingThis": { "message": "Why am I seeing this?" + }, + "youHaveBitwardenPremium": { + "message": "Je hebt Bitwarden Premium" + }, + "viewAndManagePremiumSubscription": { + "message": "Premium-abonnement bekijken en beheren" + }, + "youNeedToUpdateLicenseFile": { + "message": "Je moet je licentiebestand bijwerken" + }, + "youNeedToUpdateLicenseFileDate": { + "message": "$DATE$.", + "placeholders": { + "date": { + "content": "$1", + "example": "June 12, 2026" + } + } + }, + "uploadLicenseFile": { + "message": "Licentiebestand uploaden" + }, + "uploadYourLicenseFile": { + "message": "Uploadt je licentiebestand" + }, + "uploadYourPremiumLicenseFile": { + "message": "Uploadt je Premium-licentiebestand" + }, + "uploadLicenseFileDesc": { + "message": "De naam van je licensiebestand lijkt op: $FILE_NAME$", + "placeholders": { + "file_name": { + "content": "$1", + "example": "bitwarden_license.json" + } + } + }, + "alreadyHaveSubscriptionQuestion": { + "message": "Heb je al een abonnement?" + }, + "alreadyHaveSubscriptionSelfHostedMessage": { + "message": "Open de abonnementspagina op je Bitwarden-cloudaccount en download je licentiebestand. Ga daarna terug naar dit scherm en upload het hieronder." + }, + "viewAllPlans": { + "message": "Alle abonnementen bekijken" + }, + "planDescPremium": { + "message": "Online beveiliging voltooien" } } diff --git a/apps/web/src/locales/nn/messages.json b/apps/web/src/locales/nn/messages.json index 0fba039741e..d7cfdd21d2f 100644 --- a/apps/web/src/locales/nn/messages.json +++ b/apps/web/src/locales/nn/messages.json @@ -1969,8 +1969,13 @@ "encExportAccountWarningDesc": { "message": "Account encryption keys are unique to each Bitwarden user account, so you can't import an encrypted export into a different account." }, - "export": { - "message": "Export" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, "exportFrom": { "message": "Export from" @@ -2297,8 +2302,13 @@ "tools": { "message": "Tools" }, - "import": { - "message": "Import" + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "importData": { "message": "Import data" @@ -3283,6 +3293,9 @@ "launchCloudSubscription": { "message": "Launch Cloud Subscription" }, + "launchCloudSubscriptionSentenceCase": { + "message": "Launch cloud subscription" + }, "storage": { "message": "Storage" }, @@ -4198,6 +4211,12 @@ } } }, + "userAcceptedTransfer": { + "message": "Accepted transfer to organization ownership." + }, + "userDeclinedTransfer": { + "message": "Revoked for declining transfer to organization ownership." + }, "invitedUserId": { "message": "Invited user $ID$.", "placeholders": { @@ -12412,5 +12431,53 @@ }, "whyAmISeeingThis": { "message": "Why am I seeing this?" + }, + "youHaveBitwardenPremium": { + "message": "You have Bitwarden Premium" + }, + "viewAndManagePremiumSubscription": { + "message": "View and manage your Premium subscription" + }, + "youNeedToUpdateLicenseFile": { + "message": "You'll need to update your license file" + }, + "youNeedToUpdateLicenseFileDate": { + "message": "$DATE$.", + "placeholders": { + "date": { + "content": "$1", + "example": "June 12, 2026" + } + } + }, + "uploadLicenseFile": { + "message": "Upload license file" + }, + "uploadYourLicenseFile": { + "message": "Upload your license file" + }, + "uploadYourPremiumLicenseFile": { + "message": "Upload your Premium license file" + }, + "uploadLicenseFileDesc": { + "message": "Your license file name will be similar to: $FILE_NAME$", + "placeholders": { + "file_name": { + "content": "$1", + "example": "bitwarden_license.json" + } + } + }, + "alreadyHaveSubscriptionQuestion": { + "message": "Already have a subscription?" + }, + "alreadyHaveSubscriptionSelfHostedMessage": { + "message": "Open the subscription page on your Bitwarden cloud account and download your license file. Then return to this screen and upload it below." + }, + "viewAllPlans": { + "message": "View all plans" + }, + "planDescPremium": { + "message": "Complete online security" } } diff --git a/apps/web/src/locales/or/messages.json b/apps/web/src/locales/or/messages.json index d84c2ae7b30..f570d355369 100644 --- a/apps/web/src/locales/or/messages.json +++ b/apps/web/src/locales/or/messages.json @@ -1969,8 +1969,13 @@ "encExportAccountWarningDesc": { "message": "Account encryption keys are unique to each Bitwarden user account, so you can't import an encrypted export into a different account." }, - "export": { - "message": "Export" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, "exportFrom": { "message": "Export from" @@ -2297,8 +2302,13 @@ "tools": { "message": "Tools" }, - "import": { - "message": "Import" + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "importData": { "message": "Import data" @@ -3283,6 +3293,9 @@ "launchCloudSubscription": { "message": "Launch Cloud Subscription" }, + "launchCloudSubscriptionSentenceCase": { + "message": "Launch cloud subscription" + }, "storage": { "message": "Storage" }, @@ -4198,6 +4211,12 @@ } } }, + "userAcceptedTransfer": { + "message": "Accepted transfer to organization ownership." + }, + "userDeclinedTransfer": { + "message": "Revoked for declining transfer to organization ownership." + }, "invitedUserId": { "message": "Invited user $ID$.", "placeholders": { @@ -12412,5 +12431,53 @@ }, "whyAmISeeingThis": { "message": "Why am I seeing this?" + }, + "youHaveBitwardenPremium": { + "message": "You have Bitwarden Premium" + }, + "viewAndManagePremiumSubscription": { + "message": "View and manage your Premium subscription" + }, + "youNeedToUpdateLicenseFile": { + "message": "You'll need to update your license file" + }, + "youNeedToUpdateLicenseFileDate": { + "message": "$DATE$.", + "placeholders": { + "date": { + "content": "$1", + "example": "June 12, 2026" + } + } + }, + "uploadLicenseFile": { + "message": "Upload license file" + }, + "uploadYourLicenseFile": { + "message": "Upload your license file" + }, + "uploadYourPremiumLicenseFile": { + "message": "Upload your Premium license file" + }, + "uploadLicenseFileDesc": { + "message": "Your license file name will be similar to: $FILE_NAME$", + "placeholders": { + "file_name": { + "content": "$1", + "example": "bitwarden_license.json" + } + } + }, + "alreadyHaveSubscriptionQuestion": { + "message": "Already have a subscription?" + }, + "alreadyHaveSubscriptionSelfHostedMessage": { + "message": "Open the subscription page on your Bitwarden cloud account and download your license file. Then return to this screen and upload it below." + }, + "viewAllPlans": { + "message": "View all plans" + }, + "planDescPremium": { + "message": "Complete online security" } } diff --git a/apps/web/src/locales/pl/messages.json b/apps/web/src/locales/pl/messages.json index ca0aee0f039..93843ca45b7 100644 --- a/apps/web/src/locales/pl/messages.json +++ b/apps/web/src/locales/pl/messages.json @@ -1969,8 +1969,13 @@ "encExportAccountWarningDesc": { "message": "Klucze szyfrowania konta są unikalne dla każdego użytkownika Bitwarden, więc nie możesz zaimportować zaszyfrowanego pliku eksportu na inne konto." }, - "export": { - "message": "Eksportuj" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, "exportFrom": { "message": "Eksportuj z" @@ -2297,8 +2302,13 @@ "tools": { "message": "Narzędzia" }, - "import": { - "message": "Import" + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "importData": { "message": "Importuj dane" @@ -3283,6 +3293,9 @@ "launchCloudSubscription": { "message": "Uruchom subskrypcję chmury" }, + "launchCloudSubscriptionSentenceCase": { + "message": "Launch cloud subscription" + }, "storage": { "message": "Przestrzeń" }, @@ -4198,6 +4211,12 @@ } } }, + "userAcceptedTransfer": { + "message": "Accepted transfer to organization ownership." + }, + "userDeclinedTransfer": { + "message": "Revoked for declining transfer to organization ownership." + }, "invitedUserId": { "message": "Użytkownik $ID$ został zaproszony.", "placeholders": { @@ -12412,5 +12431,53 @@ }, "whyAmISeeingThis": { "message": "Why am I seeing this?" + }, + "youHaveBitwardenPremium": { + "message": "You have Bitwarden Premium" + }, + "viewAndManagePremiumSubscription": { + "message": "View and manage your Premium subscription" + }, + "youNeedToUpdateLicenseFile": { + "message": "You'll need to update your license file" + }, + "youNeedToUpdateLicenseFileDate": { + "message": "$DATE$.", + "placeholders": { + "date": { + "content": "$1", + "example": "June 12, 2026" + } + } + }, + "uploadLicenseFile": { + "message": "Upload license file" + }, + "uploadYourLicenseFile": { + "message": "Upload your license file" + }, + "uploadYourPremiumLicenseFile": { + "message": "Upload your Premium license file" + }, + "uploadLicenseFileDesc": { + "message": "Your license file name will be similar to: $FILE_NAME$", + "placeholders": { + "file_name": { + "content": "$1", + "example": "bitwarden_license.json" + } + } + }, + "alreadyHaveSubscriptionQuestion": { + "message": "Already have a subscription?" + }, + "alreadyHaveSubscriptionSelfHostedMessage": { + "message": "Open the subscription page on your Bitwarden cloud account and download your license file. Then return to this screen and upload it below." + }, + "viewAllPlans": { + "message": "View all plans" + }, + "planDescPremium": { + "message": "Complete online security" } } diff --git a/apps/web/src/locales/pt_BR/messages.json b/apps/web/src/locales/pt_BR/messages.json index 959de6d0f2a..4ca2020c7b1 100644 --- a/apps/web/src/locales/pt_BR/messages.json +++ b/apps/web/src/locales/pt_BR/messages.json @@ -1969,8 +1969,13 @@ "encExportAccountWarningDesc": { "message": "Chaves de criptografia da conta são únicas para cada conta de usuário do Bitwarden, então você não pode importar uma exportação criptografada para uma conta diferente." }, - "export": { - "message": "Exportar" + "exportNoun": { + "message": "Exportação", + "description": "The noun form of the word Export" + }, + "exportVerb": { + "message": "Exportar", + "description": "The verb form of the word Export" }, "exportFrom": { "message": "Exportar de" @@ -2297,8 +2302,13 @@ "tools": { "message": "Ferramentas" }, - "import": { - "message": "Importar" + "importNoun": { + "message": "Importação", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Importar", + "description": "The verb form of the word Import" }, "importData": { "message": "Importar dados" @@ -3283,6 +3293,9 @@ "launchCloudSubscription": { "message": "Iniciar Assinatura na Nuvem" }, + "launchCloudSubscriptionSentenceCase": { + "message": "Launch cloud subscription" + }, "storage": { "message": "Armazenamento" }, @@ -4198,6 +4211,12 @@ } } }, + "userAcceptedTransfer": { + "message": "Accepted transfer to organization ownership." + }, + "userDeclinedTransfer": { + "message": "Revoked for declining transfer to organization ownership." + }, "invitedUserId": { "message": "Convidou o usuário $ID$.", "placeholders": { @@ -12412,5 +12431,53 @@ }, "whyAmISeeingThis": { "message": "Por que estou vendo isso?" + }, + "youHaveBitwardenPremium": { + "message": "Você tem o Bitwarden Premium" + }, + "viewAndManagePremiumSubscription": { + "message": "Veja e gerencie sua assinatura Premium" + }, + "youNeedToUpdateLicenseFile": { + "message": "Você precisará atualizar seu arquivo de licença" + }, + "youNeedToUpdateLicenseFileDate": { + "message": "$DATE$.", + "placeholders": { + "date": { + "content": "$1", + "example": "June 12, 2026" + } + } + }, + "uploadLicenseFile": { + "message": "Enviar arquivo de licença" + }, + "uploadYourLicenseFile": { + "message": "Envie seu arquivo de licença" + }, + "uploadYourPremiumLicenseFile": { + "message": "Envie seu arquivo de licença Premium" + }, + "uploadLicenseFileDesc": { + "message": "O nome do seu arquivo de licença será parecido com: $FILE_NAME$", + "placeholders": { + "file_name": { + "content": "$1", + "example": "bitwarden_license.json" + } + } + }, + "alreadyHaveSubscriptionQuestion": { + "message": "Já possui uma assinatura?" + }, + "alreadyHaveSubscriptionSelfHostedMessage": { + "message": "Abra a página de assinatura na sua conta da nuvem do Bitwarden e baixe o arquivo da sua licença. E então, volte nesta tela e o envie abaixo." + }, + "viewAllPlans": { + "message": "Ver todos os planos" + }, + "planDescPremium": { + "message": "Segurança on-line completa" } } diff --git a/apps/web/src/locales/pt_PT/messages.json b/apps/web/src/locales/pt_PT/messages.json index 0810368879c..ae80280caed 100644 --- a/apps/web/src/locales/pt_PT/messages.json +++ b/apps/web/src/locales/pt_PT/messages.json @@ -1969,8 +1969,13 @@ "encExportAccountWarningDesc": { "message": "As chaves de encriptação da conta são únicas para cada conta de utilizador Bitwarden, pelo que não é possível importar uma exportação encriptada para uma conta diferente." }, - "export": { - "message": "Exportar" + "exportNoun": { + "message": "Exportação", + "description": "The noun form of the word Export" + }, + "exportVerb": { + "message": "Exportar", + "description": "The verb form of the word Export" }, "exportFrom": { "message": "Exportar de" @@ -2297,8 +2302,13 @@ "tools": { "message": "Ferramentas" }, - "import": { - "message": "Importar" + "importNoun": { + "message": "Importação", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Importar", + "description": "The verb form of the word Import" }, "importData": { "message": "Importar dados" @@ -3283,6 +3293,9 @@ "launchCloudSubscription": { "message": "Iniciar subscrição na nuvem" }, + "launchCloudSubscriptionSentenceCase": { + "message": "Iniciar subscrição na nuvem" + }, "storage": { "message": "Armazenamento" }, @@ -4198,6 +4211,12 @@ } } }, + "userAcceptedTransfer": { + "message": "Accepted transfer to organization ownership." + }, + "userDeclinedTransfer": { + "message": "Revoked for declining transfer to organization ownership." + }, "invitedUserId": { "message": "Utilizador $ID$ convidado.", "placeholders": { @@ -12412,5 +12431,53 @@ }, "whyAmISeeingThis": { "message": "Porque é que estou a ver isto?" + }, + "youHaveBitwardenPremium": { + "message": "Tem o Bitwarden Premium" + }, + "viewAndManagePremiumSubscription": { + "message": "Ver e gerir a sua subscrição Premium" + }, + "youNeedToUpdateLicenseFile": { + "message": "Terá de atualizar o seu ficheiro de licença" + }, + "youNeedToUpdateLicenseFileDate": { + "message": "$DATE$.", + "placeholders": { + "date": { + "content": "$1", + "example": "June 12, 2026" + } + } + }, + "uploadLicenseFile": { + "message": "Carregar ficheiro de licença" + }, + "uploadYourLicenseFile": { + "message": "Carregue o seu ficheiro de licença" + }, + "uploadYourPremiumLicenseFile": { + "message": "Carregue o seu ficheiro de licença Premium" + }, + "uploadLicenseFileDesc": { + "message": "O nome do seu ficheiro de licença será semelhante a: $FILE_NAME$", + "placeholders": { + "file_name": { + "content": "$1", + "example": "bitwarden_license.json" + } + } + }, + "alreadyHaveSubscriptionQuestion": { + "message": "Já tem uma subscrição?" + }, + "alreadyHaveSubscriptionSelfHostedMessage": { + "message": "Abra a página de subscrição da sua conta Bitwarden na cloud e transfira o ficheiro de licença. Depois, regresse a este ecrã e carregue-o abaixo." + }, + "viewAllPlans": { + "message": "Ver todos os planos" + }, + "planDescPremium": { + "message": "Segurança total online" } } diff --git a/apps/web/src/locales/ro/messages.json b/apps/web/src/locales/ro/messages.json index 2738efe1169..80ee2d5e9b6 100644 --- a/apps/web/src/locales/ro/messages.json +++ b/apps/web/src/locales/ro/messages.json @@ -1969,8 +1969,13 @@ "encExportAccountWarningDesc": { "message": "Cheile de criptare a contului sunt unice fiecărui cont de utilizator Bitwarden, astfel încât nu puteți importa un export criptat într-un cont diferit." }, - "export": { - "message": "Exportați" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, "exportFrom": { "message": "Export from" @@ -2297,8 +2302,13 @@ "tools": { "message": "Unelte" }, - "import": { - "message": "Import" + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "importData": { "message": "Importare date" @@ -3283,6 +3293,9 @@ "launchCloudSubscription": { "message": "Launch Cloud Subscription" }, + "launchCloudSubscriptionSentenceCase": { + "message": "Launch cloud subscription" + }, "storage": { "message": "Stocare" }, @@ -4198,6 +4211,12 @@ } } }, + "userAcceptedTransfer": { + "message": "Accepted transfer to organization ownership." + }, + "userDeclinedTransfer": { + "message": "Revoked for declining transfer to organization ownership." + }, "invitedUserId": { "message": "Utilizatorul $ID$ a fost invitat.", "placeholders": { @@ -12412,5 +12431,53 @@ }, "whyAmISeeingThis": { "message": "Why am I seeing this?" + }, + "youHaveBitwardenPremium": { + "message": "You have Bitwarden Premium" + }, + "viewAndManagePremiumSubscription": { + "message": "View and manage your Premium subscription" + }, + "youNeedToUpdateLicenseFile": { + "message": "You'll need to update your license file" + }, + "youNeedToUpdateLicenseFileDate": { + "message": "$DATE$.", + "placeholders": { + "date": { + "content": "$1", + "example": "June 12, 2026" + } + } + }, + "uploadLicenseFile": { + "message": "Upload license file" + }, + "uploadYourLicenseFile": { + "message": "Upload your license file" + }, + "uploadYourPremiumLicenseFile": { + "message": "Upload your Premium license file" + }, + "uploadLicenseFileDesc": { + "message": "Your license file name will be similar to: $FILE_NAME$", + "placeholders": { + "file_name": { + "content": "$1", + "example": "bitwarden_license.json" + } + } + }, + "alreadyHaveSubscriptionQuestion": { + "message": "Already have a subscription?" + }, + "alreadyHaveSubscriptionSelfHostedMessage": { + "message": "Open the subscription page on your Bitwarden cloud account and download your license file. Then return to this screen and upload it below." + }, + "viewAllPlans": { + "message": "View all plans" + }, + "planDescPremium": { + "message": "Complete online security" } } diff --git a/apps/web/src/locales/ru/messages.json b/apps/web/src/locales/ru/messages.json index e62657ee432..f82aa6d4417 100644 --- a/apps/web/src/locales/ru/messages.json +++ b/apps/web/src/locales/ru/messages.json @@ -1969,8 +1969,13 @@ "encExportAccountWarningDesc": { "message": "Ключи шифрования уникальны для каждой учетной записи Bitwarden, поэтому нельзя импортировать зашифрованное хранилище в другой аккаунт." }, - "export": { - "message": "Экспорт" + "exportNoun": { + "message": "Экспорт", + "description": "The noun form of the word Export" + }, + "exportVerb": { + "message": "Экспортировать", + "description": "The verb form of the word Export" }, "exportFrom": { "message": "Экспорт из" @@ -2297,8 +2302,13 @@ "tools": { "message": "Инструменты" }, - "import": { - "message": "Импорт" + "importNoun": { + "message": "Импорт", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Импортировать", + "description": "The verb form of the word Import" }, "importData": { "message": "Импорт данных" @@ -3283,6 +3293,9 @@ "launchCloudSubscription": { "message": "Запустить облачную подписку" }, + "launchCloudSubscriptionSentenceCase": { + "message": "Запустить облачную подписку" + }, "storage": { "message": "Хранилище" }, @@ -4198,6 +4211,12 @@ } } }, + "userAcceptedTransfer": { + "message": "Принятая передача в собственность организации." + }, + "userDeclinedTransfer": { + "message": "Отозван в связи с отказом в передаче в собственность организации." + }, "invitedUserId": { "message": "Приглашен пользователь $ID$.", "placeholders": { @@ -12412,5 +12431,53 @@ }, "whyAmISeeingThis": { "message": "Почему я это вижу?" + }, + "youHaveBitwardenPremium": { + "message": "У вас Bitwarden Премиум" + }, + "viewAndManagePremiumSubscription": { + "message": "Просматривайте свою премиум-подписку и управляйте ею" + }, + "youNeedToUpdateLicenseFile": { + "message": "Вам нужно будет обновить свой файл лицензии" + }, + "youNeedToUpdateLicenseFileDate": { + "message": "$DATE$.", + "placeholders": { + "date": { + "content": "$1", + "example": "June 12, 2026" + } + } + }, + "uploadLicenseFile": { + "message": "Загрузить файл лицензии" + }, + "uploadYourLicenseFile": { + "message": "Загрузите ваш файл лицензии" + }, + "uploadYourPremiumLicenseFile": { + "message": "Загрузите ваш файл лицензии Премиум" + }, + "uploadLicenseFileDesc": { + "message": "Название вашего файла лицензии будет похоже на $FILE_NAME$", + "placeholders": { + "file_name": { + "content": "$1", + "example": "bitwarden_license.json" + } + } + }, + "alreadyHaveSubscriptionQuestion": { + "message": "Уже есть подписка?" + }, + "alreadyHaveSubscriptionSelfHostedMessage": { + "message": "Откройте страницу подписки в своем аккаунте Bitwarden cloud и скачайте файл лицензии. Затем вернитесь на этот экран и загрузите его ниже." + }, + "viewAllPlans": { + "message": "Посмотреть все планы" + }, + "planDescPremium": { + "message": "Полная онлайн-защищенность" } } diff --git a/apps/web/src/locales/si/messages.json b/apps/web/src/locales/si/messages.json index e9ed3f035d3..e4166c07db2 100644 --- a/apps/web/src/locales/si/messages.json +++ b/apps/web/src/locales/si/messages.json @@ -1969,8 +1969,13 @@ "encExportAccountWarningDesc": { "message": "Account encryption keys are unique to each Bitwarden user account, so you can't import an encrypted export into a different account." }, - "export": { - "message": "Export" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, "exportFrom": { "message": "Export from" @@ -2297,8 +2302,13 @@ "tools": { "message": "Tools" }, - "import": { - "message": "Import" + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "importData": { "message": "Import data" @@ -3283,6 +3293,9 @@ "launchCloudSubscription": { "message": "Launch Cloud Subscription" }, + "launchCloudSubscriptionSentenceCase": { + "message": "Launch cloud subscription" + }, "storage": { "message": "Storage" }, @@ -4198,6 +4211,12 @@ } } }, + "userAcceptedTransfer": { + "message": "Accepted transfer to organization ownership." + }, + "userDeclinedTransfer": { + "message": "Revoked for declining transfer to organization ownership." + }, "invitedUserId": { "message": "Invited user $ID$.", "placeholders": { @@ -12412,5 +12431,53 @@ }, "whyAmISeeingThis": { "message": "Why am I seeing this?" + }, + "youHaveBitwardenPremium": { + "message": "You have Bitwarden Premium" + }, + "viewAndManagePremiumSubscription": { + "message": "View and manage your Premium subscription" + }, + "youNeedToUpdateLicenseFile": { + "message": "You'll need to update your license file" + }, + "youNeedToUpdateLicenseFileDate": { + "message": "$DATE$.", + "placeholders": { + "date": { + "content": "$1", + "example": "June 12, 2026" + } + } + }, + "uploadLicenseFile": { + "message": "Upload license file" + }, + "uploadYourLicenseFile": { + "message": "Upload your license file" + }, + "uploadYourPremiumLicenseFile": { + "message": "Upload your Premium license file" + }, + "uploadLicenseFileDesc": { + "message": "Your license file name will be similar to: $FILE_NAME$", + "placeholders": { + "file_name": { + "content": "$1", + "example": "bitwarden_license.json" + } + } + }, + "alreadyHaveSubscriptionQuestion": { + "message": "Already have a subscription?" + }, + "alreadyHaveSubscriptionSelfHostedMessage": { + "message": "Open the subscription page on your Bitwarden cloud account and download your license file. Then return to this screen and upload it below." + }, + "viewAllPlans": { + "message": "View all plans" + }, + "planDescPremium": { + "message": "Complete online security" } } diff --git a/apps/web/src/locales/sk/messages.json b/apps/web/src/locales/sk/messages.json index fc71b3caf00..6e36b52a098 100644 --- a/apps/web/src/locales/sk/messages.json +++ b/apps/web/src/locales/sk/messages.json @@ -1969,8 +1969,13 @@ "encExportAccountWarningDesc": { "message": "Šifrovacie kľúče účtu sú jedinečné pre každý používateľský účet Bitwarden, takže nemôžete importovať šifrovaný export do iného účtu." }, - "export": { - "message": "Exportovať" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, "exportFrom": { "message": "Exportovať z" @@ -2297,8 +2302,13 @@ "tools": { "message": "Nástroje" }, - "import": { - "message": "Import" + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "importData": { "message": "Import dát" @@ -3283,6 +3293,9 @@ "launchCloudSubscription": { "message": "Launch Cloud Subscription" }, + "launchCloudSubscriptionSentenceCase": { + "message": "Launch cloud subscription" + }, "storage": { "message": "Ukladací priestor" }, @@ -4198,6 +4211,12 @@ } } }, + "userAcceptedTransfer": { + "message": "Accepted transfer to organization ownership." + }, + "userDeclinedTransfer": { + "message": "Revoked for declining transfer to organization ownership." + }, "invitedUserId": { "message": "Používateľ $ID$ pozvaný.", "placeholders": { @@ -12412,5 +12431,53 @@ }, "whyAmISeeingThis": { "message": "Why am I seeing this?" + }, + "youHaveBitwardenPremium": { + "message": "You have Bitwarden Premium" + }, + "viewAndManagePremiumSubscription": { + "message": "View and manage your Premium subscription" + }, + "youNeedToUpdateLicenseFile": { + "message": "You'll need to update your license file" + }, + "youNeedToUpdateLicenseFileDate": { + "message": "$DATE$.", + "placeholders": { + "date": { + "content": "$1", + "example": "June 12, 2026" + } + } + }, + "uploadLicenseFile": { + "message": "Upload license file" + }, + "uploadYourLicenseFile": { + "message": "Upload your license file" + }, + "uploadYourPremiumLicenseFile": { + "message": "Upload your Premium license file" + }, + "uploadLicenseFileDesc": { + "message": "Your license file name will be similar to: $FILE_NAME$", + "placeholders": { + "file_name": { + "content": "$1", + "example": "bitwarden_license.json" + } + } + }, + "alreadyHaveSubscriptionQuestion": { + "message": "Already have a subscription?" + }, + "alreadyHaveSubscriptionSelfHostedMessage": { + "message": "Open the subscription page on your Bitwarden cloud account and download your license file. Then return to this screen and upload it below." + }, + "viewAllPlans": { + "message": "View all plans" + }, + "planDescPremium": { + "message": "Complete online security" } } diff --git a/apps/web/src/locales/sl/messages.json b/apps/web/src/locales/sl/messages.json index 0ff309c5e0e..1f880d87412 100644 --- a/apps/web/src/locales/sl/messages.json +++ b/apps/web/src/locales/sl/messages.json @@ -1969,8 +1969,13 @@ "encExportAccountWarningDesc": { "message": "Šifrirni ključ je vezan na posamezen Bitwardnov račun, zato šifriranega izvoza ne boste mogli uvoziti v drug račun." }, - "export": { - "message": "Izvozi" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, "exportFrom": { "message": "Export from" @@ -2297,8 +2302,13 @@ "tools": { "message": "Orodja" }, - "import": { - "message": "Import" + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "importData": { "message": "Uvozi podatke" @@ -3283,6 +3293,9 @@ "launchCloudSubscription": { "message": "Launch Cloud Subscription" }, + "launchCloudSubscriptionSentenceCase": { + "message": "Launch cloud subscription" + }, "storage": { "message": "Hramba" }, @@ -4198,6 +4211,12 @@ } } }, + "userAcceptedTransfer": { + "message": "Accepted transfer to organization ownership." + }, + "userDeclinedTransfer": { + "message": "Revoked for declining transfer to organization ownership." + }, "invitedUserId": { "message": "Invited user $ID$.", "placeholders": { @@ -12412,5 +12431,53 @@ }, "whyAmISeeingThis": { "message": "Why am I seeing this?" + }, + "youHaveBitwardenPremium": { + "message": "You have Bitwarden Premium" + }, + "viewAndManagePremiumSubscription": { + "message": "View and manage your Premium subscription" + }, + "youNeedToUpdateLicenseFile": { + "message": "You'll need to update your license file" + }, + "youNeedToUpdateLicenseFileDate": { + "message": "$DATE$.", + "placeholders": { + "date": { + "content": "$1", + "example": "June 12, 2026" + } + } + }, + "uploadLicenseFile": { + "message": "Upload license file" + }, + "uploadYourLicenseFile": { + "message": "Upload your license file" + }, + "uploadYourPremiumLicenseFile": { + "message": "Upload your Premium license file" + }, + "uploadLicenseFileDesc": { + "message": "Your license file name will be similar to: $FILE_NAME$", + "placeholders": { + "file_name": { + "content": "$1", + "example": "bitwarden_license.json" + } + } + }, + "alreadyHaveSubscriptionQuestion": { + "message": "Already have a subscription?" + }, + "alreadyHaveSubscriptionSelfHostedMessage": { + "message": "Open the subscription page on your Bitwarden cloud account and download your license file. Then return to this screen and upload it below." + }, + "viewAllPlans": { + "message": "View all plans" + }, + "planDescPremium": { + "message": "Complete online security" } } diff --git a/apps/web/src/locales/sr_CS/messages.json b/apps/web/src/locales/sr_CS/messages.json index aa5436ed321..3ebf0057bb9 100644 --- a/apps/web/src/locales/sr_CS/messages.json +++ b/apps/web/src/locales/sr_CS/messages.json @@ -1969,8 +1969,13 @@ "encExportAccountWarningDesc": { "message": "Account encryption keys are unique to each Bitwarden user account, so you can't import an encrypted export into a different account." }, - "export": { - "message": "Export" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, "exportFrom": { "message": "Export from" @@ -2297,8 +2302,13 @@ "tools": { "message": "Alati" }, - "import": { - "message": "Import" + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "importData": { "message": "Uvezi podatke" @@ -3283,6 +3293,9 @@ "launchCloudSubscription": { "message": "Launch Cloud Subscription" }, + "launchCloudSubscriptionSentenceCase": { + "message": "Launch cloud subscription" + }, "storage": { "message": "Storage" }, @@ -4198,6 +4211,12 @@ } } }, + "userAcceptedTransfer": { + "message": "Accepted transfer to organization ownership." + }, + "userDeclinedTransfer": { + "message": "Revoked for declining transfer to organization ownership." + }, "invitedUserId": { "message": "Invited user $ID$.", "placeholders": { @@ -12412,5 +12431,53 @@ }, "whyAmISeeingThis": { "message": "Why am I seeing this?" + }, + "youHaveBitwardenPremium": { + "message": "You have Bitwarden Premium" + }, + "viewAndManagePremiumSubscription": { + "message": "View and manage your Premium subscription" + }, + "youNeedToUpdateLicenseFile": { + "message": "You'll need to update your license file" + }, + "youNeedToUpdateLicenseFileDate": { + "message": "$DATE$.", + "placeholders": { + "date": { + "content": "$1", + "example": "June 12, 2026" + } + } + }, + "uploadLicenseFile": { + "message": "Upload license file" + }, + "uploadYourLicenseFile": { + "message": "Upload your license file" + }, + "uploadYourPremiumLicenseFile": { + "message": "Upload your Premium license file" + }, + "uploadLicenseFileDesc": { + "message": "Your license file name will be similar to: $FILE_NAME$", + "placeholders": { + "file_name": { + "content": "$1", + "example": "bitwarden_license.json" + } + } + }, + "alreadyHaveSubscriptionQuestion": { + "message": "Already have a subscription?" + }, + "alreadyHaveSubscriptionSelfHostedMessage": { + "message": "Open the subscription page on your Bitwarden cloud account and download your license file. Then return to this screen and upload it below." + }, + "viewAllPlans": { + "message": "View all plans" + }, + "planDescPremium": { + "message": "Complete online security" } } diff --git a/apps/web/src/locales/sr_CY/messages.json b/apps/web/src/locales/sr_CY/messages.json index 152ec208e16..b7934ec62fb 100644 --- a/apps/web/src/locales/sr_CY/messages.json +++ b/apps/web/src/locales/sr_CY/messages.json @@ -1969,8 +1969,13 @@ "encExportAccountWarningDesc": { "message": "Кључеви за шифровање налога су јединствени за сваки Bitwarden кориснички налог, тако да не можете да увезете шифровани извоз на други налог." }, - "export": { - "message": "Извези" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, "exportFrom": { "message": "Извоз од" @@ -2297,8 +2302,13 @@ "tools": { "message": "Алатке" }, - "import": { - "message": "Увоз" + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "importData": { "message": "Увези податке" @@ -3283,6 +3293,9 @@ "launchCloudSubscription": { "message": "Покренути Cloud претплату" }, + "launchCloudSubscriptionSentenceCase": { + "message": "Launch cloud subscription" + }, "storage": { "message": "Складиште" }, @@ -4198,6 +4211,12 @@ } } }, + "userAcceptedTransfer": { + "message": "Accepted transfer to organization ownership." + }, + "userDeclinedTransfer": { + "message": "Revoked for declining transfer to organization ownership." + }, "invitedUserId": { "message": "Корисник $ID$ позван.", "placeholders": { @@ -12412,5 +12431,53 @@ }, "whyAmISeeingThis": { "message": "Why am I seeing this?" + }, + "youHaveBitwardenPremium": { + "message": "You have Bitwarden Premium" + }, + "viewAndManagePremiumSubscription": { + "message": "View and manage your Premium subscription" + }, + "youNeedToUpdateLicenseFile": { + "message": "You'll need to update your license file" + }, + "youNeedToUpdateLicenseFileDate": { + "message": "$DATE$.", + "placeholders": { + "date": { + "content": "$1", + "example": "June 12, 2026" + } + } + }, + "uploadLicenseFile": { + "message": "Upload license file" + }, + "uploadYourLicenseFile": { + "message": "Upload your license file" + }, + "uploadYourPremiumLicenseFile": { + "message": "Upload your Premium license file" + }, + "uploadLicenseFileDesc": { + "message": "Your license file name will be similar to: $FILE_NAME$", + "placeholders": { + "file_name": { + "content": "$1", + "example": "bitwarden_license.json" + } + } + }, + "alreadyHaveSubscriptionQuestion": { + "message": "Already have a subscription?" + }, + "alreadyHaveSubscriptionSelfHostedMessage": { + "message": "Open the subscription page on your Bitwarden cloud account and download your license file. Then return to this screen and upload it below." + }, + "viewAllPlans": { + "message": "View all plans" + }, + "planDescPremium": { + "message": "Complete online security" } } diff --git a/apps/web/src/locales/sv/messages.json b/apps/web/src/locales/sv/messages.json index db44953b83f..c428b1561ed 100644 --- a/apps/web/src/locales/sv/messages.json +++ b/apps/web/src/locales/sv/messages.json @@ -1969,8 +1969,13 @@ "encExportAccountWarningDesc": { "message": "Kypteringsnycklar är unika för varje Bitwarden-konto, så du kan inte importera en krypterad export till ett annat konto." }, - "export": { - "message": "Exportera" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "exportVerb": { + "message": "Exportera", + "description": "The verb form of the word Export" }, "exportFrom": { "message": "Exportera från" @@ -2297,8 +2302,13 @@ "tools": { "message": "Verktyg" }, - "import": { - "message": "Importera" + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Importera", + "description": "The verb form of the word Import" }, "importData": { "message": "Importera data" @@ -3283,6 +3293,9 @@ "launchCloudSubscription": { "message": "Starta molnprenumeration" }, + "launchCloudSubscriptionSentenceCase": { + "message": "Starta molnprenumeration" + }, "storage": { "message": "Lagring" }, @@ -4198,6 +4211,12 @@ } } }, + "userAcceptedTransfer": { + "message": "Godkände överföring till organisationens ägarskap." + }, + "userDeclinedTransfer": { + "message": "Återkallad för att överföring till organisationens ägarskap avböjdes." + }, "invitedUserId": { "message": "Bjöd in användaren $ID$.", "placeholders": { @@ -12412,5 +12431,53 @@ }, "whyAmISeeingThis": { "message": "Varför ser jag det här?" + }, + "youHaveBitwardenPremium": { + "message": "Du har Bitwarden Premium" + }, + "viewAndManagePremiumSubscription": { + "message": "Visa och hantera ditt Premium-abonnemang" + }, + "youNeedToUpdateLicenseFile": { + "message": "Du måste uppdatera din licensfil" + }, + "youNeedToUpdateLicenseFileDate": { + "message": "$DATE$.", + "placeholders": { + "date": { + "content": "$1", + "example": "June 12, 2026" + } + } + }, + "uploadLicenseFile": { + "message": "Ladda upp licensfil" + }, + "uploadYourLicenseFile": { + "message": "Ladda upp din licensfil" + }, + "uploadYourPremiumLicenseFile": { + "message": "Ladda upp din Premium-licensfil" + }, + "uploadLicenseFileDesc": { + "message": "Ditt licensfilnamn kommer att likna $FILE_NAME$", + "placeholders": { + "file_name": { + "content": "$1", + "example": "bitwarden_license.json" + } + } + }, + "alreadyHaveSubscriptionQuestion": { + "message": "Har du redan ett abonnemang?" + }, + "alreadyHaveSubscriptionSelfHostedMessage": { + "message": "Öppna prenumerationssidan på ditt Bitwarden-molnkonto och ladda ner din licensfil. Återvänd sedan till den här skärmen och ladda upp den nedan." + }, + "viewAllPlans": { + "message": "Visa alla planer" + }, + "planDescPremium": { + "message": "Komplett säkerhet online" } } diff --git a/apps/web/src/locales/ta/messages.json b/apps/web/src/locales/ta/messages.json index 5e1441bc316..5ad16f4e27e 100644 --- a/apps/web/src/locales/ta/messages.json +++ b/apps/web/src/locales/ta/messages.json @@ -1969,8 +1969,13 @@ "encExportAccountWarningDesc": { "message": "கணக்கு என்க்ரிப்ஷன் சாவிகள் ஒவ்வொரு Bitwarden பயனர் கணக்கிற்கும் தனித்துவமானவை, எனவே நீங்கள் என்க்ரிப்ட் செய்யப்பட்ட ஏற்றுமதியை வேறு கணக்கில் இறக்குமதி செய்ய முடியாது." }, - "export": { - "message": "ஏற்றுமதி" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, "exportFrom": { "message": "இதிலிருந்து ஏற்றுமதி" @@ -2297,8 +2302,13 @@ "tools": { "message": "கருவிகள்" }, - "import": { - "message": "Import" + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "importData": { "message": "தரவை இம்போர்ட் செய்யவும்" @@ -3283,6 +3293,9 @@ "launchCloudSubscription": { "message": "கிளவுட் சந்தாவைத் தொடங்கவும்" }, + "launchCloudSubscriptionSentenceCase": { + "message": "Launch cloud subscription" + }, "storage": { "message": "சேமிப்பகம்" }, @@ -4198,6 +4211,12 @@ } } }, + "userAcceptedTransfer": { + "message": "Accepted transfer to organization ownership." + }, + "userDeclinedTransfer": { + "message": "Revoked for declining transfer to organization ownership." + }, "invitedUserId": { "message": "பயனர் $ID$ அழைக்கப்பட்டார்.", "placeholders": { @@ -12412,5 +12431,53 @@ }, "whyAmISeeingThis": { "message": "Why am I seeing this?" + }, + "youHaveBitwardenPremium": { + "message": "You have Bitwarden Premium" + }, + "viewAndManagePremiumSubscription": { + "message": "View and manage your Premium subscription" + }, + "youNeedToUpdateLicenseFile": { + "message": "You'll need to update your license file" + }, + "youNeedToUpdateLicenseFileDate": { + "message": "$DATE$.", + "placeholders": { + "date": { + "content": "$1", + "example": "June 12, 2026" + } + } + }, + "uploadLicenseFile": { + "message": "Upload license file" + }, + "uploadYourLicenseFile": { + "message": "Upload your license file" + }, + "uploadYourPremiumLicenseFile": { + "message": "Upload your Premium license file" + }, + "uploadLicenseFileDesc": { + "message": "Your license file name will be similar to: $FILE_NAME$", + "placeholders": { + "file_name": { + "content": "$1", + "example": "bitwarden_license.json" + } + } + }, + "alreadyHaveSubscriptionQuestion": { + "message": "Already have a subscription?" + }, + "alreadyHaveSubscriptionSelfHostedMessage": { + "message": "Open the subscription page on your Bitwarden cloud account and download your license file. Then return to this screen and upload it below." + }, + "viewAllPlans": { + "message": "View all plans" + }, + "planDescPremium": { + "message": "Complete online security" } } diff --git a/apps/web/src/locales/te/messages.json b/apps/web/src/locales/te/messages.json index d84c2ae7b30..f570d355369 100644 --- a/apps/web/src/locales/te/messages.json +++ b/apps/web/src/locales/te/messages.json @@ -1969,8 +1969,13 @@ "encExportAccountWarningDesc": { "message": "Account encryption keys are unique to each Bitwarden user account, so you can't import an encrypted export into a different account." }, - "export": { - "message": "Export" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, "exportFrom": { "message": "Export from" @@ -2297,8 +2302,13 @@ "tools": { "message": "Tools" }, - "import": { - "message": "Import" + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "importData": { "message": "Import data" @@ -3283,6 +3293,9 @@ "launchCloudSubscription": { "message": "Launch Cloud Subscription" }, + "launchCloudSubscriptionSentenceCase": { + "message": "Launch cloud subscription" + }, "storage": { "message": "Storage" }, @@ -4198,6 +4211,12 @@ } } }, + "userAcceptedTransfer": { + "message": "Accepted transfer to organization ownership." + }, + "userDeclinedTransfer": { + "message": "Revoked for declining transfer to organization ownership." + }, "invitedUserId": { "message": "Invited user $ID$.", "placeholders": { @@ -12412,5 +12431,53 @@ }, "whyAmISeeingThis": { "message": "Why am I seeing this?" + }, + "youHaveBitwardenPremium": { + "message": "You have Bitwarden Premium" + }, + "viewAndManagePremiumSubscription": { + "message": "View and manage your Premium subscription" + }, + "youNeedToUpdateLicenseFile": { + "message": "You'll need to update your license file" + }, + "youNeedToUpdateLicenseFileDate": { + "message": "$DATE$.", + "placeholders": { + "date": { + "content": "$1", + "example": "June 12, 2026" + } + } + }, + "uploadLicenseFile": { + "message": "Upload license file" + }, + "uploadYourLicenseFile": { + "message": "Upload your license file" + }, + "uploadYourPremiumLicenseFile": { + "message": "Upload your Premium license file" + }, + "uploadLicenseFileDesc": { + "message": "Your license file name will be similar to: $FILE_NAME$", + "placeholders": { + "file_name": { + "content": "$1", + "example": "bitwarden_license.json" + } + } + }, + "alreadyHaveSubscriptionQuestion": { + "message": "Already have a subscription?" + }, + "alreadyHaveSubscriptionSelfHostedMessage": { + "message": "Open the subscription page on your Bitwarden cloud account and download your license file. Then return to this screen and upload it below." + }, + "viewAllPlans": { + "message": "View all plans" + }, + "planDescPremium": { + "message": "Complete online security" } } diff --git a/apps/web/src/locales/th/messages.json b/apps/web/src/locales/th/messages.json index a8b00018d8c..8efc54bf052 100644 --- a/apps/web/src/locales/th/messages.json +++ b/apps/web/src/locales/th/messages.json @@ -1969,8 +1969,13 @@ "encExportAccountWarningDesc": { "message": "คีย์การเข้ารหัสบัญชีจะไม่ซ้ำกันสำหรับบัญชีผู้ใช้ Bitwarden แต่ละบัญชี ดังนั้นคุณจึงไม่สามารถนำเข้าการส่งออกที่เข้ารหัสไปยังบัญชีอื่นได้" }, - "export": { - "message": "ส่งออก" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, "exportFrom": { "message": "Export from" @@ -2297,8 +2302,13 @@ "tools": { "message": "Tools" }, - "import": { - "message": "Import" + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "importData": { "message": "Import data" @@ -3283,6 +3293,9 @@ "launchCloudSubscription": { "message": "Launch Cloud Subscription" }, + "launchCloudSubscriptionSentenceCase": { + "message": "Launch cloud subscription" + }, "storage": { "message": "Storage" }, @@ -4198,6 +4211,12 @@ } } }, + "userAcceptedTransfer": { + "message": "Accepted transfer to organization ownership." + }, + "userDeclinedTransfer": { + "message": "Revoked for declining transfer to organization ownership." + }, "invitedUserId": { "message": "Invited user $ID$.", "placeholders": { @@ -12412,5 +12431,53 @@ }, "whyAmISeeingThis": { "message": "Why am I seeing this?" + }, + "youHaveBitwardenPremium": { + "message": "You have Bitwarden Premium" + }, + "viewAndManagePremiumSubscription": { + "message": "View and manage your Premium subscription" + }, + "youNeedToUpdateLicenseFile": { + "message": "You'll need to update your license file" + }, + "youNeedToUpdateLicenseFileDate": { + "message": "$DATE$.", + "placeholders": { + "date": { + "content": "$1", + "example": "June 12, 2026" + } + } + }, + "uploadLicenseFile": { + "message": "Upload license file" + }, + "uploadYourLicenseFile": { + "message": "Upload your license file" + }, + "uploadYourPremiumLicenseFile": { + "message": "Upload your Premium license file" + }, + "uploadLicenseFileDesc": { + "message": "Your license file name will be similar to: $FILE_NAME$", + "placeholders": { + "file_name": { + "content": "$1", + "example": "bitwarden_license.json" + } + } + }, + "alreadyHaveSubscriptionQuestion": { + "message": "Already have a subscription?" + }, + "alreadyHaveSubscriptionSelfHostedMessage": { + "message": "Open the subscription page on your Bitwarden cloud account and download your license file. Then return to this screen and upload it below." + }, + "viewAllPlans": { + "message": "View all plans" + }, + "planDescPremium": { + "message": "Complete online security" } } diff --git a/apps/web/src/locales/tr/messages.json b/apps/web/src/locales/tr/messages.json index 079557adf81..27e1740fca7 100644 --- a/apps/web/src/locales/tr/messages.json +++ b/apps/web/src/locales/tr/messages.json @@ -1969,8 +1969,13 @@ "encExportAccountWarningDesc": { "message": "Hesap şifreleme anahtarları her Bitwarden kullanıcı hesabı için farklıdır. Dolayısıyla şifrelenmiş bir dışa aktarmayı başka bir hesapta içe aktaramazsınız." }, - "export": { - "message": "Dışarı aktar" + "exportNoun": { + "message": "Dışa aktar", + "description": "The noun form of the word Export" + }, + "exportVerb": { + "message": "Dışa aktar", + "description": "The verb form of the word Export" }, "exportFrom": { "message": "Dışa aktarılacak konum" @@ -2297,8 +2302,13 @@ "tools": { "message": "Araçlar" }, - "import": { - "message": "İçe aktar" + "importNoun": { + "message": "İçe aktar", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "İçe aktar", + "description": "The verb form of the word Import" }, "importData": { "message": "Verileri içe aktar" @@ -3283,6 +3293,9 @@ "launchCloudSubscription": { "message": "Bulut aboneliğini aç" }, + "launchCloudSubscriptionSentenceCase": { + "message": "Bulut aboneliğini aç" + }, "storage": { "message": "Depolama" }, @@ -4198,6 +4211,12 @@ } } }, + "userAcceptedTransfer": { + "message": "Accepted transfer to organization ownership." + }, + "userDeclinedTransfer": { + "message": "Revoked for declining transfer to organization ownership." + }, "invitedUserId": { "message": "Kullanıcı davet edildi: $ID$.", "placeholders": { @@ -12412,5 +12431,53 @@ }, "whyAmISeeingThis": { "message": "Why am I seeing this?" + }, + "youHaveBitwardenPremium": { + "message": "Bitwarden Premium abonesisiniz" + }, + "viewAndManagePremiumSubscription": { + "message": "View and manage your Premium subscription" + }, + "youNeedToUpdateLicenseFile": { + "message": "You'll need to update your license file" + }, + "youNeedToUpdateLicenseFileDate": { + "message": "$DATE$.", + "placeholders": { + "date": { + "content": "$1", + "example": "June 12, 2026" + } + } + }, + "uploadLicenseFile": { + "message": "Upload license file" + }, + "uploadYourLicenseFile": { + "message": "Upload your license file" + }, + "uploadYourPremiumLicenseFile": { + "message": "Upload your Premium license file" + }, + "uploadLicenseFileDesc": { + "message": "Your license file name will be similar to: $FILE_NAME$", + "placeholders": { + "file_name": { + "content": "$1", + "example": "bitwarden_license.json" + } + } + }, + "alreadyHaveSubscriptionQuestion": { + "message": "Already have a subscription?" + }, + "alreadyHaveSubscriptionSelfHostedMessage": { + "message": "Open the subscription page on your Bitwarden cloud account and download your license file. Then return to this screen and upload it below." + }, + "viewAllPlans": { + "message": "View all plans" + }, + "planDescPremium": { + "message": "Eksiksiz çevrimiçi güvenlik" } } diff --git a/apps/web/src/locales/uk/messages.json b/apps/web/src/locales/uk/messages.json index bcb7818fdbd..f604d38059f 100644 --- a/apps/web/src/locales/uk/messages.json +++ b/apps/web/src/locales/uk/messages.json @@ -1969,8 +1969,13 @@ "encExportAccountWarningDesc": { "message": "Ключі шифрування унікальні для кожного облікового запису користувача Bitwarden, тому ви не можете імпортувати зашифрований експорт до іншого облікового запису." }, - "export": { - "message": "Експорт" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, "exportFrom": { "message": "Експортувати з" @@ -2297,8 +2302,13 @@ "tools": { "message": "Інструменти" }, - "import": { - "message": "Import" + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "importData": { "message": "Імпортувати дані" @@ -3283,6 +3293,9 @@ "launchCloudSubscription": { "message": "Запустити хмарну передплату" }, + "launchCloudSubscriptionSentenceCase": { + "message": "Launch cloud subscription" + }, "storage": { "message": "Сховище" }, @@ -4198,6 +4211,12 @@ } } }, + "userAcceptedTransfer": { + "message": "Accepted transfer to organization ownership." + }, + "userDeclinedTransfer": { + "message": "Revoked for declining transfer to organization ownership." + }, "invitedUserId": { "message": "Запрошено користувача $ID$.", "placeholders": { @@ -12412,5 +12431,53 @@ }, "whyAmISeeingThis": { "message": "Why am I seeing this?" + }, + "youHaveBitwardenPremium": { + "message": "You have Bitwarden Premium" + }, + "viewAndManagePremiumSubscription": { + "message": "View and manage your Premium subscription" + }, + "youNeedToUpdateLicenseFile": { + "message": "You'll need to update your license file" + }, + "youNeedToUpdateLicenseFileDate": { + "message": "$DATE$.", + "placeholders": { + "date": { + "content": "$1", + "example": "June 12, 2026" + } + } + }, + "uploadLicenseFile": { + "message": "Upload license file" + }, + "uploadYourLicenseFile": { + "message": "Upload your license file" + }, + "uploadYourPremiumLicenseFile": { + "message": "Upload your Premium license file" + }, + "uploadLicenseFileDesc": { + "message": "Your license file name will be similar to: $FILE_NAME$", + "placeholders": { + "file_name": { + "content": "$1", + "example": "bitwarden_license.json" + } + } + }, + "alreadyHaveSubscriptionQuestion": { + "message": "Already have a subscription?" + }, + "alreadyHaveSubscriptionSelfHostedMessage": { + "message": "Open the subscription page on your Bitwarden cloud account and download your license file. Then return to this screen and upload it below." + }, + "viewAllPlans": { + "message": "View all plans" + }, + "planDescPremium": { + "message": "Complete online security" } } diff --git a/apps/web/src/locales/vi/messages.json b/apps/web/src/locales/vi/messages.json index 83c31ac0471..1fb9e911225 100644 --- a/apps/web/src/locales/vi/messages.json +++ b/apps/web/src/locales/vi/messages.json @@ -1969,8 +1969,13 @@ "encExportAccountWarningDesc": { "message": "Khóa mã hóa tài khoản là duy nhất cho mỗi tài khoản Bitwarden, vì vậy bạn không thể nhập tệp xuất được mã hóa vào một tài khoản khác." }, - "export": { - "message": "Xuất" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, "exportFrom": { "message": "Xuất từ" @@ -2297,8 +2302,13 @@ "tools": { "message": "Công cụ" }, - "import": { - "message": "Import" + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "importData": { "message": "Nhập dữ liệu" @@ -3283,6 +3293,9 @@ "launchCloudSubscription": { "message": "Bắt đầu đăng ký Đám Mây" }, + "launchCloudSubscriptionSentenceCase": { + "message": "Launch cloud subscription" + }, "storage": { "message": "Lưu trữ" }, @@ -4198,6 +4211,12 @@ } } }, + "userAcceptedTransfer": { + "message": "Accepted transfer to organization ownership." + }, + "userDeclinedTransfer": { + "message": "Revoked for declining transfer to organization ownership." + }, "invitedUserId": { "message": "Đã mời người dùng $ID$.", "placeholders": { @@ -12412,5 +12431,53 @@ }, "whyAmISeeingThis": { "message": "Tại sao tôi thấy điều này?" + }, + "youHaveBitwardenPremium": { + "message": "You have Bitwarden Premium" + }, + "viewAndManagePremiumSubscription": { + "message": "View and manage your Premium subscription" + }, + "youNeedToUpdateLicenseFile": { + "message": "You'll need to update your license file" + }, + "youNeedToUpdateLicenseFileDate": { + "message": "$DATE$.", + "placeholders": { + "date": { + "content": "$1", + "example": "June 12, 2026" + } + } + }, + "uploadLicenseFile": { + "message": "Upload license file" + }, + "uploadYourLicenseFile": { + "message": "Upload your license file" + }, + "uploadYourPremiumLicenseFile": { + "message": "Upload your Premium license file" + }, + "uploadLicenseFileDesc": { + "message": "Your license file name will be similar to: $FILE_NAME$", + "placeholders": { + "file_name": { + "content": "$1", + "example": "bitwarden_license.json" + } + } + }, + "alreadyHaveSubscriptionQuestion": { + "message": "Already have a subscription?" + }, + "alreadyHaveSubscriptionSelfHostedMessage": { + "message": "Open the subscription page on your Bitwarden cloud account and download your license file. Then return to this screen and upload it below." + }, + "viewAllPlans": { + "message": "View all plans" + }, + "planDescPremium": { + "message": "Complete online security" } } diff --git a/apps/web/src/locales/zh_CN/messages.json b/apps/web/src/locales/zh_CN/messages.json index f60ad672ced..cc6e71b1f08 100644 --- a/apps/web/src/locales/zh_CN/messages.json +++ b/apps/web/src/locales/zh_CN/messages.json @@ -1969,8 +1969,13 @@ "encExportAccountWarningDesc": { "message": "每个 Bitwarden 用户账户的账户加密密钥都是唯一的,因此您无法将加密的导出导入到另一个账户。" }, - "export": { - "message": "导出" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, "exportFrom": { "message": "导出自" @@ -2297,8 +2302,13 @@ "tools": { "message": "工具" }, - "import": { - "message": "导入" + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "importData": { "message": "导入数据" @@ -3283,6 +3293,9 @@ "launchCloudSubscription": { "message": "启动云订阅" }, + "launchCloudSubscriptionSentenceCase": { + "message": "Launch cloud subscription" + }, "storage": { "message": "存储" }, @@ -4198,6 +4211,12 @@ } } }, + "userAcceptedTransfer": { + "message": "Accepted transfer to organization ownership." + }, + "userDeclinedTransfer": { + "message": "Revoked for declining transfer to organization ownership." + }, "invitedUserId": { "message": "邀请了用户 $ID$。", "placeholders": { @@ -12412,5 +12431,53 @@ }, "whyAmISeeingThis": { "message": "为什么我会看到这个?" + }, + "youHaveBitwardenPremium": { + "message": "You have Bitwarden Premium" + }, + "viewAndManagePremiumSubscription": { + "message": "View and manage your Premium subscription" + }, + "youNeedToUpdateLicenseFile": { + "message": "You'll need to update your license file" + }, + "youNeedToUpdateLicenseFileDate": { + "message": "$DATE$.", + "placeholders": { + "date": { + "content": "$1", + "example": "June 12, 2026" + } + } + }, + "uploadLicenseFile": { + "message": "Upload license file" + }, + "uploadYourLicenseFile": { + "message": "Upload your license file" + }, + "uploadYourPremiumLicenseFile": { + "message": "Upload your Premium license file" + }, + "uploadLicenseFileDesc": { + "message": "Your license file name will be similar to: $FILE_NAME$", + "placeholders": { + "file_name": { + "content": "$1", + "example": "bitwarden_license.json" + } + } + }, + "alreadyHaveSubscriptionQuestion": { + "message": "Already have a subscription?" + }, + "alreadyHaveSubscriptionSelfHostedMessage": { + "message": "Open the subscription page on your Bitwarden cloud account and download your license file. Then return to this screen and upload it below." + }, + "viewAllPlans": { + "message": "View all plans" + }, + "planDescPremium": { + "message": "Complete online security" } } diff --git a/apps/web/src/locales/zh_TW/messages.json b/apps/web/src/locales/zh_TW/messages.json index 7608d8f6b86..b1cda1343d4 100644 --- a/apps/web/src/locales/zh_TW/messages.json +++ b/apps/web/src/locales/zh_TW/messages.json @@ -1969,8 +1969,13 @@ "encExportAccountWarningDesc": { "message": "每個 Bitwarden 使用者帳戶的帳戶加密金鑰都不相同,因此無法將已加密匯出的檔案匯入至不同帳戶中。" }, - "export": { - "message": "匯出" + "exportNoun": { + "message": "Export", + "description": "The noun form of the word Export" + }, + "exportVerb": { + "message": "Export", + "description": "The verb form of the word Export" }, "exportFrom": { "message": "匯出自" @@ -2297,8 +2302,13 @@ "tools": { "message": "工具" }, - "import": { - "message": "Import" + "importNoun": { + "message": "Import", + "description": "The noun form of the word Import" + }, + "importVerb": { + "message": "Import", + "description": "The verb form of the word Import" }, "importData": { "message": "匯入資料" @@ -3283,6 +3293,9 @@ "launchCloudSubscription": { "message": "啟動雲端訂閱" }, + "launchCloudSubscriptionSentenceCase": { + "message": "Launch cloud subscription" + }, "storage": { "message": "儲存空間" }, @@ -4198,6 +4211,12 @@ } } }, + "userAcceptedTransfer": { + "message": "Accepted transfer to organization ownership." + }, + "userDeclinedTransfer": { + "message": "Revoked for declining transfer to organization ownership." + }, "invitedUserId": { "message": "已邀請使用者 $ID$。", "placeholders": { @@ -12412,5 +12431,53 @@ }, "whyAmISeeingThis": { "message": "Why am I seeing this?" + }, + "youHaveBitwardenPremium": { + "message": "You have Bitwarden Premium" + }, + "viewAndManagePremiumSubscription": { + "message": "View and manage your Premium subscription" + }, + "youNeedToUpdateLicenseFile": { + "message": "You'll need to update your license file" + }, + "youNeedToUpdateLicenseFileDate": { + "message": "$DATE$.", + "placeholders": { + "date": { + "content": "$1", + "example": "June 12, 2026" + } + } + }, + "uploadLicenseFile": { + "message": "Upload license file" + }, + "uploadYourLicenseFile": { + "message": "Upload your license file" + }, + "uploadYourPremiumLicenseFile": { + "message": "Upload your Premium license file" + }, + "uploadLicenseFileDesc": { + "message": "Your license file name will be similar to: $FILE_NAME$", + "placeholders": { + "file_name": { + "content": "$1", + "example": "bitwarden_license.json" + } + } + }, + "alreadyHaveSubscriptionQuestion": { + "message": "Already have a subscription?" + }, + "alreadyHaveSubscriptionSelfHostedMessage": { + "message": "Open the subscription page on your Bitwarden cloud account and download your license file. Then return to this screen and upload it below." + }, + "viewAllPlans": { + "message": "View all plans" + }, + "planDescPremium": { + "message": "Complete online security" } } From 6ed60823ccdcc35a56baa7ee867943e5035e504d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 19 Dec 2025 08:47:50 -0500 Subject: [PATCH 131/188] [deps] UI Foundation: Update storybook to v9.1.17 [SECURITY] (#18052) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Bryan Cunningham <bcunningham@bitwarden.com> --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index dab01bdb56a..373cc4b0876 100644 --- a/package-lock.json +++ b/package-lock.json @@ -167,7 +167,7 @@ "rimraf": "6.1.2", "sass": "1.95.1", "sass-loader": "16.0.6", - "storybook": "9.1.16", + "storybook": "9.1.17", "style-loader": "4.0.0", "tailwindcss": "3.4.18", "ts-jest": "29.4.5", @@ -39204,9 +39204,9 @@ } }, "node_modules/storybook": { - "version": "9.1.16", - "resolved": "https://registry.npmjs.org/storybook/-/storybook-9.1.16.tgz", - "integrity": "sha512-339U14K6l46EFyRvaPS2ZlL7v7Pb+LlcXT8KAETrGPxq8v1sAjj2HAOB6zrlAK3M+0+ricssfAwsLCwt7Eg8TQ==", + "version": "9.1.17", + "resolved": "https://registry.npmjs.org/storybook/-/storybook-9.1.17.tgz", + "integrity": "sha512-kfr6kxQAjA96ADlH6FMALJwJ+eM80UqXy106yVHNgdsAP/CdzkkicglRAhZAvUycXK9AeadF6KZ00CWLtVMN4w==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 555ed0d5de8..9276b94286e 100644 --- a/package.json +++ b/package.json @@ -129,7 +129,7 @@ "rimraf": "6.1.2", "sass": "1.95.1", "sass-loader": "16.0.6", - "storybook": "9.1.16", + "storybook": "9.1.17", "style-loader": "4.0.0", "tailwindcss": "3.4.18", "ts-jest": "29.4.5", From 44b31fdadeb7ccc3e282c1cd893eaa1c384d7a02 Mon Sep 17 00:00:00 2001 From: Oscar Hinton <Hinton@users.noreply.github.com> Date: Fri, 19 Dec 2025 16:00:37 +0100 Subject: [PATCH 132/188] [PM-29448] Consume the new send table in web (#17923) 2nd part of PM-29448. Consumes the new send table in the web vault. Also updates the send table to contain the responsive behaviour. --- apps/desktop/tailwind.config.js | 1 + .../src/app/tools/send/send.component.html | 122 +--------- apps/web/src/app/tools/send/send.component.ts | 10 +- apps/web/tailwind.config.js | 1 + .../src/send-table/send-table.component.html | 212 +++++++++--------- 5 files changed, 133 insertions(+), 213 deletions(-) diff --git a/apps/desktop/tailwind.config.js b/apps/desktop/tailwind.config.js index e67c0c38010..0c4ed8c5a7e 100644 --- a/apps/desktop/tailwind.config.js +++ b/apps/desktop/tailwind.config.js @@ -10,6 +10,7 @@ config.content = [ "../../libs/angular/src/**/*.{html,ts}", "../../libs/vault/src/**/*.{html,ts,mdx}", "../../libs/pricing/src/**/*.{html,ts}", + "../../libs/tools/send/send-ui/src/**/*.{html,ts}", ]; module.exports = config; diff --git a/apps/web/src/app/tools/send/send.component.html b/apps/web/src/app/tools/send/send.component.html index e593f5c1176..6418744a727 100644 --- a/apps/web/src/app/tools/send/send.component.html +++ b/apps/web/src/app/tools/send/send.component.html @@ -80,117 +80,17 @@ </div> </div> </div> - <div class="tw-col-span-9 tw-@container/send-table"> - <!--Listing Table--> - <bit-table [dataSource]="dataSource" *ngIf="filteredSends && filteredSends.length"> - <ng-container header> - <tr> - <th bitCell bitSortable="name" default>{{ "name" | i18n }}</th> - <th bitCell bitSortable="deletionDate" class="@lg/send-table:tw-table-cell tw-hidden"> - {{ "deletionDate" | i18n }} - </th> - <th bitCell>{{ "options" | i18n }}</th> - </tr> - </ng-container> - <ng-template body let-rows$> - <tr bitRow *ngFor="let s of rows$ | async"> - <td bitCell (click)="editSend(s)" class="tw-cursor-pointer"> - <div class="tw-flex tw-gap-2 tw-items-center"> - <span aria-hidden="true"> - <i class="bwi bwi-fw bwi-lg bwi-file" *ngIf="s.type == sendType.File"></i> - <i class="bwi bwi-fw bwi-lg bwi-file-text" *ngIf="s.type == sendType.Text"></i> - </span> - <button type="button" bitLink> - {{ s.name }} - </button> - <ng-container *ngIf="s.disabled"> - <i - class="bwi bwi-exclamation-triangle" - appStopProp - title="{{ 'disabled' | i18n }}" - aria-hidden="true" - ></i> - <span class="tw-sr-only">{{ "disabled" | i18n }}</span> - </ng-container> - <ng-container *ngIf="s.password"> - <i - class="bwi bwi-key" - appStopProp - title="{{ 'password' | i18n }}" - aria-hidden="true" - ></i> - <span class="tw-sr-only">{{ "password" | i18n }}</span> - </ng-container> - <ng-container *ngIf="s.maxAccessCountReached"> - <i - class="bwi bwi-exclamation-triangle" - appStopProp - title="{{ 'maxAccessCountReached' | i18n }}" - aria-hidden="true" - ></i> - <span class="tw-sr-only">{{ "maxAccessCountReached" | i18n }}</span> - </ng-container> - <ng-container *ngIf="s.expired"> - <i - class="bwi bwi-clock" - appStopProp - title="{{ 'expired' | i18n }}" - aria-hidden="true" - ></i> - <span class="tw-sr-only">{{ "expired" | i18n }}</span> - </ng-container> - <ng-container *ngIf="s.pendingDelete"> - <i - class="bwi bwi-trash" - appStopProp - title="{{ 'pendingDeletion' | i18n }}" - aria-hidden="true" - ></i> - <span class="tw-sr-only">{{ "pendingDeletion" | i18n }}</span> - </ng-container> - </div> - </td> - <td - bitCell - (click)="editSend(s)" - class="tw-text-muted tw-cursor-pointer @lg/send-table:tw-table-cell tw-hidden" - > - <small bitTypography="body2" appStopProp> - {{ s.deletionDate | date: "medium" }} - </small> - </td> - <td bitCell class="tw-w-0 tw-text-right"> - <button - type="button" - [bitMenuTriggerFor]="sendOptions" - bitIconButton="bwi-ellipsis-v" - label="{{ 'options' | i18n }}" - ></button> - <bit-menu #sendOptions> - <button type="button" bitMenuItem (click)="copy(s)"> - <i class="bwi bwi-fw bwi-clone" aria-hidden="true"></i> - {{ "copySendLink" | i18n }} - </button> - <button - type="button" - bitMenuItem - (click)="removePassword(s)" - *ngIf="s.password && !disableSend" - > - <i class="bwi bwi-fw bwi-close" aria-hidden="true"></i> - {{ "removePassword" | i18n }} - </button> - <button type="button" bitMenuItem (click)="delete(s)"> - <span class="tw-text-danger"> - <i class="bwi bwi-fw bwi-trash" aria-hidden="true"></i> - {{ "delete" | i18n }} - </span> - </button> - </bit-menu> - </td> - </tr> - </ng-template> - </bit-table> + <div class="tw-col-span-9"> + <tools-send-table + *ngIf="filteredSends && filteredSends.length" + [dataSource]="dataSource" + [disableSend]="disableSend" + (editSend)="editSend($event)" + (copySend)="copy($event)" + (removePassword)="removePassword($event)" + (deleteSend)="delete($event)" + /> + <div *ngIf="filteredSends && !filteredSends.length"> <ng-container *ngIf="!loaded"> <i diff --git a/apps/web/src/app/tools/send/send.component.ts b/apps/web/src/app/tools/send/send.component.ts index 928945f9782..7c0e03e3e21 100644 --- a/apps/web/src/app/tools/send/send.component.ts +++ b/apps/web/src/app/tools/send/send.component.ts @@ -32,6 +32,7 @@ import { SendFormConfig, SendAddEditDialogComponent, SendItemDialogResult, + SendTableComponent, } from "@bitwarden/send-ui"; import { HeaderModule } from "../../layouts/header/header.module"; @@ -45,7 +46,14 @@ const BroadcasterSubscriptionId = "SendComponent"; // eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "app-send", - imports: [SharedModule, SearchModule, NoItemsModule, HeaderModule, NewSendDropdownComponent], + imports: [ + SharedModule, + SearchModule, + NoItemsModule, + HeaderModule, + NewSendDropdownComponent, + SendTableComponent, + ], templateUrl: "send.component.html", providers: [DefaultSendFormConfigService], }) diff --git a/apps/web/tailwind.config.js b/apps/web/tailwind.config.js index 865d681e91d..16bfdcac611 100644 --- a/apps/web/tailwind.config.js +++ b/apps/web/tailwind.config.js @@ -11,6 +11,7 @@ config.content = [ path.resolve(__dirname, "../../libs/vault/src/**/*.{html,ts}"), path.resolve(__dirname, "../../libs/angular/src/**/*.{html,ts}"), path.resolve(__dirname, "../../libs/tools/generator/components/src/**/*.{html,ts}"), + path.resolve(__dirname, "../../libs/tools/send/send-ui/src/**/*.{html,ts}"), path.resolve(__dirname, "../../bitwarden_license/bit-web/src/**/*.{html,ts}"), ]; config.corePlugins.preflight = true; diff --git a/libs/tools/send/send-ui/src/send-table/send-table.component.html b/libs/tools/send/send-ui/src/send-table/send-table.component.html index 6b93b9d879e..96b9519019e 100644 --- a/libs/tools/send/send-ui/src/send-table/send-table.component.html +++ b/libs/tools/send/send-ui/src/send-table/send-table.component.html @@ -1,102 +1,112 @@ -<bit-table [dataSource]="dataSource()"> - <ng-container header> - <tr> - <th bitCell bitSortable="name" default>{{ "name" | i18n }}</th> - <th bitCell bitSortable="deletionDate">{{ "deletionDate" | i18n }}</th> - <th bitCell>{{ "options" | i18n }}</th> - </tr> - </ng-container> - <ng-template body let-rows$> - <tr bitRow *ngFor="let s of rows$ | async"> - <td bitCell (click)="onEditSend(s)" class="tw-cursor-pointer"> - <div class="tw-flex tw-gap-2 tw-items-center"> - <span aria-hidden="true"> - @if (s.type == sendType.File) { - <i class="bwi bwi-fw bwi-lg bwi-file"></i> - } - @if (s.type == sendType.Text) { - <i class="bwi bwi-fw bwi-lg bwi-file-text"></i> - } - </span> - <button type="button" bitLink> - {{ s.name }} - </button> - @if (s.disabled) { - <i - class="bwi bwi-exclamation-triangle" - appStopProp - title="{{ 'disabled' | i18n }}" - aria-hidden="true" - ></i> - <span class="tw-sr-only">{{ "disabled" | i18n }}</span> - } - @if (s.password) { - <i - class="bwi bwi-key" - appStopProp - title="{{ 'password' | i18n }}" - aria-hidden="true" - ></i> - <span class="tw-sr-only">{{ "password" | i18n }}</span> - } - @if (s.maxAccessCountReached) { - <i - class="bwi bwi-exclamation-triangle" - appStopProp - title="{{ 'maxAccessCountReached' | i18n }}" - aria-hidden="true" - ></i> - <span class="tw-sr-only">{{ "maxAccessCountReached" | i18n }}</span> - } - @if (s.expired) { - <i - class="bwi bwi-clock" - appStopProp - title="{{ 'expired' | i18n }}" - aria-hidden="true" - ></i> - <span class="tw-sr-only">{{ "expired" | i18n }}</span> - } - @if (s.pendingDelete) { - <i - class="bwi bwi-trash" - appStopProp - title="{{ 'pendingDeletion' | i18n }}" - aria-hidden="true" - ></i> - <span class="tw-sr-only">{{ "pendingDeletion" | i18n }}</span> - } - </div> - </td> - <td bitCell (click)="onEditSend(s)" class="tw-text-muted tw-cursor-pointer"> - <small bitTypography="body2" appStopProp>{{ s.deletionDate | date: "medium" }}</small> - </td> - <td bitCell class="tw-w-0 tw-text-right"> - <button - type="button" - [bitMenuTriggerFor]="sendOptions" - bitIconButton="bwi-ellipsis-v" - label="{{ 'options' | i18n }}" - ></button> - <bit-menu #sendOptions> - <button type="button" bitMenuItem (click)="onCopy(s)"> - <i class="bwi bwi-fw bwi-clone" aria-hidden="true"></i> - {{ "copySendLink" | i18n }} - </button> - @if (s.password && !disableSend()) { - <button type="button" bitMenuItem (click)="onRemovePassword(s)"> - <i class="bwi bwi-fw bwi-close" aria-hidden="true"></i> - {{ "removePassword" | i18n }} - </button> - } - <button type="button" bitMenuItem (click)="onDelete(s)"> - <span class="tw-text-danger"> - <i class="bwi bwi-fw bwi-trash" aria-hidden="true"></i> - {{ "delete" | i18n }} +<div class="tw-@container/send-table"> + <bit-table [dataSource]="dataSource()"> + <ng-container header> + <tr> + <th bitCell bitSortable="name" default>{{ "name" | i18n }}</th> + <th bitCell bitSortable="deletionDate" class="@lg/send-table:tw-table-cell tw-hidden"> + {{ "deletionDate" | i18n }} + </th> + <th bitCell>{{ "options" | i18n }}</th> + </tr> + </ng-container> + <ng-template body let-rows$> + <tr bitRow *ngFor="let s of rows$ | async"> + <td bitCell (click)="onEditSend(s)" class="tw-cursor-pointer"> + <div class="tw-flex tw-gap-2 tw-items-center"> + <span aria-hidden="true"> + @if (s.type == sendType.File) { + <i class="bwi bwi-fw bwi-lg bwi-file"></i> + } + @if (s.type == sendType.Text) { + <i class="bwi bwi-fw bwi-lg bwi-file-text"></i> + } </span> - </button> - </bit-menu> - </td> - </tr> - </ng-template> -</bit-table> + <button type="button" bitLink> + {{ s.name }} + </button> + @if (s.disabled) { + <i + class="bwi bwi-exclamation-triangle" + appStopProp + title="{{ 'disabled' | i18n }}" + aria-hidden="true" + ></i> + <span class="tw-sr-only">{{ "disabled" | i18n }}</span> + } + @if (s.password) { + <i + class="bwi bwi-key" + appStopProp + title="{{ 'password' | i18n }}" + aria-hidden="true" + ></i> + <span class="tw-sr-only">{{ "password" | i18n }}</span> + } + @if (s.maxAccessCountReached) { + <i + class="bwi bwi-exclamation-triangle" + appStopProp + title="{{ 'maxAccessCountReached' | i18n }}" + aria-hidden="true" + ></i> + <span class="tw-sr-only">{{ "maxAccessCountReached" | i18n }}</span> + } + @if (s.expired) { + <i + class="bwi bwi-clock" + appStopProp + title="{{ 'expired' | i18n }}" + aria-hidden="true" + ></i> + <span class="tw-sr-only">{{ "expired" | i18n }}</span> + } + @if (s.pendingDelete) { + <i + class="bwi bwi-trash" + appStopProp + title="{{ 'pendingDeletion' | i18n }}" + aria-hidden="true" + ></i> + <span class="tw-sr-only">{{ "pendingDeletion" | i18n }}</span> + } + </div> + </td> + <td + bitCell + (click)="onEditSend(s)" + class="tw-text-muted tw-cursor-pointer @lg/send-table:tw-table-cell tw-hidden" + > + <small bitTypography="body2" appStopProp> + {{ s.deletionDate | date: "medium" }} + </small> + </td> + <td bitCell class="tw-w-0 tw-text-right"> + <button + type="button" + [bitMenuTriggerFor]="sendOptions" + bitIconButton="bwi-ellipsis-v" + label="{{ 'options' | i18n }}" + ></button> + <bit-menu #sendOptions> + <button type="button" bitMenuItem (click)="onCopy(s)"> + <i class="bwi bwi-fw bwi-clone" aria-hidden="true"></i> + {{ "copySendLink" | i18n }} + </button> + @if (s.password && !disableSend()) { + <button type="button" bitMenuItem (click)="onRemovePassword(s)"> + <i class="bwi bwi-fw bwi-close" aria-hidden="true"></i> + {{ "removePassword" | i18n }} + </button> + } + <button type="button" bitMenuItem (click)="onDelete(s)"> + <span class="tw-text-danger"> + <i class="bwi bwi-fw bwi-trash" aria-hidden="true"></i> + {{ "delete" | i18n }} + </span> + </button> + </bit-menu> + </td> + </tr> + </ng-template> + </bit-table> +</div> From 3bd9ee19258aa8745bcdb7674e4def2a4763685c Mon Sep 17 00:00:00 2001 From: Daniel James Smith <2670567+djsmith85@users.noreply.github.com> Date: Fri, 19 Dec 2025 16:33:40 +0100 Subject: [PATCH 133/188] Delete unused models (#18063) These were used prior to the extension refresh work. Co-authored-by: Daniel James Smith <djsmith85@users.noreply.github.com> --- .github/whitelist-capital-letters.txt | 2 - .../src/models/browserComponentState.ts | 16 ------- .../models/browserGroupingsComponentState.ts | 46 ------------------- 3 files changed, 64 deletions(-) delete mode 100644 apps/browser/src/models/browserComponentState.ts delete mode 100644 apps/browser/src/models/browserGroupingsComponentState.ts diff --git a/.github/whitelist-capital-letters.txt b/.github/whitelist-capital-letters.txt index db5097e5268..b9f904d7613 100644 --- a/.github/whitelist-capital-letters.txt +++ b/.github/whitelist-capital-letters.txt @@ -19,8 +19,6 @@ ./apps/cli/stores/chocolatey/tools/VERIFICATION.txt ./apps/browser/store/windows/AppxManifest.xml ./apps/browser/src/background/nativeMessaging.background.ts -./apps/browser/src/models/browserComponentState.ts -./apps/browser/src/models/browserGroupingsComponentState.ts ./apps/browser/src/models/biometricErrors.ts ./apps/browser/src/browser/safariApp.ts ./apps/browser/src/safari/desktop/ViewController.swift diff --git a/apps/browser/src/models/browserComponentState.ts b/apps/browser/src/models/browserComponentState.ts deleted file mode 100644 index 50ee9fe34d4..00000000000 --- a/apps/browser/src/models/browserComponentState.ts +++ /dev/null @@ -1,16 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Jsonify } from "type-fest"; - -export class BrowserComponentState { - scrollY: number; - searchText: string; - - static fromJSON(json: Jsonify<BrowserComponentState>) { - if (json == null) { - return null; - } - - return Object.assign(new BrowserComponentState(), json); - } -} diff --git a/apps/browser/src/models/browserGroupingsComponentState.ts b/apps/browser/src/models/browserGroupingsComponentState.ts deleted file mode 100644 index 364f4beb6bf..00000000000 --- a/apps/browser/src/models/browserGroupingsComponentState.ts +++ /dev/null @@ -1,46 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { CollectionView } from "@bitwarden/admin-console/common"; -import { Utils } from "@bitwarden/common/platform/misc/utils"; -import { DeepJsonify } from "@bitwarden/common/types/deep-jsonify"; -import { CipherType } from "@bitwarden/common/vault/enums"; -import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; -import { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; - -import { BrowserComponentState } from "./browserComponentState"; - -export class BrowserGroupingsComponentState extends BrowserComponentState { - favoriteCiphers: CipherView[]; - noFolderCiphers: CipherView[]; - ciphers: CipherView[]; - collectionCounts: Map<string, number>; - folderCounts: Map<string, number>; - typeCounts: Map<CipherType, number>; - folders: FolderView[]; - collections: CollectionView[]; - deletedCount: number; - - toJSON() { - return Utils.merge(this, { - collectionCounts: Utils.mapToRecord(this.collectionCounts), - folderCounts: Utils.mapToRecord(this.folderCounts), - typeCounts: Utils.mapToRecord(this.typeCounts), - }); - } - - static fromJSON(json: DeepJsonify<BrowserGroupingsComponentState>) { - if (json == null) { - return null; - } - - return Object.assign(new BrowserGroupingsComponentState(), json, { - favoriteCiphers: json.favoriteCiphers?.map((c) => CipherView.fromJSON(c)), - noFolderCiphers: json.noFolderCiphers?.map((c) => CipherView.fromJSON(c)), - ciphers: json.ciphers?.map((c) => CipherView.fromJSON(c)), - collectionCounts: Utils.recordToMap(json.collectionCounts), - folderCounts: Utils.recordToMap(json.folderCounts), - typeCounts: Utils.recordToMap(json.typeCounts), - folders: json.folders?.map((f) => FolderView.fromJSON(f)), - }); - } -} From f4037f404ebfcd68207d87b93c61a6d614b20a63 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann <mail@quexten.com> Date: Fri, 19 Dec 2025 17:34:34 +0100 Subject: [PATCH 134/188] [PM-29419] Fix ssh account switching (#18060) * Fix ssh account switching * Npx prettier --- apps/desktop/src/autofill/services/ssh-agent.service.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/desktop/src/autofill/services/ssh-agent.service.ts b/apps/desktop/src/autofill/services/ssh-agent.service.ts index a61903a5c82..8339f635c1b 100644 --- a/apps/desktop/src/autofill/services/ssh-agent.service.ts +++ b/apps/desktop/src/autofill/services/ssh-agent.service.ts @@ -89,7 +89,6 @@ export class SshAgentService implements OnDestroy { filter(({ enabled }) => enabled), map(({ message }) => message), withLatestFrom(this.authService.activeAccountStatus$, this.accountService.activeAccount$), - filter(([, , account]) => account != null), // This switchMap handles unlocking the vault if it is locked: // - If the vault is locked, we will wait for it to be unlocked. // - If the vault is not unlocked within the timeout, we will abort the flow. @@ -97,7 +96,7 @@ export class SshAgentService implements OnDestroy { // switchMap is used here to prevent multiple requests from being processed at the same time, // and will cancel the previous request if a new one is received. switchMap(([message, status, account]) => { - if (status !== AuthenticationStatus.Unlocked) { + if (status !== AuthenticationStatus.Unlocked || account == null) { ipc.platform.focusWindow(); this.toastService.showToast({ variant: "info", From 60b84361d24578df921e601f095c3ccb10ccd491 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann <mail@quexten.com> Date: Fri, 19 Dec 2025 17:47:14 +0100 Subject: [PATCH 135/188] Follow-up comment fix (#18067) * Fix ssh account switching * Npx prettier * Follow-up comment fix --- apps/desktop/src/autofill/services/ssh-agent.service.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/desktop/src/autofill/services/ssh-agent.service.ts b/apps/desktop/src/autofill/services/ssh-agent.service.ts index 8339f635c1b..7e289720ec8 100644 --- a/apps/desktop/src/autofill/services/ssh-agent.service.ts +++ b/apps/desktop/src/autofill/services/ssh-agent.service.ts @@ -89,9 +89,9 @@ export class SshAgentService implements OnDestroy { filter(({ enabled }) => enabled), map(({ message }) => message), withLatestFrom(this.authService.activeAccountStatus$, this.accountService.activeAccount$), - // This switchMap handles unlocking the vault if it is locked: - // - If the vault is locked, we will wait for it to be unlocked. - // - If the vault is not unlocked within the timeout, we will abort the flow. + // This switchMap handles unlocking the vault if it is not unlocked: + // - If the vault is locked or logged out, we will wait for it to be unlocked: + // - If the vault is not unlocked in within the timeout, we will abort the flow. // - If the vault is unlocked, we will continue with the flow. // switchMap is used here to prevent multiple requests from being processed at the same time, // and will cancel the previous request if a new one is received. From ea4666e3c1f70d3bcada332ab301627c1a58ac2c Mon Sep 17 00:00:00 2001 From: Leslie Tilton <23057410+Banrion@users.noreply.github.com> Date: Fri, 19 Dec 2025 11:58:14 -0600 Subject: [PATCH 136/188] [PM-25884] Move Phishing Detection Safari check to PhishingDetectionSettingsService (#18042) * Move safari check to phishing detection settings to expose to all places using phishing detection * Remove duplicate comment --- apps/browser/src/background/main.background.ts | 1 + .../services/phishing-detection.service.ts | 13 +------------ apps/browser/src/popup/services/services.module.ts | 1 + .../phishing-detection-settings.service.spec.ts | 5 +++++ .../phishing-detection-settings.service.ts | 7 +++++++ 5 files changed, 15 insertions(+), 12 deletions(-) diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index c224dcf581e..2d1da8510c1 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -1490,6 +1490,7 @@ export default class MainBackground { this.billingAccountProfileStateService, this.configService, this.organizationService, + this.platformUtilsService, this.stateProvider, ); 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 501dfbf7a50..e04d08559ab 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 @@ -5,7 +5,6 @@ import { filter, map, merge, - of, Subject, switchMap, tap, @@ -112,17 +111,7 @@ export class PhishingDetectionService { .messages$(PHISHING_DETECTION_CANCEL_COMMAND) .pipe(switchMap((message) => BrowserApi.closeTab(message.tabId))); - // Phishing detection is unavailable on Safari due to platform limitations - if (BrowserApi.isSafariApi) { - logService.debug( - "[PhishingDetectionService] Disabling phishing detection service for Safari.", - ); - } - - // Watching for settings changes to enable/disable phishing detection - const phishingDetectionActive$ = BrowserApi.isSafariApi - ? of(false) - : phishingDetectionSettingsService.on$; + const phishingDetectionActive$ = phishingDetectionSettingsService.on$; const initSub = phishingDetectionActive$ .pipe( diff --git a/apps/browser/src/popup/services/services.module.ts b/apps/browser/src/popup/services/services.module.ts index 39c53b7da56..739166ff6f8 100644 --- a/apps/browser/src/popup/services/services.module.ts +++ b/apps/browser/src/popup/services/services.module.ts @@ -523,6 +523,7 @@ const safeProviders: SafeProvider[] = [ BillingAccountProfileStateService, ConfigService, OrganizationService, + PlatformUtilsService, StateProvider, ], }), diff --git a/libs/common/src/dirt/services/phishing-detection/phishing-detection-settings.service.spec.ts b/libs/common/src/dirt/services/phishing-detection/phishing-detection-settings.service.spec.ts index 23e311d9445..e6363b490cb 100644 --- a/libs/common/src/dirt/services/phishing-detection/phishing-detection-settings.service.spec.ts +++ b/libs/common/src/dirt/services/phishing-detection/phishing-detection-settings.service.spec.ts @@ -7,6 +7,7 @@ import { Account, AccountService } from "@bitwarden/common/auth/abstractions/acc import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions"; import { ProductTierType } from "@bitwarden/common/billing/enums"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { FakeAccountService, FakeStateProvider, mockAccountServiceWith } from "../../../../spec"; import { UserId } from "../../../types/guid"; @@ -19,6 +20,7 @@ describe("PhishingDetectionSettingsService", () => { let mockBillingService: MockProxy<BillingAccountProfileStateService>; let mockConfigService: MockProxy<ConfigService>; let mockOrganizationService: MockProxy<OrganizationService>; + let mockPlatformService: MockProxy<PlatformUtilsService>; // RxJS Subjects we control in the tests let activeAccountSubject: BehaviorSubject<Account | null>; @@ -76,12 +78,15 @@ describe("PhishingDetectionSettingsService", () => { mockOrganizationService = mock<OrganizationService>(); mockOrganizationService.organizations$.mockReturnValue(organizationsSubject.asObservable()); + mockPlatformService = mock<PlatformUtilsService>(); + stateProvider = new FakeStateProvider(accountService); service = new PhishingDetectionSettingsService( mockAccountService, mockBillingService, mockConfigService, mockOrganizationService, + mockPlatformService, stateProvider, ); }); diff --git a/libs/common/src/dirt/services/phishing-detection/phishing-detection-settings.service.ts b/libs/common/src/dirt/services/phishing-detection/phishing-detection-settings.service.ts index 36d50f60de7..e30592b2f68 100644 --- a/libs/common/src/dirt/services/phishing-detection/phishing-detection-settings.service.ts +++ b/libs/common/src/dirt/services/phishing-detection/phishing-detection-settings.service.ts @@ -8,6 +8,7 @@ import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abs import { ProductTierType } from "@bitwarden/common/billing/enums"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { UserId } from "@bitwarden/user-core"; import { PHISHING_DETECTION_DISK, StateProvider, UserKeyDefinition } from "../../../platform/state"; @@ -32,6 +33,7 @@ export class PhishingDetectionSettingsService implements PhishingDetectionSettin private billingService: BillingAccountProfileStateService, private configService: ConfigService, private organizationService: OrganizationService, + private platformService: PlatformUtilsService, private stateProvider: StateProvider, ) { this.available$ = this.buildAvailablePipeline$().pipe( @@ -60,6 +62,11 @@ export class PhishingDetectionSettingsService implements PhishingDetectionSettin * @returns An observable pipeline that determines if phishing detection is available */ private buildAvailablePipeline$(): Observable<boolean> { + // Phishing detection is unavailable on Safari due to platform limitations. + if (this.platformService.isSafari()) { + return of(false); + } + return combineLatest([ this.accountService.activeAccount$, this.configService.getFeatureFlag$(FeatureFlag.PhishingDetection), From 481386218a62b3d18288f3be3ade88bde50cfe5d Mon Sep 17 00:00:00 2001 From: bmbitwarden <bmcferren@bitwarden.com> Date: Fri, 19 Dec 2025 13:26:40 -0500 Subject: [PATCH 137/188] PM-24189 improved screen reader parsing for special character checkbox (#17361) * PM-24189 improved screen reader parsing for special character checkbox * PM-24189 resolved voiceOver issue * PM-24189 resolved voiceOver issue * PM-27628 resolved pr comment re id and aria labels and comments * PM-24189 resolved pr comment --- .../src/password-settings.component.html | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/libs/tools/generator/components/src/password-settings.component.html b/libs/tools/generator/components/src/password-settings.component.html index b435a13fe4c..33958f0c169 100644 --- a/libs/tools/generator/components/src/password-settings.component.html +++ b/libs/tools/generator/components/src/password-settings.component.html @@ -52,11 +52,7 @@ <input bitCheckbox type="checkbox" formControlName="number" (change)="save('number')" /> <bit-label>{{ "numbersLabel" | i18n }}</bit-label> </bit-form-control> - <bit-form-control - class="!tw-mb-0" - attr.aria-description="{{ 'specialCharactersDescription' | i18n }}" - title="{{ 'specialCharactersDescription' | i18n }}" - > + <bit-form-control class="!tw-mb-0" title="{{ 'specialCharactersDescription' | i18n }}"> <input bitCheckbox type="checkbox" @@ -64,10 +60,15 @@ (change)="save('special')" /> <!-- hard-coded the special characters string because `$` indicates an i18n interpolation, - and is handled inconsistently across browsers. Angular template syntax is used to - ensure special characters are entity-encoded. + and is handled inconsistently across browsers. Angular template syntax is used to + ensure special characters are entity-encoded. --> - <bit-label>{{ "!@#$%^&*" }}</bit-label> + <bit-label> + <span aria-hidden="true">{{ "!@#$%^&*" }}</span> + <span class="tw-sr-only"> + {{ "specialCharactersDescription" | i18n }} + </span> + </bit-label> </bit-form-control> </div> <div class="tw-flex"> From 0064f18ccd415f852b4639ba346470a882d80260 Mon Sep 17 00:00:00 2001 From: Dave <3836813+enmande@users.noreply.github.com> Date: Fri, 19 Dec 2025 14:56:13 -0500 Subject: [PATCH 138/188] fix(set-initial-password) [PM-28494]: Newly created master password not accepted on unlock until after re-login on browser extension (#17930) * fix(set-initial-password-service) [PM-28494]: Update MP data and decryption property sets to accommodate legacy and new paths for service. * fix(set-initial-password-component) [PM-28494]: Add salt and mp data to credentials object. * refactor(set-initial-password-service) [PM-28494]: Additional comments. * test(set-initial-password-service) [PM-28494]: Update tests for added credential members. --- ...initial-password.service.implementation.ts | 48 +++++++++++++ ...fault-set-initial-password.service.spec.ts | 72 +++++++++++++++++++ .../set-initial-password.component.ts | 4 ++ ...et-initial-password.service.abstraction.ts | 3 + 4 files changed, 127 insertions(+) diff --git a/libs/angular/src/auth/password-management/set-initial-password/default-set-initial-password.service.implementation.ts b/libs/angular/src/auth/password-management/set-initial-password/default-set-initial-password.service.implementation.ts index bd3f78b9290..2f5c43e2db9 100644 --- a/libs/angular/src/auth/password-management/set-initial-password/default-set-initial-password.service.implementation.ts +++ b/libs/angular/src/auth/password-management/set-initial-password/default-set-initial-password.service.implementation.ts @@ -19,6 +19,7 @@ import { AccountCryptographicStateService } from "@bitwarden/common/key-manageme import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string"; import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; +import { MasterPasswordSalt } from "@bitwarden/common/key-management/master-password/types/master-password.types"; import { KeysRequest } from "@bitwarden/common/models/request/keys.request"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; @@ -62,6 +63,8 @@ export class DefaultSetInitialPasswordService implements SetInitialPasswordServi orgSsoIdentifier, orgId, resetPasswordAutoEnroll, + newPassword, + salt, } = credentials; for (const [key, value] of Object.entries(credentials)) { @@ -155,6 +158,20 @@ export class DefaultSetInitialPasswordService implements SetInitialPasswordServi userId, ); + // Set master password unlock data for unlock path pointed to with + // MasterPasswordUnlockData feature development + // (requires: password, salt, kdf, userKey). + // As migration to this strategy continues, both unlock paths need supported. + // Several invocations in this file become redundant and can be removed once + // the feature is enshrined/unwound. These are marked with [PM-23246] below. + await this.setMasterPasswordUnlockData( + newPassword, + salt, + kdfConfig, + masterKeyEncryptedUserKey[0], + userId, + ); + /** * Set the private key only for new JIT provisioned users in MP encryption orgs. * (Existing TDE users will have their private key set on sync or on login.) @@ -174,6 +191,7 @@ export class DefaultSetInitialPasswordService implements SetInitialPasswordServi ); } + // [PM-23246] "Legacy" master key setting path - to be removed once unlock path migration is complete await this.masterPasswordService.setMasterKeyHash(newLocalMasterKeyHash, userId); if (resetPasswordAutoEnroll) { @@ -216,10 +234,40 @@ export class DefaultSetInitialPasswordService implements SetInitialPasswordServi userDecryptionOpts, ); await this.kdfConfigService.setKdfConfig(userId, kdfConfig); + // [PM-23246] "Legacy" master key setting path - to be removed once unlock path migration is complete await this.masterPasswordService.setMasterKey(masterKey, userId); + // [PM-23246] "Legacy" master key setting path - to be removed once unlock path migration is complete + await this.masterPasswordService.setMasterKeyEncryptedUserKey( + masterKeyEncryptedUserKey[1], + userId, + ); await this.keyService.setUserKey(masterKeyEncryptedUserKey[0], userId); } + /** + * As part of [PM-28494], adding this setting path to accommodate the changes that are + * emerging with pm-23246-unlock-with-master-password-unlock-data. + * Without this, immediately locking/unlocking the vault with the new password _may_ still fail + * if sync has not completed. Sync will eventually set this data, but we want to ensure it's + * set right away here to prevent a race condition UX issue that prevents immediate unlock. + */ + private async setMasterPasswordUnlockData( + password: string, + salt: MasterPasswordSalt, + kdfConfig: KdfConfig, + userKey: UserKey, + userId: UserId, + ): Promise<void> { + const masterPasswordUnlockData = await this.masterPasswordService.makeMasterPasswordUnlockData( + password, + kdfConfig, + salt, + userKey, + ); + + await this.masterPasswordService.setMasterPasswordUnlockData(masterPasswordUnlockData, userId); + } + private async handleResetPasswordAutoEnroll( masterKeyHash: string, orgId: string, diff --git a/libs/angular/src/auth/password-management/set-initial-password/default-set-initial-password.service.spec.ts b/libs/angular/src/auth/password-management/set-initial-password/default-set-initial-password.service.spec.ts index cfea011d0d9..af4505371d3 100644 --- a/libs/angular/src/auth/password-management/set-initial-password/default-set-initial-password.service.spec.ts +++ b/libs/angular/src/auth/password-management/set-initial-password/default-set-initial-password.service.spec.ts @@ -134,6 +134,8 @@ describe("DefaultSetInitialPasswordService", () => { orgSsoIdentifier: "orgSsoIdentifier", orgId: "orgId", resetPasswordAutoEnroll: false, + newPassword: "Test@Password123!", + salt: "user@example.com" as any, }; userType = SetInitialPasswordUserType.JIT_PROVISIONED_MP_ORG_USER; @@ -226,6 +228,8 @@ describe("DefaultSetInitialPasswordService", () => { "orgSsoIdentifier", "orgId", "resetPasswordAutoEnroll", + "newPassword", + "salt", ].forEach((key) => { it(`should throw if ${key} is not provided on the SetInitialPasswordCredentials object`, async () => { // Arrange @@ -357,6 +361,10 @@ describe("DefaultSetInitialPasswordService", () => { ForceSetPasswordReason.None, userId, ); + expect(masterPasswordService.setMasterKeyEncryptedUserKey).toHaveBeenCalledWith( + masterKeyEncryptedUserKey[1], + userId, + ); }); it("should update account decryption properties", async () => { @@ -417,6 +425,36 @@ describe("DefaultSetInitialPasswordService", () => { ); }); + it("should create and set master password unlock data to prevent race condition with sync", async () => { + // Arrange + setupMocks(); + + const mockUnlockData = { + salt: credentials.salt, + kdf: credentials.kdfConfig, + masterKeyWrappedUserKey: "wrapped_key_string", + }; + + masterPasswordService.makeMasterPasswordUnlockData.mockResolvedValue( + mockUnlockData as any, + ); + + // Act + await sut.setInitialPassword(credentials, userType, userId); + + // Assert + expect(masterPasswordService.makeMasterPasswordUnlockData).toHaveBeenCalledWith( + credentials.newPassword, + credentials.kdfConfig, + credentials.salt, + masterKeyEncryptedUserKey[0], + ); + expect(masterPasswordService.setMasterPasswordUnlockData).toHaveBeenCalledWith( + mockUnlockData, + userId, + ); + }); + describe("given resetPasswordAutoEnroll is true", () => { it(`should handle reset password (account recovery) auto enroll`, async () => { // Arrange @@ -586,6 +624,10 @@ describe("DefaultSetInitialPasswordService", () => { credentials.newMasterKey, userId, ); + expect(masterPasswordService.setMasterKeyEncryptedUserKey).toHaveBeenCalledWith( + masterKeyEncryptedUserKey[1], + userId, + ); expect(keyService.setUserKey).toHaveBeenCalledWith(masterKeyEncryptedUserKey[0], userId); }); @@ -616,6 +658,36 @@ describe("DefaultSetInitialPasswordService", () => { ); }); + it("should create and set master password unlock data to prevent race condition with sync", async () => { + // Arrange + setupMocks({ ...defaultMockConfig, userType }); + + const mockUnlockData = { + salt: credentials.salt, + kdf: credentials.kdfConfig, + masterKeyWrappedUserKey: "wrapped_key_string", + }; + + masterPasswordService.makeMasterPasswordUnlockData.mockResolvedValue( + mockUnlockData as any, + ); + + // Act + await sut.setInitialPassword(credentials, userType, userId); + + // Assert + expect(masterPasswordService.makeMasterPasswordUnlockData).toHaveBeenCalledWith( + credentials.newPassword, + credentials.kdfConfig, + credentials.salt, + masterKeyEncryptedUserKey[0], + ); + expect(masterPasswordService.setMasterPasswordUnlockData).toHaveBeenCalledWith( + mockUnlockData, + userId, + ); + }); + describe("given resetPasswordAutoEnroll is true", () => { it(`should handle reset password (account recovery) auto enroll`, async () => { // Arrange diff --git a/libs/angular/src/auth/password-management/set-initial-password/set-initial-password.component.ts b/libs/angular/src/auth/password-management/set-initial-password/set-initial-password.component.ts index 805fe3c0173..0e0bae62b9a 100644 --- a/libs/angular/src/auth/password-management/set-initial-password/set-initial-password.component.ts +++ b/libs/angular/src/auth/password-management/set-initial-password/set-initial-password.component.ts @@ -214,6 +214,8 @@ export class SetInitialPasswordComponent implements OnInit { assertTruthy(passwordInputResult.newServerMasterKeyHash, "newServerMasterKeyHash", ctx); assertTruthy(passwordInputResult.newLocalMasterKeyHash, "newLocalMasterKeyHash", ctx); assertTruthy(passwordInputResult.kdfConfig, "kdfConfig", ctx); + assertTruthy(passwordInputResult.newPassword, "newPassword", ctx); + assertTruthy(passwordInputResult.salt, "salt", ctx); assertTruthy(this.orgSsoIdentifier, "orgSsoIdentifier", ctx); assertTruthy(this.orgId, "orgId", ctx); assertTruthy(this.userType, "userType", ctx); @@ -231,6 +233,8 @@ export class SetInitialPasswordComponent implements OnInit { orgSsoIdentifier: this.orgSsoIdentifier, orgId: this.orgId, resetPasswordAutoEnroll: this.resetPasswordAutoEnroll, + newPassword: passwordInputResult.newPassword, + salt: passwordInputResult.salt, }; await this.setInitialPasswordService.setInitialPassword( diff --git a/libs/angular/src/auth/password-management/set-initial-password/set-initial-password.service.abstraction.ts b/libs/angular/src/auth/password-management/set-initial-password/set-initial-password.service.abstraction.ts index c1f6ba1a5ec..5620194e1bb 100644 --- a/libs/angular/src/auth/password-management/set-initial-password/set-initial-password.service.abstraction.ts +++ b/libs/angular/src/auth/password-management/set-initial-password/set-initial-password.service.abstraction.ts @@ -1,3 +1,4 @@ +import { MasterPasswordSalt } from "@bitwarden/common/key-management/master-password/types/master-password.types"; import { UserId } from "@bitwarden/common/types/guid"; import { MasterKey } from "@bitwarden/common/types/key"; import { KdfConfig } from "@bitwarden/key-management"; @@ -50,6 +51,8 @@ export interface SetInitialPasswordCredentials { orgSsoIdentifier: string; orgId: string; resetPasswordAutoEnroll: boolean; + newPassword: string; + salt: MasterPasswordSalt; } export interface SetInitialPasswordTdeOffboardingCredentials { From ea975610e6cc77d8b2257dbd7edd35fdfe9a6a60 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 19 Dec 2025 17:39:59 -0500 Subject: [PATCH 139/188] [deps] Platform: Update electron to v39 (#17301) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [deps] Platform: Update electron to v39 * Update and change builder --------- 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 6e89799e9c4..f979df81fd0 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": "37.7.0", + "electronVersion": "39.2.6", "generateUpdatesFilesForAllChannels": true, "publish": { "provider": "generic", diff --git a/package-lock.json b/package-lock.json index 373cc4b0876..c24e8cc45a3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -131,7 +131,7 @@ "copy-webpack-plugin": "13.0.1", "cross-env": "10.1.0", "css-loader": "7.1.2", - "electron": "37.7.0", + "electron": "39.2.6", "electron-builder": "26.0.12", "electron-log": "5.4.3", "electron-reload": "2.0.0-alpha.1", @@ -21928,9 +21928,9 @@ } }, "node_modules/electron": { - "version": "37.7.0", - "resolved": "https://registry.npmjs.org/electron/-/electron-37.7.0.tgz", - "integrity": "sha512-LBzvfrS0aalynOsnC11AD7zeoU8eOois090mzLpQM3K8yZ2N04i2ZW9qmHOTFLrXlKvrwRc7EbyQf1u8XHMl6Q==", + "version": "39.2.6", + "resolved": "https://registry.npmjs.org/electron/-/electron-39.2.6.tgz", + "integrity": "sha512-dHBgTodWBZd+tL1Dt0PSh/CFLHeDkFCTKCTXu1dgPhlE9Z3k2zzlBQ9B2oW55CFsKanBDHiUomHJNw0XaSdQpA==", "dev": true, "hasInstallScript": true, "license": "MIT", diff --git a/package.json b/package.json index 9276b94286e..7b2b3701fdf 100644 --- a/package.json +++ b/package.json @@ -93,7 +93,7 @@ "copy-webpack-plugin": "13.0.1", "cross-env": "10.1.0", "css-loader": "7.1.2", - "electron": "37.7.0", + "electron": "39.2.6", "electron-builder": "26.0.12", "electron-log": "5.4.3", "electron-reload": "2.0.0-alpha.1", From 2d6d1dfe5341738b54d2749024e454e51aae166f Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann <mail@quexten.com> Date: Sun, 21 Dec 2025 21:46:18 +0100 Subject: [PATCH 140/188] [PM-29929] Exclude organization vault items in data recovery tool (#18044) * Exclude organization vault items in data recovery tool * Allow undefined organization id --- .../data-recovery/steps/cipher-step.spec.ts | 241 ++++++++++++++++++ .../data-recovery/steps/cipher-step.ts | 6 +- 2 files changed, 246 insertions(+), 1 deletion(-) create mode 100644 apps/web/src/app/key-management/data-recovery/steps/cipher-step.spec.ts diff --git a/apps/web/src/app/key-management/data-recovery/steps/cipher-step.spec.ts b/apps/web/src/app/key-management/data-recovery/steps/cipher-step.spec.ts new file mode 100644 index 00000000000..a894fce0c41 --- /dev/null +++ b/apps/web/src/app/key-management/data-recovery/steps/cipher-step.spec.ts @@ -0,0 +1,241 @@ +import { mock, MockProxy } from "jest-mock-extended"; + +import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { CipherEncryptionService } from "@bitwarden/common/vault/abstractions/cipher-encryption.service"; +import { Cipher } from "@bitwarden/common/vault/models/domain/cipher"; +import { DialogService } from "@bitwarden/components"; +import { UserId } from "@bitwarden/user-core"; + +import { LogRecorder } from "../log-recorder"; + +import { CipherStep } from "./cipher-step"; +import { RecoveryWorkingData } from "./recovery-step"; + +describe("CipherStep", () => { + let cipherStep: CipherStep; + let apiService: MockProxy<ApiService>; + let cipherEncryptionService: MockProxy<CipherEncryptionService>; + let dialogService: MockProxy<DialogService>; + let logger: MockProxy<LogRecorder>; + + beforeEach(() => { + apiService = mock<ApiService>(); + cipherEncryptionService = mock<CipherEncryptionService>(); + dialogService = mock<DialogService>(); + logger = mock<LogRecorder>(); + + cipherStep = new CipherStep(apiService, cipherEncryptionService, dialogService); + }); + + describe("runDiagnostics", () => { + it("returns false and logs error when userId is missing", async () => { + const workingData: RecoveryWorkingData = { + userId: null, + userKey: null, + encryptedPrivateKey: null, + isPrivateKeyCorrupt: false, + ciphers: [], + folders: [], + }; + + const result = await cipherStep.runDiagnostics(workingData, logger); + + expect(result).toBe(false); + expect(logger.record).toHaveBeenCalledWith("Missing user ID"); + }); + + it("returns true when all user ciphers are decryptable", async () => { + const userId = "user-id" as UserId; + const cipher1 = { id: "cipher-1", organizationId: null } as Cipher; + const cipher2 = { id: "cipher-2", organizationId: null } as Cipher; + + const workingData: RecoveryWorkingData = { + userId, + userKey: null, + encryptedPrivateKey: null, + isPrivateKeyCorrupt: false, + ciphers: [cipher1, cipher2], + folders: [], + }; + + cipherEncryptionService.decrypt.mockResolvedValue({} as any); + + const result = await cipherStep.runDiagnostics(workingData, logger); + + expect(result).toBe(true); + expect(cipherEncryptionService.decrypt).toHaveBeenCalledWith(cipher1, userId); + expect(cipherEncryptionService.decrypt).toHaveBeenCalledWith(cipher2, userId); + }); + + it("filters out organization ciphers (organizationId !== null) and only processes user ciphers", async () => { + const userId = "user-id" as UserId; + const userCipher = { id: "user-cipher", organizationId: null } as Cipher; + const orgCipher1 = { id: "org-cipher-1", organizationId: "org-1" } as Cipher; + const orgCipher2 = { id: "org-cipher-2", organizationId: "org-2" } as Cipher; + + const workingData: RecoveryWorkingData = { + userId, + userKey: null, + encryptedPrivateKey: null, + isPrivateKeyCorrupt: false, + ciphers: [userCipher, orgCipher1, orgCipher2], + folders: [], + }; + + cipherEncryptionService.decrypt.mockResolvedValue({} as any); + + const result = await cipherStep.runDiagnostics(workingData, logger); + + expect(result).toBe(true); + // Only user cipher should be processed + expect(cipherEncryptionService.decrypt).toHaveBeenCalledTimes(1); + expect(cipherEncryptionService.decrypt).toHaveBeenCalledWith(userCipher, userId); + // Organization ciphers should not be processed + expect(cipherEncryptionService.decrypt).not.toHaveBeenCalledWith(orgCipher1, userId); + expect(cipherEncryptionService.decrypt).not.toHaveBeenCalledWith(orgCipher2, userId); + }); + + it("returns false and records undecryptable user ciphers", async () => { + const userId = "user-id" as UserId; + const cipher1 = { id: "cipher-1", organizationId: null } as Cipher; + const cipher2 = { id: "cipher-2", organizationId: null } as Cipher; + const cipher3 = { id: "cipher-3", organizationId: null } as Cipher; + + const workingData: RecoveryWorkingData = { + userId, + userKey: null, + encryptedPrivateKey: null, + isPrivateKeyCorrupt: false, + ciphers: [cipher1, cipher2, cipher3], + folders: [], + }; + + cipherEncryptionService.decrypt + .mockResolvedValueOnce({} as any) // cipher1 succeeds + .mockRejectedValueOnce(new Error("Decryption failed")) // cipher2 fails + .mockRejectedValueOnce(new Error("Decryption failed")); // cipher3 fails + + const result = await cipherStep.runDiagnostics(workingData, logger); + + expect(result).toBe(false); + expect(logger.record).toHaveBeenCalledWith("Cipher ID cipher-2 was undecryptable"); + expect(logger.record).toHaveBeenCalledWith("Cipher ID cipher-3 was undecryptable"); + expect(logger.record).toHaveBeenCalledWith("Found 2 undecryptable ciphers"); + }); + }); + + describe("canRecover", () => { + it("returns false when there are no undecryptable ciphers", async () => { + const userId = "user-id" as UserId; + const workingData: RecoveryWorkingData = { + userId, + userKey: null, + encryptedPrivateKey: null, + isPrivateKeyCorrupt: false, + ciphers: [{ id: "cipher-1", organizationId: null } as Cipher], + folders: [], + }; + + cipherEncryptionService.decrypt.mockResolvedValue({} as any); + + await cipherStep.runDiagnostics(workingData, logger); + const result = cipherStep.canRecover(workingData); + + expect(result).toBe(false); + }); + + it("returns true when there are undecryptable ciphers", async () => { + const userId = "user-id" as UserId; + const workingData: RecoveryWorkingData = { + userId, + userKey: null, + encryptedPrivateKey: null, + isPrivateKeyCorrupt: false, + ciphers: [{ id: "cipher-1", organizationId: null } as Cipher], + folders: [], + }; + + cipherEncryptionService.decrypt.mockRejectedValue(new Error("Decryption failed")); + + await cipherStep.runDiagnostics(workingData, logger); + const result = cipherStep.canRecover(workingData); + + expect(result).toBe(true); + }); + }); + + describe("runRecovery", () => { + it("logs and returns early when there are no undecryptable ciphers", async () => { + const workingData: RecoveryWorkingData = { + userId: "user-id" as UserId, + userKey: null, + encryptedPrivateKey: null, + isPrivateKeyCorrupt: false, + ciphers: [], + folders: [], + }; + + await cipherStep.runRecovery(workingData, logger); + + expect(logger.record).toHaveBeenCalledWith("No undecryptable ciphers to recover"); + expect(dialogService.openSimpleDialog).not.toHaveBeenCalled(); + expect(apiService.deleteCipher).not.toHaveBeenCalled(); + }); + + it("throws error when user cancels deletion", async () => { + const userId = "user-id" as UserId; + const workingData: RecoveryWorkingData = { + userId, + userKey: null, + encryptedPrivateKey: null, + isPrivateKeyCorrupt: false, + ciphers: [{ id: "cipher-1", organizationId: null } as Cipher], + folders: [], + }; + + cipherEncryptionService.decrypt.mockRejectedValue(new Error("Decryption failed")); + await cipherStep.runDiagnostics(workingData, logger); + + dialogService.openSimpleDialog.mockResolvedValue(false); + + await expect(cipherStep.runRecovery(workingData, logger)).rejects.toThrow( + "Cipher recovery cancelled by user", + ); + + expect(logger.record).toHaveBeenCalledWith("Showing confirmation dialog for 1 ciphers"); + expect(logger.record).toHaveBeenCalledWith("User cancelled cipher deletion"); + expect(apiService.deleteCipher).not.toHaveBeenCalled(); + }); + + it("deletes undecryptable ciphers when user confirms", async () => { + const userId = "user-id" as UserId; + const cipher1 = { id: "cipher-1", organizationId: null } as Cipher; + const cipher2 = { id: "cipher-2", organizationId: null } as Cipher; + + const workingData: RecoveryWorkingData = { + userId, + userKey: null, + encryptedPrivateKey: null, + isPrivateKeyCorrupt: false, + ciphers: [cipher1, cipher2], + folders: [], + }; + + cipherEncryptionService.decrypt.mockRejectedValue(new Error("Decryption failed")); + await cipherStep.runDiagnostics(workingData, logger); + + dialogService.openSimpleDialog.mockResolvedValue(true); + apiService.deleteCipher.mockResolvedValue(undefined); + + await cipherStep.runRecovery(workingData, logger); + + expect(logger.record).toHaveBeenCalledWith("Showing confirmation dialog for 2 ciphers"); + expect(logger.record).toHaveBeenCalledWith("Deleting 2 ciphers"); + expect(apiService.deleteCipher).toHaveBeenCalledWith("cipher-1"); + expect(apiService.deleteCipher).toHaveBeenCalledWith("cipher-2"); + expect(logger.record).toHaveBeenCalledWith("Deleted cipher cipher-1"); + expect(logger.record).toHaveBeenCalledWith("Deleted cipher cipher-2"); + expect(logger.record).toHaveBeenCalledWith("Successfully deleted 2 ciphers"); + }); + }); +}); diff --git a/apps/web/src/app/key-management/data-recovery/steps/cipher-step.ts b/apps/web/src/app/key-management/data-recovery/steps/cipher-step.ts index 34e8cbdc9f3..b44e8afc54d 100644 --- a/apps/web/src/app/key-management/data-recovery/steps/cipher-step.ts +++ b/apps/web/src/app/key-management/data-recovery/steps/cipher-step.ts @@ -24,7 +24,11 @@ export class CipherStep implements RecoveryStep { } this.undecryptableCipherIds = []; - for (const cipher of workingData.ciphers) { + // The tool is currently only implemented to handle ciphers that are corrupt for a user. For an organization, the case of + // local user not having access to the organization key is not properly handled here, and should be implemented separately. + // For now, this just filters out and does not consider corrupt organization ciphers. + const userCiphers = workingData.ciphers.filter((c) => c.organizationId == null); + for (const cipher of userCiphers) { try { await this.cipherService.decrypt(cipher, workingData.userId); } catch { From 5c2cfee8dfe279f57376240cb4857d1581152816 Mon Sep 17 00:00:00 2001 From: "bw-ghapp[bot]" <178206702+bw-ghapp[bot]@users.noreply.github.com> Date: Mon, 22 Dec 2025 07:42:07 +0000 Subject: [PATCH 141/188] Autosync the updated translations (#18087) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/desktop/src/locales/az/messages.json | 8 +-- apps/desktop/src/locales/sk/messages.json | 6 +- apps/desktop/src/locales/zh_CN/messages.json | 8 +-- apps/desktop/src/locales/zh_TW/messages.json | 60 ++++++++++---------- 4 files changed, 41 insertions(+), 41 deletions(-) diff --git a/apps/desktop/src/locales/az/messages.json b/apps/desktop/src/locales/az/messages.json index a031860334d..ba6cad30e4f 100644 --- a/apps/desktop/src/locales/az/messages.json +++ b/apps/desktop/src/locales/az/messages.json @@ -1776,19 +1776,19 @@ "message": "Buradan xaricə köçür" }, "exportNoun": { - "message": "Export", + "message": "Xaricə köçürmə", "description": "The noun form of the word Export" }, "exportVerb": { - "message": "Export", + "message": "Xaricə köçür", "description": "The verb form of the word Export" }, "importNoun": { - "message": "Import", + "message": "Daxilə köçürmə", "description": "The noun form of the word Import" }, "importVerb": { - "message": "Import", + "message": "Daxilə köçür", "description": "The verb form of the word Import" }, "fileFormat": { diff --git a/apps/desktop/src/locales/sk/messages.json b/apps/desktop/src/locales/sk/messages.json index 6e76a04a9fa..23c3d3ae3d0 100644 --- a/apps/desktop/src/locales/sk/messages.json +++ b/apps/desktop/src/locales/sk/messages.json @@ -1414,7 +1414,7 @@ "message": "Zobraziť Bitwarden v Docku aj keď je minimalizovaný na panel úloh." }, "confirmTrayTitle": { - "message": "Potvrdiť vypnutie systémovej lišty" + "message": "Potvrdiť skrývanie systémovej lišty" }, "confirmTrayDesc": { "message": "Vypnutím tohto nastavenia vypnete aj ostatné nastavenia súvisiace so systémovou lištou." @@ -2849,10 +2849,10 @@ "message": "Použiť možnosti subadresovania svojho poskytovateľa e-mailu." }, "catchallEmail": { - "message": "E-mail Catch-all" + "message": "Doménový kôš" }, "catchallEmailDesc": { - "message": "Použiť doručenú poštu typu catch-all nastavenú na doméne." + "message": "Použiť nastavený doménový kôš." }, "useThisEmail": { "message": "Použiť tento e-mail" diff --git a/apps/desktop/src/locales/zh_CN/messages.json b/apps/desktop/src/locales/zh_CN/messages.json index f4640ea9d00..7d1c1648bb6 100644 --- a/apps/desktop/src/locales/zh_CN/messages.json +++ b/apps/desktop/src/locales/zh_CN/messages.json @@ -1776,19 +1776,19 @@ "message": "导出自" }, "exportNoun": { - "message": "Export", + "message": "导出", "description": "The noun form of the word Export" }, "exportVerb": { - "message": "Export", + "message": "导出", "description": "The verb form of the word Export" }, "importNoun": { - "message": "Import", + "message": "导入", "description": "The noun form of the word Import" }, "importVerb": { - "message": "Import", + "message": "导入", "description": "The verb form of the word Import" }, "fileFormat": { diff --git a/apps/desktop/src/locales/zh_TW/messages.json b/apps/desktop/src/locales/zh_TW/messages.json index ea4d8cbc1b0..7b5b352d5cb 100644 --- a/apps/desktop/src/locales/zh_TW/messages.json +++ b/apps/desktop/src/locales/zh_TW/messages.json @@ -709,7 +709,7 @@ "message": "新增附件" }, "itemsTransferred": { - "message": "Items transferred" + "message": "項目已轉移" }, "fixEncryption": { "message": "修正加密" @@ -1199,7 +1199,7 @@ "message": "關注我們" }, "syncNow": { - "message": "Sync now" + "message": "立即同步" }, "changeMasterPass": { "message": "變更主密碼" @@ -1776,19 +1776,19 @@ "message": "匯出自" }, "exportNoun": { - "message": "Export", + "message": "匯出", "description": "The noun form of the word Export" }, "exportVerb": { - "message": "Export", + "message": "匯出", "description": "The verb form of the word Export" }, "importNoun": { - "message": "Import", + "message": "匯入", "description": "The noun form of the word Import" }, "importVerb": { - "message": "Import", + "message": "匯入", "description": "The verb form of the word Import" }, "fileFormat": { @@ -4344,43 +4344,43 @@ "message": "升級到 Premium" }, "removeMasterPasswordForOrgUserKeyConnector": { - "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + "message": "您的組織已不再使用主密碼登入 Bitwarden。若要繼續,請驗證組織與網域。" }, "continueWithLogIn": { - "message": "Continue with log in" + "message": "繼續登入" }, "doNotContinue": { - "message": "Do not continue" + "message": "不要繼續" }, "domain": { - "message": "Domain" + "message": "網域" }, "keyConnectorDomainTooltip": { - "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + "message": "此網域將儲存您帳號的加密金鑰,請確認您信任它。若不確定,請洽詢您的管理員。" }, "verifyYourOrganization": { - "message": "Verify your organization to log in" + "message": "驗證您的組織以登入" }, "organizationVerified": { - "message": "Organization verified" + "message": "組織已驗證" }, "domainVerified": { - "message": "Domain verified" + "message": "已驗證網域" }, "leaveOrganizationContent": { - "message": "If you don't verify your organization, your access to the organization will be revoked." + "message": "若您未驗證組織,將會被撤銷對該組織的存取權限。" }, "leaveNow": { - "message": "Leave now" + "message": "立即離開" }, "verifyYourDomainToLogin": { - "message": "Verify your domain to log in" + "message": "驗證您的網域以登入" }, "verifyYourDomainDescription": { - "message": "To continue with log in, verify this domain." + "message": "若要繼續登入,請驗證此網域。" }, "confirmKeyConnectorOrganizationUserDescription": { - "message": "To continue with log in, verify the organization and domain." + "message": "若要繼續登入,請驗證組織與網域。" }, "sessionTimeoutSettingsAction": { "message": "逾時後動作" @@ -4430,19 +4430,19 @@ "message": "設定一個解鎖方式來變更您的密碼庫逾時動作。" }, "upgrade": { - "message": "Upgrade" + "message": "升級" }, "leaveConfirmationDialogTitle": { - "message": "Are you sure you want to leave?" + "message": "確定要離開嗎?" }, "leaveConfirmationDialogContentOne": { - "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + "message": "若選擇拒絕,您的個人項目將保留在帳號中,但您將失去對共用項目與組織功能的存取權。" }, "leaveConfirmationDialogContentTwo": { - "message": "Contact your admin to regain access." + "message": "請聯絡您的管理員以重新取得存取權限。" }, "leaveConfirmationDialogConfirmButton": { - "message": "Leave $ORGANIZATION$", + "message": "離開 $ORGANIZATION$", "placeholders": { "organization": { "content": "$1", @@ -4451,10 +4451,10 @@ } }, "howToManageMyVault": { - "message": "How do I manage my vault?" + "message": "我要如何管理我的密碼庫?" }, "transferItemsToOrganizationTitle": { - "message": "Transfer items to $ORGANIZATION$", + "message": "將項目轉移至 $ORGANIZATION$", "placeholders": { "organization": { "content": "$1", @@ -4463,7 +4463,7 @@ } }, "transferItemsToOrganizationContent": { - "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "message": "$ORGANIZATION$ 為了安全性與合規性,要求所有項目皆由組織擁有。點擊接受即可轉移您項目的擁有權。", "placeholders": { "organization": { "content": "$1", @@ -4472,12 +4472,12 @@ } }, "acceptTransfer": { - "message": "Accept transfer" + "message": "同意轉移" }, "declineAndLeave": { - "message": "Decline and leave" + "message": "拒絕並離開" }, "whyAmISeeingThis": { - "message": "Why am I seeing this?" + "message": "為什麼我會看到此訊息?" } } From e73d5770d338cdffd7a50f9cd2ba5cf2a7a718fc Mon Sep 17 00:00:00 2001 From: "bw-ghapp[bot]" <178206702+bw-ghapp[bot]@users.noreply.github.com> Date: Mon, 22 Dec 2025 07:42:26 +0000 Subject: [PATCH 142/188] Autosync the updated translations (#18088) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/browser/src/_locales/az/messages.json | 8 +-- apps/browser/src/_locales/sk/messages.json | 6 +- apps/browser/src/_locales/th/messages.json | 8 +-- apps/browser/src/_locales/zh_CN/messages.json | 8 +-- apps/browser/src/_locales/zh_TW/messages.json | 66 +++++++++---------- 5 files changed, 48 insertions(+), 48 deletions(-) diff --git a/apps/browser/src/_locales/az/messages.json b/apps/browser/src/_locales/az/messages.json index 3efc2627018..3f98313c2b8 100644 --- a/apps/browser/src/_locales/az/messages.json +++ b/apps/browser/src/_locales/az/messages.json @@ -1323,19 +1323,19 @@ "message": "Buradan xaricə köçür" }, "exportVerb": { - "message": "Export", + "message": "Xaricə köçür", "description": "The verb form of the word Export" }, "exportNoun": { - "message": "Export", + "message": "Xaricə köçürmə", "description": "The noun form of the word Export" }, "importNoun": { - "message": "Import", + "message": "Daxilə köçürmə", "description": "The noun form of the word Import" }, "importVerb": { - "message": "Import", + "message": "Daxilə köçür", "description": "The verb form of the word Import" }, "fileFormat": { diff --git a/apps/browser/src/_locales/sk/messages.json b/apps/browser/src/_locales/sk/messages.json index 343f16a921b..ffda610b8f0 100644 --- a/apps/browser/src/_locales/sk/messages.json +++ b/apps/browser/src/_locales/sk/messages.json @@ -1486,7 +1486,7 @@ "message": "Vyberte súbor" }, "itemsTransferred": { - "message": "Items transferred" + "message": "Položky boli prenesené" }, "maxFileSize": { "message": "Maximálna veľkosť súboru je 500 MB." @@ -3408,10 +3408,10 @@ "message": "Použiť možnosti subadresovania svojho poskytovateľa e-mailu." }, "catchallEmail": { - "message": "Catch-all e-mail" + "message": "Doménový kôš" }, "catchallEmailDesc": { - "message": "Použiť doručenú poštu typu catch-all nastavenú na doméne." + "message": "Použiť nastavený doménový kôš." }, "random": { "message": "Náhodné" diff --git a/apps/browser/src/_locales/th/messages.json b/apps/browser/src/_locales/th/messages.json index 50bac4d6a44..49bda58b558 100644 --- a/apps/browser/src/_locales/th/messages.json +++ b/apps/browser/src/_locales/th/messages.json @@ -1323,19 +1323,19 @@ "message": "ส่งออกจาก" }, "exportVerb": { - "message": "Export", + "message": "ส่งออก", "description": "The verb form of the word Export" }, "exportNoun": { - "message": "Export", + "message": "ส่งออก", "description": "The noun form of the word Export" }, "importNoun": { - "message": "Import", + "message": "นำเข้า", "description": "The noun form of the word Import" }, "importVerb": { - "message": "Import", + "message": "นำเข้า", "description": "The verb form of the word Import" }, "fileFormat": { diff --git a/apps/browser/src/_locales/zh_CN/messages.json b/apps/browser/src/_locales/zh_CN/messages.json index 53d51a8e16f..a699be016eb 100644 --- a/apps/browser/src/_locales/zh_CN/messages.json +++ b/apps/browser/src/_locales/zh_CN/messages.json @@ -1323,19 +1323,19 @@ "message": "导出自" }, "exportVerb": { - "message": "Export", + "message": "导出", "description": "The verb form of the word Export" }, "exportNoun": { - "message": "Export", + "message": "导出", "description": "The noun form of the word Export" }, "importNoun": { - "message": "Import", + "message": "导入", "description": "The noun form of the word Import" }, "importVerb": { - "message": "Import", + "message": "导入", "description": "The verb form of the word Import" }, "fileFormat": { diff --git a/apps/browser/src/_locales/zh_TW/messages.json b/apps/browser/src/_locales/zh_TW/messages.json index b36ba76f0a1..abb25c48b43 100644 --- a/apps/browser/src/_locales/zh_TW/messages.json +++ b/apps/browser/src/_locales/zh_TW/messages.json @@ -437,7 +437,7 @@ "message": "同步" }, "syncNow": { - "message": "Sync now" + "message": "立即同步" }, "lastSync": { "message": "上次同步於:" @@ -1323,19 +1323,19 @@ "message": "匯出自" }, "exportVerb": { - "message": "Export", + "message": "匯出", "description": "The verb form of the word Export" }, "exportNoun": { - "message": "Export", + "message": "匯出", "description": "The noun form of the word Export" }, "importNoun": { - "message": "Import", + "message": "匯入", "description": "The noun form of the word Import" }, "importVerb": { - "message": "Import", + "message": "匯入", "description": "The verb form of the word Import" }, "fileFormat": { @@ -1486,7 +1486,7 @@ "message": "選取檔案" }, "itemsTransferred": { - "message": "Items transferred" + "message": "項目已轉移" }, "maxFileSize": { "message": "檔案最大為 500MB。" @@ -4812,13 +4812,13 @@ "message": "帳戶安全性" }, "phishingBlocker": { - "message": "Phishing Blocker" + "message": "釣魚封鎖器" }, "enablePhishingDetection": { - "message": "Phishing detection" + "message": "釣魚偵測" }, "enablePhishingDetectionDesc": { - "message": "Display warning before accessing suspected phishing sites" + "message": "在存取疑似釣魚網站前顯示警告" }, "notifications": { "message": "通知" @@ -5904,43 +5904,43 @@ "message": "支付卡號碼" }, "removeMasterPasswordForOrgUserKeyConnector": { - "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + "message": "您的組織已不再使用主密碼登入 Bitwarden。若要繼續,請驗證組織與網域。" }, "continueWithLogIn": { - "message": "Continue with log in" + "message": "繼續登入" }, "doNotContinue": { - "message": "Do not continue" + "message": "不要繼續" }, "domain": { - "message": "Domain" + "message": "網域" }, "keyConnectorDomainTooltip": { - "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + "message": "此網域將儲存您帳號的加密金鑰,請確認您信任它。若不確定,請洽詢您的管理員。" }, "verifyYourOrganization": { - "message": "Verify your organization to log in" + "message": "驗證您的組織以登入" }, "organizationVerified": { - "message": "Organization verified" + "message": "組織已驗證" }, "domainVerified": { - "message": "Domain verified" + "message": "已驗證網域" }, "leaveOrganizationContent": { - "message": "If you don't verify your organization, your access to the organization will be revoked." + "message": "若您未驗證組織,將會被撤銷對該組織的存取權限。" }, "leaveNow": { - "message": "Leave now" + "message": "立即離開" }, "verifyYourDomainToLogin": { - "message": "Verify your domain to log in" + "message": "驗證您的網域以登入" }, "verifyYourDomainDescription": { - "message": "To continue with log in, verify this domain." + "message": "若要繼續登入,請驗證此網域。" }, "confirmKeyConnectorOrganizationUserDescription": { - "message": "To continue with log in, verify the organization and domain." + "message": "若要繼續登入,請驗證組織與網域。" }, "sessionTimeoutSettingsAction": { "message": "逾時後動作" @@ -5990,19 +5990,19 @@ "message": "設定一個解鎖方式來變更您的密碼庫逾時動作。" }, "upgrade": { - "message": "Upgrade" + "message": "升級" }, "leaveConfirmationDialogTitle": { - "message": "Are you sure you want to leave?" + "message": "確定要離開嗎?" }, "leaveConfirmationDialogContentOne": { - "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + "message": "若選擇拒絕,您的個人項目將保留在帳號中,但您將失去對共用項目與組織功能的存取權。" }, "leaveConfirmationDialogContentTwo": { - "message": "Contact your admin to regain access." + "message": "請聯絡您的管理員以重新取得存取權限。" }, "leaveConfirmationDialogConfirmButton": { - "message": "Leave $ORGANIZATION$", + "message": "離開 $ORGANIZATION$", "placeholders": { "organization": { "content": "$1", @@ -6011,10 +6011,10 @@ } }, "howToManageMyVault": { - "message": "How do I manage my vault?" + "message": "我要如何管理我的密碼庫?" }, "transferItemsToOrganizationTitle": { - "message": "Transfer items to $ORGANIZATION$", + "message": "將項目轉移至 $ORGANIZATION$", "placeholders": { "organization": { "content": "$1", @@ -6023,7 +6023,7 @@ } }, "transferItemsToOrganizationContent": { - "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "message": "$ORGANIZATION$ 為了安全性與合規性,要求所有項目皆由組織擁有。點擊接受即可轉移您項目的擁有權。", "placeholders": { "organization": { "content": "$1", @@ -6032,12 +6032,12 @@ } }, "acceptTransfer": { - "message": "Accept transfer" + "message": "同意轉移" }, "declineAndLeave": { - "message": "Decline and leave" + "message": "拒絕並離開" }, "whyAmISeeingThis": { - "message": "Why am I seeing this?" + "message": "為什麼我會看到此訊息?" } } From ec20e5937a2da88e62a9e5d3627901de309d48e2 Mon Sep 17 00:00:00 2001 From: "bw-ghapp[bot]" <178206702+bw-ghapp[bot]@users.noreply.github.com> Date: Mon, 22 Dec 2025 07:43:04 +0000 Subject: [PATCH 143/188] Autosync the updated translations (#18089) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/web/src/locales/az/messages.json | 54 +++++----- apps/web/src/locales/de/messages.json | 14 +-- apps/web/src/locales/hu/messages.json | 4 +- apps/web/src/locales/lv/messages.json | 14 +-- apps/web/src/locales/pt_BR/messages.json | 4 +- apps/web/src/locales/pt_PT/messages.json | 4 +- apps/web/src/locales/sk/messages.json | 4 +- apps/web/src/locales/zh_CN/messages.json | 34 +++---- apps/web/src/locales/zh_TW/messages.json | 124 +++++++++++------------ 9 files changed, 128 insertions(+), 128 deletions(-) diff --git a/apps/web/src/locales/az/messages.json b/apps/web/src/locales/az/messages.json index 1b6593578a6..275ee56dd5c 100644 --- a/apps/web/src/locales/az/messages.json +++ b/apps/web/src/locales/az/messages.json @@ -392,7 +392,7 @@ "message": "Yeni tətbiqlər incələ" }, "reviewNewAppsDescription": { - "message": "Review new applications with vulnerable items and mark those you’d like to monitor closely as critical. Then, you’ll be able to assign security tasks to members to remove risks." + "message": "Həssas elementlərə sahib yeni tətbiqləri incələyin və diqqətlə izləmək istədiklərinizi kritik olaraq işarələyin. Sonra, riskləri xaric etmək üçün üzvlərə təhlükəsizlik tapşırıqları təyin edə biləcəksiniz." }, "clickIconToMarkAppAsCritical": { "message": "Bir tətbiqi kritik olaraq işarələmək üçün ulduz ikonuna klikləyin" @@ -1970,11 +1970,11 @@ "message": "Hesab şifrələmə açarları, hər Bitwarden istifadəçi hesabı üçün unikaldır, buna görə də şifrələnmiş bir ixracı, fərqli bir hesaba idxal edə bilməzsiniz." }, "exportNoun": { - "message": "Export", + "message": "Xaricə köçürmə", "description": "The noun form of the word Export" }, "exportVerb": { - "message": "Export", + "message": "Xaricə köçür", "description": "The verb form of the word Export" }, "exportFrom": { @@ -2303,11 +2303,11 @@ "message": "Alətlər" }, "importNoun": { - "message": "Import", + "message": "Daxilə köçürmə", "description": "The noun form of the word Import" }, "importVerb": { - "message": "Import", + "message": "Daxilə köçür", "description": "The verb form of the word Import" }, "importData": { @@ -3294,7 +3294,7 @@ "message": "Bulud Abunəliyini Başlat" }, "launchCloudSubscriptionSentenceCase": { - "message": "Launch cloud subscription" + "message": "Bulud abunəliyini başlat" }, "storage": { "message": "Saxlama" @@ -4212,10 +4212,10 @@ } }, "userAcceptedTransfer": { - "message": "Accepted transfer to organization ownership." + "message": "Təşkilatın sahibliyinə ötürülmə qəbul edildi." }, "userDeclinedTransfer": { - "message": "Revoked for declining transfer to organization ownership." + "message": "Təşkilatın sahibliyinə ötürülməyə rədd cavabı verildiyi üçün ləğv edildi." }, "invitedUserId": { "message": "$ID$ istifadəçisi dəvət edildi.", @@ -6758,10 +6758,10 @@ "message": "Cihaz mühafizəsi barədə daha ətraflı" }, "sessionTimeoutConfirmationOnSystemLockTitle": { - "message": "\"System lock\" will only apply to the browser and desktop app" + "message": "\"Sistem kilidi\", yalnız brauzer və masaüstü tətbiqi üçün qüvvəyə minəcək" }, "sessionTimeoutConfirmationOnSystemLockDescription": { - "message": "The mobile and web app will use \"on app restart\" as their maximum allowed timeout, since the option is not supported." + "message": "Mobil və veb tətbiqi, dəstəklənməyən bir seçim olduğu üçün icazə verilən maksimum bitmə vaxtı olaraq \"tətbiq başladılanda\"nı istifadə edəcək. " }, "vaultTimeoutPolicyInEffect": { "message": "Təşkilatınızın siyasətləri, icazə verilən maksimum seyf bitmə vaxtını $HOURS$ saat $MINUTES$ dəqiqə olaraq ayarladı.", @@ -9905,11 +9905,11 @@ "description": "An option for the offboarding survey shown when a user cancels their subscription." }, "switchToFreePlan": { - "message": "Switching to free plan", + "message": "Ödənişsiz plana keçilir", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, "switchToFreeOrg": { - "message": "Switching to free organization", + "message": "Ödənişsiz təşkilata keçilir", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, "freeForOneYear": { @@ -9943,7 +9943,7 @@ "message": "Tapşırıq təyin et" }, "assignSecurityTasksToMembers": { - "message": "Send notifications to change passwords" + "message": "Parol dəyişdirmə bildirişlərini göndər" }, "assignToCollections": { "message": "Kolleksiyalara təyin et" @@ -12208,13 +12208,13 @@ "message": "Ödənişsiz Ailələr sınağını başlat" }, "blockClaimedDomainAccountCreation": { - "message": "Block account creation for claimed domains" + "message": "Götürülmüş domenlər üçün hesab yaradılmasını əngəllə" }, "blockClaimedDomainAccountCreationDesc": { - "message": "Prevent users from creating accounts outside of your organization using email addresses from claimed domains." + "message": "İstifadəçilərin, götürülmüş domenlərə aid e-poçt ünvanlarını istifadə edərək təşkilatınızın xaricində hesab yaratmasını önləyin." }, "blockClaimedDomainAccountCreationPrerequisite": { - "message": "A domain must be claimed before activating this policy." + "message": "Bu siyasət aktivləşdirilməzdən əvvəl bir domen götürülməlidir." }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Seyf vaxt bitmə əməliyyatınızı dəyişdirmək üçün bir kilid açma üsulu qurun." @@ -12433,13 +12433,13 @@ "message": "Bunu niyə görürəm?" }, "youHaveBitwardenPremium": { - "message": "You have Bitwarden Premium" + "message": "Sizdə Bitwarden Premium var" }, "viewAndManagePremiumSubscription": { - "message": "View and manage your Premium subscription" + "message": "Premium abunəliyinizi görün və idarə edin" }, "youNeedToUpdateLicenseFile": { - "message": "You'll need to update your license file" + "message": "Lisenziya faylınızı güncəlləməlisiniz" }, "youNeedToUpdateLicenseFileDate": { "message": "$DATE$.", @@ -12451,16 +12451,16 @@ } }, "uploadLicenseFile": { - "message": "Upload license file" + "message": "Lisenziya faylını yüklə" }, "uploadYourLicenseFile": { - "message": "Upload your license file" + "message": "Lisenziya faylınızı yükləyin" }, "uploadYourPremiumLicenseFile": { - "message": "Upload your Premium license file" + "message": "Premium lisenziya faylınızı yükləyin" }, "uploadLicenseFileDesc": { - "message": "Your license file name will be similar to: $FILE_NAME$", + "message": "Lisenziya faylınızın adı $FILE_NAME$ faylı ilə oxşardır", "placeholders": { "file_name": { "content": "$1", @@ -12469,15 +12469,15 @@ } }, "alreadyHaveSubscriptionQuestion": { - "message": "Already have a subscription?" + "message": "Artıq abunəliyiniz var?" }, "alreadyHaveSubscriptionSelfHostedMessage": { - "message": "Open the subscription page on your Bitwarden cloud account and download your license file. Then return to this screen and upload it below." + "message": "Bitwarden bulud hesabınızdakı abunəlik səhifəsini açın və lisenziya faylınızı endirin. Sonra bu ekrana qayıdın və aşağıda yükləyin." }, "viewAllPlans": { - "message": "View all plans" + "message": "Bütün planlara bax" }, "planDescPremium": { - "message": "Complete online security" + "message": "Tam onlayn təhlükəsizlik" } } diff --git a/apps/web/src/locales/de/messages.json b/apps/web/src/locales/de/messages.json index 608421ef132..ccde12d8614 100644 --- a/apps/web/src/locales/de/messages.json +++ b/apps/web/src/locales/de/messages.json @@ -3294,7 +3294,7 @@ "message": "Cloud-Abonnement starten" }, "launchCloudSubscriptionSentenceCase": { - "message": "Launch cloud subscription" + "message": "Cloud-Abonnement starten" }, "storage": { "message": "Speicher" @@ -12433,13 +12433,13 @@ "message": "Warum wird mir das angezeigt?" }, "youHaveBitwardenPremium": { - "message": "You have Bitwarden Premium" + "message": "Du hast Bitwarden Premium" }, "viewAndManagePremiumSubscription": { "message": "View and manage your Premium subscription" }, "youNeedToUpdateLicenseFile": { - "message": "You'll need to update your license file" + "message": "Du musst deine Lizenzdatei aktualisieren" }, "youNeedToUpdateLicenseFileDate": { "message": "$DATE$.", @@ -12457,10 +12457,10 @@ "message": "Lade deine Lizenzdatei hoch" }, "uploadYourPremiumLicenseFile": { - "message": "Upload your Premium license file" + "message": "Lade deine Premium-Lizenzdatei hoch" }, "uploadLicenseFileDesc": { - "message": "Your license file name will be similar to: $FILE_NAME$", + "message": "Dein Lizenzdateiname wird in etwa so aussehen: $FILE_NAME$", "placeholders": { "file_name": { "content": "$1", @@ -12469,13 +12469,13 @@ } }, "alreadyHaveSubscriptionQuestion": { - "message": "Already have a subscription?" + "message": "Du hast bereits ein Abonnement?" }, "alreadyHaveSubscriptionSelfHostedMessage": { "message": "Open the subscription page on your Bitwarden cloud account and download your license file. Then return to this screen and upload it below." }, "viewAllPlans": { - "message": "View all plans" + "message": "Alle Tarife anzeigen" }, "planDescPremium": { "message": "Umfassende Online-Sicherheit" diff --git a/apps/web/src/locales/hu/messages.json b/apps/web/src/locales/hu/messages.json index 7e296cd7092..dff04ac5b3b 100644 --- a/apps/web/src/locales/hu/messages.json +++ b/apps/web/src/locales/hu/messages.json @@ -4212,10 +4212,10 @@ } }, "userAcceptedTransfer": { - "message": "Accepted transfer to organization ownership." + "message": "Az átruházás a szervezet tulajdonába elfogadásra került." }, "userDeclinedTransfer": { - "message": "Revoked for declining transfer to organization ownership." + "message": "A szervezet tulajdonába átruházás visszavonásra került elutasítás miatt." }, "invitedUserId": { "message": "$ID$ azonosítójú felhasználó meghívásra került.", diff --git a/apps/web/src/locales/lv/messages.json b/apps/web/src/locales/lv/messages.json index 6cb8e0a4a6b..eb39c3b8eee 100644 --- a/apps/web/src/locales/lv/messages.json +++ b/apps/web/src/locales/lv/messages.json @@ -4212,10 +4212,10 @@ } }, "userAcceptedTransfer": { - "message": "Accepted transfer to organization ownership." + "message": "Pieņemta īpašumtiesību nodošana apvienībai." }, "userDeclinedTransfer": { - "message": "Revoked for declining transfer to organization ownership." + "message": "Atsaukts īpašumtiesību nodošanas apvienībai noraidīšanas dēļ." }, "invitedUserId": { "message": "Uzaicināts lietotājs $ID$.", @@ -12460,7 +12460,7 @@ "message": "Jāaugšupielādē sava Premium licences datne" }, "uploadLicenseFileDesc": { - "message": "Your license file name will be similar to: $FILE_NAME$", + "message": "Licences datnes nosaukums būs līdzīgs šim: $FILE_NAME$", "placeholders": { "file_name": { "content": "$1", @@ -12469,15 +12469,15 @@ } }, "alreadyHaveSubscriptionQuestion": { - "message": "Already have a subscription?" + "message": "Jau ir abonements?" }, "alreadyHaveSubscriptionSelfHostedMessage": { - "message": "Open the subscription page on your Bitwarden cloud account and download your license file. Then return to this screen and upload it below." + "message": "Jāatver abonementu lapa Bitwarden mākoņa kontā un jālejupielādē licences datne. Tad jāatgriežas šajā skatā un zemāk jāaugšupielādē." }, "viewAllPlans": { - "message": "View all plans" + "message": "Apskatīt visus plānus" }, "planDescPremium": { - "message": "Complete online security" + "message": "Pilnīga drošība tiešsaistē" } } diff --git a/apps/web/src/locales/pt_BR/messages.json b/apps/web/src/locales/pt_BR/messages.json index 4ca2020c7b1..fbfaf08d030 100644 --- a/apps/web/src/locales/pt_BR/messages.json +++ b/apps/web/src/locales/pt_BR/messages.json @@ -4212,10 +4212,10 @@ } }, "userAcceptedTransfer": { - "message": "Accepted transfer to organization ownership." + "message": "Aceitou a transferência da propriedade da organização." }, "userDeclinedTransfer": { - "message": "Revoked for declining transfer to organization ownership." + "message": "Não aceitou a transferência da propriedade da organização." }, "invitedUserId": { "message": "Convidou o usuário $ID$.", diff --git a/apps/web/src/locales/pt_PT/messages.json b/apps/web/src/locales/pt_PT/messages.json index ae80280caed..929be5c7456 100644 --- a/apps/web/src/locales/pt_PT/messages.json +++ b/apps/web/src/locales/pt_PT/messages.json @@ -4212,10 +4212,10 @@ } }, "userAcceptedTransfer": { - "message": "Accepted transfer to organization ownership." + "message": "Transferência para propriedade da organização aceite." }, "userDeclinedTransfer": { - "message": "Revoked for declining transfer to organization ownership." + "message": "Revogado por recusa de transferência para propriedade da organização." }, "invitedUserId": { "message": "Utilizador $ID$ convidado.", diff --git a/apps/web/src/locales/sk/messages.json b/apps/web/src/locales/sk/messages.json index 6e36b52a098..ea2d12bdb2c 100644 --- a/apps/web/src/locales/sk/messages.json +++ b/apps/web/src/locales/sk/messages.json @@ -7583,10 +7583,10 @@ "message": "Použiť možnosti subadresovania svojho poskytovateľa e-mailu." }, "catchallEmail": { - "message": "Catch-all e-mail" + "message": "Doménový kôš" }, "catchallEmailDesc": { - "message": "Použiť doručenú poštu typu catch-all nastavenú na doméne." + "message": "Použiť nastavený doménový kôš." }, "useThisEmail": { "message": "Použiť tento e-mail" diff --git a/apps/web/src/locales/zh_CN/messages.json b/apps/web/src/locales/zh_CN/messages.json index cc6e71b1f08..d1ee6e0f659 100644 --- a/apps/web/src/locales/zh_CN/messages.json +++ b/apps/web/src/locales/zh_CN/messages.json @@ -1970,11 +1970,11 @@ "message": "每个 Bitwarden 用户账户的账户加密密钥都是唯一的,因此您无法将加密的导出导入到另一个账户。" }, "exportNoun": { - "message": "Export", + "message": "导出", "description": "The noun form of the word Export" }, "exportVerb": { - "message": "Export", + "message": "导出", "description": "The verb form of the word Export" }, "exportFrom": { @@ -2303,11 +2303,11 @@ "message": "工具" }, "importNoun": { - "message": "Import", + "message": "导入", "description": "The noun form of the word Import" }, "importVerb": { - "message": "Import", + "message": "导入", "description": "The verb form of the word Import" }, "importData": { @@ -3294,7 +3294,7 @@ "message": "启动云订阅" }, "launchCloudSubscriptionSentenceCase": { - "message": "Launch cloud subscription" + "message": "启动云订阅" }, "storage": { "message": "存储" @@ -12433,16 +12433,16 @@ "message": "为什么我会看到这个?" }, "youHaveBitwardenPremium": { - "message": "You have Bitwarden Premium" + "message": "您有 Bitwarden 高级版" }, "viewAndManagePremiumSubscription": { - "message": "View and manage your Premium subscription" + "message": "查看和管理您的高级版订阅" }, "youNeedToUpdateLicenseFile": { - "message": "You'll need to update your license file" + "message": "您需要更新您的许可文件" }, "youNeedToUpdateLicenseFileDate": { - "message": "$DATE$.", + "message": "$DATE$。", "placeholders": { "date": { "content": "$1", @@ -12451,16 +12451,16 @@ } }, "uploadLicenseFile": { - "message": "Upload license file" + "message": "上传许可证文件" }, "uploadYourLicenseFile": { - "message": "Upload your license file" + "message": "上传您的许可证文件" }, "uploadYourPremiumLicenseFile": { - "message": "Upload your Premium license file" + "message": "上传您的高级版许可证文件" }, "uploadLicenseFileDesc": { - "message": "Your license file name will be similar to: $FILE_NAME$", + "message": "您的许可证文件名将类似于:$FILE_NAME$", "placeholders": { "file_name": { "content": "$1", @@ -12469,15 +12469,15 @@ } }, "alreadyHaveSubscriptionQuestion": { - "message": "Already have a subscription?" + "message": "已经有一个订阅?" }, "alreadyHaveSubscriptionSelfHostedMessage": { - "message": "Open the subscription page on your Bitwarden cloud account and download your license file. Then return to this screen and upload it below." + "message": "打开您的 Bitwarden 云账户上的订阅页面并下载您的许可证文件,然后返回此屏幕并上传。" }, "viewAllPlans": { - "message": "View all plans" + "message": "查看所有套餐" }, "planDescPremium": { - "message": "Complete online security" + "message": "全面的在线安全防护" } } diff --git a/apps/web/src/locales/zh_TW/messages.json b/apps/web/src/locales/zh_TW/messages.json index b1cda1343d4..4fbf08c28a7 100644 --- a/apps/web/src/locales/zh_TW/messages.json +++ b/apps/web/src/locales/zh_TW/messages.json @@ -1970,11 +1970,11 @@ "message": "每個 Bitwarden 使用者帳戶的帳戶加密金鑰都不相同,因此無法將已加密匯出的檔案匯入至不同帳戶中。" }, "exportNoun": { - "message": "Export", + "message": "匯出", "description": "The noun form of the word Export" }, "exportVerb": { - "message": "Export", + "message": "匯出", "description": "The verb form of the word Export" }, "exportFrom": { @@ -2303,11 +2303,11 @@ "message": "工具" }, "importNoun": { - "message": "Import", + "message": "匯入", "description": "The noun form of the word Import" }, "importVerb": { - "message": "Import", + "message": "匯入", "description": "The verb form of the word Import" }, "importData": { @@ -3294,7 +3294,7 @@ "message": "啟動雲端訂閱" }, "launchCloudSubscriptionSentenceCase": { - "message": "Launch cloud subscription" + "message": "啟動雲端訂閱" }, "storage": { "message": "儲存空間" @@ -4212,10 +4212,10 @@ } }, "userAcceptedTransfer": { - "message": "Accepted transfer to organization ownership." + "message": "已接受轉移至組織擁有權。" }, "userDeclinedTransfer": { - "message": "Revoked for declining transfer to organization ownership." + "message": "因拒絕轉移至組織擁有權而遭撤銷。" }, "invitedUserId": { "message": "已邀請使用者 $ID$。", @@ -5195,7 +5195,7 @@ "message": "需要先修正密碼庫中舊的檔案附件,然後才能輪換帳戶的加密金鑰。" }, "itemsTransferred": { - "message": "Items transferred" + "message": "項目已轉移" }, "yourAccountsFingerprint": { "message": "您帳戶的指紋短語", @@ -6825,7 +6825,7 @@ "message": "密碼庫逾時時間不在允許的範圍內。" }, "disableExport": { - "message": "Remove export" + "message": "移除匯出" }, "disablePersonalVaultExportDescription": { "message": "不允許成員從其個人密碼庫匯出資料。" @@ -9494,7 +9494,7 @@ "message": "需要登入 SSO" }, "emailRequiredForSsoLogin": { - "message": "Email is required for SSO" + "message": "使用 SSO 需要電子郵件" }, "selectedRegionFlag": { "message": "選定的區域標記" @@ -11607,7 +11607,7 @@ "message": "取消封存" }, "unArchiveAndSave": { - "message": "Unarchive and save" + "message": "取消封存並儲存" }, "itemsInArchive": { "message": "封存中的項目" @@ -12251,43 +12251,43 @@ } }, "removeMasterPasswordForOrgUserKeyConnector": { - "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + "message": "您的組織已不再使用主密碼登入 Bitwarden。若要繼續,請驗證組織與網域。" }, "continueWithLogIn": { - "message": "Continue with log in" + "message": "繼續登入" }, "doNotContinue": { - "message": "Do not continue" + "message": "不要繼續" }, "domain": { - "message": "Domain" + "message": "網域" }, "keyConnectorDomainTooltip": { - "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + "message": "此網域將儲存您帳號的加密金鑰,請確認您信任它。若不確定,請洽詢您的管理員。" }, "verifyYourOrganization": { - "message": "Verify your organization to log in" + "message": "驗證您的組織以登入" }, "organizationVerified": { - "message": "Organization verified" + "message": "組織已驗證" }, "domainVerified": { - "message": "Domain verified" + "message": "已驗證網域" }, "leaveOrganizationContent": { - "message": "If you don't verify your organization, your access to the organization will be revoked." + "message": "若您未驗證組織,將會被撤銷對該組織的存取權限。" }, "leaveNow": { - "message": "Leave now" + "message": "立即離開" }, "verifyYourDomainToLogin": { - "message": "Verify your domain to log in" + "message": "驗證您的網域以登入" }, "verifyYourDomainDescription": { - "message": "To continue with log in, verify this domain." + "message": "若要繼續登入,請驗證此網域。" }, "confirmKeyConnectorOrganizationUserDescription": { - "message": "To continue with log in, verify the organization and domain." + "message": "若要繼續登入,請驗證組織與網域。" }, "confirmNoSelectedCriticalApplicationsTitle": { "message": "未選取任何關鍵應用程式" @@ -12299,52 +12299,52 @@ "message": "使用者驗證失敗。" }, "recoveryDeleteCiphersTitle": { - "message": "Delete unrecoverable vault items" + "message": "刪除無法復原的密碼庫項目" }, "recoveryDeleteCiphersDesc": { - "message": "Some of your vault items could not be recovered. Do you want to delete these unrecoverable items from your vault?" + "message": "部分密碼庫項目無法復原。是否要從您的密碼庫中刪除這些無法復原的項目?" }, "recoveryDeleteFoldersTitle": { - "message": "Delete unrecoverable folders" + "message": "刪除無法復原的資料夾" }, "recoveryDeleteFoldersDesc": { - "message": "Some of your folders could not be recovered. Do you want to delete these unrecoverable folders from your vault?" + "message": "部分資料夾無法復原。是否要從您的密碼庫中刪除這些無法復原的資料夾?" }, "recoveryReplacePrivateKeyTitle": { - "message": "Replace encryption key" + "message": "更換加密金鑰" }, "recoveryReplacePrivateKeyDesc": { - "message": "Your public-key encryption key pair could not be recovered. Do you want to replace your encryption key with a new key pair? This will require you to set up existing emergency-access and organization memberships again." + "message": "您的公開金鑰加密金鑰組無法復原。是否要以新的金鑰組取代目前的加密金鑰?這將需要您重新設定現有的緊急存取與組織成員資格。" }, "recoveryStepSyncTitle": { - "message": "Synchronizing data" + "message": "正在同步資料" }, "recoveryStepPrivateKeyTitle": { - "message": "Verifying encryption key integrity" + "message": "正在驗證加密金鑰完整性" }, "recoveryStepUserInfoTitle": { - "message": "Verifying user information" + "message": "正在驗證使用者資訊" }, "recoveryStepCipherTitle": { - "message": "Verifying vault item integrity" + "message": "正在驗證密碼庫項目完整性" }, "recoveryStepFoldersTitle": { - "message": "Verifying folder integrity" + "message": "正在驗證資料夾完整性" }, "dataRecoveryTitle": { - "message": "Data Recovery and Diagnostics" + "message": "資料復原與診斷" }, "dataRecoveryDescription": { - "message": "Use the data recovery tool to diagnose and repair issues with your account. After running diagnostics you have the option to save diagnostic logs for support and the option to repair any detected issues." + "message": "使用資料復原工具來診斷並修復您帳號的問題。完成診斷後,您可以選擇儲存診斷記錄以供支援使用,並修復任何偵測到的問題。" }, "runDiagnostics": { - "message": "Run Diagnostics" + "message": "執行診斷" }, "repairIssues": { - "message": "Repair Issues" + "message": "修復問題" }, "saveDiagnosticLogs": { - "message": "Save Diagnostic Logs" + "message": "儲存診斷記錄" }, "sessionTimeoutSettingsManagedByOrganization": { "message": "此設定由您的組織管理。" @@ -12385,16 +12385,16 @@ "message": "設定一個解鎖方式來變更您的密碼庫逾時動作。" }, "leaveConfirmationDialogTitle": { - "message": "Are you sure you want to leave?" + "message": "確定要離開嗎?" }, "leaveConfirmationDialogContentOne": { - "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + "message": "若選擇拒絕,您的個人項目將保留在帳號中,但您將失去對共用項目與組織功能的存取權。" }, "leaveConfirmationDialogContentTwo": { - "message": "Contact your admin to regain access." + "message": "請聯絡您的管理員以重新取得存取權限。" }, "leaveConfirmationDialogConfirmButton": { - "message": "Leave $ORGANIZATION$", + "message": "離開 $ORGANIZATION$", "placeholders": { "organization": { "content": "$1", @@ -12403,10 +12403,10 @@ } }, "howToManageMyVault": { - "message": "How do I manage my vault?" + "message": "我要如何管理我的密碼庫?" }, "transferItemsToOrganizationTitle": { - "message": "Transfer items to $ORGANIZATION$", + "message": "將項目轉移至 $ORGANIZATION$", "placeholders": { "organization": { "content": "$1", @@ -12415,7 +12415,7 @@ } }, "transferItemsToOrganizationContent": { - "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "message": "$ORGANIZATION$ 為了安全性與合規性,要求所有項目皆由組織擁有。點擊接受即可轉移您項目的擁有權。", "placeholders": { "organization": { "content": "$1", @@ -12424,25 +12424,25 @@ } }, "acceptTransfer": { - "message": "Accept transfer" + "message": "同意轉移" }, "declineAndLeave": { - "message": "Decline and leave" + "message": "拒絕並離開" }, "whyAmISeeingThis": { - "message": "Why am I seeing this?" + "message": "為什麼我會看到此訊息?" }, "youHaveBitwardenPremium": { - "message": "You have Bitwarden Premium" + "message": "您已擁有 Bitwarden 進階版" }, "viewAndManagePremiumSubscription": { - "message": "View and manage your Premium subscription" + "message": "檢視並管理您的進階版訂閱" }, "youNeedToUpdateLicenseFile": { - "message": "You'll need to update your license file" + "message": "您需要更新您的授權檔案" }, "youNeedToUpdateLicenseFileDate": { - "message": "$DATE$.", + "message": "$DATE$。", "placeholders": { "date": { "content": "$1", @@ -12451,16 +12451,16 @@ } }, "uploadLicenseFile": { - "message": "Upload license file" + "message": "上傳授權檔案" }, "uploadYourLicenseFile": { - "message": "Upload your license file" + "message": "上傳您的授權檔案" }, "uploadYourPremiumLicenseFile": { - "message": "Upload your Premium license file" + "message": "上傳您的進階版授權檔案" }, "uploadLicenseFileDesc": { - "message": "Your license file name will be similar to: $FILE_NAME$", + "message": "您的授權檔案名稱將類似於:$FILE_NAME$", "placeholders": { "file_name": { "content": "$1", @@ -12469,15 +12469,15 @@ } }, "alreadyHaveSubscriptionQuestion": { - "message": "Already have a subscription?" + "message": "已經有訂閱了嗎?" }, "alreadyHaveSubscriptionSelfHostedMessage": { - "message": "Open the subscription page on your Bitwarden cloud account and download your license file. Then return to this screen and upload it below." + "message": "請在您的 Bitwarden 雲端帳號中開啟訂閱頁面並下載授權檔案,接著返回此畫面並於下方上傳。" }, "viewAllPlans": { - "message": "View all plans" + "message": "查看所有方案" }, "planDescPremium": { - "message": "Complete online security" + "message": "完整的線上安全防護" } } From dfb597c236876e8c7438d4a6dae37d7dde9b7868 Mon Sep 17 00:00:00 2001 From: Mike Amirault <mamirault@bitwarden.com> Date: Mon, 22 Dec 2025 11:04:14 -0500 Subject: [PATCH 144/188] [PM-24015] Handle Send form empty password field properly (#17911) --- .../options/send-options.component.spec.ts | 67 +++++++++++++++++++ .../options/send-options.component.ts | 32 ++++++--- .../components/send-form.component.ts | 5 -- 3 files changed, 88 insertions(+), 16 deletions(-) create mode 100644 libs/tools/send/send-ui/src/send-form/components/options/send-options.component.spec.ts diff --git a/libs/tools/send/send-ui/src/send-form/components/options/send-options.component.spec.ts b/libs/tools/send/send-ui/src/send-form/components/options/send-options.component.spec.ts new file mode 100644 index 00000000000..6724bb324c3 --- /dev/null +++ b/libs/tools/send/send-ui/src/send-form/components/options/send-options.component.spec.ts @@ -0,0 +1,67 @@ +import { ComponentFixture, TestBed } from "@angular/core/testing"; +import { mock } from "jest-mock-extended"; +import { of } from "rxjs"; + +import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; +import { Account, AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { SendType } from "@bitwarden/common/tools/send/enums/send-type"; +import { SendView } from "@bitwarden/common/tools/send/models/view/send.view"; +import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction"; +import { DialogService, ToastService } from "@bitwarden/components"; +import { CredentialGeneratorService } from "@bitwarden/generator-core"; + +import { SendFormContainer } from "../../send-form-container"; + +import { SendOptionsComponent } from "./send-options.component"; + +describe("SendOptionsComponent", () => { + let component: SendOptionsComponent; + let fixture: ComponentFixture<SendOptionsComponent>; + const mockSendFormContainer = mock<SendFormContainer>(); + const mockAccountService = mock<AccountService>(); + + beforeAll(() => { + mockAccountService.activeAccount$ = of({ id: "myTestAccount" } as Account); + }); + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [SendOptionsComponent], + declarations: [], + providers: [ + { provide: SendFormContainer, useValue: mockSendFormContainer }, + { provide: DialogService, useValue: mock<DialogService>() }, + { provide: SendApiService, useValue: mock<SendApiService>() }, + { provide: PolicyService, useValue: mock<PolicyService>() }, + { provide: I18nService, useValue: mock<I18nService>() }, + { provide: ToastService, useValue: mock<ToastService>() }, + { provide: CredentialGeneratorService, useValue: mock<CredentialGeneratorService>() }, + { provide: AccountService, useValue: mockAccountService }, + { provide: PlatformUtilsService, useValue: mock<PlatformUtilsService>() }, + ], + }).compileComponents(); + fixture = TestBed.createComponent(SendOptionsComponent); + component = fixture.componentInstance; + component.config = { areSendsAllowed: true, mode: "add", sendType: SendType.Text }; + fixture.detectChanges(); + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + it("should create", () => { + expect(component).toBeTruthy(); + }); + + it("should emit a null password when password textbox is empty", async () => { + const newSend = {} as SendView; + mockSendFormContainer.patchSend.mockImplementation((updateFn) => updateFn(newSend)); + component.sendOptionsForm.patchValue({ password: "testing" }); + expect(newSend.password).toBe("testing"); + component.sendOptionsForm.patchValue({ password: "" }); + expect(newSend.password).toBe(null); + }); +}); diff --git a/libs/tools/send/send-ui/src/send-form/components/options/send-options.component.ts b/libs/tools/send/send-ui/src/send-form/components/options/send-options.component.ts index 2ddb10dc80b..ae8706a375e 100644 --- a/libs/tools/send/send-ui/src/send-form/components/options/send-options.component.ts +++ b/libs/tools/send/send-ui/src/send-form/components/options/send-options.component.ts @@ -4,7 +4,7 @@ import { CommonModule } from "@angular/common"; import { Component, Input, OnInit } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { FormBuilder, ReactiveFormsModule } from "@angular/forms"; -import { BehaviorSubject, firstValueFrom, map, switchMap } from "rxjs"; +import { BehaviorSubject, firstValueFrom, map, switchMap, tap } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; @@ -12,6 +12,7 @@ import { PolicyType } from "@bitwarden/common/admin-console/enums"; 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 { Utils } from "@bitwarden/common/platform/misc/utils"; import { pin } from "@bitwarden/common/tools/rx"; import { SendView } from "@bitwarden/common/tools/send/models/view/send.view"; import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction"; @@ -112,18 +113,27 @@ export class SendOptionsComponent implements OnInit { this.disableHideEmail = disableHideEmail; }); - this.sendOptionsForm.valueChanges.pipe(takeUntilDestroyed()).subscribe((value) => { - this.sendFormContainer.patchSend((send) => { - Object.assign(send, { - maxAccessCount: value.maxAccessCount, - accessCount: value.accessCount, - password: value.password, - hideEmail: value.hideEmail, - notes: value.notes, + this.sendOptionsForm.valueChanges + .pipe( + tap((value) => { + if (Utils.isNullOrWhitespace(value.password)) { + value.password = null; + } + }), + takeUntilDestroyed(), + ) + .subscribe((value) => { + this.sendFormContainer.patchSend((send) => { + Object.assign(send, { + maxAccessCount: value.maxAccessCount, + accessCount: value.accessCount, + password: value.password, + hideEmail: value.hideEmail, + notes: value.notes, + }); + return send; }); - return send; }); - }); } generatePassword = async () => { diff --git a/libs/tools/send/send-ui/src/send-form/components/send-form.component.ts b/libs/tools/send/send-ui/src/send-form/components/send-form.component.ts index fcd3b0cb7ea..0471ed90eef 100644 --- a/libs/tools/send/send-ui/src/send-form/components/send-form.component.ts +++ b/libs/tools/send/send-ui/src/send-form/components/send-form.component.ts @@ -18,7 +18,6 @@ import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { FormBuilder, ReactiveFormsModule } from "@angular/forms"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { Utils } from "@bitwarden/common/platform/misc/utils"; import { SendType } from "@bitwarden/common/tools/send/enums/send-type"; import { SendView } from "@bitwarden/common/tools/send/models/view/send.view"; import { @@ -227,10 +226,6 @@ export class SendFormComponent implements AfterViewInit, OnInit, OnChanges, Send return; } - if (Utils.isNullOrWhitespace(this.updatedSendView.password)) { - this.updatedSendView.password = null; - } - this.toastService.showToast({ variant: "success", title: null, From 5d79d4401534ab460b1f0c583cfe43d91f2e0951 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85berg?= <anders@andersaberg.com> Date: Mon, 22 Dec 2025 18:13:39 +0100 Subject: [PATCH 145/188] chore: move @nx packages to devDependencies (#18062) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These packages are build/tooling dependencies and should not be in production dependencies. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> --- package-lock.json | 999 ++++++++++++++++++++++++++++++++++++++++++++-- package.json | 10 +- 2 files changed, 962 insertions(+), 47 deletions(-) diff --git a/package-lock.json b/package-lock.json index c24e8cc45a3..deb3a9f261c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,11 +32,6 @@ "@microsoft/signalr": "8.0.7", "@microsoft/signalr-protocol-msgpack": "8.0.7", "@ng-select/ng-select": "20.7.0", - "@nx/devkit": "21.6.10", - "@nx/eslint": "21.6.10", - "@nx/jest": "21.6.10", - "@nx/js": "21.6.10", - "@nx/webpack": "21.6.10", "big-integer": "1.6.52", "braintree-web-drop-in": "1.46.0", "buffer": "6.0.3", @@ -88,6 +83,11 @@ "@eslint/compat": "2.0.0", "@lit-labs/signals": "0.1.3", "@ngtools/webpack": "20.3.12", + "@nx/devkit": "21.6.10", + "@nx/eslint": "21.6.10", + "@nx/jest": "21.6.10", + "@nx/js": "21.6.10", + "@nx/webpack": "21.6.10", "@storybook/addon-a11y": "9.1.16", "@storybook/addon-designs": "9.0.0-next.3", "@storybook/addon-docs": "9.1.16", @@ -3006,6 +3006,7 @@ "version": "7.28.5", "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz", "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -3015,6 +3016,7 @@ "version": "7.28.5", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", + "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.27.1", @@ -3045,6 +3047,7 @@ "version": "7.28.5", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", + "dev": true, "license": "MIT", "dependencies": { "@babel/parser": "^7.28.5", @@ -3061,12 +3064,14 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, "license": "MIT" }, "node_modules/@babel/core/node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -3076,6 +3081,7 @@ "version": "7.28.3", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz", "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==", + "dev": true, "license": "MIT", "dependencies": { "@babel/parser": "^7.28.3", @@ -3092,6 +3098,7 @@ "version": "7.27.3", "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", + "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.27.3" @@ -3104,6 +3111,7 @@ "version": "7.27.2", "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "dev": true, "license": "MIT", "dependencies": { "@babel/compat-data": "^7.27.2", @@ -3120,6 +3128,7 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -3129,6 +3138,7 @@ "version": "7.28.5", "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.5.tgz", "integrity": "sha512-q3WC4JfdODypvxArsJQROfupPBq9+lMwjKq7C33GhbFYJsufD0yd/ziwD+hJucLeWsnFPWZjsU2DNFqBPE7jwQ==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", @@ -3150,6 +3160,7 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -3159,6 +3170,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.27.1.tgz", "integrity": "sha512-uVDC72XVf8UbrH5qQTc18Agb8emwjTiZrQE11Nv3CuBEZmVvTwwE9CBUEvHku06gQCAyYf8Nv6ja1IN+6LMbxQ==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.1", @@ -3176,6 +3188,7 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -3185,6 +3198,7 @@ "version": "0.6.5", "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.5.tgz", "integrity": "sha512-uJnGFcPsWQK8fvjgGP5LZUZZsYGIoPeRjSF5PGwrelYgq7Q15/Ft9NGFp1zglwgIv//W0uG4BevRuSJRyylZPg==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-compilation-targets": "^7.27.2", @@ -3210,6 +3224,7 @@ "version": "7.28.5", "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.28.5.tgz", "integrity": "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==", + "dev": true, "license": "MIT", "dependencies": { "@babel/traverse": "^7.28.5", @@ -3236,6 +3251,7 @@ "version": "7.28.3", "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-module-imports": "^7.27.1", @@ -3253,6 +3269,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", + "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.27.1" @@ -3265,6 +3282,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -3274,6 +3292,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.27.1.tgz", "integrity": "sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.1", @@ -3291,6 +3310,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.27.1.tgz", "integrity": "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-member-expression-to-functions": "^7.27.1", @@ -3308,6 +3328,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", + "dev": true, "license": "MIT", "dependencies": { "@babel/traverse": "^7.27.1", @@ -3352,6 +3373,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -3361,6 +3383,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.27.1.tgz", "integrity": "sha512-NFJK2sHUvrjo8wAU/nQTWU890/zB2jj0qBcCbZbbf+005cAsv6tMjXz31fBign6M5ov1o0Bllu+9nbqkfsjjJQ==", + "dev": true, "license": "MIT", "dependencies": { "@babel/template": "^7.27.1", @@ -3375,6 +3398,7 @@ "version": "7.28.4", "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", + "dev": true, "license": "MIT", "dependencies": { "@babel/template": "^7.27.2", @@ -3403,6 +3427,7 @@ "version": "7.28.5", "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.28.5.tgz", "integrity": "sha512-87GDMS3tsmMSi/3bWOte1UblL+YUTFMV8SZPZ2eSEL17s74Cw/l63rR6NmGVKMYW2GYi85nE+/d6Hw5N0bEk2Q==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", @@ -3419,6 +3444,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.27.1.tgz", "integrity": "sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" @@ -3434,6 +3460,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.27.1.tgz", "integrity": "sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" @@ -3449,6 +3476,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.27.1.tgz", "integrity": "sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", @@ -3466,6 +3494,7 @@ "version": "7.28.3", "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.28.3.tgz", "integrity": "sha512-b6YTX108evsvE4YgWyQ921ZAFFQm3Bn+CA3+ZXlNVnPhx+UfsVURoPjfGAPCjBgrqo30yX/C2nZGX96DxvR9Iw==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", @@ -3482,6 +3511,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.27.1.tgz", "integrity": "sha512-DTxe4LBPrtFdsWzgpmbBKevg3e9PBy+dXRt19kSbucbZvL2uqtdqwwpluL1jfxYE0wIDTFp1nTy/q6gNLsxXrg==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-create-class-features-plugin": "^7.27.1", @@ -3499,6 +3529,7 @@ "version": "7.21.0-placeholder-for-preset-env.2", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -3511,6 +3542,7 @@ "version": "7.8.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" @@ -3523,6 +3555,7 @@ "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" @@ -3535,6 +3568,7 @@ "version": "7.12.13", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.12.13" @@ -3547,6 +3581,7 @@ "version": "7.14.5", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.14.5" @@ -3562,6 +3597,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.27.1.tgz", "integrity": "sha512-YMq8Z87Lhl8EGkmb0MwYkt36QnxC+fzCgrl66ereamPlYToRpIk5nUjKUY3QKLWq8mwUB1BgbeXcTJhZOCDg5A==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" @@ -3577,6 +3613,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.27.1.tgz", "integrity": "sha512-UT/Jrhw57xg4ILHLFnzFpPDlMbcdEicaAtjPQpbj9wa8T4r5KVWCimHcL/460g8Ht0DMxDyjsLgiWSkVjnwPFg==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" @@ -3592,6 +3629,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" @@ -3607,6 +3645,7 @@ "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" @@ -3619,6 +3658,7 @@ "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" @@ -3631,6 +3671,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" @@ -3646,6 +3687,7 @@ "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" @@ -3658,6 +3700,7 @@ "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" @@ -3670,6 +3713,7 @@ "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" @@ -3682,6 +3726,7 @@ "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" @@ -3694,6 +3739,7 @@ "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" @@ -3706,6 +3752,7 @@ "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" @@ -3718,6 +3765,7 @@ "version": "7.14.5", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.14.5" @@ -3733,6 +3781,7 @@ "version": "7.14.5", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.14.5" @@ -3748,6 +3797,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" @@ -3763,6 +3813,7 @@ "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.18.6", @@ -3779,6 +3830,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.27.1.tgz", "integrity": "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" @@ -3794,6 +3846,7 @@ "version": "7.28.0", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.28.0.tgz", "integrity": "sha512-BEOdvX4+M765icNPZeidyADIvQ1m1gmunXufXxvRESy/jNNyfovIqUyE7MVgGBjWktCoJlzvFA1To2O4ymIO3Q==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", @@ -3811,6 +3864,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.27.1.tgz", "integrity": "sha512-NREkZsZVJS4xmTr8qzE5y8AfIPqsdQfRuUiLRTEzb7Qii8iFWCyDKaUV2c0rCuh4ljDZ98ALHP/PetiBV2nddA==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-module-imports": "^7.27.1", @@ -3828,6 +3882,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.27.1.tgz", "integrity": "sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" @@ -3843,6 +3898,7 @@ "version": "7.28.5", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.5.tgz", "integrity": "sha512-45DmULpySVvmq9Pj3X9B+62Xe+DJGov27QravQJU1LLcapR6/10i+gYVAucGGJpHBp5mYxIMK4nDAT/QDLr47g==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" @@ -3858,6 +3914,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.27.1.tgz", "integrity": "sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-create-class-features-plugin": "^7.27.1", @@ -3874,6 +3931,7 @@ "version": "7.28.3", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.3.tgz", "integrity": "sha512-LtPXlBbRoc4Njl/oh1CeD/3jC+atytbnf/UqLoqTDcEYGUPj022+rvfkbDYieUrSj3CaV4yHDByPE+T2HwfsJg==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-create-class-features-plugin": "^7.28.3", @@ -3890,6 +3948,7 @@ "version": "7.28.4", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.4.tgz", "integrity": "sha512-cFOlhIYPBv/iBoc+KS3M6et2XPtbT2HiCRfBXWtfpc9OAyostldxIf9YAYB6ypURBBbx+Qv6nyrLzASfJe+hBA==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", @@ -3910,6 +3969,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.27.1.tgz", "integrity": "sha512-lj9PGWvMTVksbWiDT2tW68zGS/cyo4AkZ/QTp0sQT0mjPopCmrSkzxeXkznjqBxzDI6TclZhOJbBmbBLjuOZUw==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", @@ -3926,6 +3986,7 @@ "version": "7.28.5", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.5.tgz", "integrity": "sha512-Kl9Bc6D0zTUcFUvkNuQh4eGXPKKNDOJQXVyyM4ZAQPMveniJdxi8XMJwLo+xSoW3MIq81bD33lcUe9kZpl0MCw==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", @@ -3942,6 +4003,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.27.1.tgz", "integrity": "sha512-gEbkDVGRvjj7+T1ivxrfgygpT7GUd4vmODtYpbs0gZATdkX8/iSnOtZSxiZnsgm1YjTgjI6VKBGSJJevkrclzw==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.27.1", @@ -3958,6 +4020,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.27.1.tgz", "integrity": "sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" @@ -3973,6 +4036,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.27.1.tgz", "integrity": "sha512-hkGcueTEzuhB30B3eJCbCYeCaaEQOmQR0AdvzpD4LoN0GXMWzzGSuRrxR2xTnCrvNbVwK9N6/jQ92GSLfiZWoQ==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.27.1", @@ -3989,6 +4053,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.27.1.tgz", "integrity": "sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" @@ -4004,6 +4069,7 @@ "version": "7.28.0", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.28.0.tgz", "integrity": "sha512-K8nhUcn3f6iB+P3gwCv/no7OdzOZQcKchW6N389V6PD8NUWKZHzndOd9sPDVbMoBsbmjMqlB4L9fm+fEFNVlwQ==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", @@ -4020,6 +4086,7 @@ "version": "7.28.5", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.28.5.tgz", "integrity": "sha512-D4WIMaFtwa2NizOp+dnoFjRez/ClKiC2BqqImwKd1X28nqBtZEyCYJ2ozQrrzlxAFrcrjxo39S6khe9RNDlGzw==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" @@ -4035,6 +4102,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.27.1.tgz", "integrity": "sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" @@ -4050,6 +4118,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.27.1.tgz", "integrity": "sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", @@ -4066,6 +4135,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.27.1.tgz", "integrity": "sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-compilation-targets": "^7.27.1", @@ -4083,6 +4153,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.27.1.tgz", "integrity": "sha512-6WVLVJiTjqcQauBhn1LkICsR2H+zm62I3h9faTDKt1qP4jn2o72tSvqMwtGFKGTpojce0gJs+76eZ2uCHRZh0Q==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" @@ -4098,6 +4169,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.27.1.tgz", "integrity": "sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" @@ -4113,6 +4185,7 @@ "version": "7.28.5", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.28.5.tgz", "integrity": "sha512-axUuqnUTBuXyHGcJEVVh9pORaN6wC5bYfE7FGzPiaWa3syib9m7g+/IT/4VgCOe2Upef43PHzeAvcrVek6QuuA==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" @@ -4128,6 +4201,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.27.1.tgz", "integrity": "sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" @@ -4143,6 +4217,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.27.1.tgz", "integrity": "sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-module-transforms": "^7.27.1", @@ -4159,6 +4234,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.27.1.tgz", "integrity": "sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-module-transforms": "^7.27.1", @@ -4175,6 +4251,7 @@ "version": "7.28.5", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.28.5.tgz", "integrity": "sha512-vn5Jma98LCOeBy/KpeQhXcV2WZgaRUtjwQmjoBuLNlOmkg0fB5pdvYVeWRYI69wWKwK2cD1QbMiUQnoujWvrew==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-module-transforms": "^7.28.3", @@ -4193,6 +4270,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.27.1.tgz", "integrity": "sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-module-transforms": "^7.27.1", @@ -4209,6 +4287,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.27.1.tgz", "integrity": "sha512-SstR5JYy8ddZvD6MhV0tM/j16Qds4mIpJTOd1Yu9J9pJjH93bxHECF7pgtc28XvkzTD6Pxcm/0Z73Hvk7kb3Ng==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.27.1", @@ -4225,6 +4304,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.27.1.tgz", "integrity": "sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" @@ -4240,6 +4320,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.27.1.tgz", "integrity": "sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" @@ -4255,6 +4336,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.27.1.tgz", "integrity": "sha512-fdPKAcujuvEChxDBJ5c+0BTaS6revLV7CJL08e4m3de8qJfNIuCc2nc7XJYOjBoTMJeqSmwXJ0ypE14RCjLwaw==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" @@ -4270,6 +4352,7 @@ "version": "7.28.4", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.4.tgz", "integrity": "sha512-373KA2HQzKhQCYiRVIRr+3MjpCObqzDlyrM6u4I201wL8Mp2wHf7uB8GhDwis03k2ti8Zr65Zyyqs1xOxUF/Ew==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-compilation-targets": "^7.27.2", @@ -4289,6 +4372,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.27.1.tgz", "integrity": "sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", @@ -4305,6 +4389,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.27.1.tgz", "integrity": "sha512-txEAEKzYrHEX4xSZN4kJ+OfKXFVSWKB2ZxM9dpcE3wT7smwkNmXo5ORRlVzMVdJbD+Q8ILTgSD7959uj+3Dm3Q==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" @@ -4320,6 +4405,7 @@ "version": "7.28.5", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.28.5.tgz", "integrity": "sha512-N6fut9IZlPnjPwgiQkXNhb+cT8wQKFlJNqcZkWlcTqkcqx6/kU4ynGmLFoa4LViBSirn05YAwk+sQBbPfxtYzQ==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", @@ -4336,6 +4422,7 @@ "version": "7.27.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.7.tgz", "integrity": "sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" @@ -4351,6 +4438,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.27.1.tgz", "integrity": "sha512-10FVt+X55AjRAYI9BrdISN9/AQWHqldOeZDUoLyif1Kn05a56xVBXb8ZouL8pZ9jem8QpXaOt8TS7RHUIS+GPA==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-create-class-features-plugin": "^7.27.1", @@ -4367,6 +4455,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.27.1.tgz", "integrity": "sha512-5J+IhqTi1XPa0DXF83jYOaARrX+41gOewWbkPyjMNRDqgOCqdffGh8L3f/Ek5utaEBZExjSAzcyjmV9SSAWObQ==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.1", @@ -4384,6 +4473,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.27.1.tgz", "integrity": "sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" @@ -4399,6 +4489,7 @@ "version": "7.28.4", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.28.4.tgz", "integrity": "sha512-+ZEdQlBoRg9m2NnzvEeLgtvBMO4tkFBw5SQIUgLICgTrumLoU7lr+Oghi6km2PFj+dbUt2u1oby2w3BDO9YQnA==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" @@ -4414,6 +4505,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.27.1.tgz", "integrity": "sha512-TtEciroaiODtXvLZv4rmfMhkCv8jx3wgKpL68PuiPh2M4fvz5jhsA7697N1gMvkvr/JTF13DrFYyEbY9U7cVPA==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.27.1", @@ -4430,6 +4522,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.27.1.tgz", "integrity": "sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" @@ -4445,6 +4538,7 @@ "version": "7.28.3", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.28.3.tgz", "integrity": "sha512-Y6ab1kGqZ0u42Zv/4a7l0l72n9DKP/MKoKWaUSBylrhNZO2prYuqFOLbn5aW5SIFXwSH93yfjbgllL8lxuGKLg==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-module-imports": "^7.27.1", @@ -4465,6 +4559,7 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -4474,6 +4569,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.27.1.tgz", "integrity": "sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" @@ -4489,6 +4585,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.27.1.tgz", "integrity": "sha512-kpb3HUqaILBJcRFVhFUs6Trdd4mkrzcGXss+6/mxUd273PfbWqSDHRzMT2234gIg2QYfAjvXLSquP1xECSg09Q==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", @@ -4505,6 +4602,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.27.1.tgz", "integrity": "sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" @@ -4520,6 +4618,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.27.1.tgz", "integrity": "sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" @@ -4535,6 +4634,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.27.1.tgz", "integrity": "sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" @@ -4550,6 +4650,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.27.1.tgz", "integrity": "sha512-Q5sT5+O4QUebHdbwKedFBEwRLb02zJ7r4A5Gg2hUoLuU3FjdMcyqcywqUrLCaDsFCxzokf7u9kuy7qz51YUuAg==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.1", @@ -4569,6 +4670,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.27.1.tgz", "integrity": "sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" @@ -4584,6 +4686,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.27.1.tgz", "integrity": "sha512-uW20S39PnaTImxp39O5qFlHLS9LJEmANjMG7SxIhap8rCHqu0Ik+tLEPX5DKmHn6CsWQ7j3lix2tFOa5YtL12Q==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.27.1", @@ -4600,6 +4703,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.27.1.tgz", "integrity": "sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.27.1", @@ -4616,6 +4720,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.27.1.tgz", "integrity": "sha512-EtkOujbc4cgvb0mlpQefi4NTPBzhSIevblFevACNLUspmrALgmEBdL/XfnyyITfd8fKBZrZys92zOWcik7j9Tw==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.27.1", @@ -4632,6 +4737,7 @@ "version": "7.28.5", "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.28.5.tgz", "integrity": "sha512-S36mOoi1Sb6Fz98fBfE+UZSpYw5mJm0NUHtIKrOuNcqeFauy1J6dIvXm2KRVKobOSaGq4t/hBXdN4HGU3wL9Wg==", + "dev": true, "license": "MIT", "dependencies": { "@babel/compat-data": "^7.28.5", @@ -4716,6 +4822,7 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -4725,6 +4832,7 @@ "version": "0.1.6-no-external-plugins", "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", @@ -4739,6 +4847,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.27.1.tgz", "integrity": "sha512-l7WfQfX0WK4M0v2RudjuQK4u99BS6yLHYEmdtVPP7lKV013zr9DygFuWNlnbvQ9LR+LS0Egz/XAvGx5U9MX0fQ==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", @@ -4828,6 +4937,7 @@ "version": "0.2.3", "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true, "license": "MIT" }, "node_modules/@bitwarden/admin-console": { @@ -5096,6 +5206,7 @@ "version": "2.9.0", "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.9.0.tgz", "integrity": "sha512-rnJenoStJ8nvmt9Gzye8nkYd6V22xUAnu4086ER7h1zJ508vStko4pMvDeQ446ilDTFpV5wnoc5YS7XvMwwMqA==", + "dev": true, "license": "(Apache-2.0 AND BSD-3-Clause)" }, "node_modules/@compodoc/compodoc": { @@ -6358,6 +6469,7 @@ "version": "1.7.1", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.7.1.tgz", "integrity": "sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg==", + "dev": true, "license": "MIT", "dependencies": { "@emnapi/wasi-threads": "1.1.0", @@ -6368,6 +6480,7 @@ "version": "1.7.1", "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.7.1.tgz", "integrity": "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==", + "dev": true, "license": "MIT", "dependencies": { "tslib": "^2.4.0" @@ -6377,6 +6490,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz", "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==", + "dev": true, "license": "MIT", "dependencies": { "tslib": "^2.4.0" @@ -6938,6 +7052,7 @@ "version": "4.7.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", + "dev": true, "license": "MIT", "dependencies": { "eslint-visitor-keys": "^3.4.3" @@ -6956,6 +7071,7 @@ "version": "4.12.1", "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, "license": "MIT", "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" @@ -6999,6 +7115,7 @@ "version": "0.20.1", "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.1.tgz", "integrity": "sha512-OL0RJzC/CBzli0DrrR31qzj6d6i6Mm3HByuhflhl4LOBiWxN+3i6/t/ZQQNii4tjksXi8r2CRW1wMpWA2ULUEw==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@eslint/object-schema": "^2.1.6", @@ -7013,6 +7130,7 @@ "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -7023,6 +7141,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -7035,6 +7154,7 @@ "version": "0.2.3", "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.3.tgz", "integrity": "sha512-u180qk2Um1le4yf0ruXH3PYFeEZeYC3p/4wCTKrr2U1CmGdzGi3KtY0nuPDH48UJxlKCC5RDzbcbh4X0XlqgHg==", + "dev": true, "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -7044,6 +7164,7 @@ "version": "0.13.0", "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.13.0.tgz", "integrity": "sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@types/json-schema": "^7.0.15" @@ -7056,6 +7177,7 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "dev": true, "license": "MIT", "dependencies": { "ajv": "^6.12.4", @@ -7079,6 +7201,7 @@ "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", @@ -7095,6 +7218,7 @@ "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -7105,6 +7229,7 @@ "version": "14.0.0", "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -7117,6 +7242,7 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, "license": "MIT", "engines": { "node": ">= 4" @@ -7126,6 +7252,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -7138,12 +7265,14 @@ "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==", + "dev": true, "license": "MIT" }, "node_modules/@eslint/eslintrc/node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -7156,6 +7285,7 @@ "version": "9.26.0", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.26.0.tgz", "integrity": "sha512-I9XlJawFdSMvWjDt6wksMCrgns5ggLNfFwFvnShsleWruvXM514Qxk8V246efTw+eo9JABvVz+u3q2RiAowKxQ==", + "dev": true, "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -7165,6 +7295,7 @@ "version": "2.1.6", "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", + "dev": true, "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -7174,6 +7305,7 @@ "version": "0.2.8", "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.8.tgz", "integrity": "sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@eslint/core": "^0.13.0", @@ -7329,6 +7461,7 @@ "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, "license": "Apache-2.0", "engines": { "node": ">=18.18.0" @@ -7338,6 +7471,7 @@ "version": "0.16.6", "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@humanfs/core": "^0.19.1", @@ -7351,6 +7485,7 @@ "version": "0.3.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "dev": true, "license": "Apache-2.0", "engines": { "node": ">=18.18" @@ -7364,6 +7499,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, "license": "Apache-2.0", "engines": { "node": ">=12.22" @@ -7377,6 +7513,7 @@ "version": "0.4.3", "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, "license": "Apache-2.0", "engines": { "node": ">=18.18" @@ -7787,6 +7924,7 @@ "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, "license": "ISC", "dependencies": { "string-width": "^5.1.2", @@ -7804,6 +7942,7 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -7816,6 +7955,7 @@ "version": "6.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -7828,12 +7968,14 @@ "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, "license": "MIT" }, "node_modules/@isaacs/cliui/node_modules/string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, "license": "MIT", "dependencies": { "eastasianwidth": "^0.2.0", @@ -7851,6 +7993,7 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^6.0.1" @@ -7866,6 +8009,7 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^6.1.0", @@ -7896,6 +8040,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, "license": "ISC", "dependencies": { "camelcase": "^5.3.1", @@ -7912,6 +8057,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, "license": "MIT", "dependencies": { "locate-path": "^5.0.0", @@ -7925,6 +8071,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, "license": "MIT", "dependencies": { "p-locate": "^4.1.0" @@ -7937,6 +8084,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, "license": "MIT", "dependencies": { "p-try": "^2.0.0" @@ -7952,6 +8100,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, "license": "MIT", "dependencies": { "p-limit": "^2.2.0" @@ -7964,6 +8113,7 @@ "version": "0.1.3", "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -8087,6 +8237,7 @@ "version": "30.0.1", "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.0.1.tgz", "integrity": "sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw==", + "dev": true, "license": "MIT", "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -8157,6 +8308,7 @@ "version": "30.1.0", "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.1.0.tgz", "integrity": "sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==", + "dev": true, "license": "MIT", "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -8182,6 +8334,7 @@ "version": "30.0.1", "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz", "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==", + "dev": true, "license": "MIT", "dependencies": { "@types/node": "*", @@ -8195,6 +8348,7 @@ "version": "30.0.1", "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz", "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==", + "dev": true, "license": "MIT", "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -8294,6 +8448,7 @@ "version": "29.6.3", "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, "license": "MIT", "dependencies": { "@sinclair/typebox": "^0.27.8" @@ -8306,6 +8461,7 @@ "version": "30.2.0", "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.2.0.tgz", "integrity": "sha512-0aVxM3RH6DaiLcjj/b0KrIBZhSX1373Xci4l3cW5xiUWPctZ59zQ7jj4rqcJQ/Z8JuN/4wX3FpJSa3RssVvCug==", + "dev": true, "license": "MIT", "dependencies": { "@jest/types": "30.2.0", @@ -8321,6 +8477,7 @@ "version": "30.0.5", "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "dev": true, "license": "MIT", "dependencies": { "@sinclair/typebox": "^0.34.0" @@ -8333,6 +8490,7 @@ "version": "30.2.0", "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.2.0.tgz", "integrity": "sha512-H9xg1/sfVvyfU7o3zMfBEjQ1gcsdeTMgqHoYdN79tuLqfTtuu7WckRA1R5whDwOzxaZAeMKTYWqP+WCAi0CHsg==", + "dev": true, "license": "MIT", "dependencies": { "@jest/pattern": "30.0.1", @@ -8351,6 +8509,7 @@ "version": "0.34.41", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.41.tgz", "integrity": "sha512-6gS8pZzSXdyRHTIqoqSVknxolr1kzfy4/CeDnrzsVz8TTIWUbOBr6gnzOmTYJ3eXQNh4IYHIGi5aIL7sOZ2G/g==", + "dev": true, "license": "MIT" }, "node_modules/@jest/source-map": { @@ -8438,6 +8597,7 @@ "version": "29.6.3", "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dev": true, "license": "MIT", "dependencies": { "@jest/schemas": "^29.6.3", @@ -8465,6 +8625,7 @@ "version": "2.3.5", "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, "license": "MIT", "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", @@ -8484,6 +8645,7 @@ "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", @@ -8510,6 +8672,7 @@ "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" @@ -8526,6 +8689,7 @@ "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", @@ -8548,6 +8712,7 @@ "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" @@ -8592,6 +8757,7 @@ "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": { @@ -8878,6 +9044,7 @@ "version": "1.17.3", "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.17.3.tgz", "integrity": "sha512-JPwUKWSsbzx+DLFznf/QZ32Qa+ptfbUlHhRLrBQBAFu9iI1iYvizM4p+zhhRDceSsPutXp4z+R/HPVphlIiclg==", + "dev": true, "license": "MIT", "dependencies": { "ajv": "^6.12.6", @@ -8901,6 +9068,7 @@ "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", @@ -8917,6 +9085,7 @@ "version": "3.0.7", "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", + "dev": true, "license": "MIT", "dependencies": { "eventsource-parser": "^3.0.1" @@ -8929,6 +9098,7 @@ "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==", + "dev": true, "license": "MIT" }, "node_modules/@msgpack/msgpack": { @@ -10082,6 +10252,7 @@ "version": "0.2.4", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.4.tgz", "integrity": "sha512-9zESzOO5aDByvhIAsOy9TbpZ0Ur2AJbUI7UT73kcUTS2mxAMHOBaa1st/jAymNoCtvrit99kkzT1FZuXVcgfIQ==", + "dev": true, "license": "MIT", "dependencies": { "@emnapi/core": "^1.1.0", @@ -10397,6 +10568,7 @@ "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", @@ -10410,6 +10582,7 @@ "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" @@ -10419,6 +10592,7 @@ "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", @@ -10875,6 +11049,7 @@ "version": "21.6.10", "resolved": "https://registry.npmjs.org/@nx/devkit/-/devkit-21.6.10.tgz", "integrity": "sha512-h2ZpwhKk9p1kWgokMXP6F4PVakUA3jPbKmjtY+wCsW2VZg72tIVVzs33DGUxTvN6WG6Z4xbLKc0LJkgaOdDTOw==", + "dev": true, "license": "MIT", "dependencies": { "ejs": "^3.1.7", @@ -10893,6 +11068,7 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, "license": "MIT", "engines": { "node": ">= 4" @@ -10902,6 +11078,7 @@ "version": "21.6.10", "resolved": "https://registry.npmjs.org/@nx/eslint/-/eslint-21.6.10.tgz", "integrity": "sha512-cZPXFZsgzGrOBetSdcIR9Kb28H9+lHsaubAGeCAjS8GSvRoQBKLdgtfuB5mpnmOLRqGsiIhZ701DfekLitRnmQ==", + "dev": true, "license": "MIT", "dependencies": { "@nx/devkit": "21.6.10", @@ -10924,6 +11101,7 @@ "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -10937,6 +11115,7 @@ "version": "21.6.10", "resolved": "https://registry.npmjs.org/@nx/jest/-/jest-21.6.10.tgz", "integrity": "sha512-JAYMD/RwKP/mgr7R0uC6R7/DGsluajiQsHipbp6JhbwmqxOK+tTdWBHrYzKWXyRZaCSqqmrN55ocVfuynZDP4Q==", + "dev": true, "license": "MIT", "dependencies": { "@jest/reporters": "^30.0.2", @@ -10960,6 +11139,7 @@ "version": "30.2.0", "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.2.0.tgz", "integrity": "sha512-+O1ifRjkvYIkBqASKWgLxrpEhQAAE7hY77ALLUufSk5717KfOShg6IbqLmdsLMPdUiFvA2kTs0R7YZy+l0IzZQ==", + "dev": true, "license": "MIT", "dependencies": { "@jest/types": "30.2.0", @@ -10977,6 +11157,7 @@ "version": "30.2.0", "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.2.0.tgz", "integrity": "sha512-/QPTL7OBJQ5ac09UDRa3EQes4gt1FTEG/8jZ/4v5IVzx+Cv7dLxlVIvfvSVRiiX2drWyXeBjkMSR8hvOWSog5g==", + "dev": true, "license": "MIT", "dependencies": { "@jest/fake-timers": "30.2.0", @@ -10992,6 +11173,7 @@ "version": "30.2.0", "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.2.0.tgz", "integrity": "sha512-V9yxQK5erfzx99Sf+7LbhBwNWEZ9eZay8qQ9+JSC0TrMR1pMDHLMY+BnVPacWU6Jamrh252/IKo4F1Xn/zfiqA==", + "dev": true, "license": "MIT", "dependencies": { "expect": "30.2.0", @@ -11005,6 +11187,7 @@ "version": "30.2.0", "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.2.0.tgz", "integrity": "sha512-1JnRfhqpD8HGpOmQp180Fo9Zt69zNtC+9lR+kT7NVL05tNXIi+QC8Csz7lfidMoVLPD3FnOtcmp0CEFnxExGEA==", + "dev": true, "license": "MIT", "dependencies": { "@jest/get-type": "30.1.0" @@ -11017,6 +11200,7 @@ "version": "30.2.0", "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.2.0.tgz", "integrity": "sha512-HI3tRLjRxAbBy0VO8dqqm7Hb2mIa8d5bg/NJkyQcOk7V118ObQML8RC5luTF/Zsg4474a+gDvhce7eTnP4GhYw==", + "dev": true, "license": "MIT", "dependencies": { "@jest/types": "30.2.0", @@ -11034,6 +11218,7 @@ "version": "30.2.0", "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.2.0.tgz", "integrity": "sha512-b63wmnKPaK+6ZZfpYhz9K61oybvbI1aMcIs80++JI1O1rR1vaxHUCNqo3ITu6NU0d4V34yZFoHMn/uoKr/Rwfw==", + "dev": true, "license": "MIT", "dependencies": { "@jest/environment": "30.2.0", @@ -11049,6 +11234,7 @@ "version": "30.2.0", "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.2.0.tgz", "integrity": "sha512-DRyW6baWPqKMa9CzeiBjHwjd8XeAyco2Vt8XbcLFjiwCOEKOvy82GJ8QQnJE9ofsxCMPjH4MfH8fCWIHHDKpAQ==", + "dev": true, "license": "MIT", "dependencies": { "@bcoe/v8-coverage": "^0.2.3", @@ -11091,6 +11277,7 @@ "version": "30.0.5", "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "dev": true, "license": "MIT", "dependencies": { "@sinclair/typebox": "^0.34.0" @@ -11103,6 +11290,7 @@ "version": "30.0.1", "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-30.0.1.tgz", "integrity": "sha512-MIRWMUUR3sdbP36oyNyhbThLHyJ2eEDClPCiHVbrYAe5g3CHRArIVpBw7cdSB5fr+ofSfIb2Tnsw8iEHL0PYQg==", + "dev": true, "license": "MIT", "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", @@ -11117,6 +11305,7 @@ "version": "30.2.0", "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.2.0.tgz", "integrity": "sha512-RF+Z+0CCHkARz5HT9mcQCBulb1wgCP3FBvl9VFokMX27acKphwyQsNuWH3c+ojd1LeWBLoTYoxF0zm6S/66mjg==", + "dev": true, "license": "MIT", "dependencies": { "@jest/console": "30.2.0", @@ -11132,6 +11321,7 @@ "version": "30.2.0", "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.2.0.tgz", "integrity": "sha512-wXKgU/lk8fKXMu/l5Hog1R61bL4q5GCdT6OJvdAFz1P+QrpoFuLU68eoKuVc4RbrTtNnTL5FByhWdLgOPSph+Q==", + "dev": true, "license": "MIT", "dependencies": { "@jest/test-result": "30.2.0", @@ -11147,6 +11337,7 @@ "version": "30.2.0", "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.2.0.tgz", "integrity": "sha512-XsauDV82o5qXbhalKxD7p4TZYYdwcaEXC77PPD2HixEFF+6YGppjrAAQurTl2ECWcEomHBMMNS9AH3kcCFx8jA==", + "dev": true, "license": "MIT", "dependencies": { "@babel/core": "^7.27.4", @@ -11173,6 +11364,7 @@ "version": "30.2.0", "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.2.0.tgz", "integrity": "sha512-H9xg1/sfVvyfU7o3zMfBEjQ1gcsdeTMgqHoYdN79tuLqfTtuu7WckRA1R5whDwOzxaZAeMKTYWqP+WCAi0CHsg==", + "dev": true, "license": "MIT", "dependencies": { "@jest/pattern": "30.0.1", @@ -11191,12 +11383,14 @@ "version": "0.34.41", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.41.tgz", "integrity": "sha512-6gS8pZzSXdyRHTIqoqSVknxolr1kzfy4/CeDnrzsVz8TTIWUbOBr6gnzOmTYJ3eXQNh4IYHIGi5aIL7sOZ2G/g==", + "dev": true, "license": "MIT" }, "node_modules/@nx/jest/node_modules/@sinonjs/fake-timers": { "version": "13.0.5", "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-13.0.5.tgz", "integrity": "sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw==", + "dev": true, "license": "BSD-3-Clause", "dependencies": { "@sinonjs/commons": "^3.0.1" @@ -11206,6 +11400,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -11218,6 +11413,7 @@ "version": "30.2.0", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.2.0.tgz", "integrity": "sha512-0YiBEOxWqKkSQWL9nNGGEgndoeL0ZpWrbLMNL5u/Kaxrli3Eaxlt3ZtIDktEvXt4L/R9r3ODr2zKwGM/2BjxVw==", + "dev": true, "license": "MIT", "dependencies": { "@jest/transform": "30.2.0", @@ -11239,6 +11435,7 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.1.tgz", "integrity": "sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA==", + "dev": true, "license": "BSD-3-Clause", "workspaces": [ "test/babel-8" @@ -11258,6 +11455,7 @@ "version": "30.2.0", "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.2.0.tgz", "integrity": "sha512-ftzhzSGMUnOzcCXd6WHdBGMyuwy15Wnn0iyyWGKgBDLxf9/s5ABuraCSpBX2uG0jUg4rqJnxsLc5+oYBqoxVaA==", + "dev": true, "license": "MIT", "dependencies": { "@types/babel__core": "^7.20.5" @@ -11270,6 +11468,7 @@ "version": "30.2.0", "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.2.0.tgz", "integrity": "sha512-US4Z3NOieAQumwFnYdUWKvUKh8+YSnS/gB3t6YBiz0bskpu7Pine8pPCheNxlPEW4wnUkma2a94YuW2q3guvCQ==", + "dev": true, "license": "MIT", "dependencies": { "babel-plugin-jest-hoist": "30.2.0", @@ -11286,6 +11485,7 @@ "version": "6.3.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -11298,6 +11498,7 @@ "version": "4.3.1", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.1.tgz", "integrity": "sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==", + "dev": true, "funding": [ { "type": "github", @@ -11313,18 +11514,21 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.1.1.tgz", "integrity": "sha512-+CmxIZ/L2vNcEfvNtLdU0ZQ6mbq3FZnwAP2PPTiKP+1QOoKwlKlPgb8UKV0Dds7QVaMnHm+FwSft2VB0s/SLjQ==", + "dev": true, "license": "MIT" }, "node_modules/@nx/jest/node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, "license": "MIT" }, "node_modules/@nx/jest/node_modules/expect": { "version": "30.2.0", "resolved": "https://registry.npmjs.org/expect/-/expect-30.2.0.tgz", "integrity": "sha512-u/feCi0GPsI+988gU2FLcsHyAHTU0MX1Wg68NhAnN7z/+C5wqG+CY8J53N9ioe8RXgaoz0nBR/TYMf3AycUuPw==", + "dev": true, "license": "MIT", "dependencies": { "@jest/expect-utils": "30.2.0", @@ -11342,6 +11546,7 @@ "version": "10.5.0", "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "dev": true, "license": "ISC", "dependencies": { "foreground-child": "^3.1.0", @@ -11362,6 +11567,7 @@ "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" @@ -11377,6 +11583,7 @@ "version": "5.0.6", "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", + "dev": true, "license": "BSD-3-Clause", "dependencies": { "@jridgewell/trace-mapping": "^0.3.23", @@ -11391,6 +11598,7 @@ "version": "3.4.3", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/cliui": "^8.0.2" @@ -11406,6 +11614,7 @@ "version": "30.2.0", "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.2.0.tgz", "integrity": "sha512-Fh0096NC3ZkFx05EP2OXCxJAREVxj1BcW/i6EWqqymcgYKWjyyDpral3fMxVcHXg6oZM7iULer9wGRFvfpl+Tg==", + "dev": true, "license": "MIT", "dependencies": { "@jest/environment": "30.2.0", @@ -11437,6 +11646,7 @@ "version": "30.2.0", "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.2.0.tgz", "integrity": "sha512-g4WkyzFQVWHtu6uqGmQR4CQxz/CH3yDSlhzXMWzNjDx843gYjReZnMRanjRCq5XZFuQrGDxgUaiYWE8BRfVckA==", + "dev": true, "license": "MIT", "dependencies": { "@babel/core": "^7.27.4", @@ -11488,6 +11698,7 @@ "version": "30.2.0", "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-30.2.0.tgz", "integrity": "sha512-tR/FFgZKS1CXluOQzZvNH3+0z9jXr3ldGSD8bhyuxvlVUwbeLOGynkunvlTMxchC5urrKndYiwCFC0DLVjpOCA==", + "dev": true, "license": "MIT", "dependencies": { "detect-newline": "^3.1.0" @@ -11500,6 +11711,7 @@ "version": "30.2.0", "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.2.0.tgz", "integrity": "sha512-lpWlJlM7bCUf1mfmuqTA8+j2lNURW9eNafOy99knBM01i5CQeY5UH1vZjgT9071nDJac1M4XsbyI44oNOdhlDQ==", + "dev": true, "license": "MIT", "dependencies": { "@jest/get-type": "30.1.0", @@ -11516,6 +11728,7 @@ "version": "30.2.0", "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.2.0.tgz", "integrity": "sha512-ElU8v92QJ9UrYsKrxDIKCxu6PfNj4Hdcktcn0JX12zqNdqWHB0N+hwOnnBBXvjLd2vApZtuLUGs1QSY+MsXoNA==", + "dev": true, "license": "MIT", "dependencies": { "@jest/environment": "30.2.0", @@ -11534,6 +11747,7 @@ "version": "30.2.0", "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.2.0.tgz", "integrity": "sha512-sQA/jCb9kNt+neM0anSj6eZhLZUIhQgwDt7cPGjumgLM4rXsfb9kpnlacmvZz3Q5tb80nS+oG/if+NBKrHC+Xw==", + "dev": true, "license": "MIT", "dependencies": { "@jest/types": "30.2.0", @@ -11558,6 +11772,7 @@ "version": "30.2.0", "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.2.0.tgz", "integrity": "sha512-M6jKAjyzjHG0SrQgwhgZGy9hFazcudwCNovY/9HPIicmNSBuockPSedAP9vlPK6ONFJ1zfyH/M2/YYJxOz5cdQ==", + "dev": true, "license": "MIT", "dependencies": { "@jest/get-type": "30.1.0", @@ -11571,6 +11786,7 @@ "version": "30.2.0", "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.2.0.tgz", "integrity": "sha512-dQ94Nq4dbzmUWkQ0ANAWS9tBRfqCrn0bV9AMYdOi/MHW726xn7eQmMeRTpX2ViC00bpNaWXq+7o4lIQ3AX13Hg==", + "dev": true, "license": "MIT", "dependencies": { "@jest/get-type": "30.1.0", @@ -11586,6 +11802,7 @@ "version": "30.2.0", "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.2.0.tgz", "integrity": "sha512-y4DKFLZ2y6DxTWD4cDe07RglV88ZiNEdlRfGtqahfbIjfsw1nMCPx49Uev4IA/hWn3sDKyAnSPwoYSsAEdcimw==", + "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.27.1", @@ -11606,6 +11823,7 @@ "version": "30.2.0", "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.2.0.tgz", "integrity": "sha512-JNNNl2rj4b5ICpmAcq+WbLH83XswjPbjH4T7yvGzfAGCPh1rw+xVNbtk+FnRslvt9lkCcdn9i1oAoKUuFsOxRw==", + "dev": true, "license": "MIT", "dependencies": { "@jest/types": "30.2.0", @@ -11620,6 +11838,7 @@ "version": "30.0.1", "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz", "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==", + "dev": true, "license": "MIT", "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -11629,6 +11848,7 @@ "version": "30.2.0", "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.2.0.tgz", "integrity": "sha512-TCrHSxPlx3tBY3hWNtRQKbtgLhsXa1WmbJEqBlTBrGafd5fiQFByy2GNCEoGR+Tns8d15GaL9cxEzKOO3GEb2A==", + "dev": true, "license": "MIT", "dependencies": { "chalk": "^4.1.2", @@ -11648,6 +11868,7 @@ "version": "30.2.0", "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.2.0.tgz", "integrity": "sha512-PqvZ2B2XEyPEbclp+gV6KO/F1FIFSbIwewRgmROCMBo/aZ6J1w8Qypoj2pEOcg3G2HzLlaP6VUtvwCI8dM3oqQ==", + "dev": true, "license": "MIT", "dependencies": { "@jest/console": "30.2.0", @@ -11681,6 +11902,7 @@ "version": "30.2.0", "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.2.0.tgz", "integrity": "sha512-p1+GVX/PJqTucvsmERPMgCPvQJpFt4hFbM+VN3n8TMo47decMUcJbt+rgzwrEme0MQUA/R+1de2axftTHkKckg==", + "dev": true, "license": "MIT", "dependencies": { "@jest/environment": "30.2.0", @@ -11714,6 +11936,7 @@ "version": "30.2.0", "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.2.0.tgz", "integrity": "sha512-5WEtTy2jXPFypadKNpbNkZ72puZCa6UjSr/7djeecHWOu7iYhSXSnHScT8wBz3Rn8Ena5d5RYRcsyKIeqG1IyA==", + "dev": true, "license": "MIT", "dependencies": { "@babel/core": "^7.27.4", @@ -11746,6 +11969,7 @@ "version": "30.2.0", "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.2.0.tgz", "integrity": "sha512-QKNsM0o3Xe6ISQU869e+DhG+4CK/48aHYdJZGlFQVTjnbvgpcKyxpzk29fGiO7i/J8VENZ+d2iGnSsvmuHywlA==", + "dev": true, "license": "MIT", "dependencies": { "@jest/types": "30.2.0", @@ -11763,6 +11987,7 @@ "version": "30.2.0", "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.2.0.tgz", "integrity": "sha512-FBGWi7dP2hpdi8nBoWxSsLvBFewKAg0+uSQwBaof4Y4DPgBabXgpSYC5/lR7VmnIlSpASmCi/ntRWPbv7089Pw==", + "dev": true, "license": "MIT", "dependencies": { "@jest/get-type": "30.1.0", @@ -11780,6 +12005,7 @@ "version": "30.2.0", "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.2.0.tgz", "integrity": "sha512-PYxa28dxJ9g777pGm/7PrbnMeA0Jr7osHP9bS7eJy9DuAjMgdGtxgf0uKMyoIsTWAkIbUW5hSDdJ3urmgXBqxg==", + "dev": true, "license": "MIT", "dependencies": { "@jest/test-result": "30.2.0", @@ -11799,6 +12025,7 @@ "version": "30.2.0", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.2.0.tgz", "integrity": "sha512-0Q4Uk8WF7BUwqXHuAjc23vmopWJw5WH7w2tqBoUOZpOjW/ZnR44GXXd1r82RvnmI2GZge3ivrYXk/BE2+VtW2g==", + "dev": true, "license": "MIT", "dependencies": { "@types/node": "*", @@ -11815,12 +12042,14 @@ "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, "license": "ISC" }, "node_modules/@nx/jest/node_modules/path-scurry": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, "license": "BlueOak-1.0.0", "dependencies": { "lru-cache": "^10.2.0", @@ -11837,6 +12066,7 @@ "version": "30.2.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.2.0.tgz", "integrity": "sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA==", + "dev": true, "license": "MIT", "dependencies": { "@jest/schemas": "30.0.5", @@ -11851,6 +12081,7 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-7.0.1.tgz", "integrity": "sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ==", + "dev": true, "funding": [ { "type": "individual", @@ -11867,12 +12098,14 @@ "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, "license": "MIT" }, "node_modules/@nx/jest/node_modules/source-map": { "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" @@ -11882,6 +12115,7 @@ "version": "0.5.13", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, "license": "MIT", "dependencies": { "buffer-from": "^1.0.0", @@ -11892,6 +12126,7 @@ "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" @@ -11907,6 +12142,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", + "dev": true, "license": "ISC", "dependencies": { "imurmurhash": "^0.1.4", @@ -11920,6 +12156,7 @@ "version": "21.6.10", "resolved": "https://registry.npmjs.org/@nx/js/-/js-21.6.10.tgz", "integrity": "sha512-8d+Q5v/9/he8mq6aRfhHWORZb/WkJ7OTegF4QX2g+yVkocEKIyuUx/BC9rGBRvlZpB2xcJlU9kNcfrhuoKbehQ==", + "dev": true, "license": "MIT", "dependencies": { "@babel/core": "^7.23.2", @@ -11965,6 +12202,7 @@ "version": "7.0.2", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", + "dev": true, "license": "ISC", "dependencies": { "lru-cache": "^10.0.1" @@ -11977,6 +12215,7 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, "license": "MIT", "engines": { "node": ">= 4" @@ -11986,18 +12225,21 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", + "dev": true, "license": "MIT" }, "node_modules/@nx/js/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==", + "dev": true, "license": "ISC" }, "node_modules/@nx/js/node_modules/npm-package-arg": { "version": "11.0.1", "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-11.0.1.tgz", "integrity": "sha512-M7s1BD4NxdAvBKUPqqRW957Xwcl/4Zvo8Aj+ANrzvIPzGJZElrH7Z//rSaec2ORcND6FHHLnZeY8qgTpXDMFQQ==", + "dev": true, "license": "ISC", "dependencies": { "hosted-git-info": "^7.0.0", @@ -12013,6 +12255,7 @@ "version": "5.3.0", "resolved": "https://registry.npmjs.org/ora/-/ora-5.3.0.tgz", "integrity": "sha512-zAKMgGXUim0Jyd6CXK9lraBnD3H5yPGBPPOkC23a2BG6hsm4Zu6OQSjQuEtV0BHDf4aKHcUFvJiGRrFuW3MG8g==", + "dev": true, "license": "MIT", "dependencies": { "bl": "^4.0.3", @@ -12035,6 +12278,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-3.0.0.tgz", "integrity": "sha512-++Vn7NS4Xf9NacaU9Xq3URUuqZETPsf8L4j5/ckhaRYsfPeRyzGw+iDjFhV/Jr3uNmTvvddEJFWh5R1gRgUH8A==", + "dev": true, "license": "ISC", "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" @@ -12044,6 +12288,7 @@ "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" @@ -12053,6 +12298,7 @@ "version": "0.5.19", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", + "dev": true, "license": "MIT", "dependencies": { "buffer-from": "^1.0.0", @@ -12063,6 +12309,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.1.tgz", "integrity": "sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==", + "dev": true, "license": "ISC", "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" @@ -12075,6 +12322,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -12088,6 +12336,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -12101,6 +12350,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -12114,6 +12364,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -12127,6 +12378,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -12140,6 +12392,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -12153,6 +12406,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -12166,6 +12420,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -12179,6 +12434,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -12192,6 +12448,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -12202,6 +12459,7 @@ "version": "21.6.10", "resolved": "https://registry.npmjs.org/@nx/webpack/-/webpack-21.6.10.tgz", "integrity": "sha512-T+eB9c3lflqWuegrsW47zzkZlSQ6YNEucEknUpWyDrKLCihucKe9siuj5s2gPkgdY6DXX4sjZcA5xgnxHNBWag==", + "dev": true, "license": "MIT", "dependencies": { "@babel/core": "^7.23.2", @@ -12246,6 +12504,7 @@ "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -12256,6 +12515,7 @@ "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", @@ -12280,6 +12540,7 @@ "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" @@ -12292,6 +12553,7 @@ "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==", + "dev": true, "license": "MIT", "dependencies": { "fast-glob": "^3.2.7", @@ -12316,6 +12578,7 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "dev": true, "license": "MIT", "dependencies": { "@types/parse-json": "^4.0.0", @@ -12332,6 +12595,7 @@ "version": "6.11.0", "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.11.0.tgz", "integrity": "sha512-CTJ+AEQJjq5NzLga5pE39qdiSV56F8ywCIsqNIRF0r7BDgWsN25aazToqAFg7ZrtA/U016xudB3ffgweORxX7g==", + "dev": true, "license": "MIT", "dependencies": { "icss-utils": "^5.1.0", @@ -12367,6 +12631,7 @@ "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==", + "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.16.7", @@ -12401,6 +12666,7 @@ "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", @@ -12417,6 +12683,7 @@ "version": "3.5.2", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, "license": "MIT", "peerDependencies": { "ajv": "^6.9.1" @@ -12426,6 +12693,7 @@ "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==", + "dev": true, "license": "MIT", "dependencies": { "@types/json-schema": "^7.0.8", @@ -12444,6 +12712,7 @@ "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==", + "dev": true, "license": "MIT", "dependencies": { "graceful-fs": "^4.2.0", @@ -12458,12 +12727,14 @@ "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==", + "dev": true, "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==", + "dev": true, "license": "MIT", "engines": { "node": ">= 14.15.0" @@ -12481,6 +12752,7 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", + "dev": true, "license": "MIT", "dependencies": { "big.js": "^5.2.2", @@ -12495,6 +12767,7 @@ "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==", + "dev": true, "license": "MIT", "dependencies": { "schema-utils": "^4.0.0" @@ -12514,6 +12787,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -12526,12 +12800,14 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/parse5/-/parse5-4.0.0.tgz", "integrity": "sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA==", + "dev": true, "license": "MIT" }, "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==", + "dev": true, "license": "MIT", "engines": { "node": ">=8.6" @@ -12544,6 +12820,7 @@ "version": "14.1.0", "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-14.1.0.tgz", "integrity": "sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw==", + "dev": true, "license": "MIT", "dependencies": { "postcss-value-parser": "^4.0.0", @@ -12561,6 +12838,7 @@ "version": "6.2.1", "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-6.2.1.tgz", "integrity": "sha512-WbbYpmAaKcux/P66bZ40bpWsBucjx/TTgVVzRZ9yUO8yQfVBlameJ0ZGVaPfH64hNSBh63a+ICP5nqOpBA0w+Q==", + "dev": true, "license": "MIT", "dependencies": { "cosmiconfig": "^7.0.0", @@ -12583,6 +12861,7 @@ "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" @@ -12595,6 +12874,7 @@ "version": "3.3.4", "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.4.tgz", "integrity": "sha512-0WqXzrsMTyb8yjZJHDqwmnwRJvhALK9LfRtRc6B4UTWe8AijYLZYZ9thuJTZc2VfQWINADW/j+LiJnfy2RoC1w==", + "dev": true, "license": "MIT", "engines": { "node": ">= 12.13.0" @@ -12611,6 +12891,7 @@ "version": "1.10.2", "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true, "license": "ISC", "engines": { "node": ">= 6" @@ -12620,6 +12901,7 @@ "version": "21.6.10", "resolved": "https://registry.npmjs.org/@nx/workspace/-/workspace-21.6.10.tgz", "integrity": "sha512-6OkXs4gAVjDtrfqhJf7lHZX/VlCFLRZpywfgvmije40wrExkJDNEHx3Gf6dvSVwl0vE6Gz8D2t6luO02hGGz4w==", + "dev": true, "license": "MIT", "dependencies": { "@nx/devkit": "21.6.10", @@ -12803,6 +13085,7 @@ "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, @@ -12842,6 +13125,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -12862,6 +13146,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -12882,6 +13167,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -12902,6 +13188,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -12922,6 +13209,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -12942,6 +13230,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -12962,6 +13251,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -12982,6 +13272,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -13002,6 +13293,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -13022,6 +13314,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -13042,6 +13335,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -13062,6 +13356,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -13082,6 +13377,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -13099,6 +13395,7 @@ "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": { @@ -13112,6 +13409,7 @@ "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 }, @@ -13149,6 +13447,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/@phenomnomnominal/tsquery/-/tsquery-5.0.1.tgz", "integrity": "sha512-3nVv+e2FQwsW8Aw6qTU6f+1rfcJ3hrcnvH/mu9i8YhxO+9sqbOfpL8m6PbET5+xKOlz/VSbp0RoYWYCtIsnmuA==", + "dev": true, "license": "MIT", "dependencies": { "esquery": "^1.4.0" @@ -13161,6 +13460,7 @@ "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, "license": "MIT", "optional": true, "engines": { @@ -13171,6 +13471,7 @@ "version": "0.2.9", "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==", + "dev": true, "license": "MIT", "engines": { "node": "^12.20.0 || ^14.18.0 || >=16.0.0" @@ -13926,6 +14227,7 @@ "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true, "license": "MIT" }, "node_modules/@sindresorhus/is": { @@ -13945,6 +14247,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, "license": "BSD-3-Clause", "dependencies": { "type-detect": "4.0.8" @@ -14828,6 +15131,7 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==", + "dev": true, "license": "ISC", "engines": { "node": ">=10.13.0" @@ -14929,6 +15233,7 @@ "version": "0.9.0", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.9.0.tgz", "integrity": "sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==", + "dev": true, "license": "MIT", "dependencies": { "tslib": "^2.4.0" @@ -14956,6 +15261,7 @@ "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, "license": "MIT", "dependencies": { "@babel/parser": "^7.20.7", @@ -14969,6 +15275,7 @@ "version": "7.27.0", "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.0.0" @@ -14978,6 +15285,7 @@ "version": "7.4.4", "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, "license": "MIT", "dependencies": { "@babel/parser": "^7.1.0", @@ -14988,6 +15296,7 @@ "version": "7.20.7", "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.7.tgz", "integrity": "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==", + "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.20.7" @@ -14997,6 +15306,7 @@ "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": "*", @@ -15007,6 +15317,7 @@ "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": "*" @@ -15051,6 +15362,7 @@ "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": "*" @@ -15060,6 +15372,7 @@ "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": "*", @@ -15107,6 +15420,7 @@ "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": "*", @@ -15117,6 +15431,7 @@ "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": "*", @@ -15127,12 +15442,14 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "devOptional": true, "license": "MIT" }, "node_modules/@types/express": { "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": "*", @@ -15144,6 +15461,7 @@ "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": "*", @@ -15190,7 +15508,7 @@ "version": "4.1.9", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "@types/node": "*" @@ -15215,7 +15533,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==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/@types/http-assert": { @@ -15236,12 +15554,14 @@ "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": "*" @@ -15262,12 +15582,14 @@ "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true, "license": "MIT" }, "node_modules/@types/istanbul-lib-report": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, "license": "MIT", "dependencies": { "@types/istanbul-lib-coverage": "*" @@ -15277,6 +15599,7 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, "license": "MIT", "dependencies": { "@types/istanbul-lib-report": "*" @@ -15344,6 +15667,7 @@ "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, "license": "MIT" }, "node_modules/@types/json5": { @@ -15489,6 +15813,7 @@ "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": { @@ -15561,6 +15886,7 @@ "version": "1.3.14", "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.14.tgz", "integrity": "sha512-mhVF2BnD4BO+jtOp7z1CdzaK4mbuK0LLQYAvdOLqHTavxFNq4zA1EmYkpnFjP8HOUzedfQkRnp0E2ulSAYSzAw==", + "dev": true, "license": "MIT", "dependencies": { "@types/node": "*" @@ -15616,12 +15942,14 @@ "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": { @@ -15664,6 +15992,7 @@ "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", @@ -15674,6 +16003,7 @@ "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": "*" @@ -15683,6 +16013,7 @@ "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": "*", @@ -15694,6 +16025,7 @@ "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": "*" @@ -15703,6 +16035,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true, "license": "MIT" }, "node_modules/@types/through": { @@ -15757,6 +16090,7 @@ "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": "*" @@ -15766,6 +16100,7 @@ "version": "17.0.33", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "dev": true, "license": "MIT", "dependencies": { "@types/yargs-parser": "*" @@ -15775,6 +16110,7 @@ "version": "21.0.3", "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true, "license": "MIT" }, "node_modules/@types/yauzl": { @@ -16318,6 +16654,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "dev": true, "license": "ISC" }, "node_modules/@unrs/resolver-binding-android-arm-eabi": { @@ -16327,6 +16664,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -16340,6 +16678,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -16353,6 +16692,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -16366,6 +16706,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -16379,6 +16720,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -16392,6 +16734,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -16405,6 +16748,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -16418,6 +16762,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -16431,6 +16776,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -16444,6 +16790,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -16457,6 +16804,7 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -16470,6 +16818,7 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -16483,6 +16832,7 @@ "cpu": [ "s390x" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -16496,6 +16846,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -16509,6 +16860,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -16522,6 +16874,7 @@ "cpu": [ "wasm32" ], + "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -16535,6 +16888,7 @@ "version": "0.2.11", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.11.tgz", "integrity": "sha512-9DPkXtvHydrcOsopiYpUgPHpmj0HWZKMUnL2dZqpvC42lsratuBG06V5ipyno0fUek5VlFsNQ+AcFATSrJXgMA==", + "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -16550,6 +16904,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -16563,6 +16918,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -16576,6 +16932,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -16684,6 +17041,7 @@ "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", @@ -16694,24 +17052,28 @@ "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", @@ -16723,12 +17085,14 @@ "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", @@ -16741,6 +17105,7 @@ "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" @@ -16750,6 +17115,7 @@ "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" @@ -16759,12 +17125,14 @@ "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", @@ -16781,6 +17149,7 @@ "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", @@ -16794,6 +17163,7 @@ "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", @@ -16806,6 +17176,7 @@ "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", @@ -16820,6 +17191,7 @@ "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", @@ -16894,12 +17266,14 @@ "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": { @@ -17108,12 +17482,14 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==", + "dev": true, "license": "BSD-2-Clause" }, "node_modules/@yarnpkg/parsers": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/@yarnpkg/parsers/-/parsers-3.0.2.tgz", "integrity": "sha512-/HcYgtUSiJiot/XWGLOlGxPYUG65+/31V8oqk17vZLW1xlCoR4PampyePljOxY2n8/3jz9+tIFzICsyGujJZoA==", + "dev": true, "license": "BSD-2-Clause", "dependencies": { "js-yaml": "^3.10.0", @@ -17127,6 +17503,7 @@ "version": "0.0.7", "resolved": "https://registry.npmjs.org/@zkochan/js-yaml/-/js-yaml-0.0.7.tgz", "integrity": "sha512-nrUSn7hzt7J6JWgWGz78ZYI8wj+gdIJdk0Ynjpp8l+trkn58Uqsf6RYrYkEK+3X18EX+TNdtJI0WxAtc+L84SQ==", + "dev": true, "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -17173,6 +17550,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "dev": true, "license": "MIT", "dependencies": { "mime-types": "^3.0.0", @@ -17209,6 +17587,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=10.13.0" @@ -17221,6 +17600,7 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, "license": "MIT", "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" @@ -17242,6 +17622,7 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/address/-/address-1.2.2.tgz", "integrity": "sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA==", + "dev": true, "license": "MIT", "engines": { "node": ">= 10.0.0" @@ -17316,6 +17697,7 @@ "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", @@ -17350,6 +17732,7 @@ "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" @@ -17411,6 +17794,7 @@ "version": "4.1.3", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -17447,6 +17831,7 @@ "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" ], @@ -17490,6 +17875,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, "license": "ISC", "dependencies": { "normalize-path": "^3.0.0", @@ -17503,6 +17889,7 @@ "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" @@ -17731,6 +18118,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, "license": "Python-2.0" }, "node_modules/aria-query": { @@ -17764,6 +18152,7 @@ "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": { @@ -17930,6 +18319,7 @@ "version": "3.2.6", "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "dev": true, "license": "MIT" }, "node_modules/async-exit-hook": { @@ -17981,6 +18371,7 @@ "version": "10.4.22", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.22.tgz", "integrity": "sha512-ARe0v/t9gO28Bznv6GgqARmVqcWOV3mfgUPn9becPHMiD3o9BwlRgaeccZnwTpZ7Zwqrm+c1sUSsMxIzQzc8Xg==", + "dev": true, "funding": [ { "type": "opencollective", @@ -18018,6 +18409,7 @@ "version": "5.3.4", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", + "dev": true, "license": "MIT", "engines": { "node": "*" @@ -18090,6 +18482,7 @@ "version": "1.13.2", "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", + "dev": true, "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", @@ -18133,6 +18526,7 @@ "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", @@ -18150,6 +18544,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/babel-plugin-const-enum/-/babel-plugin-const-enum-1.2.0.tgz", "integrity": "sha512-o1m/6iyyFnp9MRsK1dHF3bneqyf3AlM2q3A/YbgQr2pCat6B6XJVDv2TXqzfY2RYUi4mak6WAksSBPlyYGx9dg==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", @@ -18264,6 +18659,7 @@ "version": "0.4.14", "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.14.tgz", "integrity": "sha512-Co2Y9wX854ts6U8gAAPXfn0GmAyctHuK8n0Yhfjd6t30g7yvKjspvvOo9yG+z52PZRgFErt7Ka2pYnXCjLKEpg==", + "dev": true, "license": "MIT", "dependencies": { "@babel/compat-data": "^7.27.7", @@ -18278,6 +18674,7 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -18287,6 +18684,7 @@ "version": "0.13.0", "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.13.0.tgz", "integrity": "sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-define-polyfill-provider": "^0.6.5", @@ -18300,6 +18698,7 @@ "version": "0.6.5", "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.5.tgz", "integrity": "sha512-ISqQ2frbiNU9vIJkzg7dlPpznPZ4jOiUQ1uSmB0fEHeowtN3COYRsXr/xexn64NpU13P06jc/L5TgiJXOgrbEg==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-define-polyfill-provider": "^0.6.5" @@ -18312,6 +18711,7 @@ "version": "0.3.2", "resolved": "https://registry.npmjs.org/babel-plugin-transform-typescript-metadata/-/babel-plugin-transform-typescript-metadata-0.3.2.tgz", "integrity": "sha512-mWEvCQTgXQf48yDqgN7CH50waTyYBeP2Lpqx4nNWab9sxEpdXVeKgfj1qYI2/TgUPQtNFZ85i3PemRtnXVYYJg==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.0.0" @@ -18321,6 +18721,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz", "integrity": "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==", + "dev": true, "license": "MIT", "dependencies": { "@babel/plugin-syntax-async-generators": "^7.8.4", @@ -18375,6 +18776,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, "license": "MIT" }, "node_modules/base64-js": { @@ -18408,6 +18810,7 @@ "version": "2.9.3", "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.3.tgz", "integrity": "sha512-8QdH6czo+G7uBsNo0GiUfouPN1lRzKdJTGnKXwe12gkFbnnOUaUKGN55dMkfy+mnxmvjwl9zcI4VncczcVXDhA==", + "dev": true, "license": "Apache-2.0", "bin": { "baseline-browser-mapping": "dist/cli.js" @@ -18437,6 +18840,7 @@ "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": { @@ -18598,6 +19002,7 @@ "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": "*" @@ -18607,6 +19012,7 @@ "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" @@ -18675,6 +19081,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", + "dev": true, "license": "MIT", "dependencies": { "bytes": "^3.1.2", @@ -18695,6 +19102,7 @@ "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", @@ -18705,6 +19113,7 @@ "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": { @@ -18736,6 +19145,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" @@ -18745,6 +19155,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, "license": "MIT", "dependencies": { "fill-range": "^7.1.1" @@ -18800,6 +19211,7 @@ "version": "4.28.1", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "dev": true, "funding": [ { "type": "opencollective", @@ -18846,6 +19258,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, "license": "Apache-2.0", "dependencies": { "node-int64": "^0.4.0" @@ -18879,6 +19292,7 @@ "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==", + "dev": true, "license": "MIT/X11" }, "node_modules/buffer-crc32": { @@ -19338,7 +19752,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "pascal-case": "^3.1.2", @@ -19349,6 +19763,7 @@ "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -19368,6 +19783,7 @@ "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==", + "dev": true, "license": "MIT", "dependencies": { "browserslist": "^4.0.0", @@ -19380,6 +19796,7 @@ "version": "1.0.30001759", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001759.tgz", "integrity": "sha512-Pzfx9fOKoKvevQf8oCXoyNRQ5QyxJj+3O0Rqx2V5oxT61KGx8+n6hV/IUyJeifUci2clnmmKVpvtiqRzgiWjSw==", + "dev": true, "funding": [ { "type": "opencollective", @@ -19470,6 +19887,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -19550,6 +19968,7 @@ "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" @@ -19599,6 +20018,7 @@ "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" @@ -19615,6 +20035,7 @@ "version": "3.9.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, "funding": [ { "type": "github", @@ -19637,7 +20058,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==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "source-map": "~0.6.0" @@ -19650,7 +20071,7 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "devOptional": true, + "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -19738,6 +20159,7 @@ "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, "license": "ISC", "dependencies": { "string-width": "^4.2.0", @@ -19752,6 +20174,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", @@ -19819,6 +20242,7 @@ "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, "license": "MIT", "engines": { "iojs": ">= 1.0.0", @@ -19922,6 +20346,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", + "dev": true, "license": "MIT" }, "node_modules/color-convert": { @@ -19956,18 +20381,21 @@ "version": "2.9.3", "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", + "dev": true, "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==", + "dev": true, "license": "MIT" }, "node_modules/colors": { @@ -19984,6 +20412,7 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/columnify/-/columnify-1.6.0.tgz", "integrity": "sha512-lomjuFZKfM6MSAnV9aCZC9sc0qGbmZdfygNv+nCpqVkSKdCxCklLtd16O0EILGkImHw9ZpHkAnHaB+8Zxq5W6Q==", + "dev": true, "license": "MIT", "dependencies": { "strip-ansi": "^6.0.1", @@ -20018,6 +20447,7 @@ "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": { @@ -20065,6 +20495,7 @@ "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" @@ -20077,6 +20508,7 @@ "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", @@ -20095,6 +20527,7 @@ "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" @@ -20104,12 +20537,14 @@ "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" @@ -20119,6 +20554,7 @@ "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, "license": "MIT" }, "node_modules/concat-stream": { @@ -20343,6 +20779,7 @@ "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" @@ -20369,6 +20806,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", + "dev": true, "license": "MIT", "dependencies": { "safe-buffer": "5.2.1" @@ -20396,6 +20834,7 @@ "version": "0.7.2", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -20405,6 +20844,7 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.6.0" @@ -20427,6 +20867,7 @@ "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" @@ -20480,6 +20921,7 @@ "version": "3.43.0", "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.43.0.tgz", "integrity": "sha512-2GML2ZsCc5LR7hZYz4AXmjQw8zuy2T//2QntwdnpuYI7jteT6GVYJL7F6C2C57R7gSYrcqVW3lAALefdbhBLDA==", + "dev": true, "license": "MIT", "dependencies": { "browserslist": "^4.25.0" @@ -20499,6 +20941,7 @@ "version": "2.8.5", "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dev": true, "license": "MIT", "dependencies": { "object-assign": "^4", @@ -20650,6 +21093,7 @@ "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -20670,6 +21114,7 @@ "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==", + "dev": true, "license": "ISC", "engines": { "node": "^14 || ^16 || >=18" @@ -20718,6 +21163,7 @@ "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==", + "dev": true, "license": "MIT", "dependencies": { "@jridgewell/trace-mapping": "^0.3.18", @@ -20762,6 +21208,7 @@ "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", @@ -20778,6 +21225,7 @@ "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==", + "dev": true, "license": "MIT", "dependencies": { "mdn-data": "2.0.30", @@ -20791,6 +21239,7 @@ "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" @@ -20810,6 +21259,7 @@ "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" @@ -20822,6 +21272,7 @@ "version": "6.1.2", "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-6.1.2.tgz", "integrity": "sha512-rYk5UeX7VAM/u0lNqewCdasdtPK81CgX8wJFLEIXHbV2oldWRgJAsZrdhRXkV1NJzA2g850KiFm9mMU2HxNxMA==", + "dev": true, "license": "MIT", "dependencies": { "cssnano-preset-default": "^6.1.2", @@ -20842,6 +21293,7 @@ "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==", + "dev": true, "license": "MIT", "dependencies": { "browserslist": "^4.23.0", @@ -20886,6 +21338,7 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-4.0.2.tgz", "integrity": "sha512-ZR1jHg+wZ8o4c3zqf1SIUSTIvm/9mU343FMR6Obe/unskbvpGhZOo1J6d/r8D1pzkRQYuwbcH3hToOuoA2G7oQ==", + "dev": true, "license": "MIT", "engines": { "node": "^14 || ^16 || >=18.0" @@ -20898,6 +21351,7 @@ "version": "5.0.5", "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==", + "dev": true, "license": "MIT", "dependencies": { "css-tree": "~2.2.0" @@ -20911,6 +21365,7 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz", "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==", + "dev": true, "license": "MIT", "dependencies": { "mdn-data": "2.0.28", @@ -20925,6 +21380,7 @@ "version": "2.0.28", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz", "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==", + "dev": true, "license": "CC0-1.0" }, "node_modules/cssom": { @@ -21143,6 +21599,7 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.6.0.tgz", "integrity": "sha512-F1Z+5UCFpmQUzJa11agbyPVMbpgT/qA3/SKyJ1jyBgm7dUcUEa8v9JwDkerSQXfakBwFljIxhOJqGkjUwZ9FSA==", + "dev": true, "license": "MIT", "peerDependencies": { "babel-plugin-macros": "^3.1.0" @@ -21183,12 +21640,14 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, "license": "MIT" }, "node_modules/deepmerge": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -21366,6 +21825,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -21375,12 +21835,14 @@ "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": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/detect-port/-/detect-port-1.6.1.tgz", "integrity": "sha512-CmnVc+Hek2egPx1PeTFVta2W78xy2K/9Rkf6cC4T59S50tVnzKj+tnx5mmx5lwvCkujZ4uRrpRSuV+IVs3f90Q==", + "dev": true, "license": "MIT", "dependencies": { "address": "^1.0.1", @@ -21582,6 +22044,7 @@ "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" @@ -21700,6 +22163,7 @@ "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" @@ -21733,7 +22197,7 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", "integrity": "sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "utila": "~0.4" @@ -21743,6 +22207,7 @@ "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", @@ -21757,6 +22222,7 @@ "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", @@ -21783,6 +22249,7 @@ "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" @@ -21798,6 +22265,7 @@ "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", @@ -21819,7 +22287,7 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "no-case": "^3.0.4", @@ -21846,6 +22314,7 @@ "version": "16.5.0", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz", "integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==", + "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=12" @@ -21858,6 +22327,7 @@ "version": "11.0.7", "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-11.0.7.tgz", "integrity": "sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA==", + "dev": true, "license": "BSD-2-Clause", "dependencies": { "dotenv": "^16.4.5" @@ -21904,6 +22374,7 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, "license": "MIT" }, "node_modules/ee-first": { @@ -21916,6 +22387,7 @@ "version": "3.1.10", "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "dev": true, "license": "Apache-2.0", "dependencies": { "jake": "^10.8.5" @@ -22134,6 +22606,7 @@ "version": "1.5.266", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.266.tgz", "integrity": "sha512-kgWEglXvkEfMH7rxP5OSZZwnaDWT7J9EoZCujhnpLbfi0bbNtRkgdX2E3gt0Uer11c61qCYktB3hwkAS325sJg==", + "dev": true, "license": "ISC" }, "node_modules/electron-updater": { @@ -22259,6 +22732,7 @@ "version": "0.13.1", "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -22292,6 +22766,7 @@ "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" @@ -22334,6 +22809,7 @@ "version": "1.4.5", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dev": true, "license": "MIT", "dependencies": { "once": "^1.4.0" @@ -22343,6 +22819,7 @@ "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", @@ -22356,6 +22833,7 @@ "version": "2.3.6", "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "dev": true, "license": "MIT", "dependencies": { "ansi-colors": "^4.1.1" @@ -22368,6 +22846,7 @@ "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=0.12" @@ -22436,6 +22915,7 @@ "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": { @@ -22545,6 +23025,7 @@ "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": { @@ -22676,7 +23157,7 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/esbuild-register/-/esbuild-register-3.6.0.tgz", "integrity": "sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "debug": "^4.3.4" @@ -22702,6 +23183,7 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -22762,6 +23244,7 @@ "version": "9.26.0", "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.26.0.tgz", "integrity": "sha512-Hx0MOjPh6uK9oq9nVsATZKE/Wlbai7KFjfCuw9UHaguDW3x+HF0O5nIi3ud39TWgrTjTO5nHxmL3R1eANinWHQ==", + "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", @@ -23089,6 +23572,7 @@ "version": "8.4.0", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, "license": "BSD-2-Clause", "dependencies": { "esrecurse": "^4.3.0", @@ -23105,6 +23589,7 @@ "version": "3.4.3", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, "license": "Apache-2.0", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -23117,6 +23602,7 @@ "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", @@ -23133,6 +23619,7 @@ "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -23143,6 +23630,7 @@ "version": "4.2.1", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -23155,6 +23643,7 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, "license": "MIT", "engines": { "node": ">= 4" @@ -23164,12 +23653,14 @@ "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==", + "dev": true, "license": "MIT" }, "node_modules/eslint/node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -23182,6 +23673,7 @@ "version": "10.4.0", "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, "license": "BSD-2-Clause", "dependencies": { "acorn": "^8.15.0", @@ -23199,6 +23691,7 @@ "version": "4.2.1", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -23224,6 +23717,7 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, "license": "BSD-3-Clause", "dependencies": { "estraverse": "^5.1.0" @@ -23236,6 +23730,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, "license": "BSD-2-Clause", "dependencies": { "estraverse": "^5.2.0" @@ -23248,6 +23743,7 @@ "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=4.0" @@ -23267,6 +23763,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=0.10.0" @@ -23276,6 +23773,7 @@ "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -23310,12 +23808,14 @@ "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" @@ -23334,6 +23834,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.2.tgz", "integrity": "sha512-6RxOBZ/cYgd8usLwsEl+EC09Au/9BcmCKYF2/xbml6DNczf7nv0MQb+7BA2F+li6//I+28VNlQR37XfQtcAJuA==", + "dev": true, "license": "MIT", "engines": { "node": ">=18.0.0" @@ -23396,6 +23897,7 @@ "version": "0.2.2", "resolved": "https://registry.npmjs.org/exit-x/-/exit-x-0.2.2.tgz", "integrity": "sha512-+I6B/IkJc1o/2tiURyz/ivu/O0nKNEArIUB5O7zBrlDVJr22SCLH3xTeEry428LvFhRzIA1g8izguxJ/gbNcVQ==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.8.0" @@ -23459,6 +23961,7 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", + "dev": true, "license": "MIT", "dependencies": { "accepts": "^2.0.0", @@ -23501,6 +24004,7 @@ "version": "7.5.1", "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.1.tgz", "integrity": "sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==", + "dev": true, "license": "MIT", "engines": { "node": ">= 16" @@ -23516,6 +24020,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", + "dev": true, "license": "MIT", "dependencies": { "debug": "^4.4.0", @@ -23640,12 +24145,14 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, "license": "MIT" }, "node_modules/fast-glob": { "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", @@ -23662,6 +24169,7 @@ "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" @@ -23674,18 +24182,21 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, "license": "MIT" }, "node_modules/fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, "license": "MIT" }, "node_modules/fast-uri": { "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", @@ -23712,6 +24223,7 @@ "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" @@ -23721,6 +24233,7 @@ "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" @@ -23733,6 +24246,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, "license": "Apache-2.0", "dependencies": { "bser": "2.1.1" @@ -23752,6 +24266,7 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "devOptional": true, "license": "MIT", "engines": { "node": ">=12.0.0" @@ -23803,6 +24318,7 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, "license": "MIT", "dependencies": { "flat-cache": "^4.0.0" @@ -23815,6 +24331,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", + "dev": true, "license": "Apache-2.0", "dependencies": { "minimatch": "^5.0.1" @@ -23824,6 +24341,7 @@ "version": "5.1.6", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" @@ -23836,6 +24354,7 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" @@ -23917,6 +24436,7 @@ "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", @@ -23991,6 +24511,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, "license": "MIT", "dependencies": { "locate-path": "^6.0.0", @@ -24007,6 +24528,7 @@ "version": "5.0.2", "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, "license": "BSD-3-Clause", "bin": { "flat": "cli.js" @@ -24016,6 +24538,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, "license": "MIT", "dependencies": { "flatted": "^3.2.9", @@ -24029,12 +24552,14 @@ "version": "3.3.3", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, "license": "ISC" }, "node_modules/follow-redirects": { "version": "1.15.9", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "dev": true, "funding": [ { "type": "individual", @@ -24071,6 +24596,7 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, "license": "ISC", "dependencies": { "cross-spawn": "^7.0.6", @@ -24336,6 +24862,7 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -24368,6 +24895,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.8" @@ -24416,6 +24944,7 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/front-matter/-/front-matter-4.0.2.tgz", "integrity": "sha512-I8ZuJ/qG92NWX8i5x1Y8qyj3vizhXS31OxjKDu3LKP+7/qBgfIKValiZIEwoVoJKUHlhWtYrktkxV1XsX+pPlg==", + "dev": true, "license": "MIT", "dependencies": { "js-yaml": "^3.13.1" @@ -24425,6 +24954,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "dev": true, "license": "MIT" }, "node_modules/fs-exists-sync": { @@ -24489,18 +25019,21 @@ "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": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, "license": "ISC" }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, @@ -24555,6 +25088,7 @@ "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -24564,6 +25098,7 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, "license": "ISC", "engines": { "node": "6.* || 8.* || >= 10.*" @@ -24610,6 +25145,7 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, "license": "MIT", "engines": { "node": ">=8.0.0" @@ -24710,6 +25246,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, "license": "ISC", "dependencies": { "is-glob": "^4.0.3" @@ -24722,6 +25259,7 @@ "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": { @@ -24830,6 +25368,7 @@ "version": "12.2.0", "resolved": "https://registry.npmjs.org/globby/-/globby-12.2.0.tgz", "integrity": "sha512-wiSuFQLZ+urS9x2gGPl1H5drc5twabmm4m2gTR27XDFyjUHJUNsS8o/2aKyIF6IoBaR630atdher0XJ5g6OMmA==", + "dev": true, "license": "MIT", "dependencies": { "array-union": "^3.0.1", @@ -24850,6 +25389,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/array-union/-/array-union-3.0.1.tgz", "integrity": "sha512-1OvF9IbWwaeiM9VhzYXVQacMibxpXOMYVNIvMtKRyX9SImBXpKcFr8XvFDeEslCyuH/t6KRt7HEO94AlP8Iatw==", + "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -24862,6 +25402,7 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, "license": "MIT", "engines": { "node": ">= 4" @@ -24871,6 +25412,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", + "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -24940,6 +25482,7 @@ "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": { @@ -24978,6 +25521,7 @@ "version": "1.6.2", "resolved": "https://registry.npmjs.org/harmony-reflect/-/harmony-reflect-1.6.2.tgz", "integrity": "sha512-HIp/n38R9kQjDEziXyDTuW3vvoxxyxjxFzXLrBr18uB47GnSt+G9D29fqrpM5ZkspMcPICud3XsBJQ4Y2URg8g==", + "dev": true, "license": "(Apache-2.0 OR MPL-1.1)" }, "node_modules/has-bigints": { @@ -25101,7 +25645,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "devOptional": true, + "dev": true, "license": "MIT", "bin": { "he": "bin/he" @@ -25157,6 +25701,7 @@ "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", @@ -25198,6 +25743,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, "license": "MIT" }, "node_modules/html-loader": { @@ -25264,7 +25810,7 @@ "version": "5.6.5", "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.6.5.tgz", "integrity": "sha512-4xynFbKNNk+WlzXeQQ+6YYsH2g7mpfPszQZUi3ovKlj+pDmngQ7vRXjrrmGROabmKwyQkcgcX5hqfOwHbFmK5g==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "@types/html-minifier-terser": "^6.0.0", @@ -25297,7 +25843,7 @@ "version": "8.3.0", "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">= 12" @@ -25307,7 +25853,7 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", "integrity": "sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "camel-case": "^4.1.2", @@ -25452,6 +25998,7 @@ "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": { @@ -25483,12 +26030,14 @@ "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", @@ -25597,6 +26146,7 @@ "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" @@ -25676,6 +26226,7 @@ "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" @@ -25688,6 +26239,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/identity-obj-proxy/-/identity-obj-proxy-3.0.0.tgz", "integrity": "sha512-00n6YnVHKrinT9t0d9+5yZC6UBNJANpYEQvL2LlX6Ab9lnmxzIRcEmTPuyGScvl1+jKuCICX1Z0Ab1pPKKdikA==", + "dev": true, "license": "MIT", "dependencies": { "harmony-reflect": "^1.4.6" @@ -25759,6 +26311,7 @@ "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": { @@ -25778,6 +26331,7 @@ "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": { @@ -25898,6 +26452,7 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.8.19" @@ -25934,6 +26489,7 @@ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, "license": "ISC", "dependencies": { "once": "^1.3.0", @@ -26063,6 +26619,7 @@ "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.10" @@ -26149,6 +26706,7 @@ "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" @@ -26279,6 +26837,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -26313,6 +26872,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -26341,6 +26901,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" @@ -26425,6 +26986,7 @@ "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" @@ -26437,6 +26999,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.12.0" @@ -26685,6 +27248,7 @@ "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": { @@ -26736,6 +27300,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, "license": "ISC" }, "node_modules/isobject": { @@ -26752,6 +27317,7 @@ "version": "3.2.2", "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=8" @@ -26774,6 +27340,7 @@ "version": "6.0.3", "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "dev": true, "license": "BSD-3-Clause", "dependencies": { "@babel/core": "^7.23.9", @@ -26894,6 +27461,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, "license": "BSD-3-Clause", "dependencies": { "istanbul-lib-coverage": "^3.0.0", @@ -26933,6 +27501,7 @@ "version": "3.1.7", "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "dev": true, "license": "BSD-3-Clause", "dependencies": { "html-escaper": "^2.0.0", @@ -26962,6 +27531,7 @@ "version": "10.9.2", "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz", "integrity": "sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==", + "dev": true, "license": "Apache-2.0", "dependencies": { "async": "^3.2.3", @@ -26980,6 +27550,7 @@ "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -26990,6 +27561,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -27272,6 +27844,7 @@ "version": "30.2.0", "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.2.0.tgz", "integrity": "sha512-dQHFo3Pt4/NLlG5z4PxZ/3yZTZ1C7s9hveiOj+GCN+uT109NC2QgsoVZsVOAvbJ3RgKkvyLGXZV9+piDpWbm6A==", + "dev": true, "license": "MIT", "dependencies": { "@jest/diff-sequences": "30.0.1", @@ -27287,6 +27860,7 @@ "version": "30.0.5", "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "dev": true, "license": "MIT", "dependencies": { "@sinclair/typebox": "^0.34.0" @@ -27299,12 +27873,14 @@ "version": "0.34.41", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.41.tgz", "integrity": "sha512-6gS8pZzSXdyRHTIqoqSVknxolr1kzfy4/CeDnrzsVz8TTIWUbOBr6gnzOmTYJ3eXQNh4IYHIGi5aIL7sOZ2G/g==", + "dev": true, "license": "MIT" }, "node_modules/jest-diff/node_modules/ansi-styles": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -27317,6 +27893,7 @@ "version": "30.2.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.2.0.tgz", "integrity": "sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA==", + "dev": true, "license": "MIT", "dependencies": { "@jest/schemas": "30.0.5", @@ -27331,6 +27908,7 @@ "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, "license": "MIT" }, "node_modules/jest-docblock": { @@ -27669,7 +28247,7 @@ "version": "29.6.3", "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -27679,7 +28257,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "@jest/types": "^29.6.3", @@ -28026,6 +28604,7 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -28158,7 +28737,7 @@ "version": "29.6.3", "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -28168,7 +28747,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "chalk": "^4.0.0", @@ -28430,6 +29009,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, "license": "MIT", "dependencies": { "@jest/types": "^29.6.3", @@ -28447,6 +29027,7 @@ "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" @@ -28459,7 +29040,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "@jest/types": "^29.6.3", @@ -28477,7 +29058,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -28490,7 +29071,7 @@ "version": "6.3.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -28503,7 +29084,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "@jest/schemas": "^29.6.3", @@ -28518,7 +29099,7 @@ "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/jest-watch-typeahead": { @@ -28662,6 +29243,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "dev": true, "license": "MIT", "dependencies": { "@types/node": "*", @@ -28677,6 +29259,7 @@ "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" @@ -28692,7 +29275,7 @@ "version": "1.21.7", "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", - "devOptional": true, + "dev": true, "license": "MIT", "bin": { "jiti": "bin/jiti.js" @@ -28722,6 +29305,7 @@ "version": "3.14.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, "license": "MIT", "dependencies": { "argparse": "^1.0.7", @@ -28735,6 +29319,7 @@ "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, "license": "MIT", "dependencies": { "sprintf-js": "~1.0.2" @@ -28744,6 +29329,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true, "license": "BSD-3-Clause" }, "node_modules/jsbn": { @@ -28838,6 +29424,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, "license": "MIT" }, "node_modules/json-parse-even-better-errors": { @@ -28854,6 +29441,7 @@ "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": { @@ -28867,6 +29455,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, "license": "MIT" }, "node_modules/json-stringify-safe": { @@ -28879,6 +29468,7 @@ "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, "license": "MIT", "bin": { "json5": "lib/cli.js" @@ -29014,6 +29604,7 @@ "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, "license": "MIT", "dependencies": { "json-buffer": "3.0.1" @@ -29043,6 +29634,7 @@ "version": "2.0.6", "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.6.tgz", "integrity": "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==", + "dev": true, "license": "MIT", "engines": { "node": ">= 8" @@ -29224,6 +29816,7 @@ "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", @@ -29241,6 +29834,7 @@ "version": "4.4.0", "resolved": "https://registry.npmjs.org/less/-/less-4.4.0.tgz", "integrity": "sha512-kdTwsyRuncDfjEs0DlRILWNvxhDG/Zij4YLO4TMJgDLW+8OzpfkdPnRgrsRuY1o+oaxJGWsps5f/RVBgGmmN0w==", + "dev": true, "license": "Apache-2.0", "dependencies": { "copy-anything": "^2.0.1", @@ -29294,6 +29888,7 @@ "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": { @@ -29308,6 +29903,7 @@ "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": { @@ -29321,6 +29917,7 @@ "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": { @@ -29331,6 +29928,7 @@ "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": { @@ -29341,6 +29939,7 @@ "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": { @@ -29351,6 +29950,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -29360,6 +29960,7 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, "license": "MIT", "dependencies": { "prelude-ls": "^1.2.1", @@ -29373,6 +29974,7 @@ "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" @@ -29399,6 +30001,7 @@ "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" @@ -29411,6 +30014,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-2.0.3.tgz", "integrity": "sha512-cNOjgCnLB+FnvWWtyRTzmB3POJ+cXxTA81LoW7u8JdmhfXzriropYwpjShnz1QLLWsQwY7nIxoDmcPTwphDK9w==", + "dev": true, "license": "MIT", "engines": { "node": "^12.20.0 || ^14.13.1 || >=16.0.0" @@ -29844,6 +30448,7 @@ "version": "4.3.1", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.1.tgz", "integrity": "sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.11.5" @@ -29867,6 +30472,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, "license": "MIT", "dependencies": { "p-locate": "^5.0.0" @@ -29888,6 +30494,7 @@ "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "dev": true, "license": "MIT" }, "node_modules/lodash.escaperegexp": { @@ -29916,18 +30523,21 @@ "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": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, "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==", + "dev": true, "license": "MIT" }, "node_modules/log-symbols": { @@ -30221,7 +30831,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "tslib": "^2.0.3" @@ -30241,6 +30851,7 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, "license": "ISC", "dependencies": { "yallist": "^3.0.2" @@ -30290,6 +30901,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, "license": "MIT", "dependencies": { "semver": "^7.5.3" @@ -30421,6 +31033,7 @@ "version": "1.0.12", "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, "license": "BSD-3-Clause", "dependencies": { "tmpl": "1.0.5" @@ -30710,6 +31323,7 @@ "version": "2.0.30", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", + "dev": true, "license": "CC0-1.0" }, "node_modules/media-typer": { @@ -30725,6 +31339,7 @@ "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" @@ -30737,6 +31352,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -30749,12 +31365,14 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, "license": "MIT" }, "node_modules/merge2": { "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" @@ -30764,6 +31382,7 @@ "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" @@ -31364,6 +31983,7 @@ "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, "license": "MIT", "dependencies": { "braces": "^3.0.3", @@ -31377,6 +31997,7 @@ "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" @@ -31487,12 +32108,14 @@ "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": { "version": "9.0.3", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" @@ -31517,6 +32140,7 @@ "version": "7.1.2", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, "license": "ISC", "engines": { "node": ">=16 || 14 >=14.17" @@ -31920,6 +32544,7 @@ "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", @@ -32018,6 +32643,7 @@ "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "devOptional": true, "funding": [ { "type": "github", @@ -32043,6 +32669,7 @@ "version": "0.2.4", "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.2.4.tgz", "integrity": "sha512-ZEzHJwBhZ8qQSbknHqYcdtQVr8zUgGyM/q6h6qAyhtyVMNrSgDhrC4disf03dYW0e+czXyLnZINnCTEkWy0eJg==", + "dev": true, "license": "MIT", "bin": { "napi-postinstall": "lib/cli.js" @@ -32058,12 +32685,14 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, "license": "MIT" }, "node_modules/needle": { "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": { @@ -32081,6 +32710,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -32090,6 +32720,7 @@ "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": { @@ -32120,7 +32751,7 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "lower-case": "^2.0.2", @@ -32144,6 +32775,7 @@ "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-addon-api": { @@ -32633,12 +33265,14 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true, "license": "MIT" }, "node_modules/node-machine-id": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/node-machine-id/-/node-machine-id-1.1.12.tgz", "integrity": "sha512-QNABxbrPa3qEIfrE6GOJ7BYIuignnJw7iQ2YPbc3Nla1HzRJjXzZOiikfF8m7eAMfichLt3M4VgLOetqgDmgGQ==", + "dev": true, "license": "MIT" }, "node_modules/node-preload": { @@ -32658,6 +33292,7 @@ "version": "2.0.27", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "dev": true, "license": "MIT" }, "node_modules/nopt": { @@ -32680,6 +33315,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -32689,6 +33325,7 @@ "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" @@ -33224,6 +33861,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, "license": "MIT", "dependencies": { "path-key": "^3.0.0" @@ -33236,6 +33874,7 @@ "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" @@ -33254,6 +33893,7 @@ "version": "21.6.10", "resolved": "https://registry.npmjs.org/nx/-/nx-21.6.10.tgz", "integrity": "sha512-iKSyAg0VGG1MEOnlyyseMOt4n9J7I955VC+0UPQbNQTLdIUW8ibIHubpQyjd8Qvq4CfrLxzm+iq1AmbZ5vEG4A==", + "dev": true, "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -33326,6 +33966,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -33335,6 +33976,7 @@ "version": "16.4.7", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", + "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=12" @@ -33347,6 +33989,7 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, "license": "MIT", "engines": { "node": ">= 4" @@ -33356,6 +33999,7 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true, "license": "MIT", "bin": { "is-docker": "cli.js" @@ -33371,6 +34015,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, "license": "MIT", "dependencies": { "is-docker": "^2.0.0" @@ -33383,12 +34028,14 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", + "dev": true, "license": "MIT" }, "node_modules/nx/node_modules/open": { "version": "8.4.2", "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", + "dev": true, "license": "MIT", "dependencies": { "define-lazy-prop": "^2.0.0", @@ -33406,6 +34053,7 @@ "version": "5.3.0", "resolved": "https://registry.npmjs.org/ora/-/ora-5.3.0.tgz", "integrity": "sha512-zAKMgGXUim0Jyd6CXK9lraBnD3H5yPGBPPOkC23a2BG6hsm4Zu6OQSjQuEtV0BHDf4aKHcUFvJiGRrFuW3MG8g==", + "dev": true, "license": "MIT", "dependencies": { "bl": "^4.0.3", @@ -33428,6 +34076,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, "license": "MIT", "engines": { "node": ">=4" @@ -33437,6 +34086,7 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", + "dev": true, "license": "MIT", "dependencies": { "json5": "^2.2.2", @@ -33900,6 +34550,7 @@ "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": { @@ -33931,6 +34582,7 @@ "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" @@ -33940,6 +34592,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, "license": "ISC", "dependencies": { "wrappy": "1" @@ -34019,6 +34672,7 @@ "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, "license": "MIT", "dependencies": { "deep-is": "^0.1.3", @@ -34141,6 +34795,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, "license": "MIT", "dependencies": { "yocto-queue": "^0.1.0" @@ -34156,6 +34811,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, "license": "MIT", "dependencies": { "p-limit": "^3.0.2" @@ -34187,6 +34843,7 @@ "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", @@ -34204,12 +34861,14 @@ "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" @@ -34219,6 +34878,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -34244,6 +34904,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, "license": "BlueOak-1.0.0" }, "node_modules/pacote": { @@ -34575,7 +35236,7 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "dot-case": "^3.0.4", @@ -34628,6 +35289,7 @@ "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" @@ -34787,7 +35449,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "no-case": "^3.0.4", @@ -34805,6 +35467,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -34814,6 +35477,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -34823,6 +35487,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -34934,6 +35599,7 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -34968,6 +35634,7 @@ "version": "4.0.7", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, "license": "MIT", "engines": { "node": ">= 6" @@ -34990,6 +35657,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.0.tgz", "integrity": "sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=16.20.0" @@ -34999,6 +35667,7 @@ "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" @@ -35014,6 +35683,7 @@ "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", @@ -35030,6 +35700,7 @@ "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" @@ -35045,6 +35716,7 @@ "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" @@ -35060,6 +35732,7 @@ "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" @@ -35075,6 +35748,7 @@ "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" @@ -35084,6 +35758,7 @@ "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" @@ -35258,6 +35933,7 @@ "version": "8.5.6", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "devOptional": true, "funding": [ { "type": "opencollective", @@ -35286,6 +35962,7 @@ "version": "9.0.1", "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-9.0.1.tgz", "integrity": "sha512-TipgjGyzP5QzEhsOZUaIkeO5mKeMFpebWzRogWG/ysonUlnHcq5aJe0jOjpfzUU8PeSaBQnrE8ehR0QA5vs8PQ==", + "dev": true, "license": "MIT", "dependencies": { "postcss-selector-parser": "^6.0.11", @@ -35302,6 +35979,7 @@ "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==", + "dev": true, "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -35315,6 +35993,7 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-6.1.0.tgz", "integrity": "sha512-x9yX7DOxeMAR+BgGVnNSAxmAj98NX/YxEMNFP+SDCEeNLb2r3i6Hh1ksMsnW8Ub5SLCpbescQqn9YEbE9554Sw==", + "dev": true, "license": "MIT", "dependencies": { "browserslist": "^4.23.0", @@ -35333,6 +36012,7 @@ "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==", + "dev": true, "license": "MIT", "dependencies": { "browserslist": "^4.23.0", @@ -35349,6 +36029,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-6.0.2.tgz", "integrity": "sha512-65w/uIqhSBBfQmYnG92FO1mWZjJ4GL5b8atm5Yw2UgrwD7HiNiSSNwJor1eCFGzUgYnN/iIknhNRVqjrrpuglw==", + "dev": true, "license": "MIT", "engines": { "node": "^14 || ^16 || >=18.0" @@ -35361,6 +36042,7 @@ "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==", + "dev": true, "license": "MIT", "engines": { "node": "^14 || ^16 || >=18.0" @@ -35373,6 +36055,7 @@ "version": "6.0.3", "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-6.0.3.tgz", "integrity": "sha512-znyno9cHKQsK6PtxL5D19Fj9uwSzC2mB74cpT66fhgOadEUPyXFkbgwm5tvc3bt3NAy8ltE5MrghxovZRVnOjQ==", + "dev": true, "license": "MIT", "engines": { "node": "^14 || ^16 || >=18.0" @@ -35385,6 +36068,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-6.0.2.tgz", "integrity": "sha512-j87xzI4LUggC5zND7KdjsI25APtyMuynXZSujByMaav2roV6OZX+8AaCUcZSWqckZpjAjRyFDdpqybgjFO0HJQ==", + "dev": true, "license": "MIT", "engines": { "node": "^14 || ^16 || >=18.0" @@ -35520,6 +36204,7 @@ "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==", + "dev": true, "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0", @@ -35536,6 +36221,7 @@ "version": "6.1.1", "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-6.1.1.tgz", "integrity": "sha512-KOdWF0gju31AQPZiD+2Ar9Qjowz1LTChSjFFbS+e2sFgc4uHOp3ZvVX4sNeTlk0w2O31ecFGgrFzhO0RSWbWwQ==", + "dev": true, "license": "MIT", "dependencies": { "browserslist": "^4.23.0", @@ -35554,6 +36240,7 @@ "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==", + "dev": true, "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -35567,6 +36254,7 @@ "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==", + "dev": true, "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" @@ -35582,6 +36270,7 @@ "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==", + "dev": true, "license": "MIT", "dependencies": { "colord": "^2.9.3", @@ -35599,6 +36288,7 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-6.1.0.tgz", "integrity": "sha512-bmSKnDtyyE8ujHQK0RQJDIKhQ20Jq1LYiez54WiaOoBtcSuflfK3Nm596LvbtlFcpipMjgClQGyGr7GAs+H1uA==", + "dev": true, "license": "MIT", "dependencies": { "browserslist": "^4.23.0", @@ -35616,6 +36306,7 @@ "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==", + "dev": true, "license": "MIT", "dependencies": { "postcss-selector-parser": "^6.0.16" @@ -35631,6 +36322,7 @@ "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==", + "dev": true, "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -35644,6 +36336,7 @@ "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" @@ -35656,6 +36349,7 @@ "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", @@ -35673,6 +36367,7 @@ "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" @@ -35688,6 +36383,7 @@ "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" @@ -35743,6 +36439,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-6.0.2.tgz", "integrity": "sha512-a8N9czmdnrjPHa3DeFlwqst5eaL5W8jYu3EBbTTkI5FHkfMhFZh1EGbku6jhHhIzTA6tquI2P42NtZ59M/H/kQ==", + "dev": true, "license": "MIT", "engines": { "node": "^14 || ^16 || >=18.0" @@ -35755,6 +36452,7 @@ "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==", + "dev": true, "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" @@ -35770,6 +36468,7 @@ "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==", + "dev": true, "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" @@ -35785,6 +36484,7 @@ "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==", + "dev": true, "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" @@ -35800,6 +36500,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-6.0.2.tgz", "integrity": "sha512-vQZIivlxlfqqMp4L9PZsFE4YUkWniziKjQWUtsxUiVsSSPelQydwS8Wwcuw0+83ZjPWNTl02oxlIvXsmmG+CiQ==", + "dev": true, "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" @@ -35815,6 +36516,7 @@ "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==", + "dev": true, "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" @@ -35830,6 +36532,7 @@ "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==", + "dev": true, "license": "MIT", "dependencies": { "browserslist": "^4.23.0", @@ -35846,6 +36549,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-6.0.2.tgz", "integrity": "sha512-kVNcWhCeKAzZ8B4pv/DnrU1wNh458zBNp8dh4y5hhxih5RZQ12QWMuQrDgPRw3LRl8mN9vOVfHl7uhvHYMoXsQ==", + "dev": true, "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" @@ -35861,6 +36565,7 @@ "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==", + "dev": true, "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" @@ -35876,6 +36581,7 @@ "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==", + "dev": true, "license": "MIT", "dependencies": { "cssnano-utils": "^4.0.2", @@ -35892,6 +36598,7 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-6.1.0.tgz", "integrity": "sha512-RarLgBK/CrL1qZags04oKbVbrrVK2wcxhvta3GCxrZO4zveibqbRPmm2VI8sSgCXwoUHEliRSbOfpR0b/VIoiw==", + "dev": true, "license": "MIT", "dependencies": { "browserslist": "^4.23.0", @@ -35908,6 +36615,7 @@ "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==", + "dev": true, "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" @@ -35923,6 +36631,7 @@ "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", @@ -35936,6 +36645,7 @@ "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==", + "dev": true, "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0", @@ -35952,6 +36662,7 @@ "version": "6.0.4", "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-6.0.4.tgz", "integrity": "sha512-K38OCaIrO8+PzpArzkLKB42dSARtC2tmG6PvD4b1o1Q2E9Os8jzfWFfSy/rixsHwohtsDdFtAWGjFVFUdwYaMg==", + "dev": true, "license": "MIT", "dependencies": { "postcss-selector-parser": "^6.0.16" @@ -35967,6 +36678,7 @@ "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==", + "dev": true, "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -35980,6 +36692,7 @@ "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": { @@ -36055,6 +36768,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.8.0" @@ -36159,7 +36873,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz", "integrity": "sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "lodash": "^4.17.20", @@ -36317,6 +37031,7 @@ "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dev": true, "license": "MIT", "dependencies": { "forwarded": "0.2.0", @@ -36330,6 +37045,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true, "license": "MIT" }, "node_modules/proxy-middleware": { @@ -36346,6 +37062,7 @@ "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 }, @@ -36438,6 +37155,7 @@ "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", @@ -36471,6 +37189,7 @@ "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" @@ -36480,6 +37199,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -36489,6 +37209,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", + "dev": true, "license": "MIT", "dependencies": { "bytes": "3.1.2", @@ -36585,6 +37306,7 @@ "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" @@ -36594,6 +37316,7 @@ "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" @@ -36630,6 +37353,7 @@ "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" @@ -36727,12 +37451,14 @@ "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "dev": true, "license": "MIT" }, "node_modules/regenerate-unicode-properties": { "version": "10.2.0", "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.0.tgz", "integrity": "sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA==", + "dev": true, "license": "MIT", "dependencies": { "regenerate": "^1.4.2" @@ -36773,6 +37499,7 @@ "version": "6.2.0", "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.2.0.tgz", "integrity": "sha512-H66BPQMrv+V16t8xtmq+UC0CBpiTBA60V8ibS1QVReIp8T1z8hwFxqcGzm9K6lgsN7sB5edVH8a+ze6Fqm4weA==", + "dev": true, "license": "MIT", "dependencies": { "regenerate": "^1.4.2", @@ -36790,12 +37517,14 @@ "version": "0.8.0", "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz", "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==", + "dev": true, "license": "MIT" }, "node_modules/regjsparser": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.12.0.tgz", "integrity": "sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ==", + "dev": true, "license": "BSD-2-Clause", "dependencies": { "jsesc": "~3.0.2" @@ -36808,6 +37537,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", + "dev": true, "license": "MIT", "bin": { "jsesc": "bin/jsesc" @@ -36820,7 +37550,7 @@ "version": "0.2.7", "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">= 0.10" @@ -36895,7 +37625,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-3.0.0.tgz", "integrity": "sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "css-select": "^4.1.3", @@ -36909,7 +37639,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", - "devOptional": true, + "dev": true, "license": "BSD-2-Clause", "dependencies": { "boolbase": "^1.0.0", @@ -36926,7 +37656,7 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "domelementtype": "^2.0.1", @@ -36941,7 +37671,7 @@ "version": "4.3.1", "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", - "devOptional": true, + "dev": true, "license": "BSD-2-Clause", "dependencies": { "domelementtype": "^2.2.0" @@ -36957,7 +37687,7 @@ "version": "2.8.0", "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", - "devOptional": true, + "dev": true, "license": "BSD-2-Clause", "dependencies": { "dom-serializer": "^1.0.1", @@ -36972,7 +37702,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", - "devOptional": true, + "dev": true, "license": "BSD-2-Clause", "funding": { "url": "https://github.com/fb55/entities?sponsor=1" @@ -36982,7 +37712,7 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", - "devOptional": true, + "dev": true, "funding": [ "https://github.com/fb55/htmlparser2?sponsor=1", { @@ -37002,6 +37732,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -37011,6 +37742,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" @@ -37115,6 +37847,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -37176,6 +37909,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz", "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==", + "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -37241,6 +37975,7 @@ "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", @@ -37373,6 +38108,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "dev": true, "license": "MIT", "dependencies": { "debug": "^4.4.0", @@ -37389,6 +38125,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "dev": true, "license": "MIT" }, "node_modules/rrweb-cssom": { @@ -37422,6 +38159,7 @@ "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", @@ -37610,6 +38348,7 @@ "version": "1.95.1", "resolved": "https://registry.npmjs.org/sass/-/sass-1.95.1.tgz", "integrity": "sha512-uPoDh5NIEZV4Dp5GBodkmNY9tSQfXY02pmCcUo+FR1P+x953HGkpw+vV28D4IqYB6f8webZtwoSaZaiPtpTeMg==", + "dev": true, "license": "MIT", "dependencies": { "chokidar": "^4.0.0", @@ -37630,6 +38369,7 @@ "version": "1.93.2", "resolved": "https://registry.npmjs.org/sass-embedded/-/sass-embedded-1.93.2.tgz", "integrity": "sha512-FvQdkn2dZ8DGiLgi0Uf4zsj7r/BsiLImNa5QJ10eZalY6NfZyjrmWGFcuCN5jNwlDlXFJnftauv+UtvBKLvepQ==", + "dev": true, "license": "MIT", "dependencies": { "@bufbuild/protobuf": "^2.5.0", @@ -37678,6 +38418,7 @@ "!riscv64", "!x64" ], + "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -37688,6 +38429,7 @@ "version": "1.93.2", "resolved": "https://registry.npmjs.org/sass/-/sass-1.93.2.tgz", "integrity": "sha512-t+YPtOQHpGW1QWsh1CHQ5cPIr9lbbGZLZnbihP/D/qZj/yuV68m8qarcV17nvkOX81BCrvzAlq2klCQFZghyTg==", + "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -37712,6 +38454,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -37728,6 +38471,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -37744,6 +38488,7 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -37760,6 +38505,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -37776,6 +38522,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -37792,6 +38539,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -37808,6 +38556,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -37824,6 +38573,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -37840,6 +38590,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -37856,6 +38607,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -37872,6 +38624,7 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -37888,6 +38641,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -37904,6 +38658,7 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -37920,6 +38675,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -37933,6 +38689,7 @@ "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==", + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -37949,6 +38706,7 @@ "version": "1.93.2", "resolved": "https://registry.npmjs.org/sass/-/sass-1.93.2.tgz", "integrity": "sha512-t+YPtOQHpGW1QWsh1CHQ5cPIr9lbbGZLZnbihP/D/qZj/yuV68m8qarcV17nvkOX81BCrvzAlq2klCQFZghyTg==", + "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -37973,6 +38731,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -37989,6 +38748,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -38002,6 +38762,7 @@ "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" @@ -38017,6 +38778,7 @@ "version": "16.0.6", "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-16.0.6.tgz", "integrity": "sha512-sglGzId5gmlfxNs4gK2U3h7HlVRfx278YK6Ono5lwzuvi1jxig80YiuHkaDBVsYIKFhx8wN7XSCI0M2IDS/3qA==", + "dev": true, "license": "MIT", "dependencies": { "neo-async": "^2.6.2" @@ -38057,7 +38819,7 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==", - "devOptional": true, + "dev": true, "license": "ISC" }, "node_modules/saxes": { @@ -38086,6 +38848,7 @@ "version": "4.3.3", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", + "dev": true, "license": "MIT", "dependencies": { "@types/json-schema": "^7.0.9", @@ -38105,6 +38868,7 @@ "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" @@ -38122,12 +38886,14 @@ "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", @@ -38161,6 +38927,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", + "dev": true, "license": "MIT", "dependencies": { "debug": "^4.3.5", @@ -38214,6 +38981,7 @@ "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" @@ -38223,6 +38991,7 @@ "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", @@ -38241,6 +39010,7 @@ "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", @@ -38254,6 +39024,7 @@ "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" @@ -38263,6 +39034,7 @@ "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" @@ -38272,6 +39044,7 @@ "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", @@ -38287,12 +39060,14 @@ "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" @@ -38302,6 +39077,7 @@ "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" @@ -38314,12 +39090,14 @@ "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" @@ -38329,12 +39107,14 @@ "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" @@ -38344,6 +39124,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", + "dev": true, "license": "MIT", "dependencies": { "encodeurl": "^2.0.0", @@ -38446,6 +39227,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" @@ -38458,6 +39240,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -38467,6 +39250,7 @@ "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" @@ -38551,6 +39335,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, "license": "ISC", "engines": { "node": ">=14" @@ -38677,6 +39462,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -38713,6 +39499,7 @@ "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", @@ -38724,6 +39511,7 @@ "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" @@ -38776,6 +39564,7 @@ "version": "0.7.6", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", + "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">= 12" @@ -38785,6 +39574,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==", + "devOptional": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -38794,6 +39584,7 @@ "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", @@ -38814,6 +39605,7 @@ "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", @@ -38824,6 +39616,7 @@ "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" @@ -39027,6 +39820,7 @@ "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", @@ -39043,6 +39837,7 @@ "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", @@ -39057,6 +39852,7 @@ "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", @@ -39131,6 +39927,7 @@ "version": "2.0.6", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, "license": "MIT", "dependencies": { "escape-string-regexp": "^2.0.0" @@ -39143,6 +39940,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -39307,6 +40105,7 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, "license": "MIT", "dependencies": { "char-regex": "^1.0.2", @@ -39335,6 +40134,7 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -39421,6 +40221,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -39433,6 +40234,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -39465,6 +40267,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -39494,6 +40297,7 @@ "version": "6.1.1", "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-6.1.1.tgz", "integrity": "sha512-gSTTEQ670cJNoaeIp9KX6lZmm8LJ3jPB5yJmX8Zq/wQxOsAFXV3qjWzHas3YYk1qesuVIyYWWUpZ0vSE/dTSGg==", + "dev": true, "license": "MIT", "dependencies": { "browserslist": "^4.23.0", @@ -39510,6 +40314,7 @@ "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==", + "dev": true, "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -39690,6 +40495,7 @@ "version": "3.3.2", "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.3.2.tgz", "integrity": "sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw==", + "dev": true, "license": "MIT", "dependencies": { "@trysound/sax": "0.2.0", @@ -39715,6 +40521,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true, "license": "MIT", "engines": { "node": ">= 10" @@ -39730,6 +40537,7 @@ "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==", + "dev": true, "license": "MIT", "dependencies": { "sync-message-port": "^1.0.0" @@ -39742,6 +40550,7 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/sync-message-port/-/sync-message-port-1.1.3.tgz", "integrity": "sha512-GTt8rSKje5FilG+wEdfCkOcLL7LWqpMlr2c3LRuKt/YXxcJ52aGSbGBAdI4L3aaqfrBt6y711El53ItyH1NWzg==", + "dev": true, "license": "MIT", "engines": { "node": ">=16.0.0" @@ -39751,6 +40560,7 @@ "version": "0.11.11", "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.11.tgz", "integrity": "sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==", + "dev": true, "license": "MIT", "dependencies": { "@pkgr/core": "^0.2.9" @@ -39899,6 +40709,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -39950,6 +40761,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, "license": "MIT", "dependencies": { "bl": "^4.0.3", @@ -39966,6 +40778,7 @@ "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", @@ -40123,6 +40936,7 @@ "version": "5.43.1", "resolved": "https://registry.npmjs.org/terser/-/terser-5.43.1.tgz", "integrity": "sha512-+6erLbBm0+LROX2sPXlUYx/ux5PyE9K/a92Wrt6oA+WDAoFTdpHE5tCYCI5PNzq2y8df4rA+QgHLJuR4jNymsg==", + "dev": true, "license": "BSD-2-Clause", "dependencies": { "@jridgewell/source-map": "^0.3.3", @@ -40141,6 +40955,7 @@ "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", @@ -40175,6 +40990,7 @@ "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": "*", @@ -40189,6 +41005,7 @@ "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" @@ -40204,12 +41021,14 @@ "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": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, "license": "ISC", "dependencies": { "@istanbuljs/schema": "^0.1.2", @@ -40224,6 +41043,7 @@ "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -40235,6 +41055,7 @@ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", @@ -40255,6 +41076,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -40290,6 +41112,7 @@ "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" @@ -40308,6 +41131,7 @@ "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": { @@ -40348,6 +41172,7 @@ "version": "0.2.14", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", + "dev": true, "license": "MIT", "dependencies": { "fdir": "^6.4.4", @@ -40402,6 +41227,7 @@ "version": "0.2.3", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", "integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==", + "dev": true, "license": "MIT", "engines": { "node": ">=14.14" @@ -40421,12 +41247,14 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true, "license": "BSD-3-Clause" }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, "license": "MIT", "dependencies": { "is-number": "^7.0.0" @@ -40494,6 +41322,7 @@ "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" @@ -40510,6 +41339,7 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, "license": "MIT", "bin": { "tree-kill": "cli.js" @@ -40664,6 +41494,7 @@ "version": "9.5.4", "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.4.tgz", "integrity": "sha512-nCz0rEwunlTZiy6rXFByQU1kVVpCIgUpc/psFiKVrUwrizdnIbRFu8w7bxhUF0X613DYwT4XzrZHpVyMe758hQ==", + "dev": true, "license": "MIT", "dependencies": { "chalk": "^4.1.0", @@ -40777,6 +41608,7 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/tsconfig-paths-webpack-plugin/-/tsconfig-paths-webpack-plugin-4.2.0.tgz", "integrity": "sha512-zbem3rfRS8BgeNK50Zz5SIQgXzLafiHjOwUAvk/38/o1jHn/V5QAgVUcz884or7WYcPaH3N2CIfUc2u0ul7UcA==", + "dev": true, "license": "MIT", "dependencies": { "chalk": "^4.1.0", @@ -40792,6 +41624,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, "license": "MIT", "engines": { "node": ">=4" @@ -40801,6 +41634,7 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", + "dev": true, "license": "MIT", "dependencies": { "json5": "^2.2.2", @@ -41226,6 +42060,7 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, "license": "MIT", "dependencies": { "prelude-ls": "^1.2.1" @@ -41238,6 +42073,7 @@ "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, "license": "MIT", "engines": { "node": ">=4" @@ -41352,6 +42188,7 @@ "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": { @@ -41591,6 +42428,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", + "dev": true, "license": "MIT", "engines": { "node": ">=4" @@ -41600,6 +42438,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "dev": true, "license": "MIT", "dependencies": { "unicode-canonical-property-names-ecmascript": "^2.0.0", @@ -41613,6 +42452,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.0.tgz", "integrity": "sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg==", + "dev": true, "license": "MIT", "engines": { "node": ">=4" @@ -41622,6 +42462,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", + "dev": true, "license": "MIT", "engines": { "node": ">=4" @@ -41795,6 +42636,7 @@ "version": "1.9.1", "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.9.1.tgz", "integrity": "sha512-4AZVxP05JGN6DwqIkSP4VKLOcwQa5l37SWHF/ahcuqBMbfxbpN1L1QKafEhWCziHhzKex9H/AR09H0OuVyU+9g==", + "dev": true, "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -41843,6 +42685,7 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.2.tgz", "integrity": "sha512-E85pfNzMQ9jpKkA7+TJAi4TJN+tBCuWh5rUcS/sv6cFi+1q9LYDwDI5dpUL0u/73EElyQ8d3TEaeW4sPedBqYA==", + "dev": true, "funding": [ { "type": "opencollective", @@ -41873,6 +42716,7 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, "license": "BSD-2-Clause", "dependencies": { "punycode": "^2.1.0" @@ -41953,13 +42797,14 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", "integrity": "sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==", - "devOptional": true, + "dev": 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" @@ -41989,6 +42834,7 @@ "version": "9.3.0", "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", + "dev": true, "license": "ISC", "dependencies": { "@jridgewell/trace-mapping": "^0.3.12", @@ -42003,6 +42849,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, "license": "MIT" }, "node_modules/validate-npm-package-license": { @@ -42030,6 +42877,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/varint/-/varint-6.0.0.tgz", "integrity": "sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==", + "dev": true, "license": "MIT" }, "node_modules/vary": { @@ -42458,6 +43306,7 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, "license": "Apache-2.0", "dependencies": { "makeerror": "1.0.12" @@ -42467,6 +43316,7 @@ "version": "2.4.4", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.4.tgz", "integrity": "sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==", + "dev": true, "license": "MIT", "dependencies": { "glob-to-regexp": "^0.4.1", @@ -42480,6 +43330,7 @@ "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" @@ -42515,6 +43366,7 @@ "version": "5.103.0", "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.103.0.tgz", "integrity": "sha512-HU1JOuV1OavsZ+mfigY0j8d1TgQgbZ6M+J75zDkpEAwYeXjWSqrGJtgnPblJjd/mAyTNQ7ygw0MiKOn6etz8yw==", + "dev": true, "license": "MIT", "dependencies": { "@types/eslint-scope": "^3.7.7", @@ -42616,6 +43468,7 @@ "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", @@ -42645,6 +43498,7 @@ "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", @@ -42664,6 +43518,7 @@ "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" @@ -42673,6 +43528,7 @@ "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" @@ -42685,6 +43541,7 @@ "version": "5.2.2", "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-5.2.2.tgz", "integrity": "sha512-QcQ72gh8a+7JO63TAx/6XZf/CWhgMzu5m0QirvPfGvptOusAxG12w2+aua1Jkjr7hzaWDnJ2n6JFeexMHI+Zjg==", + "dev": true, "license": "MIT", "dependencies": { "@types/bonjour": "^3.5.13", @@ -42742,6 +43599,7 @@ "version": "4.17.25", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.25.tgz", "integrity": "sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==", + "dev": true, "license": "MIT", "dependencies": { "@types/body-parser": "*", @@ -42754,6 +43612,7 @@ "version": "4.19.7", "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.7.tgz", "integrity": "sha512-FvPtiIf1LfhzsaIXhv/PHan/2FeQBbtBDtfX2QfvPxdUelMDEckK08SM6nqo1MIZY3RUlfA+HV8+hFUSio78qg==", + "dev": true, "license": "MIT", "dependencies": { "@types/node": "*", @@ -42766,6 +43625,7 @@ "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", @@ -42779,6 +43639,7 @@ "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", @@ -42803,6 +43664,7 @@ "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", @@ -42827,6 +43689,7 @@ "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" @@ -42839,6 +43702,7 @@ "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" @@ -42848,12 +43712,14 @@ "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" @@ -42863,12 +43729,14 @@ "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", @@ -42915,6 +43783,7 @@ "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", @@ -42933,6 +43802,7 @@ "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" @@ -42942,6 +43812,7 @@ "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" @@ -42954,6 +43825,7 @@ "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", @@ -42978,6 +43850,7 @@ "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" @@ -42990,6 +43863,7 @@ "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" @@ -42999,6 +43873,7 @@ "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" @@ -43011,6 +43886,7 @@ "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" @@ -43020,6 +43896,7 @@ "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" @@ -43029,6 +43906,7 @@ "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" @@ -43041,6 +43919,7 @@ "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" @@ -43050,6 +43929,7 @@ "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" @@ -43062,6 +43942,7 @@ "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" @@ -43071,6 +43952,7 @@ "version": "10.2.0", "resolved": "https://registry.npmjs.org/open/-/open-10.2.0.tgz", "integrity": "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==", + "dev": true, "license": "MIT", "dependencies": { "default-browser": "^5.2.1", @@ -43089,12 +43971,14 @@ "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" @@ -43107,6 +43991,7 @@ "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" @@ -43122,6 +44007,7 @@ "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", @@ -43137,6 +44023,7 @@ "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" @@ -43149,6 +44036,7 @@ "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", @@ -43173,6 +44061,7 @@ "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" @@ -43182,6 +44071,7 @@ "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", @@ -43197,6 +44087,7 @@ "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" @@ -43206,6 +44097,7 @@ "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", @@ -43246,6 +44138,7 @@ "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" @@ -43255,6 +44148,7 @@ "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" @@ -43264,6 +44158,7 @@ "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" @@ -43292,6 +44187,7 @@ "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", @@ -43305,6 +44201,7 @@ "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" @@ -43314,12 +44211,14 @@ "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" @@ -43329,6 +44228,7 @@ "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" @@ -43341,6 +44241,7 @@ "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", @@ -43355,6 +44256,7 @@ "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" @@ -43398,6 +44300,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, "license": "ISC", "dependencies": { "isexe": "^2.0.0" @@ -43573,6 +44476,7 @@ "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -43604,6 +44508,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", @@ -43621,6 +44526,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, "license": "ISC" }, "node_modules/write-file-atomic": { @@ -43669,6 +44575,7 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz", "integrity": "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==", + "dev": true, "license": "MIT", "dependencies": { "is-wsl": "^3.1.0" @@ -43725,6 +44632,7 @@ "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, "license": "ISC", "engines": { "node": ">=10" @@ -43734,12 +44642,14 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, "license": "ISC" }, "node_modules/yaml": { "version": "2.8.0", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz", "integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==", + "dev": true, "license": "ISC", "bin": { "yaml": "bin.mjs" @@ -43752,6 +44662,7 @@ "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, "license": "MIT", "dependencies": { "cliui": "^8.0.1", @@ -43770,6 +44681,7 @@ "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, "license": "ISC", "engines": { "node": ">=12" @@ -43799,6 +44711,7 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -43824,6 +44737,7 @@ "version": "3.25.76", "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" @@ -43833,6 +44747,7 @@ "version": "3.24.5", "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.5.tgz", "integrity": "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==", + "dev": true, "license": "ISC", "peerDependencies": { "zod": "^3.24.1" diff --git a/package.json b/package.json index 7b2b3701fdf..f4c484f1c61 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,11 @@ "@eslint/compat": "2.0.0", "@lit-labs/signals": "0.1.3", "@ngtools/webpack": "20.3.12", + "@nx/devkit": "21.6.10", + "@nx/eslint": "21.6.10", + "@nx/jest": "21.6.10", + "@nx/js": "21.6.10", + "@nx/webpack": "21.6.10", "@storybook/addon-a11y": "9.1.16", "@storybook/addon-designs": "9.0.0-next.3", "@storybook/addon-docs": "9.1.16", @@ -166,11 +171,6 @@ "@microsoft/signalr": "8.0.7", "@microsoft/signalr-protocol-msgpack": "8.0.7", "@ng-select/ng-select": "20.7.0", - "@nx/devkit": "21.6.10", - "@nx/eslint": "21.6.10", - "@nx/jest": "21.6.10", - "@nx/js": "21.6.10", - "@nx/webpack": "21.6.10", "big-integer": "1.6.52", "braintree-web-drop-in": "1.46.0", "buffer": "6.0.3", From 59a1b4d79ecfd639bc7d9412a93fdab701b22cde Mon Sep 17 00:00:00 2001 From: Alex Morask <144709477+amorask-bitwarden@users.noreply.github.com> Date: Mon, 22 Dec 2025 14:14:23 -0600 Subject: [PATCH 146/188] Remove FF: pm-22415-tax-id-warnings (#17871) --- ...rganization-payment-details.component.html | 2 +- .../organization-payment-details.component.ts | 62 +++++++------------ .../components/tax-id-warning.component.ts | 37 +++++------ .../provider-payment-details.component.html | 2 +- .../provider-payment-details.component.ts | 60 +++++++----------- libs/common/src/enums/feature-flag.enum.ts | 2 - 6 files changed, 63 insertions(+), 102 deletions(-) diff --git a/apps/web/src/app/billing/organizations/payment-details/organization-payment-details.component.html b/apps/web/src/app/billing/organizations/payment-details/organization-payment-details.component.html index cd31f1f33be..504cfcdeccd 100644 --- a/apps/web/src/app/billing/organizations/payment-details/organization-payment-details.component.html +++ b/apps/web/src/app/billing/organizations/payment-details/organization-payment-details.component.html @@ -29,7 +29,7 @@ <app-display-billing-address [subscriber]="view.organization" [billingAddress]="view.billingAddress" - [taxIdWarning]="enableTaxIdWarning ? view.taxIdWarning : null" + [taxIdWarning]="view.taxIdWarning" (updated)="setBillingAddress($event)" ></app-display-billing-address> 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 9609160089b..e989185e582 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 @@ -22,8 +22,6 @@ 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 { 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"; @@ -118,12 +116,9 @@ export class OrganizationPaymentDetailsComponent implements OnInit, OnDestroy { private destroy$ = new Subject<void>(); - protected enableTaxIdWarning!: boolean; - constructor( private accountService: AccountService, private activatedRoute: ActivatedRoute, - private configService: ConfigService, private dialogService: DialogService, private messageListener: MessageListener, private organizationService: OrganizationService, @@ -140,36 +135,30 @@ export class OrganizationPaymentDetailsComponent implements OnInit, OnDestroy { await this.changePaymentMethod(); } - this.enableTaxIdWarning = await this.configService.getFeatureFlag( - FeatureFlag.PM22415_TaxIDWarnings, - ); - - if (this.enableTaxIdWarning) { - this.organizationWarningsService.taxIdWarningRefreshed$ - .pipe( - switchMap((warning) => - combineLatest([ - of(warning), - this.organization$.pipe(take(1)).pipe( - mapOrganizationToSubscriber, - switchMap((organization) => - this.subscriberBillingClient.getBillingAddress(organization), - ), + this.organizationWarningsService.taxIdWarningRefreshed$ + .pipe( + switchMap((warning) => + combineLatest([ + of(warning), + this.organization$.pipe(take(1)).pipe( + mapOrganizationToSubscriber, + switchMap((organization) => + this.subscriberBillingClient.getBillingAddress(organization), ), - ]), - ), - takeUntil(this.destroy$), - ) - .subscribe(([taxIdWarning, billingAddress]) => { - if (this.viewState$.value) { - this.viewState$.next({ - ...this.viewState$.value, - taxIdWarning, - billingAddress, - }); - } - }); - } + ), + ]), + ), + takeUntil(this.destroy$), + ) + .subscribe(([taxIdWarning, billingAddress]) => { + if (this.viewState$.value) { + this.viewState$.next({ + ...this.viewState$.value, + taxIdWarning, + billingAddress, + }); + } + }); this.messageListener .messages$(BANK_ACCOUNT_VERIFIED_COMMAND) @@ -216,10 +205,7 @@ export class OrganizationPaymentDetailsComponent implements OnInit, OnDestroy { setBillingAddress = (billingAddress: BillingAddress) => { if (this.viewState$.value) { - if ( - this.enableTaxIdWarning && - this.viewState$.value.billingAddress?.taxId !== billingAddress.taxId - ) { + if (this.viewState$.value.billingAddress?.taxId !== billingAddress.taxId) { this.organizationWarningsService.refreshTaxIdWarning(); } this.viewState$.next({ diff --git a/apps/web/src/app/billing/warnings/components/tax-id-warning.component.ts b/apps/web/src/app/billing/warnings/components/tax-id-warning.component.ts index c0fe5626fcb..f2adcaccf30 100644 --- a/apps/web/src/app/billing/warnings/components/tax-id-warning.component.ts +++ b/apps/web/src/app/billing/warnings/components/tax-id-warning.component.ts @@ -12,8 +12,6 @@ import { import { Account, 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 { BannerModule, DialogService } from "@bitwarden/components"; import { BILLING_DISK, StateProvider, UserKeyDefinition } from "@bitwarden/state"; @@ -88,23 +86,21 @@ type GetWarning$ = () => Observable<TaxIdWarningType | null>; @Component({ selector: "app-tax-id-warning", template: ` - @if (enableTaxIdWarning$ | async) { - @let view = view$ | async; + @let view = view$ | async; - @if (view) { - <bit-banner id="tax-id-warning-banner" bannerType="warning" (onClose)="trackDismissal()"> - {{ view.message }} - <a - bitLink - linkType="secondary" - (click)="editBillingAddress()" - class="tw-cursor-pointer" - rel="noreferrer noopener" - > - {{ view.callToAction }} - </a> - </bit-banner> - } + @if (view) { + <bit-banner id="tax-id-warning-banner" bannerType="warning" (onClose)="trackDismissal()"> + {{ view.message }} + <a + bitLink + linkType="secondary" + (click)="editBillingAddress()" + class="tw-cursor-pointer" + rel="noreferrer noopener" + > + {{ view.callToAction }} + </a> + </bit-banner> } `, imports: [BannerModule, SharedModule], @@ -120,10 +116,6 @@ export class TaxIdWarningComponent implements OnInit { // eslint-disable-next-line @angular-eslint/prefer-output-emitter-ref @Output() billingAddressUpdated = new EventEmitter<void>(); - protected enableTaxIdWarning$ = this.configService.getFeatureFlag$( - FeatureFlag.PM22415_TaxIDWarnings, - ); - protected userId$ = this.accountService.activeAccount$.pipe( filter((account): account is Account => account !== null), getUserId, @@ -209,7 +201,6 @@ export class TaxIdWarningComponent implements OnInit { constructor( private accountService: AccountService, - private configService: ConfigService, private dialogService: DialogService, private i18nService: I18nService, private subscriberBillingClient: SubscriberBillingClient, diff --git a/bitwarden_license/bit-web/src/app/billing/providers/payment-details/provider-payment-details.component.html b/bitwarden_license/bit-web/src/app/billing/providers/payment-details/provider-payment-details.component.html index fa45bbb32d3..d0da7416115 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/payment-details/provider-payment-details.component.html +++ b/bitwarden_license/bit-web/src/app/billing/providers/payment-details/provider-payment-details.component.html @@ -21,7 +21,7 @@ <app-display-billing-address [subscriber]="view.provider" [billingAddress]="view.billingAddress" - [taxIdWarning]="enableTaxIdWarning ? view.taxIdWarning : null" + [taxIdWarning]="view.taxIdWarning" (updated)="setBillingAddress($event)" ></app-display-billing-address> 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 183e6098471..1de271bcf28 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 @@ -21,8 +21,6 @@ import { ProviderService } from "@bitwarden/common/admin-console/abstractions/pr import { Provider } from "@bitwarden/common/admin-console/models/domain/provider"; 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 { CommandDefinition, MessageListener } from "@bitwarden/messaging"; import { UserId } from "@bitwarden/user-core"; import { SubscriberBillingClient } from "@bitwarden/web-vault/app/billing/clients"; @@ -119,13 +117,10 @@ export class ProviderPaymentDetailsComponent implements OnInit, OnDestroy { private destroy$ = new Subject<void>(); - protected enableTaxIdWarning!: boolean; - constructor( private accountService: AccountService, private activatedRoute: ActivatedRoute, private billingClient: SubscriberBillingClient, - private configService: ConfigService, private messageListener: MessageListener, private providerService: ProviderService, private providerWarningsService: ProviderWarningsService, @@ -133,34 +128,28 @@ export class ProviderPaymentDetailsComponent implements OnInit, OnDestroy { ) {} async ngOnInit() { - this.enableTaxIdWarning = await this.configService.getFeatureFlag( - FeatureFlag.PM22415_TaxIDWarnings, - ); - - if (this.enableTaxIdWarning) { - this.providerWarningsService.taxIdWarningRefreshed$ - .pipe( - switchMap((warning) => - combineLatest([ - of(warning), - this.provider$.pipe(take(1)).pipe( - mapProviderToSubscriber, - switchMap((provider) => this.subscriberBillingClient.getBillingAddress(provider)), - ), - ]), - ), - takeUntil(this.destroy$), - ) - .subscribe(([taxIdWarning, billingAddress]) => { - if (this.viewState$.value) { - this.viewState$.next({ - ...this.viewState$.value, - taxIdWarning, - billingAddress, - }); - } - }); - } + this.providerWarningsService.taxIdWarningRefreshed$ + .pipe( + switchMap((warning) => + combineLatest([ + of(warning), + this.provider$.pipe(take(1)).pipe( + mapProviderToSubscriber, + switchMap((provider) => this.subscriberBillingClient.getBillingAddress(provider)), + ), + ]), + ), + takeUntil(this.destroy$), + ) + .subscribe(([taxIdWarning, billingAddress]) => { + if (this.viewState$.value) { + this.viewState$.next({ + ...this.viewState$.value, + taxIdWarning, + billingAddress, + }); + } + }); this.messageListener .messages$(BANK_ACCOUNT_VERIFIED_COMMAND) @@ -197,10 +186,7 @@ export class ProviderPaymentDetailsComponent implements OnInit, OnDestroy { setBillingAddress = (billingAddress: BillingAddress) => { if (this.viewState$.value) { - if ( - this.enableTaxIdWarning && - this.viewState$.value.billingAddress?.taxId !== billingAddress.taxId - ) { + if (this.viewState$.value.billingAddress?.taxId !== billingAddress.taxId) { this.providerWarningsService.refreshTaxIdWarning(); } this.viewState$.next({ diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts index 3bde0eada0a..f905c62288e 100644 --- a/libs/common/src/enums/feature-flag.enum.ts +++ b/libs/common/src/enums/feature-flag.enum.ts @@ -26,7 +26,6 @@ export enum FeatureFlag { /* Billing */ TrialPaymentOptional = "PM-8163-trial-payment", - PM22415_TaxIDWarnings = "pm-22415-tax-id-warnings", PM24032_NewNavigationPremiumUpgradeButton = "pm-24032-new-navigation-premium-upgrade-button", PM25379_UseNewOrganizationMetadataStructure = "pm-25379-use-new-organization-metadata-structure", PM24996_ImplementUpgradeFromFreeDialog = "pm-24996-implement-upgrade-from-free-dialog", @@ -137,7 +136,6 @@ export const DefaultFeatureFlagValue = { /* Billing */ [FeatureFlag.TrialPaymentOptional]: FALSE, - [FeatureFlag.PM22415_TaxIDWarnings]: FALSE, [FeatureFlag.PM24032_NewNavigationPremiumUpgradeButton]: FALSE, [FeatureFlag.PM25379_UseNewOrganizationMetadataStructure]: FALSE, [FeatureFlag.PM24996_ImplementUpgradeFromFreeDialog]: FALSE, From 328ff89747a83c37ebfb574f1c55bf2dd2e2f9bd Mon Sep 17 00:00:00 2001 From: gitclonebrian <235774926+gitclonebrian@users.noreply.github.com> Date: Mon, 22 Dec 2025 15:19:08 -0500 Subject: [PATCH 147/188] bumped `cargo deny` version to fix CVSS error (#18091) --- .github/workflows/lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 47c5e9faef0..b46204514b8 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -144,7 +144,7 @@ jobs: - name: Install cargo-deny uses: taiki-e/install-action@073d46cba2cde38f6698c798566c1b3e24feeb44 # v2.62.67 with: - tool: cargo-deny@0.18.5 + tool: cargo-deny@0.18.6 - name: Run cargo deny working-directory: ./apps/desktop/desktop_native From d95739191b82bbe9c2d34175c449050608847496 Mon Sep 17 00:00:00 2001 From: Jared Snider <116684653+JaredSnider-Bitwarden@users.noreply.github.com> Date: Mon, 22 Dec 2025 15:56:12 -0500 Subject: [PATCH 148/188] PM-30125 - IdentityTokenResponse - mark deprecated properties as such (#18092) --- .../models/response/identity-token.response.ts | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/libs/common/src/auth/models/response/identity-token.response.ts b/libs/common/src/auth/models/response/identity-token.response.ts index ae208ef1a36..e43697f9ebe 100644 --- a/libs/common/src/auth/models/response/identity-token.response.ts +++ b/libs/common/src/auth/models/response/identity-token.response.ts @@ -19,9 +19,21 @@ export class IdentityTokenResponse extends BaseResponse { tokenType: string; // Decryption Information - privateKey: string; // userKeyEncryptedPrivateKey + + /** + * privateKey is actually userKeyEncryptedPrivateKey + * @deprecated Use {@link accountKeysResponseModel} instead + */ + privateKey: string; + + // TODO: https://bitwarden.atlassian.net/browse/PM-30124 - Rename to just accountKeys accountKeysResponseModel: PrivateKeysResponseModel | null = null; - key?: EncString; // masterKeyEncryptedUserKey + + /** + * key is actually masterKeyEncryptedUserKey + * @deprecated Use {@link userDecryptionOptions.masterPasswordUnlock.masterKeyWrappedUserKey} instead + */ + key?: EncString; twoFactorToken: string; kdfConfig: KdfConfig; forcePasswordReset: boolean; From dc1ecaaaa29f39315ace33a186d46eae62092b09 Mon Sep 17 00:00:00 2001 From: Vicki League <vleague@bitwarden.com> Date: Mon, 22 Dec 2025 16:55:20 -0500 Subject: [PATCH 149/188] [PM-29819][CL-806] Fix focus mgmt on search and filter page navigations (#18007) --- .../collections/vault.component.ts | 3 +++ .../services/routed-vault-filter.service.ts | 3 +++ .../vault/individual-vault/vault.component.ts | 3 +++ .../src/a11y/router-focus-manager.service.ts | 19 +++++++++---------- .../tabs/tab-nav-bar/tab-link.component.html | 2 +- 5 files changed, 19 insertions(+), 11 deletions(-) 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 f827dda9a9b..4adf3739845 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 @@ -587,6 +587,9 @@ export class VaultComponent implements OnInit, OnDestroy { queryParams: { search: Utils.isNullOrEmpty(searchText) ? null : searchText }, queryParamsHandling: "merge", replaceUrl: true, + state: { + focusMainAfterNav: false, + }, }), ); diff --git a/apps/web/src/app/vault/individual-vault/vault-filter/services/routed-vault-filter.service.ts b/apps/web/src/app/vault/individual-vault/vault-filter/services/routed-vault-filter.service.ts index a5a99428b2d..bc9da5e1692 100644 --- a/apps/web/src/app/vault/individual-vault/vault-filter/services/routed-vault-filter.service.ts +++ b/apps/web/src/app/vault/individual-vault/vault-filter/services/routed-vault-filter.service.ts @@ -74,6 +74,9 @@ export class RoutedVaultFilterService implements OnDestroy { type: filter.type ?? null, }, queryParamsHandling: "merge", + state: { + focusMainAfterNav: false, + }, }; return [commands, extras]; } 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 e791ca7a90b..a5121831304 100644 --- a/apps/web/src/app/vault/individual-vault/vault.component.ts +++ b/apps/web/src/app/vault/individual-vault/vault.component.ts @@ -424,6 +424,9 @@ export class VaultComponent<C extends CipherViewLike> implements OnInit, OnDestr queryParams: { search: Utils.isNullOrEmpty(searchText) ? null : searchText }, queryParamsHandling: "merge", replaceUrl: true, + state: { + focusMainAfterNav: false, + }, }), ); diff --git a/libs/components/src/a11y/router-focus-manager.service.ts b/libs/components/src/a11y/router-focus-manager.service.ts index 27c4e0f9b1e..f7371e02a17 100644 --- a/libs/components/src/a11y/router-focus-manager.service.ts +++ b/libs/components/src/a11y/router-focus-manager.service.ts @@ -1,7 +1,7 @@ import { inject, Injectable } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { NavigationEnd, Router } from "@angular/router"; -import { skip, filter, map, combineLatestWith, tap } from "rxjs"; +import { skip, filter, combineLatestWith, tap } from "rxjs"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; @@ -19,8 +19,10 @@ export class RouterFocusManagerService { * * By default, we focus the `main` after an internal route navigation. * - * Consumers can opt out of the passing the following to the `info` input: - * `<a [routerLink]="route()" [info]="{ focusMainAfterNav: false }"></a>` + * Consumers can opt out of the passing the following to the `state` input. Using `state` + * allows us to access the value between browser back/forward arrows. + * In template: `<a [routerLink]="route()" [state]="{ focusMainAfterNav: false }"></a>` + * In typescript: `this.router.navigate([], { state: { focusMainAfterNav: false }})` * * Or, consumers can use the autofocus directive on an applicable interactive element. * The autofocus directive will take precedence over this route focus pipeline. @@ -44,15 +46,12 @@ export class RouterFocusManagerService { skip(1), combineLatestWith(this.configService.getFeatureFlag$(FeatureFlag.RouterFocusManagement)), filter(([_navEvent, flagEnabled]) => flagEnabled), - map(() => { - const currentNavData = this.router.getCurrentNavigation()?.extras; + filter(() => { + const currentNavExtras = this.router.currentNavigation()?.extras; - const info = currentNavData?.info as { focusMainAfterNav?: boolean } | undefined; + const focusMainAfterNav: boolean | undefined = currentNavExtras?.state?.focusMainAfterNav; - return info; - }), - filter((currentNavInfo) => { - return currentNavInfo === undefined ? true : currentNavInfo?.focusMainAfterNav !== false; + return focusMainAfterNav !== false; }), tap(() => { const mainEl = document.querySelector<HTMLElement>("main"); diff --git a/libs/components/src/tabs/tab-nav-bar/tab-link.component.html b/libs/components/src/tabs/tab-nav-bar/tab-link.component.html index f05ed31547b..aa36eb37f99 100644 --- a/libs/components/src/tabs/tab-nav-bar/tab-link.component.html +++ b/libs/components/src/tabs/tab-nav-bar/tab-link.component.html @@ -5,7 +5,7 @@ [routerLinkActiveOptions]="routerLinkMatchOptions" #rla="routerLinkActive" [active]="rla.isActive" - [info]="{ focusMainAfterNav: false }" + [state]="{ focusMainAfterNav: false }" [disabled]="disabled" [attr.aria-disabled]="disabled" ariaCurrentWhenActive="page" From 3fbb4aced929f3a7904c54091b26a82765ba82e6 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann <mail@quexten.com> Date: Tue, 23 Dec 2025 16:27:25 +0100 Subject: [PATCH 150/188] [PM-27239] Tde registration encryption v2 (#17831) * tmp * Implement TDE v2 registration via SDK * Undo encstring test string change * Add feature flag * Add tests * Continue tests * Cleanup * Cleanup * run prettier * Update to apply new sdk changes * Fix build * Update package lock * Fix tests --------- Co-authored-by: Bernd Schoolmann <quexten@fedora-2.fritz.box> --- ...login-decryption-options.component.spec.ts | 377 ++++++++++++++++++ .../login-decryption-options.component.ts | 120 +++++- libs/common/src/enums/feature-flag.enum.ts | 2 + .../device-trust.service.abstraction.ts | 1 + .../device-trust.service.implementation.ts | 2 +- package-lock.json | 16 +- package.json | 4 +- 7 files changed, 501 insertions(+), 21 deletions(-) create mode 100644 libs/auth/src/angular/login-decryption-options/login-decryption-options.component.spec.ts diff --git a/libs/auth/src/angular/login-decryption-options/login-decryption-options.component.spec.ts b/libs/auth/src/angular/login-decryption-options/login-decryption-options.component.spec.ts new file mode 100644 index 00000000000..07cbb680963 --- /dev/null +++ b/libs/auth/src/angular/login-decryption-options/login-decryption-options.component.spec.ts @@ -0,0 +1,377 @@ +// Mock asUuid to return the input value for test consistency +jest.mock("@bitwarden/common/platform/abstractions/sdk/sdk.service", () => ({ + asUuid: (x: any) => x, +})); + +import { DestroyRef } from "@angular/core"; +import { FormBuilder } from "@angular/forms"; +import { Router } from "@angular/router"; +import { mock, MockProxy } from "jest-mock-extended"; +import { BehaviorSubject, of } from "rxjs"; + +import { + LoginEmailServiceAbstraction, + LogoutService, + UserDecryptionOptionsServiceAbstraction, +} from "@bitwarden/auth/common"; +import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { PasswordResetEnrollmentServiceAbstraction } from "@bitwarden/common/auth/abstractions/password-reset-enrollment.service.abstraction"; +import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; +import { ClientType } from "@bitwarden/common/enums"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; +import { DeviceTrustServiceAbstraction } from "@bitwarden/common/key-management/device-trust/abstractions/device-trust.service.abstraction"; +import { SecurityStateService } from "@bitwarden/common/key-management/security-state/abstractions/security-state.service"; +import { SignedSecurityState } from "@bitwarden/common/key-management/types"; +import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { RegisterSdkService } from "@bitwarden/common/platform/abstractions/sdk/register-sdk.service"; +import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; +import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; +import { UserId } from "@bitwarden/common/types/guid"; +// eslint-disable-next-line no-restricted-imports +import { AnonLayoutWrapperDataService, DialogService, ToastService } from "@bitwarden/components"; +import { KeyService } from "@bitwarden/key-management"; + +import { LoginDecryptionOptionsComponent } from "./login-decryption-options.component"; +import { LoginDecryptionOptionsService } from "./login-decryption-options.service"; + +describe("LoginDecryptionOptionsComponent", () => { + let component: LoginDecryptionOptionsComponent; + let accountService: MockProxy<AccountService>; + let anonLayoutWrapperDataService: MockProxy<AnonLayoutWrapperDataService>; + let apiService: MockProxy<ApiService>; + let destroyRef: MockProxy<DestroyRef>; + let deviceTrustService: MockProxy<DeviceTrustServiceAbstraction>; + let dialogService: MockProxy<DialogService>; + let formBuilder: FormBuilder; + let i18nService: MockProxy<I18nService>; + let keyService: MockProxy<KeyService>; + let loginDecryptionOptionsService: MockProxy<LoginDecryptionOptionsService>; + let loginEmailService: MockProxy<LoginEmailServiceAbstraction>; + let messagingService: MockProxy<MessagingService>; + let organizationApiService: MockProxy<OrganizationApiServiceAbstraction>; + let passwordResetEnrollmentService: MockProxy<PasswordResetEnrollmentServiceAbstraction>; + let platformUtilsService: MockProxy<PlatformUtilsService>; + let router: MockProxy<Router>; + let ssoLoginService: MockProxy<SsoLoginServiceAbstraction>; + let toastService: MockProxy<ToastService>; + let userDecryptionOptionsService: MockProxy<UserDecryptionOptionsServiceAbstraction>; + let validationService: MockProxy<ValidationService>; + let logoutService: MockProxy<LogoutService>; + let registerSdkService: MockProxy<RegisterSdkService>; + let securityStateService: MockProxy<SecurityStateService>; + let appIdService: MockProxy<AppIdService>; + let configService: MockProxy<ConfigService>; + let accountCryptographicStateService: MockProxy<any>; + + const mockUserId = "user-id-123" as UserId; + const mockEmail = "test@example.com"; + const mockOrgId = "org-id-456"; + + beforeEach(() => { + accountService = mock<AccountService>(); + anonLayoutWrapperDataService = mock<AnonLayoutWrapperDataService>(); + apiService = mock<ApiService>(); + destroyRef = mock<DestroyRef>(); + deviceTrustService = mock<DeviceTrustServiceAbstraction>(); + dialogService = mock<DialogService>(); + formBuilder = new FormBuilder(); + i18nService = mock<I18nService>(); + keyService = mock<KeyService>(); + loginDecryptionOptionsService = mock<LoginDecryptionOptionsService>(); + loginEmailService = mock<LoginEmailServiceAbstraction>(); + messagingService = mock<MessagingService>(); + organizationApiService = mock<OrganizationApiServiceAbstraction>(); + passwordResetEnrollmentService = mock<PasswordResetEnrollmentServiceAbstraction>(); + platformUtilsService = mock<PlatformUtilsService>(); + router = mock<Router>(); + ssoLoginService = mock<SsoLoginServiceAbstraction>(); + toastService = mock<ToastService>(); + userDecryptionOptionsService = mock<UserDecryptionOptionsServiceAbstraction>(); + validationService = mock<ValidationService>(); + logoutService = mock<LogoutService>(); + registerSdkService = mock<RegisterSdkService>(); + securityStateService = mock<SecurityStateService>(); + appIdService = mock<AppIdService>(); + configService = mock<ConfigService>(); + accountCryptographicStateService = mock(); + + // Setup default mocks + accountService.activeAccount$ = new BehaviorSubject({ + id: mockUserId, + email: mockEmail, + name: "Test User", + emailVerified: true, + creationDate: new Date().toISOString(), + }); + platformUtilsService.getClientType.mockReturnValue(ClientType.Browser); + deviceTrustService.getShouldTrustDevice.mockResolvedValue(true); + i18nService.t.mockImplementation((key: string) => key); + + component = new LoginDecryptionOptionsComponent( + accountService, + anonLayoutWrapperDataService, + apiService, + destroyRef, + deviceTrustService, + dialogService, + formBuilder, + i18nService, + keyService, + loginDecryptionOptionsService, + loginEmailService, + messagingService, + organizationApiService, + passwordResetEnrollmentService, + platformUtilsService, + router, + ssoLoginService, + toastService, + userDecryptionOptionsService, + validationService, + logoutService, + registerSdkService, + securityStateService, + appIdService, + configService, + accountCryptographicStateService, + ); + }); + + describe("createUser with feature flag enabled", () => { + let mockPostKeysForTdeRegistration: jest.Mock; + let mockRegistration: any; + let mockAuth: any; + let mockSdkValue: any; + let mockSdkRef: any; + let mockSdk: any; + let mockDeviceKey: string; + let mockDeviceKeyObj: SymmetricCryptoKey; + let mockUserKeyBytes: Uint8Array; + let mockPrivateKey: string; + let mockSignedPublicKey: string; + let mockSigningKey: string; + let mockSecurityState: SignedSecurityState; + + beforeEach(async () => { + // Mock asUuid to return the input value for test consistency + jest.mock("@bitwarden/common/platform/abstractions/sdk/sdk.service", () => ({ + asUuid: (x: any) => x, + })); + (Symbol as any).dispose = Symbol("dispose"); + + mockPrivateKey = "mock-private-key"; + mockSignedPublicKey = "mock-signed-public-key"; + mockSigningKey = "mock-signing-key"; + mockSecurityState = { + signature: "mock-signature", + payload: { + version: 2, + timestamp: Date.now(), + privateKeyHash: "mock-hash", + }, + } as any; + const deviceKeyBytes = new Uint8Array(32).fill(5); + mockDeviceKey = Buffer.from(deviceKeyBytes).toString("base64"); + mockDeviceKeyObj = SymmetricCryptoKey.fromString(mockDeviceKey); + mockUserKeyBytes = new Uint8Array(64); + + mockPostKeysForTdeRegistration = jest.fn().mockResolvedValue({ + account_cryptographic_state: { + V2: { + private_key: mockPrivateKey, + signed_public_key: mockSignedPublicKey, + signing_key: mockSigningKey, + security_state: mockSecurityState, + }, + }, + device_key: mockDeviceKey, + user_key: mockUserKeyBytes, + }); + + mockRegistration = { + post_keys_for_tde_registration: mockPostKeysForTdeRegistration, + }; + + mockAuth = { + registration: jest.fn().mockReturnValue(mockRegistration), + }; + + mockSdkValue = { + auth: jest.fn().mockReturnValue(mockAuth), + }; + + mockSdkRef = { + value: mockSdkValue, + [Symbol.dispose]: jest.fn(), + }; + + mockSdk = { + take: jest.fn().mockReturnValue(mockSdkRef), + }; + + registerSdkService.registerClient$ = jest.fn((userId: UserId) => of(mockSdk)) as any; + + // Setup for new user state + userDecryptionOptionsService.userDecryptionOptionsById$.mockReturnValue( + of({ + trustedDeviceOption: { + hasAdminApproval: false, + hasLoginApprovingDevice: false, + hasManageResetPasswordPermission: false, + isTdeOffboarding: false, + }, + hasMasterPassword: false, + keyConnectorOption: undefined, + }), + ); + + ssoLoginService.getActiveUserOrganizationSsoIdentifier.mockResolvedValue("org-identifier"); + organizationApiService.getAutoEnrollStatus.mockResolvedValue({ + id: mockOrgId, + resetPasswordEnabled: true, + } as any); + + // Initialize component to set up new user state + await component.ngOnInit(); + }); + + it("should use SDK v2 registration when feature flag is enabled", async () => { + // Arrange + configService.getFeatureFlag.mockResolvedValue(true); + loginDecryptionOptionsService.handleCreateUserSuccess.mockResolvedValue(undefined); + router.navigate.mockResolvedValue(true); + appIdService.getAppId.mockResolvedValue("mock-app-id"); + organizationApiService.getKeys.mockResolvedValue({ + publicKey: "mock-org-public-key", + privateKey: "mock-org-private-key", + } as any); + + // Act + await component["createUser"](); + + // Assert + expect(configService.getFeatureFlag).toHaveBeenCalledWith( + FeatureFlag.PM27279_V2RegistrationTdeJit, + ); + expect(appIdService.getAppId).toHaveBeenCalled(); + expect(organizationApiService.getKeys).toHaveBeenCalledWith(mockOrgId); + expect(registerSdkService.registerClient$).toHaveBeenCalledWith(mockUserId); + + // Verify SDK registration was called with correct parameters + expect(mockSdkValue.auth).toHaveBeenCalled(); + expect(mockAuth.registration).toHaveBeenCalled(); + expect(mockPostKeysForTdeRegistration).toHaveBeenCalledWith({ + org_id: mockOrgId, + org_public_key: "mock-org-public-key", + user_id: mockUserId, + device_identifier: "mock-app-id", + trust_device: true, + }); + + const expectedDeviceKey = mockDeviceKeyObj; + const expectedUserKey = new SymmetricCryptoKey(new Uint8Array(mockUserKeyBytes)); + + // Verify keys were set + expect(keyService.setPrivateKey).toHaveBeenCalledWith(mockPrivateKey, mockUserId); + expect(keyService.setSignedPublicKey).toHaveBeenCalledWith(mockSignedPublicKey, mockUserId); + expect(keyService.setUserSigningKey).toHaveBeenCalledWith(mockSigningKey, mockUserId); + expect(securityStateService.setAccountSecurityState).toHaveBeenCalledWith( + mockSecurityState, + mockUserId, + ); + expect(accountCryptographicStateService.setAccountCryptographicState).toHaveBeenCalledWith( + expect.objectContaining({ + V2: { + private_key: mockPrivateKey, + signed_public_key: mockSignedPublicKey, + signing_key: mockSigningKey, + security_state: mockSecurityState, + }, + }), + mockUserId, + ); + + expect(validationService.showError).not.toHaveBeenCalled(); + + // Verify device and user keys were persisted + expect(deviceTrustService.setDeviceKey).toHaveBeenCalledWith( + mockUserId, + expect.any(SymmetricCryptoKey), + ); + expect(keyService.setUserKey).toHaveBeenCalledWith( + expect.any(SymmetricCryptoKey), + mockUserId, + ); + + const [, deviceKeyArg] = deviceTrustService.setDeviceKey.mock.calls[0]; + const [userKeyArg] = keyService.setUserKey.mock.calls[0]; + + expect((deviceKeyArg as SymmetricCryptoKey).keyB64).toBe(expectedDeviceKey.keyB64); + expect((userKeyArg as SymmetricCryptoKey).keyB64).toBe(expectedUserKey.keyB64); + + // Verify success toast and navigation + expect(toastService.showToast).toHaveBeenCalledWith({ + variant: "success", + title: null, + message: "accountSuccessfullyCreated", + }); + expect(loginDecryptionOptionsService.handleCreateUserSuccess).toHaveBeenCalled(); + expect(router.navigate).toHaveBeenCalledWith(["/tabs/vault"]); + }); + + it("should use legacy registration when feature flag is disabled", async () => { + // Arrange + configService.getFeatureFlag.mockResolvedValue(false); + + const mockPublicKey = "mock-public-key"; + const mockPrivateKey = { + encryptedString: "mock-encrypted-private-key", + } as any; + + keyService.initAccount.mockResolvedValue({ + publicKey: mockPublicKey, + privateKey: mockPrivateKey, + } as any); + + apiService.postAccountKeys.mockResolvedValue(undefined); + passwordResetEnrollmentService.enroll.mockResolvedValue(undefined); + deviceTrustService.trustDevice.mockResolvedValue(undefined); + loginDecryptionOptionsService.handleCreateUserSuccess.mockResolvedValue(undefined); + router.navigate.mockResolvedValue(true); + + // Act + await component["createUser"](); + + // Assert + expect(configService.getFeatureFlag).toHaveBeenCalledWith( + FeatureFlag.PM27279_V2RegistrationTdeJit, + ); + expect(keyService.initAccount).toHaveBeenCalledWith(mockUserId); + expect(apiService.postAccountKeys).toHaveBeenCalledWith( + expect.objectContaining({ + publicKey: mockPublicKey, + encryptedPrivateKey: mockPrivateKey.encryptedString, + }), + ); + expect(passwordResetEnrollmentService.enroll).toHaveBeenCalledWith(mockOrgId); + expect(deviceTrustService.trustDevice).toHaveBeenCalledWith(mockUserId); + + // Verify success toast + expect(toastService.showToast).toHaveBeenCalledWith({ + variant: "success", + title: null, + message: "accountSuccessfullyCreated", + }); + + // Verify navigation + expect(loginDecryptionOptionsService.handleCreateUserSuccess).toHaveBeenCalled(); + expect(router.navigate).toHaveBeenCalledWith(["/tabs/vault"]); + }); + }); +}); diff --git a/libs/auth/src/angular/login-decryption-options/login-decryption-options.component.ts b/libs/auth/src/angular/login-decryption-options/login-decryption-options.component.ts index fb07069998b..06263ef7371 100644 --- a/libs/auth/src/angular/login-decryption-options/login-decryption-options.component.ts +++ b/libs/auth/src/angular/login-decryption-options/login-decryption-options.component.ts @@ -5,7 +5,17 @@ import { Component, DestroyRef, OnInit } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { FormBuilder, FormControl, ReactiveFormsModule } from "@angular/forms"; import { Router } from "@angular/router"; -import { catchError, defer, firstValueFrom, from, map, of, switchMap, throwError } from "rxjs"; +import { + catchError, + concatMap, + defer, + firstValueFrom, + from, + map, + of, + switchMap, + throwError, +} from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { @@ -20,13 +30,27 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv import { PasswordResetEnrollmentServiceAbstraction } from "@bitwarden/common/auth/abstractions/password-reset-enrollment.service.abstraction"; import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; import { ClientType } from "@bitwarden/common/enums"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; +import { AccountCryptographicStateService } from "@bitwarden/common/key-management/account-cryptography/account-cryptographic-state.service"; import { DeviceTrustServiceAbstraction } from "@bitwarden/common/key-management/device-trust/abstractions/device-trust.service.abstraction"; +import { SecurityStateService } from "@bitwarden/common/key-management/security-state/abstractions/security-state.service"; +import { + SignedPublicKey, + SignedSecurityState, + WrappedSigningKey, +} from "@bitwarden/common/key-management/types"; import { KeysRequest } from "@bitwarden/common/models/request/keys.request"; +import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { RegisterSdkService } from "@bitwarden/common/platform/abstractions/sdk/register-sdk.service"; +import { asUuid } from "@bitwarden/common/platform/abstractions/sdk/sdk.service"; import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; +import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { UserId } from "@bitwarden/common/types/guid"; +import { DeviceKey, UserKey } from "@bitwarden/common/types/key"; // 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 { @@ -40,6 +64,7 @@ import { TypographyModule, } from "@bitwarden/components"; import { KeyService } from "@bitwarden/key-management"; +import { OrganizationId as SdkOrganizationId, UserId as SdkUserId } from "@bitwarden/sdk-internal"; import { LoginDecryptionOptionsService } from "./login-decryption-options.service"; @@ -112,6 +137,11 @@ export class LoginDecryptionOptionsComponent implements OnInit { private userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction, private validationService: ValidationService, private logoutService: LogoutService, + private registerSdkService: RegisterSdkService, + private securityStateService: SecurityStateService, + private appIdService: AppIdService, + private configService: ConfigService, + private accountCryptographicStateService: AccountCryptographicStateService, ) { this.clientType = this.platformUtilsService.getClientType(); } @@ -251,9 +281,85 @@ export class LoginDecryptionOptionsComponent implements OnInit { } try { - const { publicKey, privateKey } = await this.keyService.initAccount(this.activeAccountId); - const keysRequest = new KeysRequest(publicKey, privateKey.encryptedString); - await this.apiService.postAccountKeys(keysRequest); + const useSdkV2Creation = await this.configService.getFeatureFlag( + FeatureFlag.PM27279_V2RegistrationTdeJit, + ); + if (useSdkV2Creation) { + const deviceIdentifier = await this.appIdService.getAppId(); + const userId = this.activeAccountId; + const organizationId = this.newUserOrgId; + + const orgKeyResponse = await this.organizationApiService.getKeys(organizationId); + const register_result = await firstValueFrom( + this.registerSdkService.registerClient$(userId).pipe( + concatMap(async (sdk) => { + if (!sdk) { + throw new Error("SDK not available"); + } + + using ref = sdk.take(); + return await ref.value + .auth() + .registration() + .post_keys_for_tde_registration({ + org_id: asUuid<SdkOrganizationId>(organizationId), + org_public_key: orgKeyResponse.publicKey, + user_id: asUuid<SdkUserId>(userId), + device_identifier: deviceIdentifier, + trust_device: this.formGroup.value.rememberDevice, + }); + }), + ), + ); + // The keys returned here can only be v2 keys, since the SDK only implements returning V2 keys. + if ("V1" in register_result.account_cryptographic_state) { + throw new Error("Unexpected V1 account cryptographic state"); + } + + // Note: When SDK state management matures, these should be moved into post_keys_for_tde_registration + // Set account cryptography state + await this.accountCryptographicStateService.setAccountCryptographicState( + register_result.account_cryptographic_state, + userId, + ); + // Legacy individual states + await this.keyService.setPrivateKey( + register_result.account_cryptographic_state.V2.private_key, + userId, + ); + await this.keyService.setSignedPublicKey( + register_result.account_cryptographic_state.V2.signed_public_key as SignedPublicKey, + userId, + ); + await this.keyService.setUserSigningKey( + register_result.account_cryptographic_state.V2.signing_key as WrappedSigningKey, + userId, + ); + await this.securityStateService.setAccountSecurityState( + register_result.account_cryptographic_state.V2.security_state as SignedSecurityState, + userId, + ); + + // TDE unlock + await this.deviceTrustService.setDeviceKey( + userId, + SymmetricCryptoKey.fromString(register_result.device_key) as DeviceKey, + ); + + // Set user key - user is now unlocked + await this.keyService.setUserKey( + SymmetricCryptoKey.fromString(register_result.user_key) as UserKey, + userId, + ); + } else { + const { publicKey, privateKey } = await this.keyService.initAccount(this.activeAccountId); + const keysRequest = new KeysRequest(publicKey, privateKey.encryptedString); + await this.apiService.postAccountKeys(keysRequest); + await this.passwordResetEnrollmentService.enroll(this.newUserOrgId); + if (this.formGroup.value.rememberDevice) { + await this.deviceTrustService.trustDevice(this.activeAccountId); + } + } this.toastService.showToast({ variant: "success", @@ -261,12 +367,6 @@ export class LoginDecryptionOptionsComponent implements OnInit { message: this.i18nService.t("accountSuccessfullyCreated"), }); - await this.passwordResetEnrollmentService.enroll(this.newUserOrgId); - - if (this.formGroup.value.rememberDevice) { - await this.deviceTrustService.trustDevice(this.activeAccountId); - } - await this.loginDecryptionOptionsService.handleCreateUserSuccess(); if (this.clientType === ClientType.Desktop) { diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts index f905c62288e..08155bf3af2 100644 --- a/libs/common/src/enums/feature-flag.enum.ts +++ b/libs/common/src/enums/feature-flag.enum.ts @@ -44,6 +44,7 @@ export enum FeatureFlag { NoLogoutOnKdfChange = "pm-23995-no-logout-on-kdf-change", DataRecoveryTool = "pm-28813-data-recovery-tool", ConsolidatedSessionTimeoutComponent = "pm-26056-consolidated-session-timeout-component", + PM27279_V2RegistrationTdeJit = "pm-27279-v2-registration-tde-jit", /* Tools */ DesktopSendUIRefresh = "desktop-send-ui-refresh", @@ -154,6 +155,7 @@ export const DefaultFeatureFlagValue = { [FeatureFlag.NoLogoutOnKdfChange]: FALSE, [FeatureFlag.DataRecoveryTool]: FALSE, [FeatureFlag.ConsolidatedSessionTimeoutComponent]: FALSE, + [FeatureFlag.PM27279_V2RegistrationTdeJit]: FALSE, /* Platform */ [FeatureFlag.IpcChannelFramework]: FALSE, diff --git a/libs/common/src/key-management/device-trust/abstractions/device-trust.service.abstraction.ts b/libs/common/src/key-management/device-trust/abstractions/device-trust.service.abstraction.ts index 2bc99e5e5c2..ceff220fe42 100644 --- a/libs/common/src/key-management/device-trust/abstractions/device-trust.service.abstraction.ts +++ b/libs/common/src/key-management/device-trust/abstractions/device-trust.service.abstraction.ts @@ -39,6 +39,7 @@ export abstract class DeviceTrustServiceAbstraction { /** Retrieves the device key if it exists from state or secure storage if supported for the active user. */ abstract getDeviceKey(userId: UserId): Promise<DeviceKey | null>; + abstract setDeviceKey(userId: UserId, deviceKey: DeviceKey | null): Promise<void>; abstract decryptUserKeyWithDeviceKey( userId: UserId, encryptedDevicePrivateKey: EncString, diff --git a/libs/common/src/key-management/device-trust/services/device-trust.service.implementation.ts b/libs/common/src/key-management/device-trust/services/device-trust.service.implementation.ts index 59bd7bc11f2..518d16781ab 100644 --- a/libs/common/src/key-management/device-trust/services/device-trust.service.implementation.ts +++ b/libs/common/src/key-management/device-trust/services/device-trust.service.implementation.ts @@ -356,7 +356,7 @@ export class DeviceTrustService implements DeviceTrustServiceAbstraction { } } - private async setDeviceKey(userId: UserId, deviceKey: DeviceKey | null): Promise<void> { + async setDeviceKey(userId: UserId, deviceKey: DeviceKey | null): Promise<void> { if (!userId) { throw new Error("UserId is required. Cannot set device key."); } diff --git a/package-lock.json b/package-lock.json index deb3a9f261c..014c291c38c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,8 +23,8 @@ "@angular/platform-browser": "20.3.15", "@angular/platform-browser-dynamic": "20.3.15", "@angular/router": "20.3.15", - "@bitwarden/commercial-sdk-internal": "0.2.0-main.433", - "@bitwarden/sdk-internal": "0.2.0-main.433", + "@bitwarden/commercial-sdk-internal": "0.2.0-main.439", + "@bitwarden/sdk-internal": "0.2.0-main.439", "@electron/fuses": "1.8.0", "@emotion/css": "11.13.5", "@koa/multer": "4.0.0", @@ -4973,9 +4973,9 @@ "link": true }, "node_modules/@bitwarden/commercial-sdk-internal": { - "version": "0.2.0-main.433", - "resolved": "https://registry.npmjs.org/@bitwarden/commercial-sdk-internal/-/commercial-sdk-internal-0.2.0-main.433.tgz", - "integrity": "sha512-/eFzw+BUHxAmT75kKUn1r9MFsJH/GZpc3ljkjNjAqtvb3L+fz8VTHTe7FoloSoZEnAnp8OWOZy7n4DavT/XDiw==", + "version": "0.2.0-main.439", + "resolved": "https://registry.npmjs.org/@bitwarden/commercial-sdk-internal/-/commercial-sdk-internal-0.2.0-main.439.tgz", + "integrity": "sha512-Wujtym00U7XMEsf9zJ3/0Ggw9WmMcIpE9hMtcLryloX182118vnzkEQbEldqtywpMHiDsD9VmP6RiZ725nnUIQ==", "license": "BITWARDEN SOFTWARE DEVELOPMENT KIT LICENSE AGREEMENT", "dependencies": { "type-fest": "^4.41.0" @@ -5078,9 +5078,9 @@ "link": true }, "node_modules/@bitwarden/sdk-internal": { - "version": "0.2.0-main.433", - "resolved": "https://registry.npmjs.org/@bitwarden/sdk-internal/-/sdk-internal-0.2.0-main.433.tgz", - "integrity": "sha512-m2PnYR0ifF0BgZ63aAt8eag0v7LeEGTJ0sa7UMbTWLwmsNnHug4u7jxIJl0WaVILNeWWK8iD/WSiw3EJeb7Fmw==", + "version": "0.2.0-main.439", + "resolved": "https://registry.npmjs.org/@bitwarden/sdk-internal/-/sdk-internal-0.2.0-main.439.tgz", + "integrity": "sha512-uvIS8erGmzgWCZom7Kt78C4n4tbjfZuTCn7+y2+E8BTtLBqIZNtl4kC0tNh8c4GUWsmoIYlbQyz+HymWQ7J+QA==", "license": "GPL-3.0", "dependencies": { "type-fest": "^4.41.0" diff --git a/package.json b/package.json index f4c484f1c61..29ee9683464 100644 --- a/package.json +++ b/package.json @@ -162,8 +162,8 @@ "@angular/platform-browser": "20.3.15", "@angular/platform-browser-dynamic": "20.3.15", "@angular/router": "20.3.15", - "@bitwarden/sdk-internal": "0.2.0-main.433", - "@bitwarden/commercial-sdk-internal": "0.2.0-main.433", + "@bitwarden/sdk-internal": "0.2.0-main.439", + "@bitwarden/commercial-sdk-internal": "0.2.0-main.439", "@electron/fuses": "1.8.0", "@emotion/css": "11.13.5", "@koa/multer": "4.0.0", From 735f885091d020be656387c7b2afd9bf4dc44420 Mon Sep 17 00:00:00 2001 From: Vicki League <vleague@bitwarden.com> Date: Tue, 23 Dec 2025 10:55:33 -0500 Subject: [PATCH 151/188] [PM-30141] Fix page height and a11y by removing extra <main> (#18099) --- .../send-created/send-created.component.html | 72 +++++++++---------- 1 file changed, 35 insertions(+), 37 deletions(-) diff --git a/apps/browser/src/tools/popup/send-v2/send-created/send-created.component.html b/apps/browser/src/tools/popup/send-v2/send-created/send-created.component.html index 16711fabbf4..828c1667c57 100644 --- a/apps/browser/src/tools/popup/send-v2/send-created/send-created.component.html +++ b/apps/browser/src/tools/popup/send-v2/send-created/send-created.component.html @@ -1,39 +1,37 @@ -<main class="tw-top-0"> - <popup-page> - <popup-header - slot="header" - [pageTitle]="'createdSend' | i18n" - showBackButton - [backAction]="goToEditSend.bind(this)" - > - <ng-container slot="end"> - <app-pop-out></app-pop-out> - </ng-container> - </popup-header> +<popup-page> + <popup-header + slot="header" + [pageTitle]="'createdSend' | i18n" + showBackButton + [backAction]="goToEditSend.bind(this)" + > + <ng-container slot="end"> + <app-pop-out></app-pop-out> + </ng-container> + </popup-header> - <div - class="tw-flex tw-bg-background-alt tw-flex-col tw-justify-center tw-items-center tw-gap-2 tw-h-full tw-px-5" - > - <div class="tw-size-[95px] tw-content-center"> - <bit-icon [icon]="sendCreatedIcon"></bit-icon> - </div> - <h3 tabindex="0" appAutofocus class="tw-font-medium"> - {{ "createdSendSuccessfully" | i18n }} - </h3> - <p class="tw-text-center"> - {{ formatExpirationDate() }} - </p> - <button bitButton type="button" buttonType="primary" (click)="copyLink()"> - <b>{{ "copyLink" | i18n }}</b> - </button> + <div + class="tw-flex tw-bg-background-alt tw-flex-col tw-justify-center tw-items-center tw-gap-2 tw-h-full tw-px-5" + > + <div class="tw-size-[95px] tw-content-center"> + <bit-icon [icon]="sendCreatedIcon"></bit-icon> </div> - <popup-footer slot="footer"> - <button bitButton type="button" buttonType="primary" (click)="copyLink()"> - <b>{{ "copyLink" | i18n }}</b> - </button> - <button bitButton type="button" buttonType="secondary" (click)="goBack()"> - {{ "close" | i18n }} - </button> - </popup-footer> - </popup-page> -</main> + <h3 tabindex="0" appAutofocus class="tw-font-medium"> + {{ "createdSendSuccessfully" | i18n }} + </h3> + <p class="tw-text-center"> + {{ formatExpirationDate() }} + </p> + <button bitButton type="button" buttonType="primary" (click)="copyLink()"> + <b>{{ "copyLink" | i18n }}</b> + </button> + </div> + <popup-footer slot="footer"> + <button bitButton type="button" buttonType="primary" (click)="copyLink()"> + <b>{{ "copyLink" | i18n }}</b> + </button> + <button bitButton type="button" buttonType="secondary" (click)="goBack()"> + {{ "close" | i18n }} + </button> + </popup-footer> +</popup-page> From 77ccc3eb49c1dce3b4f7837ddf2f26a814497f50 Mon Sep 17 00:00:00 2001 From: Jordan Aasen <166539328+jaasen-livefront@users.noreply.github.com> Date: Tue, 23 Dec 2025 14:24:23 -0800 Subject: [PATCH 152/188] [PM-26656] - remove AutofillConfirmation feature flag (#18074) * remove AutofillConfirmation feature flag * fix tests. remove feature flag tests --- .../item-more-options.component.html | 11 -------- .../item-more-options.component.spec.ts | 27 +------------------ .../item-more-options.component.ts | 20 +------------- libs/common/src/enums/feature-flag.enum.ts | 2 -- 4 files changed, 2 insertions(+), 58 deletions(-) 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 5c5171ac81d..b86ec24fd20 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 @@ -13,17 +13,6 @@ <button type="button" bitMenuItem (click)="doAutofill()"> {{ "autofill" | i18n }} </button> - <!-- Autofill confirmation handles both 'autofill' and 'autofill and save' so no need to show both --> - @if (!(autofillConfirmationFlagEnabled$ | async)) { - <button - type="button" - bitMenuItem - *ngIf="canEdit && isLogin" - (click)="doAutofillAndSave()" - > - {{ "fillAndSave" | i18n }} - </button> - } </ng-container> </ng-container> <ng-container *ngIf="showViewOption"> diff --git a/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.spec.ts b/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.spec.ts index b9f48b7407b..bd9ce108522 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.spec.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.spec.ts @@ -13,7 +13,6 @@ import { UriMatchStrategy, UriMatchStrategySetting, } from "@bitwarden/common/models/domain/domain-service"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { CipherArchiveService } from "@bitwarden/common/vault/abstractions/cipher-archive.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; @@ -40,10 +39,6 @@ describe("ItemMoreOptionsComponent", () => { openSimpleDialog: jest.fn().mockResolvedValue(true), open: jest.fn(), }; - const featureFlag$ = new BehaviorSubject<boolean>(false); - const configService = { - getFeatureFlag$: jest.fn().mockImplementation(() => featureFlag$.asObservable()), - }; const cipherService = { getFullCipherView: jest.fn(), encrypt: jest.fn(), @@ -93,7 +88,6 @@ describe("ItemMoreOptionsComponent", () => { TestBed.configureTestingModule({ imports: [ItemMoreOptionsComponent, NoopAnimationsModule], providers: [ - { provide: ConfigService, useValue: configService }, { provide: CipherService, useValue: cipherService }, { provide: VaultPopupAutofillService, useValue: autofillSvc }, @@ -152,22 +146,6 @@ describe("ItemMoreOptionsComponent", () => { expect(passwordRepromptService.passwordRepromptCheck).toHaveBeenCalledWith(baseCipher); }); - it("calls the autofill service to autofill without showing the confirmation dialog when the feature flag is disabled", async () => { - autofillSvc.currentAutofillTab$.next({ url: "https://page.example.com" }); - - await component.doAutofill(); - - expect(cipherService.getFullCipherView).toHaveBeenCalled(); - expect(autofillSvc.doAutofill).toHaveBeenCalledTimes(1); - expect(autofillSvc.doAutofill).toHaveBeenCalledWith( - expect.objectContaining({ id: "cipher-1" }), - true, - true, - ); - expect(autofillSvc.doAutofillAndSave).not.toHaveBeenCalled(); - expect(dialogService.openSimpleDialog).not.toHaveBeenCalled(); - }); - it("does nothing if the user fails master password reprompt", async () => { baseCipher.reprompt = 2; // Master Password reprompt enabled autofillSvc.currentAutofillTab$.next({ url: "https://page.example.com" }); @@ -181,7 +159,6 @@ describe("ItemMoreOptionsComponent", () => { }); it("does not show the exact match dialog when the default match strategy is Exact and autofill confirmation is not to be shown", async () => { - // autofill confirmation dialog is not shown when either the feature flag is disabled uriMatchStrategy$.next(UriMatchStrategy.Exact); autofillSvc.currentAutofillTab$.next({ url: "https://page.example.com/path" }); await component.doAutofill(); @@ -191,8 +168,6 @@ describe("ItemMoreOptionsComponent", () => { describe("autofill confirmation dialog", () => { beforeEach(() => { - // autofill confirmation dialog is shown when feature flag is enabled - featureFlag$.next(true); uriMatchStrategy$.next(UriMatchStrategy.Domain); passwordRepromptService.passwordRepromptCheck.mockResolvedValue(true); }); @@ -206,7 +181,7 @@ describe("ItemMoreOptionsComponent", () => { expect(passwordRepromptService.passwordRepromptCheck).toHaveBeenCalledWith(baseCipher); }); - it("opens the autofill confirmation dialog with filtered saved URLs when the feature flag is enabled", async () => { + it("opens the autofill confirmation dialog with filtered saved URLs", async () => { autofillSvc.currentAutofillTab$.next({ url: "https://page.example.com/path" }); const openSpy = mockConfirmDialogResult(AutofillConfirmationDialogResult.Canceled); 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 b65acc6ca8e..c4353e17bef 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 @@ -11,9 +11,7 @@ import { OrganizationService } from "@bitwarden/common/admin-console/abstraction import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { UriMatchStrategy } from "@bitwarden/common/models/domain/domain-service"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { CipherId, UserId } from "@bitwarden/common/types/guid"; import { CipherArchiveService } from "@bitwarden/common/vault/abstractions/cipher-archive.service"; @@ -37,7 +35,6 @@ import { PasswordRepromptService } from "@bitwarden/vault"; import { BrowserPremiumUpgradePromptService } from "../../../services/browser-premium-upgrade-prompt.service"; import { VaultPopupAutofillService } from "../../../services/vault-popup-autofill.service"; -import { VaultPopupItemsService } from "../../../services/vault-popup-items.service"; import { AddEditQueryParams } from "../add-edit/add-edit-v2.component"; import { AutofillConfirmationDialogComponent, @@ -98,10 +95,6 @@ export class ItemMoreOptionsComponent { protected autofillAllowed$ = this.vaultPopupAutofillService.autofillAllowed$; - protected autofillConfirmationFlagEnabled$ = this.configService - .getFeatureFlag$(FeatureFlag.AutofillConfirmation) - .pipe(map((isFeatureFlagEnabled) => isFeatureFlagEnabled)); - protected uriMatchStrategy$ = this.domainSettingsService.resolvedDefaultUriMatchStrategy$; /** @@ -166,8 +159,6 @@ export class ItemMoreOptionsComponent { private collectionService: CollectionService, private restrictedItemTypesService: RestrictedItemTypesService, private cipherArchiveService: CipherArchiveService, - private configService: ConfigService, - private vaultPopupItemsService: VaultPopupItemsService, private domainSettingsService: DomainSettingsService, ) {} @@ -216,13 +207,9 @@ export class ItemMoreOptionsComponent { const cipherHasAllExactMatchLoginUris = uris.length > 0 && uris.every((u) => u.uri && u.match === UriMatchStrategy.Exact); - const showAutofillConfirmation = await firstValueFrom(this.autofillConfirmationFlagEnabled$); const uriMatchStrategy = await firstValueFrom(this.uriMatchStrategy$); - if ( - showAutofillConfirmation && - (cipherHasAllExactMatchLoginUris || uriMatchStrategy === UriMatchStrategy.Exact) - ) { + if (cipherHasAllExactMatchLoginUris || uriMatchStrategy === UriMatchStrategy.Exact) { await this.dialogService.openSimpleDialog({ title: { key: "cannotAutofill" }, content: { key: "cannotAutofillExactMatch" }, @@ -233,11 +220,6 @@ export class ItemMoreOptionsComponent { return; } - if (!showAutofillConfirmation) { - await this.vaultPopupAutofillService.doAutofill(cipher, true, true); - return; - } - const currentTab = await firstValueFrom(this.vaultPopupAutofillService.currentAutofillTab$); if (!currentTab?.url) { diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts index 08155bf3af2..837418f92cf 100644 --- a/libs/common/src/enums/feature-flag.enum.ts +++ b/libs/common/src/enums/feature-flag.enum.ts @@ -63,7 +63,6 @@ export enum FeatureFlag { PM22134SdkCipherListView = "pm-22134-sdk-cipher-list-view", PM22136_SdkCipherEncryption = "pm-22136-sdk-cipher-encryption", CipherKeyEncryption = "cipher-key-encryption", - AutofillConfirmation = "pm-25083-autofill-confirm-from-search", RiskInsightsForPremium = "pm-23904-risk-insights-for-premium", VaultLoadingSkeletons = "pm-25081-vault-skeleton-loaders", BrowserPremiumSpotlight = "pm-23384-browser-premium-spotlight", @@ -126,7 +125,6 @@ export const DefaultFeatureFlagValue = { [FeatureFlag.PM19941MigrateCipherDomainToSdk]: FALSE, [FeatureFlag.PM22134SdkCipherListView]: FALSE, [FeatureFlag.PM22136_SdkCipherEncryption]: FALSE, - [FeatureFlag.AutofillConfirmation]: FALSE, [FeatureFlag.RiskInsightsForPremium]: FALSE, [FeatureFlag.VaultLoadingSkeletons]: FALSE, [FeatureFlag.BrowserPremiumSpotlight]: FALSE, From 99305a534220fbdfd42d5297f88355ad0d70e770 Mon Sep 17 00:00:00 2001 From: Jordan Aasen <166539328+jaasen-livefront@users.noreply.github.com> Date: Wed, 24 Dec 2025 10:14:52 -0800 Subject: [PATCH 153/188] only pass strings to i18n pipe (#17978) --- .../autofill-confirmation-dialog.component.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/browser/src/vault/popup/components/vault-v2/autofill-confirmation-dialog/autofill-confirmation-dialog.component.html b/apps/browser/src/vault/popup/components/vault-v2/autofill-confirmation-dialog/autofill-confirmation-dialog.component.html index 88bff47191a..d8c12122120 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/autofill-confirmation-dialog/autofill-confirmation-dialog.component.html +++ b/apps/browser/src/vault/popup/components/vault-v2/autofill-confirmation-dialog/autofill-confirmation-dialog.component.html @@ -15,8 +15,8 @@ } @if (savedUrls().length > 1) { <div class="tw-flex tw-justify-between tw-items-center tw-mt-4 tw-mb-1 tw-pt-2"> - <p class="tw-text-muted tw-text-xs tw-uppercase tw-font-medium"> - {{ "savedWebsites" | i18n: savedUrls().length }} + <p class="tw-text-muted tw-text-xs tw-uppercase tw-font-medium tw-mb-0"> + {{ "savedWebsites" | i18n: savedUrls().length.toString() }} </p> <button type="button" From 91991d2da602c9274464157ef5e674a81caaedc2 Mon Sep 17 00:00:00 2001 From: Todd Martin <106564991+trmartin4@users.noreply.github.com> Date: Fri, 26 Dec 2025 14:42:19 -0500 Subject: [PATCH 154/188] feat(account): [PM-29545] Update AccountInfo creationDate to use Date instead of string * Add creationDate of account to AccountInfo * Added initialization of creationDate. * Removed extra changes. * Fixed tests to initialize creation date * Added helper method to abstract account initialization in tests. * More test updates. * Linting * Additional test fixes. * Fixed spec reference * Fixed imports * Linting. * Fixed browser test. * Modified tsconfig to reference spec file. * Fixed import. * Removed dependency on os. This is necessary so that the @bitwarden/common/spec lib package can be referenced in tests without node. * Revert "Removed dependency on os. This is necessary so that the @bitwarden/common/spec lib package can be referenced in tests without node." This reverts commit 669f6557b6561f65ff513c14c2b3e8a55bef4035. * Updated stories to hard-code new field. * Removed changes to tsconfig * Revert "Removed changes to tsconfig" This reverts commit b7d916e8dc70be453f7092138416ce2e3c09ed57. * Updated to use Date * Updated to use Date on sync. * Changes to tests that can't use mock function * Prettier updates * Update equality to handle Date type. * Change to type comparison. * Simplified equality comparison to just use properties. * Added comment. * Updated comment to reference Date. * Added back in internal method tests. --- .../navigation-switcher.stories.ts | 2 +- .../product-switcher.stories.ts | 2 +- libs/common/spec/fake-account-service.ts | 4 +- .../src/auth/abstractions/account.service.ts | 34 ++-- .../src/auth/services/account.service.spec.ts | 154 ++++++++++-------- .../src/auth/services/account.service.ts | 29 +++- .../src/platform/sync/default-sync.service.ts | 2 +- 7 files changed, 129 insertions(+), 98 deletions(-) diff --git a/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.stories.ts b/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.stories.ts index ea6e972e431..33c10309108 100644 --- a/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.stories.ts +++ b/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.stories.ts @@ -82,7 +82,7 @@ class MockAccountService implements Partial<AccountService> { name: "Test User 1", email: "test@email.com", emailVerified: true, - creationDate: "2024-01-01T00:00:00.000Z", + creationDate: new Date("2024-01-01T00:00:00.000Z"), }); } diff --git a/apps/web/src/app/layouts/product-switcher/product-switcher.stories.ts b/apps/web/src/app/layouts/product-switcher/product-switcher.stories.ts index d412530a635..ad18b2b3490 100644 --- a/apps/web/src/app/layouts/product-switcher/product-switcher.stories.ts +++ b/apps/web/src/app/layouts/product-switcher/product-switcher.stories.ts @@ -82,7 +82,7 @@ class MockAccountService implements Partial<AccountService> { name: "Test User 1", email: "test@email.com", emailVerified: true, - creationDate: "2024-01-01T00:00:00.000Z", + creationDate: new Date("2024-01-01T00:00:00.000Z"), }); } diff --git a/libs/common/spec/fake-account-service.ts b/libs/common/spec/fake-account-service.ts index ed8b7796966..db644e5e9d1 100644 --- a/libs/common/spec/fake-account-service.ts +++ b/libs/common/spec/fake-account-service.ts @@ -15,7 +15,7 @@ export function mockAccountInfoWith(info: Partial<AccountInfo> = {}): AccountInf name: "name", email: "email", emailVerified: true, - creationDate: "2024-01-01T00:00:00.000Z", + creationDate: new Date("2024-01-01T00:00:00.000Z"), ...info, }; } @@ -111,7 +111,7 @@ export class FakeAccountService implements AccountService { await this.mock.setAccountEmailVerified(userId, emailVerified); } - async setAccountCreationDate(userId: UserId, creationDate: string): Promise<void> { + async setAccountCreationDate(userId: UserId, creationDate: Date): Promise<void> { await this.mock.setAccountCreationDate(userId, creationDate); } diff --git a/libs/common/src/auth/abstractions/account.service.ts b/libs/common/src/auth/abstractions/account.service.ts index 78822f3ebd5..c80d6b0439c 100644 --- a/libs/common/src/auth/abstractions/account.service.ts +++ b/libs/common/src/auth/abstractions/account.service.ts @@ -2,33 +2,25 @@ import { Observable } from "rxjs"; import { UserId } from "../../types/guid"; +/** + * Holds state that represents a user's account with Bitwarden. + * Any additions here should be added to the equality check in the AccountService + * to ensure that emissions are done on every change. + * + * @property email - User's email address. + * @property emailVerified - Whether the email has been verified. + * @property name - User's display name (optional). + * @property creationDate - Date when the account was created. + * Will be undefined immediately after login until the first sync completes. + */ export type AccountInfo = { email: string; emailVerified: boolean; name: string | undefined; - creationDate: string | undefined; + creationDate: Date | undefined; }; export type Account = { id: UserId } & AccountInfo; - -export function accountInfoEqual(a: AccountInfo, b: AccountInfo) { - if (a == null && b == null) { - return true; - } - - if (a == null || b == null) { - return false; - } - - const keys = new Set([...Object.keys(a), ...Object.keys(b)]) as Set<keyof AccountInfo>; - for (const key of keys) { - if (a[key] !== b[key]) { - return false; - } - } - return true; -} - export abstract class AccountService { abstract accounts$: Observable<Record<UserId, AccountInfo>>; @@ -77,7 +69,7 @@ export abstract class AccountService { * @param userId * @param creationDate */ - abstract setAccountCreationDate(userId: UserId, creationDate: string): Promise<void>; + abstract setAccountCreationDate(userId: UserId, creationDate: Date): Promise<void>; /** * updates the `accounts$` observable with the new VerifyNewDeviceLogin property for the account. * @param userId diff --git a/libs/common/src/auth/services/account.service.spec.ts b/libs/common/src/auth/services/account.service.spec.ts index f517b61ffb6..6668b9c39de 100644 --- a/libs/common/src/auth/services/account.service.spec.ts +++ b/libs/common/src/auth/services/account.service.spec.ts @@ -17,7 +17,7 @@ import { LogService } from "../../platform/abstractions/log.service"; import { MessagingService } from "../../platform/abstractions/messaging.service"; import { Utils } from "../../platform/misc/utils"; import { UserId } from "../../types/guid"; -import { AccountInfo, accountInfoEqual } from "../abstractions/account.service"; +import { AccountInfo } from "../abstractions/account.service"; import { ACCOUNT_ACCOUNTS, @@ -27,63 +27,6 @@ import { AccountServiceImplementation, } from "./account.service"; -describe("accountInfoEqual", () => { - const accountInfo = mockAccountInfoWith(); - - it("compares nulls", () => { - expect(accountInfoEqual(null, null)).toBe(true); - expect(accountInfoEqual(null, accountInfo)).toBe(false); - expect(accountInfoEqual(accountInfo, null)).toBe(false); - }); - - it("compares all keys, not just those defined in AccountInfo", () => { - const different = { ...accountInfo, extra: "extra" }; - - expect(accountInfoEqual(accountInfo, different)).toBe(false); - }); - - it("compares name", () => { - const same = { ...accountInfo }; - const different = { ...accountInfo, name: "name2" }; - - expect(accountInfoEqual(accountInfo, same)).toBe(true); - expect(accountInfoEqual(accountInfo, different)).toBe(false); - }); - - it("compares email", () => { - const same = { ...accountInfo }; - const different = { ...accountInfo, email: "email2" }; - - expect(accountInfoEqual(accountInfo, same)).toBe(true); - expect(accountInfoEqual(accountInfo, different)).toBe(false); - }); - - it("compares emailVerified", () => { - const same = { ...accountInfo }; - const different = { ...accountInfo, emailVerified: false }; - - expect(accountInfoEqual(accountInfo, same)).toBe(true); - expect(accountInfoEqual(accountInfo, different)).toBe(false); - }); - - it("compares creationDate", () => { - const same = { ...accountInfo }; - const different = { ...accountInfo, creationDate: "2024-12-31T00:00:00.000Z" }; - - expect(accountInfoEqual(accountInfo, same)).toBe(true); - expect(accountInfoEqual(accountInfo, different)).toBe(false); - }); - - it("compares undefined creationDate", () => { - const accountWithoutCreationDate = mockAccountInfoWith({ creationDate: undefined }); - const same = { ...accountWithoutCreationDate }; - const different = { ...accountWithoutCreationDate, creationDate: "2024-01-01T00:00:00.000Z" }; - - expect(accountInfoEqual(accountWithoutCreationDate, same)).toBe(true); - expect(accountInfoEqual(accountWithoutCreationDate, different)).toBe(false); - }); -}); - describe("accountService", () => { let messagingService: MockProxy<MessagingService>; let logService: MockProxy<LogService>; @@ -121,6 +64,60 @@ describe("accountService", () => { jest.resetAllMocks(); }); + describe("accountInfoEqual", () => { + const accountInfo = mockAccountInfoWith(); + + it("compares nulls", () => { + expect((sut as any).accountInfoEqual(null, null)).toBe(true); + expect((sut as any).accountInfoEqual(null, accountInfo)).toBe(false); + expect((sut as any).accountInfoEqual(accountInfo, null)).toBe(false); + }); + + it("compares name", () => { + const same = { ...accountInfo }; + const different = { ...accountInfo, name: "name2" }; + + expect((sut as any).accountInfoEqual(accountInfo, same)).toBe(true); + expect((sut as any).accountInfoEqual(accountInfo, different)).toBe(false); + }); + + it("compares email", () => { + const same = { ...accountInfo }; + const different = { ...accountInfo, email: "email2" }; + + expect((sut as any).accountInfoEqual(accountInfo, same)).toBe(true); + expect((sut as any).accountInfoEqual(accountInfo, different)).toBe(false); + }); + + it("compares emailVerified", () => { + const same = { ...accountInfo }; + const different = { ...accountInfo, emailVerified: false }; + + expect((sut as any).accountInfoEqual(accountInfo, same)).toBe(true); + expect((sut as any).accountInfoEqual(accountInfo, different)).toBe(false); + }); + + it("compares creationDate", () => { + const same = { ...accountInfo }; + const different = { ...accountInfo, creationDate: new Date("2024-12-31T00:00:00.000Z") }; + + expect((sut as any).accountInfoEqual(accountInfo, same)).toBe(true); + expect((sut as any).accountInfoEqual(accountInfo, different)).toBe(false); + }); + + it("compares undefined creationDate", () => { + const accountWithoutCreationDate = mockAccountInfoWith({ creationDate: undefined }); + const same = { ...accountWithoutCreationDate }; + const different = { + ...accountWithoutCreationDate, + creationDate: new Date("2024-01-01T00:00:00.000Z"), + }; + + expect((sut as any).accountInfoEqual(accountWithoutCreationDate, same)).toBe(true); + expect((sut as any).accountInfoEqual(accountWithoutCreationDate, different)).toBe(false); + }); + }); + describe("activeAccount$", () => { it("should emit null if no account is active", () => { const emissions = trackEmissions(sut.activeAccount$); @@ -281,7 +278,7 @@ describe("accountService", () => { }); it("should update the account with a new creation date", async () => { - const newCreationDate = "2024-12-31T00:00:00.000Z"; + const newCreationDate = new Date("2024-12-31T00:00:00.000Z"); await sut.setAccountCreationDate(userId, newCreationDate); const currentState = await firstValueFrom(accountsState.state$); @@ -297,6 +294,24 @@ describe("accountService", () => { expect(currentState).toEqual(initialState); }); + it("should not update if the creation date has the same timestamp but different Date object", async () => { + const sameTimestamp = new Date(userInfo.creationDate.getTime()); + await sut.setAccountCreationDate(userId, sameTimestamp); + const currentState = await firstValueFrom(accountsState.state$); + + expect(currentState).toEqual(initialState); + }); + + it("should update if the creation date has a different timestamp", async () => { + const differentDate = new Date(userInfo.creationDate.getTime() + 1000); + await sut.setAccountCreationDate(userId, differentDate); + const currentState = await firstValueFrom(accountsState.state$); + + expect(currentState).toEqual({ + [userId]: { ...userInfo, creationDate: differentDate }, + }); + }); + it("should update from undefined to a defined creation date", async () => { const accountWithoutCreationDate = mockAccountInfoWith({ ...userInfo, @@ -304,7 +319,7 @@ describe("accountService", () => { }); accountsState.stateSubject.next({ [userId]: accountWithoutCreationDate }); - const newCreationDate = "2024-06-15T12:30:00.000Z"; + const newCreationDate = new Date("2024-06-15T12:30:00.000Z"); await sut.setAccountCreationDate(userId, newCreationDate); const currentState = await firstValueFrom(accountsState.state$); @@ -313,14 +328,19 @@ describe("accountService", () => { }); }); - it("should update to a different creation date string format", async () => { - const newCreationDate = "2023-03-15T08:45:30.123Z"; - await sut.setAccountCreationDate(userId, newCreationDate); - const currentState = await firstValueFrom(accountsState.state$); - - expect(currentState).toEqual({ - [userId]: { ...userInfo, creationDate: newCreationDate }, + it("should not update when both creation dates are undefined", async () => { + const accountWithoutCreationDate = mockAccountInfoWith({ + ...userInfo, + creationDate: undefined, }); + accountsState.stateSubject.next({ [userId]: accountWithoutCreationDate }); + + // Attempt to set to undefined (shouldn't trigger update) + const currentStateBefore = await firstValueFrom(accountsState.state$); + + // We can't directly call setAccountCreationDate with undefined, but we can verify + // the behavior through setAccountInfo which accountInfoEqual uses internally + expect(currentStateBefore[userId].creationDate).toBeUndefined(); }); }); diff --git a/libs/common/src/auth/services/account.service.ts b/libs/common/src/auth/services/account.service.ts index 1b028d1eba9..ea22bb9dd2c 100644 --- a/libs/common/src/auth/services/account.service.ts +++ b/libs/common/src/auth/services/account.service.ts @@ -18,7 +18,6 @@ import { Account, AccountInfo, InternalAccountService, - accountInfoEqual, } from "../../auth/abstractions/account.service"; import { LogService } from "../../platform/abstractions/log.service"; import { MessagingService } from "../../platform/abstractions/messaging.service"; @@ -37,7 +36,10 @@ export const ACCOUNT_ACCOUNTS = KeyDefinition.record<AccountInfo, UserId>( ACCOUNT_DISK, "accounts", { - deserializer: (accountInfo) => accountInfo, + deserializer: (accountInfo) => ({ + ...accountInfo, + creationDate: accountInfo.creationDate ? new Date(accountInfo.creationDate) : undefined, + }), }, ); @@ -111,7 +113,7 @@ export class AccountServiceImplementation implements InternalAccountService { this.activeAccount$ = this.activeAccountIdState.state$.pipe( combineLatestWith(this.accounts$), map(([id, accounts]) => (id ? ({ id, ...(accounts[id] as AccountInfo) } as Account) : null)), - distinctUntilChanged((a, b) => a?.id === b?.id && accountInfoEqual(a, b)), + distinctUntilChanged((a, b) => a?.id === b?.id && this.accountInfoEqual(a, b)), shareReplay({ bufferSize: 1, refCount: false }), ); this.accountActivity$ = this.globalStateProvider @@ -168,7 +170,7 @@ export class AccountServiceImplementation implements InternalAccountService { await this.setAccountInfo(userId, { emailVerified }); } - async setAccountCreationDate(userId: UserId, creationDate: string): Promise<void> { + async setAccountCreationDate(userId: UserId, creationDate: Date): Promise<void> { await this.setAccountInfo(userId, { creationDate }); } @@ -274,6 +276,23 @@ export class AccountServiceImplementation implements InternalAccountService { this._showHeader$.next(visible); } + private accountInfoEqual(a: AccountInfo, b: AccountInfo) { + if (a == null && b == null) { + return true; + } + + if (a == null || b == null) { + return false; + } + + return ( + a.email === b.email && + a.emailVerified === b.emailVerified && + a.name === b.name && + a.creationDate?.getTime() === b.creationDate?.getTime() + ); + } + private async setAccountInfo(userId: UserId, update: Partial<AccountInfo>): Promise<void> { function newAccountInfo(oldAccountInfo: AccountInfo): AccountInfo { return { ...oldAccountInfo, ...update }; @@ -291,7 +310,7 @@ export class AccountServiceImplementation implements InternalAccountService { throw new Error("Account does not exist"); } - return !accountInfoEqual(accounts[userId], newAccountInfo(accounts[userId])); + return !this.accountInfoEqual(accounts[userId], newAccountInfo(accounts[userId])); }, }, ); diff --git a/libs/common/src/platform/sync/default-sync.service.ts b/libs/common/src/platform/sync/default-sync.service.ts index 49fd33b8035..fdd05927b50 100644 --- a/libs/common/src/platform/sync/default-sync.service.ts +++ b/libs/common/src/platform/sync/default-sync.service.ts @@ -279,8 +279,8 @@ export class DefaultSyncService extends CoreSyncService { await this.avatarService.setSyncAvatarColor(response.id, response.avatarColor); await this.tokenService.setSecurityStamp(response.securityStamp, response.id); await this.accountService.setAccountEmailVerified(response.id, response.emailVerified); + await this.accountService.setAccountCreationDate(response.id, new Date(response.creationDate)); await this.accountService.setAccountVerifyNewDeviceLogin(response.id, response.verifyDevices); - await this.accountService.setAccountCreationDate(response.id, response.creationDate); await this.billingAccountProfileStateService.setHasPremium( response.premiumPersonally, From 5c13b07366aa0e22c4d2600a71d64b9937e86f06 Mon Sep 17 00:00:00 2001 From: Todd Martin <106564991+trmartin4@users.noreply.github.com> Date: Fri, 26 Dec 2025 15:28:58 -0500 Subject: [PATCH 155/188] chore(merge): Fixed date initialization on test --- .../login-decryption-options.component.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/auth/src/angular/login-decryption-options/login-decryption-options.component.spec.ts b/libs/auth/src/angular/login-decryption-options/login-decryption-options.component.spec.ts index 07cbb680963..248eaa608af 100644 --- a/libs/auth/src/angular/login-decryption-options/login-decryption-options.component.spec.ts +++ b/libs/auth/src/angular/login-decryption-options/login-decryption-options.component.spec.ts @@ -107,7 +107,7 @@ describe("LoginDecryptionOptionsComponent", () => { email: mockEmail, name: "Test User", emailVerified: true, - creationDate: new Date().toISOString(), + creationDate: new Date(), }); platformUtilsService.getClientType.mockReturnValue(ClientType.Browser); deviceTrustService.getShouldTrustDevice.mockResolvedValue(true); From 2da44bb30009d2fdc048d7e019edd9b2bbdf0b27 Mon Sep 17 00:00:00 2001 From: Bryan Cunningham <bcunningham@bitwarden.com> Date: Fri, 26 Dec 2025 16:18:31 -0500 Subject: [PATCH 156/188] [CL-913] add new color palette to theme and tailwind config (#17998) * add new color palette to theme and tailwind config * updated docs color names * remove safelist changes * add missing accent colors to docs * updated color mappings * combined docs in colors.mdx and reference in Claude.md * add variables for white and black * updated docs * updated list rendering style * more specific docs instructions * revert to simpler colors docs reference * remove changes to claude.md * use rgb color variables to compose semantic --- libs/components/src/stories/colors.mdx | 865 +++++++++++++++++++++--- libs/components/src/tw-theme.css | 402 +++++++++++ libs/components/tailwind.config.base.js | 157 ++++- libs/components/tailwind.config.js | 5 + 4 files changed, 1315 insertions(+), 114 deletions(-) diff --git a/libs/components/src/stories/colors.mdx b/libs/components/src/stories/colors.mdx index ca9a97b9071..3cf3b46215c 100644 --- a/libs/components/src/stories/colors.mdx +++ b/libs/components/src/stories/colors.mdx @@ -2,127 +2,772 @@ import { Meta } from "@storybook/addon-docs/blocks"; <Meta title="Documentation/Colors" /> -export const Row = (name) => ( - <tr class="tw-h-16"> - <td class="!tw-border-none">{name}</td> - <td class={"tw-bg-" + name + " !tw-border-secondary-300"}></td> - </tr> -); +# Color System -export const Table = (args) => ( - <table class={"border tw-table-auto !tw-text-main " + args.class}> - <thead> - <tr> - <th class="tw-w-40">General usage</th> - <th class="tw-w-20"></th> - </tr> - </thead> - <tbody> - {Row("background")} - {Row("background-alt")} - {Row("background-alt2")} - {Row("background-alt3")} - {Row("background-alt4")} - </tbody> - <tbody> - {Row("primary-100")} - {Row("primary-300")} - {Row("primary-600")} - {Row("primary-700")} - </tbody> - <tbody> - {Row("secondary-100")} - {Row("secondary-300")} - {Row("secondary-500")} - {Row("secondary-600")} - {Row("secondary-700")} - </tbody> - <tbody> - {Row("success-100")} - {Row("success-600")} - {Row("success-700")} - </tbody> - <tbody> - {Row("danger-100")} - {Row("danger-600")} - {Row("danger-700")} - </tbody> - <tbody> - {Row("warning-100")} - {Row("warning-600")} - {Row("warning-700")} - </tbody> - <tbody> - {Row("info-100")} - {Row("info-600")} - {Row("info-700")} - </tbody> - <tbody> - {Row("notification-100")} - {Row("notification-600")} - </tbody> - <tbody> - {Row("illustration-outline")} - {Row("illustration-bg-primary")} - {Row("illustration-bg-secondary")} - {Row("illustration-bg-tertiary")} - {Row("illustration-tertiary")} - {Row("illustration-logo")} - </tbody> +Bitwarden uses a three-tier color token architecture: - <thead> - <tr> - <th>Text</th> - <th class="tw-w-20"></th> - </tr> - </thead> - <tbody> - {Row("text-main")} - {Row("text-muted")} - {Row("text-contrast")} - {Row("text-alt2")} - {Row("text-code")} - </tbody> +- **Primitive Colors** - Raw color values from the Figma design system +- **Semantic Tokens** - Meaningful names that reference primitives +- **Tailwind Utilities** - CSS classes for components - </table> -); +## Color Token Structure -<style> - {` -table { - border-spacing: 0.5rem; - border-collapse: separate !important; -} +### Primitive Colors (Hex format) -tr { - background: none !important; - border: none !important; -} +Location: `libs/components/src/tw-theme.css` -td, th { - color: inherit !important; -} +- 10 color families: `brand`, `gray`, `red`, `orange`, `yellow`, `green`, `pink`, `coral`, `teal`, + `purple` +- 11 shades each: `050`, `100`, `200`, `300`, `400`, `500`, `600`, `700`, `800`, `900`, `950` +- Format: `--color-{family}-{shade}` (e.g., `--color-brand-600`) -th { - border: none !important; -} - `} -</style> +### Semantic Foreground Tokens -# Colors +- **Neutral**: `fg-white`, `fg-dark`, `fg-contrast`, `fg-heading`, `fg-body`, `fg-body-subtle`, + `fg-disabled` +- **Brand**: `fg-brand-soft`, `fg-brand`, `fg-brand-strong` +- **Status**: `fg-success`, `fg-success-strong`, `fg-danger`, `fg-danger-strong`, `fg-warning`, + `fg-warning-strong`, `fg-sensitive` +- **Accent**: `fg-accent-primary`, `fg-accent-secondary`, `fg-accent-tertiary` (with `-soft` and + `-strong` variants) +- Format: `--color-fg-{name}` -Tailwind traditionally has a very large color palette. Bitwarden has their own more limited color -palette instead. +### Semantic Background Tokens -This has a couple of advantages: +- **Neutral**: `bg-white`, `bg-dark`, `bg-contrast`, `bg-contrast-strong`, `bg-primary`, + `bg-secondary`, `bg-tertiary`, `bg-quaternary`, `bg-gray`, `bg-disabled` +- **Brand**: `bg-brand-softer`, `bg-brand-soft`, `bg-brand-medium`, `bg-brand`, `bg-brand-strong` +- **Status**: `bg-success-soft`, `bg-success-medium`, `bg-success`, `bg-success-strong`, + `bg-danger-soft`, `bg-danger-medium`, `bg-danger`, `bg-danger-strong`, `bg-warning-soft`, + `bg-warning-medium`, `bg-warning`, `bg-warning-strong` +- **Accent**: `bg-accent-primary-soft`, `bg-accent-primary-medium`, `bg-accent-primary`, + `bg-accent-secondary-soft`, `bg-accent-secondary-medium`, `bg-accent-secondary`, + `bg-accent-tertiary-soft`, `bg-accent-tertiary-medium`, `bg-accent-tertiary` +- **Special**: `bg-hover`, `bg-overlay` +- Format: `--color-bg-{name}` -- Promotes consistency across the application. -- Easier to maintain and make adjustments. -- Allows us to support more than two themes light & dark, should it be needed. +### Semantic Border Tokens -Below are all the permited colors. Please consult design before considering adding a new color. +- **Neutral**: `border-muted`, `border-light`, `border-base`, `border-strong`, `border-buffer` +- **Brand**: `border-brand-soft`, `border-brand`, `border-brand-strong` +- **Status**: `border-success-soft`, `border-success`, `border-success-strong`, + `border-danger-soft`, `border-danger`, `border-danger-strong`, `border-warning-soft`, + `border-warning`, `border-warning-strong` +- **Accent**: `border-accent-primary-soft`, `border-accent-primary`, `border-accent-secondary-soft`, + `border-accent-secondary`, `border-accent-tertiary-soft`, `border-accent-tertiary` +- **Focus**: `border-focus` +- Format: `--color-border-{name}` -<div class="tw-flex tw-space-x-4"> - <Table /> - <Table class="theme_dark tw-bg-background" /> +## Semantic Color Tokens + +> **Note:** Due to Tailwind's utility naming and our semantic token structure, class names will +> appear repetitive (e.g., `tw-bg-bg-primary`). This repetition is intentional: +> +> - `tw-` = Tailwind prefix +> - `bg-` = Tailwind utility type (background) +> - `bg-primary` = Our semantic token name + +### Background Colors + +Use `tw-bg-bg-*` for background colors. These tokens automatically adapt to dark mode. + +export const Swatch = ({ name }) => { + const swatchClass = `tw-h-10 tw-w-10 tw-shrink-0 tw-rounded-lg tw-border tw-border-border-base tw-bg-${name}`; + return <div className={swatchClass} />; +}; + +export const BackgroundCard = ({ name, primitiveColor }) => { + const bgClass = `tw-flex tw-items-center tw-gap-3 tw-rounded-xl tw-p-4 tw-border tw-border-border-base tw-bg-bg-primary`; + const swatchClass = `tw-h-10 tw-w-10 tw-shrink-0 tw-rounded-lg tw-border tw-border-base tw-bg-bg-${name}`; + return ( + <div className={bgClass}> + <div className="tw-flex-1 tw-min-w-0"> + <div className="tw-font-mono tw-text-sm tw-font-semibold tw-text-fg-heading">bg-{name}</div> + <div className="tw-text-xs tw-text-fg-body-subtle tw-mt-0.5">({primitiveColor})</div> + </div> + <Swatch name={`bg-${name}`} /> + </div> + ); +}; + +<div class="sb-unstyled tw-grid tw-grid-cols-2 tw-gap-8 tw-my-6"> + <div class="tw-bg-bg-primary tw-p-6 tw-rounded-lg tw-border tw-border-border-base"> + <h3 class="sb-unstyled sb-unstyled tw-mb-6 tw-text-xl tw-font-bold tw-text-fg-heading">Light mode</h3> + + <div class="sb-unstyled tw-mb-6"> + <h4 class="sb-unstyled sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Neutral</h4> + <div class="tw-grid tw-grid-cols-1 tw-gap-2"> + <BackgroundCard name="white" primitiveColor="white" /> + <BackgroundCard name="dark" primitiveColor="gray-800" /> + <BackgroundCard name="contrast" primitiveColor="gray-800" /> + <BackgroundCard name="contrast-strong" primitiveColor="gray-950" /> + <BackgroundCard name="primary" primitiveColor="white" /> + <BackgroundCard name="secondary" primitiveColor="gray-050" /> + <BackgroundCard name="tertiary" primitiveColor="gray-050" /> + <BackgroundCard name="quaternary" primitiveColor="gray-200" /> + <BackgroundCard name="gray" primitiveColor="gray-300" /> + <BackgroundCard name="disabled" primitiveColor="gray-100" /> + </div> + </div> + + <div class="sb-unstyled tw-mb-6"> + <h4 class="sb-unstyled sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Brand</h4> + <div class="tw-grid tw-grid-cols-1 tw-gap-2"> + <BackgroundCard name="brand-softer" primitiveColor="brand-050" /> + <BackgroundCard name="brand-soft" primitiveColor="brand-100" /> + <BackgroundCard name="brand-medium" primitiveColor="brand-200" /> + <BackgroundCard name="brand" primitiveColor="brand-700" /> + <BackgroundCard name="brand-strong" primitiveColor="brand-800" /> + </div> + </div> + + <div class="sb-unstyled tw-mb-6"> + <h4 class="sb-unstyled sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Status</h4> + <div class="tw-grid tw-grid-cols-1 tw-gap-2"> + <BackgroundCard name="success-soft" primitiveColor="green-050" /> + <BackgroundCard name="success-medium" primitiveColor="green-100" /> + <BackgroundCard name="success" primitiveColor="green-600" /> + <BackgroundCard name="success-strong" primitiveColor="green-700" /> + <BackgroundCard name="danger-soft" primitiveColor="red-050" /> + <BackgroundCard name="danger-medium" primitiveColor="red-100" /> + <BackgroundCard name="danger" primitiveColor="red-600" /> + <BackgroundCard name="danger-strong" primitiveColor="red-700" /> + <BackgroundCard name="warning-soft" primitiveColor="orange-050" /> + <BackgroundCard name="warning-medium" primitiveColor="orange-100" /> + <BackgroundCard name="warning" primitiveColor="orange-600" /> + <BackgroundCard name="warning-strong" primitiveColor="orange-700" /> + </div> + </div> + + <div class="sb-unstyled tw-mb-6"> + <h4 class="sb-unstyled sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Accent</h4> + <div class="tw-grid tw-grid-cols-1 tw-gap-2"> + <BackgroundCard name="accent-primary-soft" primitiveColor="teal-050" /> + <BackgroundCard name="accent-primary-medium" primitiveColor="teal-100" /> + <BackgroundCard name="accent-primary" primitiveColor="teal-400" /> + <BackgroundCard name="accent-secondary-soft" primitiveColor="coral-050" /> + <BackgroundCard name="accent-secondary-medium" primitiveColor="coral-100" /> + <BackgroundCard name="accent-secondary" primitiveColor="coral-400" /> + <BackgroundCard name="accent-tertiary-soft" primitiveColor="purple-050" /> + <BackgroundCard name="accent-tertiary-medium" primitiveColor="purple-100" /> + <BackgroundCard name="accent-tertiary" primitiveColor="purple-600" /> + </div> + </div> + + <div class="sb-unstyled tw-mb-6"> + <h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Hover</h4> + <div class="tw-grid tw-grid-cols-1 tw-gap-2"> + <BackgroundCard name="hover" primitiveColor="rgba" /> + </div> + </div> + + <div class="sb-unstyled tw-mb-6"> + <h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Overlay</h4> + <div class="tw-grid tw-grid-cols-1 tw-gap-2"> + <BackgroundCard name="overlay" primitiveColor="rgba" /> + </div> + </div> + + </div> + + <div class="theme_dark tw-bg-bg-primary tw-p-6 tw-rounded-lg"> + <h3 class="sb-unstyled sb-unstyled tw-mb-6 tw-text-xl tw-font-bold tw-text-fg-heading">Dark mode</h3> + + <div class="sb-unstyled tw-mb-6"> + <h4 class="sb-unstyled sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Neutral</h4> + <div class="tw-grid tw-grid-cols-1 tw-gap-2"> + <BackgroundCard name="white" primitiveColor="white" /> + <BackgroundCard name="dark" primitiveColor="gray-800" /> + <BackgroundCard name="contrast" primitiveColor="gray-050" /> + <BackgroundCard name="contrast-strong" primitiveColor="gray-950" /> + <BackgroundCard name="primary" primitiveColor="gray-900" /> + <BackgroundCard name="secondary" primitiveColor="gray-800" /> + <BackgroundCard name="tertiary" primitiveColor="gray-950" /> + <BackgroundCard name="quaternary" primitiveColor="gray-950" /> + <BackgroundCard name="gray" primitiveColor="gray-600" /> + <BackgroundCard name="disabled" primitiveColor="gray-950" /> + </div> + </div> + + <div class="sb-unstyled tw-mb-6"> + <h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Brand</h4> + <div class="tw-grid tw-grid-cols-1 tw-gap-2"> + <BackgroundCard name="brand-softer" primitiveColor="brand-950" /> + <BackgroundCard name="brand-soft" primitiveColor="brand-900" /> + <BackgroundCard name="brand-medium" primitiveColor="brand-800" /> + <BackgroundCard name="brand" primitiveColor="brand-400" /> + <BackgroundCard name="brand-strong" primitiveColor="brand-300" /> + </div> + </div> + + <div class="sb-unstyled tw-mb-6"> + <h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Status</h4> + <div class="tw-grid tw-grid-cols-1 tw-gap-2"> + <BackgroundCard name="success-soft" primitiveColor="green-950" /> + <BackgroundCard name="success-medium" primitiveColor="green-900" /> + <BackgroundCard name="success" primitiveColor="green-400" /> + <BackgroundCard name="success-strong" primitiveColor="green-300" /> + <BackgroundCard name="danger-soft" primitiveColor="red-950" /> + <BackgroundCard name="danger-medium" primitiveColor="red-900" /> + <BackgroundCard name="danger" primitiveColor="red-400" /> + <BackgroundCard name="danger-strong" primitiveColor="red-300" /> + <BackgroundCard name="warning-soft" primitiveColor="orange-950" /> + <BackgroundCard name="warning-medium" primitiveColor="orange-900" /> + <BackgroundCard name="warning" primitiveColor="orange-400" /> + <BackgroundCard name="warning-strong" primitiveColor="orange-300" /> + </div> + </div> + + <div class="sb-unstyled tw-mb-6"> + <h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Accent</h4> + <div class="tw-grid tw-grid-cols-1 tw-gap-2"> + <BackgroundCard name="accent-primary-soft" primitiveColor="teal-950" /> + <BackgroundCard name="accent-primary-medium" primitiveColor="teal-900" /> + <BackgroundCard name="accent-primary" primitiveColor="teal-400" /> + <BackgroundCard name="accent-secondary-soft" primitiveColor="coral-950" /> + <BackgroundCard name="accent-secondary-medium" primitiveColor="coral-900" /> + <BackgroundCard name="accent-secondary" primitiveColor="coral-400" /> + <BackgroundCard name="accent-tertiary-soft" primitiveColor="purple-950" /> + <BackgroundCard name="accent-tertiary-medium" primitiveColor="purple-900" /> + <BackgroundCard name="accent-tertiary" primitiveColor="purple-600" /> + </div> + </div> + + <div class="sb-unstyled tw-mb-6"> + <h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Hover</h4> + <div class="tw-grid tw-grid-cols-1 tw-gap-2"> + <BackgroundCard name="hover" primitiveColor="rgba" /> + </div> + </div> + + <div class="sb-unstyled tw-mb-6"> + <h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Overlay</h4> + <div class="tw-grid tw-grid-cols-1 tw-gap-2"> + <BackgroundCard name="overlay" primitiveColor="rgba" /> + </div> + </div> + + </div> +</div> + +--- + +### Foreground Colors + +Use `tw-text-fg-*` for text colors. These tokens automatically adapt to dark mode. + +export const ForegroundCard = ({ name, primitiveColor }) => { + const textClass = `tw-text-fg-${name} tw-text-2xl tw-font-bold tw-shrink-0`; + return ( + <div className="tw-flex tw-items-center tw-gap-3 tw-rounded-xl tw-p-4 tw-border tw-border-border-base tw-bg-bg-primary"> + <div className="tw-flex-1 tw-min-w-0"> + <div className="tw-font-mono tw-text-sm tw-font-semibold tw-text-fg-heading">fg-{name}</div> + <div className="tw-text-xs tw-text-fg-body-subtle tw-mt-0.5">({primitiveColor})</div> + </div> + <Swatch name={`fg-${name}`} /> + </div> + ); +}; + +<div class="sb-unstyled tw-grid tw-grid-cols-2 tw-gap-8 tw-my-6"> + <div class="tw-bg-bg-primary tw-p-6 tw-rounded-lg tw-border tw-border-border-base"> + <h3 class="sb-unstyled tw-mb-6 tw-text-xl tw-font-bold tw-text-fg-heading">Light mode</h3> + + <div class="sb-unstyled tw-mb-6"> + <h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Neutral</h4> + <div class="tw-grid tw-grid-cols-1 tw-gap-2"> + <ForegroundCard name="white" primitiveColor="#ffffff" /> + <ForegroundCard name="dark" primitiveColor="gray-900" /> + <ForegroundCard name="contrast" primitiveColor="white" /> + <ForegroundCard name="heading" primitiveColor="gray-900" /> + <ForegroundCard name="body" primitiveColor="gray-600" /> + <ForegroundCard name="body-subtle" primitiveColor="gray-500" /> + <ForegroundCard name="disabled" primitiveColor="gray-400" /> + </div> + </div> + + <div class="sb-unstyled tw-mb-6"> + <h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Brand</h4> + <div class="tw-grid tw-grid-cols-1 tw-gap-2"> + <ForegroundCard name="brand-soft" primitiveColor="brand-200" /> + <ForegroundCard name="brand" primitiveColor="brand-700" /> + <ForegroundCard name="brand-strong" primitiveColor="brand-900" /> + </div> + </div> + + <div class="sb-unstyled tw-mb-6"> + <h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Status</h4> + <div class="tw-grid tw-grid-cols-1 tw-gap-2"> + <ForegroundCard name="success" primitiveColor="green-700" /> + <ForegroundCard name="success-strong" primitiveColor="green-900" /> + <ForegroundCard name="danger" primitiveColor="red-700" /> + <ForegroundCard name="danger-strong" primitiveColor="red-900" /> + <ForegroundCard name="warning" primitiveColor="orange-600" /> + <ForegroundCard name="warning-strong" primitiveColor="orange-900" /> + <ForegroundCard name="sensitive" primitiveColor="pink-600" /> + </div> + </div> + + <div class="sb-unstyled tw-mb-6"> + <h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Accent</h4> + <div class="tw-grid tw-grid-cols-1 tw-gap-2"> + <ForegroundCard name="accent-primary-soft" primitiveColor="teal-200" /> + <ForegroundCard name="accent-primary" primitiveColor="teal-400" /> + <ForegroundCard name="accent-primary-strong" primitiveColor="teal-800" /> + <ForegroundCard name="accent-secondary-soft" primitiveColor="coral-200" /> + <ForegroundCard name="accent-secondary" primitiveColor="coral-400" /> + <ForegroundCard name="accent-secondary-strong" primitiveColor="coral-900" /> + <ForegroundCard name="accent-tertiary-soft" primitiveColor="purple-200" /> + <ForegroundCard name="accent-tertiary" primitiveColor="purple-700" /> + <ForegroundCard name="accent-tertiary-strong" primitiveColor="purple-900" /> + </div> + </div> + + </div> + + <div class="theme_dark tw-bg-bg-primary tw-p-6 tw-rounded-lg"> + <h3 class="sb-unstyled tw-mb-6 tw-text-xl tw-font-bold tw-text-fg-heading">Dark mode</h3> + + <div class="sb-unstyled tw-mb-6"> + <h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Neutral</h4> + <div class="tw-grid tw-grid-cols-1 tw-gap-2"> + <ForegroundCard name="white" primitiveColor="#ffffff" /> + <ForegroundCard name="dark" primitiveColor="gray-900" /> + <ForegroundCard name="contrast" primitiveColor="gray-900" /> + <ForegroundCard name="heading" primitiveColor="gray-050" /> + <ForegroundCard name="body" primitiveColor="gray-200" /> + <ForegroundCard name="body-subtle" primitiveColor="gray-400" /> + <ForegroundCard name="disabled" primitiveColor="gray-600" /> + </div> + </div> + + <div class="sb-unstyled tw-mb-6"> + <h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Brand</h4> + <div class="tw-grid tw-grid-cols-1 tw-gap-2"> + <ForegroundCard name="brand-soft" primitiveColor="brand-500" /> + <ForegroundCard name="brand" primitiveColor="brand-400" /> + <ForegroundCard name="brand-strong" primitiveColor="brand-200" /> + </div> + </div> + + <div class="sb-unstyled tw-mb-6"> + <h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Status</h4> + <div class="tw-grid tw-grid-cols-1 tw-gap-2"> + <ForegroundCard name="success" primitiveColor="green-400" /> + <ForegroundCard name="success-strong" primitiveColor="green-100" /> + <ForegroundCard name="danger" primitiveColor="red-400" /> + <ForegroundCard name="danger-strong" primitiveColor="red-100" /> + <ForegroundCard name="warning" primitiveColor="orange-400" /> + <ForegroundCard name="warning-strong" primitiveColor="orange-100" /> + <ForegroundCard name="sensitive" primitiveColor="pink-300" /> + </div> + </div> + + <div class="sb-unstyled tw-mb-6"> + <h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Accent</h4> + <div class="tw-grid tw-grid-cols-1 tw-gap-2"> + <ForegroundCard name="accent-primary-soft" primitiveColor="teal-400" /> + <ForegroundCard name="accent-primary" primitiveColor="teal-300" /> + <ForegroundCard name="accent-primary-strong" primitiveColor="teal-100" /> + <ForegroundCard name="accent-secondary-soft" primitiveColor="coral-500" /> + <ForegroundCard name="accent-secondary" primitiveColor="coral-400" /> + <ForegroundCard name="accent-secondary-strong" primitiveColor="coral-100" /> + <ForegroundCard name="accent-tertiary-soft" primitiveColor="purple-500" /> + <ForegroundCard name="accent-tertiary" primitiveColor="purple-400" /> + <ForegroundCard name="accent-tertiary-strong" primitiveColor="purple-100" /> + </div> + </div> + + </div> +</div> + +--- + +### Border Colors + +Use `tw-border-border-*` for border colors. These tokens automatically adapt to dark mode. + +export const BorderCard = ({ name, primitiveColor }) => { + return ( + <div className="tw-flex tw-items-center tw-gap-3 tw-rounded-xl tw-p-4 tw-border tw-border-border-base tw-bg-bg-primary"> + <div className="tw-flex-1 tw-min-w-0"> + <div className="tw-font-mono tw-text-sm tw-font-semibold tw-text-fg-heading"> + border-{name} + </div> + <div className="tw-text-xs tw-text-fg-body-subtle tw-mt-0.5">({primitiveColor})</div> + </div> + <Swatch name={`border-${name}`} /> + </div> + ); +}; + +<div class="sb-unstyled tw-grid tw-grid-cols-2 tw-gap-8 tw-my-6"> + <div class="tw-bg-bg-primary tw-p-6 tw-rounded-lg tw-border tw-border-border-base"> + <h3 class="sb-unstyled tw-mb-6 tw-text-xl tw-font-bold tw-text-fg-heading">Light mode</h3> + + <div class="sb-unstyled tw-mb-6"> + <h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Neutral</h4> + <div class="tw-grid tw-grid-cols-1 tw-gap-2"> + <BorderCard name="muted" primitiveColor="gray-100" /> + <BorderCard name="light" primitiveColor="gray-200" /> + <BorderCard name="base" primitiveColor="gray-200" /> + <BorderCard name="strong" primitiveColor="gray-300" /> + <BorderCard name="buffer" primitiveColor="gray-100" /> + </div> + </div> + + <div class="sb-unstyled tw-mb-6"> + <h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Brand</h4> + <div class="tw-grid tw-grid-cols-1 tw-gap-2"> + <BorderCard name="brand-soft" primitiveColor="brand-200" /> + <BorderCard name="brand" primitiveColor="brand-700" /> + <BorderCard name="brand-strong" primitiveColor="brand-900" /> + </div> + </div> + + <div class="sb-unstyled tw-mb-6"> + <h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Status</h4> + <div class="tw-grid tw-grid-cols-1 tw-gap-2"> + <BorderCard name="success-soft" primitiveColor="green-200" /> + <BorderCard name="success" primitiveColor="green-700" /> + <BorderCard name="success-strong" primitiveColor="green-900" /> + <BorderCard name="danger-soft" primitiveColor="red-200" /> + <BorderCard name="danger" primitiveColor="red-700" /> + <BorderCard name="danger-strong" primitiveColor="red-900" /> + <BorderCard name="warning-soft" primitiveColor="orange-200" /> + <BorderCard name="warning" primitiveColor="orange-600" /> + <BorderCard name="warning-strong" primitiveColor="orange-900" /> + </div> + </div> + + <div class="sb-unstyled tw-mb-6"> + <h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Accent</h4> + <div class="tw-grid tw-grid-cols-1 tw-gap-2"> + <BorderCard name="accent-primary-soft" primitiveColor="teal-200" /> + <BorderCard name="accent-primary" primitiveColor="teal-600" /> + <BorderCard name="accent-secondary-soft" primitiveColor="coral-200" /> + <BorderCard name="accent-secondary" primitiveColor="coral-600" /> + <BorderCard name="accent-tertiary-soft" primitiveColor="purple-200" /> + <BorderCard name="accent-tertiary" primitiveColor="purple-600" /> + </div> + </div> + + <div class="sb-unstyled tw-mb-6"> + <h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Focus</h4> + <div class="tw-grid tw-grid-cols-1 tw-gap-2"> + <BorderCard name="focus" primitiveColor="#000000" /> + </div> + </div> + + </div> + + <div class="theme_dark tw-bg-bg-primary tw-p-6 tw-rounded-lg"> + <h3 class="sb-unstyled tw-mb-6 tw-text-xl tw-font-bold tw-text-fg-heading">Dark mode</h3> + + <div class="sb-unstyled tw-mb-6"> + <h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Neutral</h4> + <div class="tw-grid tw-grid-cols-1 tw-gap-2"> + <BorderCard name="muted" primitiveColor="gray-800" /> + <BorderCard name="light" primitiveColor="gray-700" /> + <BorderCard name="base" primitiveColor="gray-700" /> + <BorderCard name="strong" primitiveColor="gray-600" /> + <BorderCard name="buffer" primitiveColor="gray-900" /> + </div> + </div> + + <div class="sb-unstyled tw-mb-6"> + <h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Brand</h4> + <div class="tw-grid tw-grid-cols-1 tw-gap-2"> + <BorderCard name="brand-soft" primitiveColor="brand-800" /> + <BorderCard name="brand" primitiveColor="brand-700" /> + <BorderCard name="brand-strong" primitiveColor="brand-600" /> + </div> + </div> + + <div class="sb-unstyled tw-mb-6"> + <h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Status</h4> + <div class="tw-grid tw-grid-cols-1 tw-gap-2"> + <BorderCard name="success-soft" primitiveColor="green-800" /> + <BorderCard name="success" primitiveColor="green-400" /> + <BorderCard name="success-strong" primitiveColor="green-200" /> + <BorderCard name="danger-soft" primitiveColor="red-800" /> + <BorderCard name="danger" primitiveColor="red-400" /> + <BorderCard name="danger-strong" primitiveColor="red-200" /> + <BorderCard name="warning-soft" primitiveColor="orange-800" /> + <BorderCard name="warning" primitiveColor="orange-400" /> + <BorderCard name="warning-strong" primitiveColor="orange-200" /> + </div> + </div> + + <div class="sb-unstyled tw-mb-6"> + <h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Accent</h4> + <div class="tw-grid tw-grid-cols-1 tw-gap-2"> + <BorderCard name="accent-primary-soft" primitiveColor="teal-800" /> + <BorderCard name="accent-primary" primitiveColor="teal-600" /> + <BorderCard name="accent-secondary-soft" primitiveColor="coral-800" /> + <BorderCard name="accent-secondary" primitiveColor="coral-500" /> + <BorderCard name="accent-tertiary-soft" primitiveColor="purple-800" /> + <BorderCard name="accent-tertiary" primitiveColor="purple-500" /> + </div> + </div> + + <div class="sb-unstyled tw-mb-6"> + <h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Focus</h4> + <div class="tw-grid tw-grid-cols-1 tw-gap-2"> + <BorderCard name="focus" primitiveColor="#ffffff" /> + </div> + </div> + + </div> +</div> + +--- + +## Usage Guidelines + +### ✅ DO - Use semantic tokens via Tailwind + +```html +<!-- Text colors --> +<h1 class="tw-text-fg-heading">Heading text</h1> +<p class="tw-text-fg-body">Body text</p> +<button class="tw-text-fg-brand">Brand action</button> +<span class="tw-text-fg-danger">Error message</span> + +<!-- Background colors --> +<div class="tw-bg-bg-primary">Primary background</div> +<div class="tw-bg-bg-secondary">Secondary background</div> +<button class="tw-bg-bg-brand tw-text-fg-white">Brand button</button> +<div class="tw-bg-bg-danger-soft tw-text-fg-danger">Danger alert</div> + +<!-- Border colors --> +<div class="tw-border tw-border-border-base">Base border</div> +<input class="tw-border tw-border-border-light focus:tw-border-border-focus" /> +<div class="tw-border-2 tw-border-border-brand">Brand border</div> +<button class="tw-border tw-border-border-danger">Danger border</button> + +<!-- Combined examples --> +<div + class="tw-bg-bg-success-soft tw-text-fg-success tw-border tw-border-border-success-soft tw-rounded tw-p-4" +> + Success alert with matching colors +</div> + +<!-- Hover states --> +<div class="hover:tw-bg-bg-hover">Hover effect</div> + +<!-- Overlays --> +<div class="tw-bg-bg-overlay">Modal overlay</div> +``` + +### ❌ DON'T - Use primitive colors directly + +```html +<!-- Bad: These Tailwind classes don't exist (primitives not exposed) --> +<p class="tw-text-brand-900">Text</p> +<div class="tw-bg-brand-600">Background</div> + +<!-- Bad: Using primitives with Tailwind bracket notation --> +<p class="tw-text-[var(--color-brand-600)]">Text</p> +<div class="tw-bg-[var(--color-success-700)]">Background</div> + +<!-- Bad: Using primitive CSS variables bypasses the semantic layer --> +<span style="color: var(--color-brand-600)">Text</span> +<div style="background: var(--color-success-700)">Background</div> +``` + +**Why this is wrong:** Primitives aren't semantic and may change. Always use semantic tokens like +`tw-text-fg-brand`, `tw-bg-success`, etc. + +--- + +## Dark Mode + +- Semantic tokens automatically adapt to dark mode via `.theme_dark` class +- No component changes needed when theme switches +- The same semantic token name works in both light and dark themes +- All color values are automatically swapped based on the active theme + +--- + +## Migration Strategy + +- **New components:** Use semantic tokens (`fg-*`, `bg-*`, `border-*`) exclusively +- **Existing components:** Keep legacy tokens until refactoring +- **When refactoring:** Replace legacy tokens with semantic equivalents + +--- + +## Legacy Colors + +**Legacy colors (RGB format)** still exist for backwards compatibility: + +- `primary-*`, `secondary-*`, `success-*`, `danger-*`, `warning-*`, etc. +- Use these only when updating existing components +- Migrate to new semantic tokens when refactoring + +The following legacy colors are displayed below with both light and dark mode values: + +export const LegacyCard = ({ name }) => { + return ( + <div className="tw-flex tw-items-center tw-gap-3 tw-rounded-xl tw-p-4 tw-border tw-border-border-base tw-bg-bg-primary"> + <div className="tw-flex-1 tw-min-w-0"> + <div className="tw-font-mono tw-text-sm tw-font-semibold tw-text-fg-heading">{name}</div> + <div className="tw-text-xs tw-text-fg-body-subtle tw-mt-0.5">(legacy RGB format)</div> + </div> + <Swatch name={name} /> + </div> + ); +}; + +<div class="tw-grid tw-grid-cols-2 tw-gap-8"> + <div class="tw-bg-bg-primary tw-p-6 tw-rounded-lg"> + <h3 class="sb-unstyled tw-mb-6 tw-text-xl tw-font-bold tw-text-fg-heading">Light mode</h3> + + <div class="sb-unstyled tw-mb-6"> + <h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">General</h4> + <div class="tw-grid tw-grid-cols-1 tw-gap-2"> + <LegacyCard name="background" /> + <LegacyCard name="background-alt" /> + <LegacyCard name="background-alt2" /> + <LegacyCard name="background-alt3" /> + <LegacyCard name="background-alt4" /> + </div> + </div> + + <div class="sb-unstyled tw-mb-6"> + <h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Primary</h4> + <div class="tw-grid tw-grid-cols-1 tw-gap-2"> + <LegacyCard name="primary-100" /> + <LegacyCard name="primary-300" /> + <LegacyCard name="primary-600" /> + <LegacyCard name="primary-700" /> + </div> + </div> + + <div class="sb-unstyled tw-mb-6"> + <h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading"> + Secondary + </h4> + <div class="tw-grid tw-grid-cols-1 tw-gap-2"> + <LegacyCard name="secondary-100" /> + <LegacyCard name="secondary-300" /> + <LegacyCard name="secondary-500" /> + <LegacyCard name="secondary-600" /> + <LegacyCard name="secondary-700" /> + </div> + </div> + + <div class="sb-unstyled tw-mb-6"> + <h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Success</h4> + <div class="tw-grid tw-grid-cols-1 tw-gap-2"> + <LegacyCard name="success-100" /> + <LegacyCard name="success-600" /> + <LegacyCard name="success-700" /> + </div> + </div> + + <div class="sb-unstyled tw-mb-6"> + <h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Danger</h4> + <div class="tw-grid tw-grid-cols-1 tw-gap-2"> + <LegacyCard name="danger-100" /> + <LegacyCard name="danger-600" /> + <LegacyCard name="danger-700" /> + </div> + </div> + + <div class="sb-unstyled tw-mb-6"> + <h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Warning</h4> + <div class="tw-grid tw-grid-cols-1 tw-gap-2"> + <LegacyCard name="warning-100" /> + <LegacyCard name="warning-600" /> + <LegacyCard name="warning-700" /> + </div> + </div> + + <div class="sb-unstyled tw-mb-6"> + <h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Info</h4> + <div class="tw-grid tw-grid-cols-1 tw-gap-2"> + <LegacyCard name="info-100" /> + <LegacyCard name="info-600" /> + <LegacyCard name="info-700" /> + </div> + </div> + + </div> + + <div class="theme_dark tw-bg-bg-primary tw-p-6 tw-rounded-lg"> + <h3 class="sb-unstyled tw-mb-6 tw-text-xl tw-font-bold tw-text-fg-heading">Dark mode</h3> + + <div class="sb-unstyled tw-mb-6"> + <h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">General</h4> + <div class="tw-grid tw-grid-cols-1 tw-gap-2"> + <LegacyCard name="background" /> + <LegacyCard name="background-alt" /> + <LegacyCard name="background-alt2" /> + <LegacyCard name="background-alt3" /> + <LegacyCard name="background-alt4" /> + </div> + </div> + + <div class="sb-unstyled tw-mb-6"> + <h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Primary</h4> + <div class="tw-grid tw-grid-cols-1 tw-gap-2"> + <LegacyCard name="primary-100" /> + <LegacyCard name="primary-300" /> + <LegacyCard name="primary-600" /> + <LegacyCard name="primary-700" /> + </div> + </div> + + <div class="sb-unstyled tw-mb-6"> + <h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading"> + Secondary + </h4> + <div class="tw-grid tw-grid-cols-1 tw-gap-2"> + <LegacyCard name="secondary-100" /> + <LegacyCard name="secondary-300" /> + <LegacyCard name="secondary-500" /> + <LegacyCard name="secondary-600" /> + <LegacyCard name="secondary-700" /> + </div> + </div> + + <div class="sb-unstyled tw-mb-6"> + <h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Success</h4> + <div class="tw-grid tw-grid-cols-1 tw-gap-2"> + <LegacyCard name="success-100" /> + <LegacyCard name="success-600" /> + <LegacyCard name="success-700" /> + </div> + </div> + + <div class="sb-unstyled tw-mb-6"> + <h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Danger</h4> + <div class="tw-grid tw-grid-cols-1 tw-gap-2"> + <LegacyCard name="danger-100" /> + <LegacyCard name="danger-600" /> + <LegacyCard name="danger-700" /> + </div> + </div> + + <div class="sb-unstyled tw-mb-6"> + <h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Warning</h4> + <div class="tw-grid tw-grid-cols-1 tw-gap-2"> + <LegacyCard name="warning-100" /> + <LegacyCard name="warning-600" /> + <LegacyCard name="warning-700" /> + </div> + </div> + + <div class="sb-unstyled tw-mb-6"> + <h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Info</h4> + <div class="tw-grid tw-grid-cols-1 tw-gap-2"> + <LegacyCard name="info-100" /> + <LegacyCard name="info-600" /> + <LegacyCard name="info-700" /> + </div> + </div> + + </div> </div> diff --git a/libs/components/src/tw-theme.css b/libs/components/src/tw-theme.css index f0e55ddd9e1..757859985d6 100644 --- a/libs/components/src/tw-theme.css +++ b/libs/components/src/tw-theme.css @@ -13,6 +13,12 @@ @tailwind utilities; :root { + /* ======================================== + * LEGACY COLORS (RGB format) + * These are the original colors used throughout the app. + * Use these for existing components until migration is complete. + * ======================================== */ + --color-transparent-hover: rgb(0 0 0 / 0.02); --color-shadow: 168 179 200; @@ -74,6 +80,279 @@ --color-illustration-bg-tertiary: 255 255 255; --color-illustration-tertiary: 255 191 0; --color-illustration-logo: 23 93 220; + + /* ======================================== + * NEW COLOR PALETTE (Hex format) + * These colors are from the new Figma design system. + * Use these for new components and features. + * Format: --color-{family}-{shade} where shade ranges from 050 to 950 + * ======================================== */ + + /* Brand Colors */ + --color-brand-050: #eef6ff; + --color-brand-100: #dbeafe; + --color-brand-200: #bedbff; + --color-brand-300: #8ec5ff; + --color-brand-400: #6baefa; + --color-brand-500: #418bfb; + --color-brand-600: #2a70f4; + --color-brand-700: #175ddc; + --color-brand-800: #0d43af; + --color-brand-900: #0c3276; + --color-brand-950: #162455; + + /* Gray Colors */ + --color-gray-050: #f9fafb; + --color-gray-100: #f3f4f6; + --color-gray-200: #e5e7eb; + --color-gray-300: #d1d5dc; + --color-gray-400: #99a1af; + --color-gray-500: #6a7282; + --color-gray-600: #4a5565; + --color-gray-700: #333e4f; + --color-gray-800: #1e2939; + --color-gray-900: #101828; + --color-gray-950: #070b18; + --color-gray-950-rgb: 7, 11, 24; + + /* Red Colors */ + --color-red-050: #fef2f2; + --color-red-100: #ffe2e2; + --color-red-200: #ffc9c9; + --color-red-300: #ffa2a2; + --color-red-400: #ff6467; + --color-red-500: #fb2c36; + --color-red-600: #e7000b; + --color-red-700: #c10007; + --color-red-800: #9f0712; + --color-red-900: #791112; + --color-red-950: #460809; + + /* Orange Colors */ + --color-orange-050: #fff8f1; + --color-orange-100: #feecdc; + --color-orange-200: #fcd9bd; + --color-orange-300: #fdba8c; + --color-orange-400: #ff8a4c; + --color-orange-500: #ff5a1f; + --color-orange-600: #d03801; + --color-orange-700: #b43403; + --color-orange-800: #8a2c0d; + --color-orange-900: #70240b; + --color-orange-950: #441306; + + /* Yellow Colors */ + --color-yellow-050: #fefce8; + --color-yellow-100: #fef9c2; + --color-yellow-200: #fff085; + --color-yellow-300: #ffdf20; + --color-yellow-400: #fdc700; + --color-yellow-500: #f0b100; + --color-yellow-600: #d08700; + --color-yellow-700: #a65f00; + --color-yellow-800: #894b00; + --color-yellow-900: #733e0a; + --color-yellow-950: #432004; + + /* Green Colors */ + --color-green-050: #f0fdf4; + --color-green-100: #dcfce7; + --color-green-200: #b9f8cf; + --color-green-300: #7bf1a8; + --color-green-400: #18dc7a; + --color-green-500: #0abf52; + --color-green-600: #00a63e; + --color-green-700: #008236; + --color-green-800: #016630; + --color-green-900: #0d542b; + --color-green-950: #032e15; + + /* Pink Colors */ + --color-pink-050: #fdf2f8; + --color-pink-100: #fce7f3; + --color-pink-200: #fccee8; + --color-pink-300: #fda5d5; + --color-pink-400: #fb64b6; + --color-pink-500: #f6339a; + --color-pink-600: #e60076; + --color-pink-700: #c6005c; + --color-pink-800: #a3004c; + --color-pink-900: #861043; + --color-pink-950: #510424; + + /* Coral Colors */ + --color-coral-050: #fff2f0; + --color-coral-100: #ffe0dc; + --color-coral-200: #ffc1b9; + --color-coral-300: #ff9585; + --color-coral-400: #ff6550; + --color-coral-500: #ff4026; + --color-coral-600: #e11f05; + --color-coral-700: #c71800; + --color-coral-800: #a81400; + --color-coral-900: #7e0f00; + --color-coral-950: #4d0900; + + /* Teal Colors */ + --color-teal-050: #ecfeff; + --color-teal-100: #cefafe; + --color-teal-200: #a2f4fd; + --color-teal-300: #70ecf5; + --color-teal-400: #2cdde9; + --color-teal-500: #00c5db; + --color-teal-600: #009cb8; + --color-teal-700: #007c95; + --color-teal-800: #006278; + --color-teal-900: #0f495c; + --color-teal-950: #042e3e; + + /* Purple Colors */ + --color-purple-050: #faf5ff; + --color-purple-100: #f3e8ff; + --color-purple-200: #e9d4ff; + --color-purple-300: #dab2ff; + --color-purple-400: #c27aff; + --color-purple-500: #ad46ff; + --color-purple-600: #9810fa; + --color-purple-700: #8200db; + --color-purple-800: #6e11b0; + --color-purple-900: #59168b; + --color-purple-950: #3c0366; + + /* White and Black */ + --color-white: #ffffff; + --color-white-rgb: 255, 255, 255; + --color-black: #000000; + + /* ======================================== + * SEMANTIC FOREGROUND COLORS (Light Mode) + * These are the tokens that should be exposed to Tailwind + * They reference the primitive colors above + * ======================================== */ + + /* Neutral Foreground */ + --color-fg-white: var(--color-white); + --color-fg-dark: var(--color-gray-900); + --color-fg-contrast: var(--color-white); + --color-fg-heading: var(--color-gray-900); + --color-fg-body: var(--color-gray-600); + --color-fg-body-subtle: var(--color-gray-500); + --color-fg-disabled: var(--color-gray-400); + + /* Brand Foreground */ + --color-fg-brand-soft: var(--color-brand-200); + --color-fg-brand: var(--color-brand-700); + --color-fg-brand-strong: var(--color-brand-900); + + /* Status Foreground */ + --color-fg-success: var(--color-green-700); + --color-fg-success-strong: var(--color-green-900); + --color-fg-danger: var(--color-red-700); + --color-fg-danger-strong: var(--color-red-900); + --color-fg-warning: var(--color-orange-600); + --color-fg-warning-strong: var(--color-orange-900); + --color-fg-sensitive: var(--color-pink-600); + + /* Accent Foreground */ + --color-fg-accent-primary-soft: var(--color-teal-200); + --color-fg-accent-primary: var(--color-teal-400); + --color-fg-accent-primary-strong: var(--color-teal-800); + --color-fg-accent-secondary-soft: var(--color-coral-200); + --color-fg-accent-secondary: var(--color-coral-400); + --color-fg-accent-secondary-strong: var(--color-coral-900); + --color-fg-accent-tertiary-soft: var(--color-purple-200); + --color-fg-accent-tertiary: var(--color-purple-700); + --color-fg-accent-tertiary-strong: var(--color-purple-900); + + /* ======================================== + * SEMANTIC BACKGROUND COLORS (Light Mode) + * ======================================== */ + + /* Neutral Background */ + --color-bg-white: var(--color-white); + --color-bg-dark: var(--color-gray-800); + --color-bg-contrast: var(--color-gray-800); + --color-bg-contrast-strong: var(--color-gray-950); + --color-bg-primary: var(--color-white); + --color-bg-secondary: var(--color-gray-050); + --color-bg-tertiary: var(--color-gray-050); + --color-bg-quaternary: var(--color-gray-200); + --color-bg-gray: var(--color-gray-300); + --color-bg-disabled: var(--color-gray-100); + + /* Brand Background */ + --color-bg-brand-softer: var(--color-brand-050); + --color-bg-brand-soft: var(--color-brand-100); + --color-bg-brand-medium: var(--color-brand-200); + --color-bg-brand: var(--color-brand-700); + --color-bg-brand-strong: var(--color-brand-800); + + /* Status Background */ + --color-bg-success-soft: var(--color-green-050); + --color-bg-success-medium: var(--color-green-100); + --color-bg-success: var(--color-green-700); + --color-bg-success-strong: var(--color-green-800); + --color-bg-danger-soft: var(--color-red-050); + --color-bg-danger-medium: var(--color-red-100); + --color-bg-danger: var(--color-red-700); + --color-bg-danger-strong: var(--color-red-800); + --color-bg-warning-soft: var(--color-orange-050); + --color-bg-warning-medium: var(--color-orange-100); + --color-bg-warning: var(--color-orange-600); + --color-bg-warning-strong: var(--color-orange-700); + + /* Accent Background */ + --color-bg-accent-primary-soft: var(--color-teal-050); + --color-bg-accent-primary-medium: var(--color-teal-100); + --color-bg-accent-primary: var(--color-teal-400); + --color-bg-accent-secondary-soft: var(--color-coral-050); + --color-bg-accent-secondary-medium: var(--color-coral-100); + --color-bg-accent-secondary: var(--color-coral-400); + --color-bg-accent-tertiary-soft: var(--color-purple-050); + --color-bg-accent-tertiary-medium: var(--color-purple-100); + --color-bg-accent-tertiary: var(--color-purple-600); + + /* Hover & Overlay */ + --color-bg-hover: rgba(var(--color-gray-950-rgb), 0.05); + --color-bg-overlay: rgba(var(--color-gray-950-rgb), 0.3); + + /* ======================================== + * SEMANTIC BORDER COLORS (Light Mode) + * ======================================== */ + + /* Neutral Border */ + --color-border-muted: var(--color-gray-050); + --color-border-light: var(--color-gray-100); + --color-border-base: var(--color-gray-200); + --color-border-strong: var(--color-gray-800); + --color-border-buffer: var(--color-white); + + /* Brand Border */ + --color-border-brand-soft: var(--color-brand-200); + --color-border-brand: var(--color-brand-700); + --color-border-brand-strong: var(--color-brand-900); + + /* Status Border */ + --color-border-success-soft: var(--color-green-200); + --color-border-success: var(--color-green-700); + --color-border-success-strong: var(--color-green-900); + --color-border-danger-soft: var(--color-red-200); + --color-border-danger: var(--color-red-700); + --color-border-danger-strong: var(--color-red-900); + --color-border-warning-soft: var(--color-orange-200); + --color-border-warning: var(--color-orange-600); + --color-border-warning-strong: var(--color-orange-900); + + /* Accent Border */ + --color-border-accent-primary-soft: var(--color-teal-200); + --color-border-accent-primary: var(--color-teal-600); + --color-border-accent-secondary-soft: var(--color-coral-200); + --color-border-accent-secondary: var(--color-coral-600); + --color-border-accent-tertiary-soft: var(--color-purple-200); + --color-border-accent-tertiary: var(--color-purple-600); + + /* Focus Border */ + --color-border-focus: var(--color-black); } .theme_light { @@ -140,6 +419,129 @@ --color-illustration-bg-tertiary: 243 246 249; --color-illustration-tertiary: 255 191 0; --color-illustration-logo: 255 255 255; + + /* ======================================== + * SEMANTIC FOREGROUND COLORS (Dark Mode Overrides) + * ======================================== */ + + /* Neutral Foreground */ + --color-fg-contrast: var(--color-gray-900); + --color-fg-heading: var(--color-gray-050); + --color-fg-body: var(--color-gray-200); + --color-fg-body-subtle: var(--color-gray-400); + --color-fg-disabled: var(--color-gray-600); + + /* Brand Foreground */ + --color-fg-brand-soft: var(--color-brand-500); + --color-fg-brand: var(--color-brand-400); + --color-fg-brand-strong: var(--color-brand-200); + + /* Status Foreground */ + --color-fg-success: var(--color-green-400); + --color-fg-success-strong: var(--color-green-100); + --color-fg-danger: var(--color-red-400); + --color-fg-danger-strong: var(--color-red-100); + --color-fg-warning: var(--color-orange-400); + --color-fg-warning-strong: var(--color-orange-100); + --color-fg-sensitive: var(--color-pink-300); + + /* Accent Foreground */ + --color-fg-accent-primary-soft: var(--color-teal-400); + --color-fg-accent-primary: var(--color-teal-300); + --color-fg-accent-primary-strong: var(--color-teal-100); + --color-fg-accent-secondary-soft: var(--color-coral-500); + --color-fg-accent-secondary: var(--color-coral-400); + --color-fg-accent-secondary-strong: var(--color-coral-100); + --color-fg-accent-tertiary-soft: var(--color-purple-500); + --color-fg-accent-tertiary: var(--color-purple-400); + --color-fg-accent-tertiary-strong: var(--color-purple-100); + + /* ======================================== + * SEMANTIC BACKGROUND COLORS (Dark Mode Overrides) + * ======================================== */ + + /* Neutral Background */ + --color-bg-contrast: var(--color-gray-050); + --color-bg-contrast-strong: var(--color-gray-050); + --color-bg-primary: var(--color-gray-900); + --color-bg-secondary: var(--color-gray-800); + --color-bg-tertiary: var(--color-gray-950); + --color-bg-quaternary: var(--color-gray-700); + --color-bg-gray: var(--color-gray-600); + --color-bg-disabled: var(--color-gray-950); + + /* Brand Background */ + --color-bg-brand-softer: var(--color-brand-950); + --color-bg-brand-soft: var(--color-brand-900); + --color-bg-brand-medium: var(--color-brand-800); + --color-bg-brand: var(--color-brand-400); + --color-bg-brand-strong: var(--color-brand-300); + + /* Status Background */ + --color-bg-success-soft: var(--color-green-950); + --color-bg-success-medium: var(--color-green-900); + --color-bg-success: var(--color-green-400); + --color-bg-success-strong: var(--color-green-300); + --color-bg-danger-soft: var(--color-red-950); + --color-bg-danger-medium: var(--color-red-900); + --color-bg-danger: var(--color-red-400); + --color-bg-danger-strong: var(--color-red-300); + --color-bg-warning-soft: var(--color-orange-950); + --color-bg-warning-medium: var(--color-orange-900); + --color-bg-warning: var(--color-orange-400); + --color-bg-warning-strong: var(--color-orange-300); + + /* Accent Background */ + --color-bg-accent-primary-soft: var(--color-teal-950); + --color-bg-accent-primary-medium: var(--color-teal-900); + --color-bg-accent-primary: var(--color-teal-400); + --color-bg-accent-secondary-soft: var(--color-coral-950); + --color-bg-accent-secondary-medium: var(--color-coral-900); + --color-bg-accent-secondary: var(--color-coral-400); + --color-bg-accent-tertiary-soft: var(--color-purple-950); + --color-bg-accent-tertiary-medium: var(--color-purple-900); + --color-bg-accent-tertiary: var(--color-purple-600); + + /* Hover & Overlay */ + --color-bg-hover: rgba(var(--color-white-rgb), 0.05); + --color-bg-overlay: rgba(var(--color-gray-950-rgb), 0.85); + + /* ======================================== + * SEMANTIC BORDER COLORS (Dark Mode Overrides) + * ======================================== */ + + /* Neutral Border */ + --color-border-muted: var(--color-gray-900); + --color-border-light: var(--color-gray-800); + --color-border-base: var(--color-gray-700); + --color-border-strong: var(--color-gray-400); + --color-border-buffer: var(--color-gray-950); + + /* Brand Border */ + --color-border-brand-soft: var(--color-brand-800); + --color-border-brand: var(--color-brand-400); + --color-border-brand-strong: var(--color-brand-200); + + /* Status Border */ + --color-border-success-soft: var(--color-green-800); + --color-border-success: var(--color-green-400); + --color-border-success-strong: var(--color-green-200); + --color-border-danger-soft: var(--color-red-800); + --color-border-danger: var(--color-red-400); + --color-border-danger-strong: var(--color-red-200); + --color-border-warning-soft: var(--color-orange-800); + --color-border-warning: var(--color-orange-400); + --color-border-warning-strong: var(--color-orange-200); + + /* Accent Border */ + --color-border-accent-primary-soft: var(--color-teal-800); + --color-border-accent-secondary-soft: var(--color-coral-800); + --color-border-accent-secondary: var(--color-coral-500); + --color-border-accent-tertiary-soft: var(--color-purple-800); + --color-border-accent-tertiary: var(--color-purple-500); + + /* Focus Border */ + --color-border-focus: var(--color-white); } @layer components { diff --git a/libs/components/tailwind.config.base.js b/libs/components/tailwind.config.base.js index e41cff16e48..bd88f5471ff 100644 --- a/libs/components/tailwind.config.base.js +++ b/libs/components/tailwind.config.base.js @@ -9,9 +9,9 @@ function rgba(color) { module.exports = { prefix: "tw-", content: [ - "./src/**/*.{html,ts}", + "./src/**/*.{html,ts,mdx}", "../../libs/assets/src/**/*.{html,ts}", - "../../libs/components/src/**/*.{html,ts}", + "../../libs/components/src/**/*.{html,ts,mdx}", "../../libs/key-management-ui/src/**/*.{html,ts}", "../../libs/auth/src/**/*.{html,ts}", ], @@ -78,6 +78,46 @@ module.exports = { alt3: rgba("--color-background-alt3"), alt4: rgba("--color-background-alt4"), }, + bg: { + white: "var(--color-bg-white)", + dark: "var(--color-bg-dark)", + contrast: "var(--color-bg-contrast)", + "contrast-strong": "var(--color-bg-contrast-strong)", + primary: "var(--color-bg-primary)", + secondary: "var(--color-bg-secondary)", + tertiary: "var(--color-bg-tertiary)", + quaternary: "var(--color-bg-quaternary)", + gray: "var(--color-bg-gray)", + disabled: "var(--color-bg-disabled)", + "brand-softer": "var(--color-bg-brand-softer)", + "brand-soft": "var(--color-bg-brand-soft)", + "brand-medium": "var(--color-bg-brand-medium)", + brand: "var(--color-bg-brand)", + "brand-strong": "var(--color-bg-brand-strong)", + "success-soft": "var(--color-bg-success-soft)", + "success-medium": "var(--color-bg-success-medium)", + success: "var(--color-bg-success)", + "success-strong": "var(--color-bg-success-strong)", + "danger-soft": "var(--color-bg-danger-soft)", + "danger-medium": "var(--color-bg-danger-medium)", + danger: "var(--color-bg-danger)", + "danger-strong": "var(--color-bg-danger-strong)", + "warning-soft": "var(--color-bg-warning-soft)", + "warning-medium": "var(--color-bg-warning-medium)", + warning: "var(--color-bg-warning)", + "warning-strong": "var(--color-bg-warning-strong)", + "accent-primary-soft": "var(--color-bg-accent-primary-soft)", + "accent-primary-medium": "var(--color-bg-accent-primary-medium)", + "accent-primary": "var(--color-bg-accent-primary)", + "accent-secondary-soft": "var(--color-bg-accent-secondary-soft)", + "accent-secondary-medium": "var(--color-bg-accent-secondary-medium)", + "accent-secondary": "var(--color-bg-accent-secondary)", + "accent-tertiary-soft": "var(--color-bg-accent-tertiary-soft)", + "accent-tertiary-medium": "var(--color-bg-accent-tertiary-medium)", + "accent-tertiary": "var(--color-bg-accent-tertiary)", + hover: "var(--color-bg-hover)", + overlay: "var(--color-bg-overlay)", + }, hover: { default: "var(--color-hover-default)", contrast: "var(--color-hover-contrast)", @@ -92,8 +132,62 @@ module.exports = { tertiary: rgba("--color-illustration-tertiary"), logo: rgba("--color-illustration-logo"), }, + fg: { + white: "var(--color-fg-white)", + dark: "var(--color-fg-dark)", + contrast: "var(--color-fg-contrast)", + heading: "var(--color-fg-heading)", + body: "var(--color-fg-body)", + "body-subtle": "var(--color-fg-body-subtle)", + disabled: "var(--color-fg-disabled)", + "brand-soft": "var(--color-fg-brand-soft)", + brand: "var(--color-fg-brand)", + "brand-strong": "var(--color-fg-brand-strong)", + success: "var(--color-fg-success)", + "success-strong": "var(--color-fg-success-strong)", + danger: "var(--color-fg-danger)", + "danger-strong": "var(--color-fg-danger-strong)", + warning: "var(--color-fg-warning)", + "warning-strong": "var(--color-fg-warning-strong)", + sensitive: "var(--color-fg-sensitive)", + "accent-primary-soft": "var(--color-fg-accent-primary-soft)", + "accent-primary": "var(--color-fg-accent-primary)", + "accent-primary-strong": "var(--color-fg-accent-primary-strong)", + "accent-secondary-soft": "var(--color-fg-accent-secondary-soft)", + "accent-secondary": "var(--color-fg-accent-secondary)", + "accent-secondary-strong": "var(--color-fg-accent-secondary-strong)", + "accent-tertiary-soft": "var(--color-fg-accent-tertiary-soft)", + "accent-tertiary": "var(--color-fg-accent-tertiary)", + "accent-tertiary-strong": "var(--color-fg-accent-tertiary-strong)", + }, + border: { + muted: "var(--color-border-muted)", + light: "var(--color-border-light)", + base: "var(--color-border-base)", + strong: "var(--color-border-strong)", + buffer: "var(--color-border-buffer)", + "brand-soft": "var(--color-border-brand-soft)", + brand: "var(--color-border-brand)", + "brand-strong": "var(--color-border-brand-strong)", + "success-soft": "var(--color-border-success-soft)", + success: "var(--color-border-success)", + "success-strong": "var(--color-border-success-strong)", + "danger-soft": "var(--color-border-danger-soft)", + danger: "var(--color-border-danger)", + "danger-strong": "var(--color-border-danger-strong)", + "warning-soft": "var(--color-border-warning-soft)", + warning: "var(--color-border-warning)", + "warning-strong": "var(--color-border-warning-strong)", + "accent-primary-soft": "var(--color-border-accent-primary-soft)", + "accent-primary": "var(--color-border-accent-primary)", + "accent-secondary-soft": "var(--color-border-accent-secondary-soft)", + "accent-secondary": "var(--color-border-accent-secondary)", + "accent-tertiary-soft": "var(--color-border-accent-tertiary-soft)", + "accent-tertiary": "var(--color-border-accent-tertiary)", + focus: "var(--color-border-focus)", + }, }, - textColor: { + textColor: () => ({ main: rgba("--color-text-main"), muted: rgba("--color-text-muted"), contrast: rgba("--color-text-contrast"), @@ -132,7 +226,62 @@ module.exports = { notification: { 600: rgba("--color-notification-600"), }, - }, + // New semantic fg tokens - manually flattened to generate tw-text-fg-* utilities + "fg-white": "var(--color-fg-white)", + "fg-dark": "var(--color-fg-dark)", + "fg-contrast": "var(--color-fg-contrast)", + "fg-heading": "var(--color-fg-heading)", + "fg-body": "var(--color-fg-body)", + "fg-body-subtle": "var(--color-fg-body-subtle)", + "fg-disabled": "var(--color-fg-disabled)", + "fg-brand-soft": "var(--color-fg-brand-soft)", + "fg-brand": "var(--color-fg-brand)", + "fg-brand-strong": "var(--color-fg-brand-strong)", + "fg-success": "var(--color-fg-success)", + "fg-success-strong": "var(--color-fg-success-strong)", + "fg-danger": "var(--color-fg-danger)", + "fg-danger-strong": "var(--color-fg-danger-strong)", + "fg-warning": "var(--color-fg-warning)", + "fg-warning-strong": "var(--color-fg-warning-strong)", + "fg-sensitive": "var(--color-fg-sensitive)", + "fg-accent-primary-soft": "var(--color-fg-accent-primary-soft)", + "fg-accent-primary": "var(--color-fg-accent-primary)", + "fg-accent-primary-strong": "var(--color-fg-accent-primary-strong)", + "fg-accent-secondary-soft": "var(--color-fg-accent-secondary-soft)", + "fg-accent-secondary": "var(--color-fg-accent-secondary)", + "fg-accent-secondary-strong": "var(--color-fg-accent-secondary-strong)", + "fg-accent-tertiary-soft": "var(--color-fg-accent-tertiary-soft)", + "fg-accent-tertiary": "var(--color-fg-accent-tertiary)", + "fg-accent-tertiary-strong": "var(--color-fg-accent-tertiary-strong)", + }), + borderColor: ({ theme }) => ({ + ...theme("colors"), + // New semantic border tokens - manually flattened to generate tw-border-border-* utilities + "border-muted": "var(--color-border-muted)", + "border-light": "var(--color-border-light)", + "border-base": "var(--color-border-base)", + "border-strong": "var(--color-border-strong)", + "border-buffer": "var(--color-border-buffer)", + "border-brand-soft": "var(--color-border-brand-soft)", + "border-brand": "var(--color-border-brand)", + "border-brand-strong": "var(--color-border-brand-strong)", + "border-success-soft": "var(--color-border-success-soft)", + "border-success": "var(--color-border-success)", + "border-success-strong": "var(--color-border-success-strong)", + "border-danger-soft": "var(--color-border-danger-soft)", + "border-danger": "var(--color-border-danger)", + "border-danger-strong": "var(--color-border-danger-strong)", + "border-warning-soft": "var(--color-border-warning-soft)", + "border-warning": "var(--color-border-warning)", + "border-warning-strong": "var(--color-border-warning-strong)", + "border-accent-primary-soft": "var(--color-border-accent-primary-soft)", + "border-accent-primary": "var(--color-border-accent-primary)", + "border-accent-secondary-soft": "var(--color-border-accent-secondary-soft)", + "border-accent-secondary": "var(--color-border-accent-secondary)", + "border-accent-tertiary-soft": "var(--color-border-accent-tertiary-soft)", + "border-accent-tertiary": "var(--color-border-accent-tertiary)", + "border-focus": "var(--color-border-focus)", + }), fontFamily: { sans: "var(--font-sans)", serif: "var(--font-serif)", diff --git a/libs/components/tailwind.config.js b/libs/components/tailwind.config.js index d8cef6596dc..0fa5b259bb6 100644 --- a/libs/components/tailwind.config.js +++ b/libs/components/tailwind.config.js @@ -11,11 +11,16 @@ config.content = [ "bitwarden_license/bit-web/src/**/*.{html,ts,mdx}", ".storybook/preview.tsx", ]; + +// Safelist is required for dynamic color classes in Storybook color documentation (colors.mdx). +// Tailwind's JIT compiler cannot detect dynamically constructed class names like `tw-bg-${name}`, +// so we must explicitly safelist these patterns to ensure all color utilities are generated. config.safelist = [ { pattern: /tw-bg-(.*)/, }, ]; + config.corePlugins.preflight = true; module.exports = config; From 5ddfd91a14c09f3fa9bdbe6db60585573767f44a Mon Sep 17 00:00:00 2001 From: Alex <55413326+AlexRubik@users.noreply.github.com> Date: Fri, 26 Dec 2025 17:28:10 -0500 Subject: [PATCH 157/188] correct virtual scroll rowSize for password reports (#18058) The Exposed Passwords and Weak Passwords reports were using an incorrect rowSize value (53px instead of 75px) for their virtual scroll tables. This caused the \"Back to reports\" button to collide with table entries. The issue was cumulative - more items meant more visible collision. This fix aligns both reports with the Reused Passwords report which correctly uses 75px for identical row structures. --- .../dirt/reports/pages/exposed-passwords-report.component.html | 2 +- .../app/dirt/reports/pages/weak-passwords-report.component.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/web/src/app/dirt/reports/pages/exposed-passwords-report.component.html b/apps/web/src/app/dirt/reports/pages/exposed-passwords-report.component.html index eb476090963..fcdb3f6ca64 100644 --- a/apps/web/src/app/dirt/reports/pages/exposed-passwords-report.component.html +++ b/apps/web/src/app/dirt/reports/pages/exposed-passwords-report.component.html @@ -26,7 +26,7 @@ </bit-toggle> </ng-container> </bit-toggle-group> - <bit-table-scroll [dataSource]="dataSource" [rowSize]="53"> + <bit-table-scroll [dataSource]="dataSource" [rowSize]="75"> <ng-container header> <th bitCell></th> <th bitCell bitSortable="name">{{ "name" | i18n }}</th> diff --git a/apps/web/src/app/dirt/reports/pages/weak-passwords-report.component.html b/apps/web/src/app/dirt/reports/pages/weak-passwords-report.component.html index 5fa2806d133..92d56c1c7a3 100644 --- a/apps/web/src/app/dirt/reports/pages/weak-passwords-report.component.html +++ b/apps/web/src/app/dirt/reports/pages/weak-passwords-report.component.html @@ -31,7 +31,7 @@ </bit-toggle> </ng-container> </bit-toggle-group> - <bit-table-scroll [dataSource]="dataSource" [rowSize]="53"> + <bit-table-scroll [dataSource]="dataSource" [rowSize]="75"> <ng-container header> <th bitCell></th> <th bitCell bitSortable="name">{{ "name" | i18n }}</th> From c5484616506f1ef5a66bcf73db1f0660e5d5403e Mon Sep 17 00:00:00 2001 From: "bw-ghapp[bot]" <178206702+bw-ghapp[bot]@users.noreply.github.com> Date: Sun, 28 Dec 2025 10:41:29 +0100 Subject: [PATCH 158/188] Autosync the updated translations (#18118) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/web/src/locales/it/messages.json | 64 ++++++++++++------------ apps/web/src/locales/pt_BR/messages.json | 4 +- 2 files changed, 34 insertions(+), 34 deletions(-) diff --git a/apps/web/src/locales/it/messages.json b/apps/web/src/locales/it/messages.json index 0b98058ae82..b4942bd75f6 100644 --- a/apps/web/src/locales/it/messages.json +++ b/apps/web/src/locales/it/messages.json @@ -1970,11 +1970,11 @@ "message": "Le chiavi di cifratura dell'account sono uniche per ogni account utente Bitwarden, quindi non è possibile importare un'esportazione cifrata in un account diverso." }, "exportNoun": { - "message": "Export", + "message": "Esporta", "description": "The noun form of the word Export" }, "exportVerb": { - "message": "Export", + "message": "Esporta", "description": "The verb form of the word Export" }, "exportFrom": { @@ -2303,11 +2303,11 @@ "message": "Strumenti" }, "importNoun": { - "message": "Import", + "message": "Importa", "description": "The noun form of the word Import" }, "importVerb": { - "message": "Import", + "message": "Importa", "description": "The verb form of the word Import" }, "importData": { @@ -3294,7 +3294,7 @@ "message": "Avvia abbonamento cloud" }, "launchCloudSubscriptionSentenceCase": { - "message": "Launch cloud subscription" + "message": "Avvia abbonamento cloud" }, "storage": { "message": "Spazio di archiviazione" @@ -4212,10 +4212,10 @@ } }, "userAcceptedTransfer": { - "message": "Accepted transfer to organization ownership." + "message": "Trasferimento di proprietà all'organizzazione accettato." }, "userDeclinedTransfer": { - "message": "Revoked for declining transfer to organization ownership." + "message": "Revocato per il rifiuto di trasferimento di proprietà all'organizzazione." }, "invitedUserId": { "message": "Utente $ID$ invitato.", @@ -11607,7 +11607,7 @@ "message": "Togli dall'archivio" }, "unArchiveAndSave": { - "message": "Unarchive and save" + "message": "Togli dall'archivio e salva" }, "itemsInArchive": { "message": "Elementi archiviati" @@ -12251,43 +12251,43 @@ } }, "removeMasterPasswordForOrgUserKeyConnector": { - "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + "message": "La tua organizzazione non utilizza più le password principali per accedere a Bitwarden. Per continuare, verifica l'organizzazione e il dominio." }, "continueWithLogIn": { - "message": "Continue with log in" + "message": "Accedi e continua" }, "doNotContinue": { - "message": "Do not continue" + "message": "Non continuare" }, "domain": { - "message": "Domain" + "message": "Dominio" }, "keyConnectorDomainTooltip": { - "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + "message": "Questo dominio memorizzerà le chiavi di crittografia del tuo account, quindi assicurati di impostarlo come affidabile. Se non hai la certezza che lo sia, verifica con l'amministratore." }, "verifyYourOrganization": { - "message": "Verify your organization to log in" + "message": "Verifica la tua organizzazione per accedere" }, "organizationVerified": { - "message": "Organization verified" + "message": "Organizzazione verificata" }, "domainVerified": { - "message": "Domain verified" + "message": "Dominio verificato" }, "leaveOrganizationContent": { - "message": "If you don't verify your organization, your access to the organization will be revoked." + "message": "Se non verifichi l'organizzazione, il tuo accesso sarà revocato." }, "leaveNow": { - "message": "Leave now" + "message": "Abbandona" }, "verifyYourDomainToLogin": { - "message": "Verify your domain to log in" + "message": "Verifica il tuo dominio per accedere" }, "verifyYourDomainDescription": { - "message": "To continue with log in, verify this domain." + "message": "Per continuare con l'accesso, verifica questo dominio." }, "confirmKeyConnectorOrganizationUserDescription": { - "message": "To continue with log in, verify the organization and domain." + "message": "Per continuare con l'accesso, verifica l'organizzazione e il dominio." }, "confirmNoSelectedCriticalApplicationsTitle": { "message": "Non ci sono applicazioni contrassegnate come critiche" @@ -12433,13 +12433,13 @@ "message": "Perché vedo questo avviso?" }, "youHaveBitwardenPremium": { - "message": "You have Bitwarden Premium" + "message": "Hai Bitwarden Premium" }, "viewAndManagePremiumSubscription": { - "message": "View and manage your Premium subscription" + "message": "Visualizza e gestisci il tuo abbonamento Premium" }, "youNeedToUpdateLicenseFile": { - "message": "You'll need to update your license file" + "message": "Dovrai aggiornare il tuo file di licenza" }, "youNeedToUpdateLicenseFileDate": { "message": "$DATE$.", @@ -12451,16 +12451,16 @@ } }, "uploadLicenseFile": { - "message": "Upload license file" + "message": "Carica il file di licenza" }, "uploadYourLicenseFile": { - "message": "Upload your license file" + "message": "Carica il file di licenza" }, "uploadYourPremiumLicenseFile": { - "message": "Upload your Premium license file" + "message": "Carica il tuo file di licenza Premium" }, "uploadLicenseFileDesc": { - "message": "Your license file name will be similar to: $FILE_NAME$", + "message": "Il nome del file di licenza sarà simile a $FILE_NAME$", "placeholders": { "file_name": { "content": "$1", @@ -12469,15 +12469,15 @@ } }, "alreadyHaveSubscriptionQuestion": { - "message": "Already have a subscription?" + "message": "Hai già un abbonamento?" }, "alreadyHaveSubscriptionSelfHostedMessage": { - "message": "Open the subscription page on your Bitwarden cloud account and download your license file. Then return to this screen and upload it below." + "message": "Vai alla pagina degli abbonamenti del tuo account Bitwarden e scarica il file di licenza, poi torna a caricarlo qui." }, "viewAllPlans": { - "message": "View all plans" + "message": "Visualizza tutti i piani" }, "planDescPremium": { - "message": "Complete online security" + "message": "Sicurezza online completa" } } diff --git a/apps/web/src/locales/pt_BR/messages.json b/apps/web/src/locales/pt_BR/messages.json index fbfaf08d030..28e95ed6379 100644 --- a/apps/web/src/locales/pt_BR/messages.json +++ b/apps/web/src/locales/pt_BR/messages.json @@ -1943,7 +1943,7 @@ "message": "Copiar UUID" }, "errorRefreshingAccessToken": { - "message": "Erro de recarregamento do token de acesso" + "message": "Erro de Recarregamento do Token de Acesso" }, "errorRefreshingAccessTokenDesc": { "message": "Nenhum token de atualização ou chave de API foi encontrado. Tente se desconectar e se conectar novamente." @@ -3294,7 +3294,7 @@ "message": "Iniciar Assinatura na Nuvem" }, "launchCloudSubscriptionSentenceCase": { - "message": "Launch cloud subscription" + "message": "Executar assinatura na nuvem" }, "storage": { "message": "Armazenamento" From 8acbb246a1e4f0b53c8176e5ad4809f21138998b Mon Sep 17 00:00:00 2001 From: "bw-ghapp[bot]" <178206702+bw-ghapp[bot]@users.noreply.github.com> Date: Sun, 28 Dec 2025 09:56:36 +0000 Subject: [PATCH 159/188] Autosync the updated translations (#18128) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/desktop/src/locales/zh_CN/messages.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/desktop/src/locales/zh_CN/messages.json b/apps/desktop/src/locales/zh_CN/messages.json index 7d1c1648bb6..b80e1cea689 100644 --- a/apps/desktop/src/locales/zh_CN/messages.json +++ b/apps/desktop/src/locales/zh_CN/messages.json @@ -709,7 +709,7 @@ "message": "添加附件" }, "itemsTransferred": { - "message": "项目已传输" + "message": "项目已转移" }, "fixEncryption": { "message": "修复加密" @@ -4454,7 +4454,7 @@ "message": "我该如何管理我的密码库?" }, "transferItemsToOrganizationTitle": { - "message": "传输项目到 $ORGANIZATION$", + "message": "转移项目到 $ORGANIZATION$", "placeholders": { "organization": { "content": "$1", @@ -4463,7 +4463,7 @@ } }, "transferItemsToOrganizationContent": { - "message": "出于安全和合规考虑,$ORGANIZATION$ 要求所有项目归组织所有。点击「接受」以传输您的项目的所有权。", + "message": "出于安全和合规考虑,$ORGANIZATION$ 要求所有项目归组织所有。点击「接受」以转移您的项目的所有权。", "placeholders": { "organization": { "content": "$1", @@ -4472,7 +4472,7 @@ } }, "acceptTransfer": { - "message": "接受传输" + "message": "接受转移" }, "declineAndLeave": { "message": "拒绝并退出" From 00b53294308d9d95313fa7304a3c422aae59815c Mon Sep 17 00:00:00 2001 From: "bw-ghapp[bot]" <178206702+bw-ghapp[bot]@users.noreply.github.com> Date: Sun, 28 Dec 2025 09:57:05 +0000 Subject: [PATCH 160/188] Autosync the updated translations (#18129) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/browser/src/_locales/az/messages.json | 4 ++-- apps/browser/src/_locales/zh_CN/messages.json | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/browser/src/_locales/az/messages.json b/apps/browser/src/_locales/az/messages.json index 3f98313c2b8..d7257bab478 100644 --- a/apps/browser/src/_locales/az/messages.json +++ b/apps/browser/src/_locales/az/messages.json @@ -2498,7 +2498,7 @@ } }, "topLayerHijackWarning": { - "message": "This page is interfering with the Bitwarden experience. The Bitwarden inline menu has been temporarily disabled as a safety measure." + "message": "Bu səhifə Bitwarden təcrübəsinə müdaxilə edir. Bitwarden daxili menyusu, təhlükəsizlik tədbiri olaraq müvəqqəti sıradan çıxarılıb." }, "setMasterPassword": { "message": "Ana parolu ayarla" @@ -4124,7 +4124,7 @@ "message": "Avto-doldurula bilmir" }, "cannotAutofillExactMatch": { - "message": "Default matching is set to 'Exact Match'. The current website does not exactly match the saved login details for this item." + "message": "İlkin uyuşma 'Tam Uyuşur' olaraq ayarlanıb. Hazırkı veb sayt, bu element üçün saxlanılmış giriş məlumatları ilə tam uyuşmur." }, "okay": { "message": "Oldu" diff --git a/apps/browser/src/_locales/zh_CN/messages.json b/apps/browser/src/_locales/zh_CN/messages.json index a699be016eb..c7f7fbcd618 100644 --- a/apps/browser/src/_locales/zh_CN/messages.json +++ b/apps/browser/src/_locales/zh_CN/messages.json @@ -1486,7 +1486,7 @@ "message": "选择一个文件" }, "itemsTransferred": { - "message": "项目已传输" + "message": "项目已转移" }, "maxFileSize": { "message": "文件最大为 500 MB。" @@ -3804,7 +3804,7 @@ "description": "Browser extension/addon" }, "desktop": { - "message": "桌面", + "message": "桌面端", "description": "Desktop app" }, "webVault": { @@ -5707,7 +5707,7 @@ "message": "导入现有密码" }, "emptyVaultNudgeBody": { - "message": "使用导入器快速将登录传输到 Bitwarden 而无需手动添加。" + "message": "使用导入器快速将登录转移到 Bitwarden 而无需手动添加。" }, "emptyVaultNudgeButton": { "message": "立即导入" @@ -6014,7 +6014,7 @@ "message": "我该如何管理我的密码库?" }, "transferItemsToOrganizationTitle": { - "message": "传输项目到 $ORGANIZATION$", + "message": "转移项目到 $ORGANIZATION$", "placeholders": { "organization": { "content": "$1", @@ -6023,7 +6023,7 @@ } }, "transferItemsToOrganizationContent": { - "message": "出于安全和合规考虑,$ORGANIZATION$ 要求所有项目归组织所有。点击「接受」以传输您的项目的所有权。", + "message": "出于安全和合规考虑,$ORGANIZATION$ 要求所有项目归组织所有。点击「接受」以转移您的项目的所有权。", "placeholders": { "organization": { "content": "$1", @@ -6032,7 +6032,7 @@ } }, "acceptTransfer": { - "message": "接受传输" + "message": "接受转移" }, "declineAndLeave": { "message": "拒绝并退出" From d4a276f1de303bb895d737b7e2ba994ccd8005c7 Mon Sep 17 00:00:00 2001 From: "bw-ghapp[bot]" <178206702+bw-ghapp[bot]@users.noreply.github.com> Date: Sun, 28 Dec 2025 09:57:37 +0000 Subject: [PATCH 161/188] Autosync the updated translations (#18130) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/web/src/locales/az/messages.json | 4 ++-- apps/web/src/locales/de/messages.json | 4 ++-- apps/web/src/locales/zh_CN/messages.json | 24 ++++++++++++------------ 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/apps/web/src/locales/az/messages.json b/apps/web/src/locales/az/messages.json index 275ee56dd5c..a86dbdb6406 100644 --- a/apps/web/src/locales/az/messages.json +++ b/apps/web/src/locales/az/messages.json @@ -5871,7 +5871,7 @@ "description": "This is the policy description shown in the policy list." }, "organizationDataOwnershipDescContent": { - "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the ", + "message": "Bütün elementlər bir təşkilata məxsus olacaq və orada saxlanılacaq, bu da təşkilat üzrə kontrollar, görünürlük və hesabatları mümkün edəcək. İşə salındığı zaman, hər üzv üçün elementləri saxlaya biləcəyi ilkin bir kolleksiya mövcud olacaq. Daha ətraflı ", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the credential lifecycle.'" }, "organizationDataOwnershipContentAnchor": { @@ -6752,7 +6752,7 @@ "message": "Bütün üzvlər üçün maksimum bitmə vaxtını \"Heç vaxt\" olaraq icazə vermək istədiyinizə əminsiniz?" }, "sessionTimeoutConfirmationNeverDescription": { - "message": "This option will save your members' encryption keys on their devices. If you choose this option, ensure that their devices are adequately protected." + "message": "Bu seçim, üzvlərinizin şifrələmə açarlarını onların cihazlarında saxlayacaq. Bu seçimi seçsəniz, onların cihazlarının lazımi səviyyədə qorunduğuna əmin olun." }, "learnMoreAboutDeviceProtection": { "message": "Cihaz mühafizəsi barədə daha ətraflı" diff --git a/apps/web/src/locales/de/messages.json b/apps/web/src/locales/de/messages.json index ccde12d8614..ae95c0ca9cb 100644 --- a/apps/web/src/locales/de/messages.json +++ b/apps/web/src/locales/de/messages.json @@ -12436,7 +12436,7 @@ "message": "Du hast Bitwarden Premium" }, "viewAndManagePremiumSubscription": { - "message": "View and manage your Premium subscription" + "message": "Dein Premium-Abonnement anzeigen und verwalten" }, "youNeedToUpdateLicenseFile": { "message": "Du musst deine Lizenzdatei aktualisieren" @@ -12472,7 +12472,7 @@ "message": "Du hast bereits ein Abonnement?" }, "alreadyHaveSubscriptionSelfHostedMessage": { - "message": "Open the subscription page on your Bitwarden cloud account and download your license file. Then return to this screen and upload it below." + "message": "Öffne die Abonnementseite in deinem Bitwarden Cloud-Konto und lade deine Lizenzdatei herunter. Gehe dann zu dieser Seite zurück und lade sie unten hoch." }, "viewAllPlans": { "message": "Alle Tarife anzeigen" diff --git a/apps/web/src/locales/zh_CN/messages.json b/apps/web/src/locales/zh_CN/messages.json index d1ee6e0f659..b41635b948c 100644 --- a/apps/web/src/locales/zh_CN/messages.json +++ b/apps/web/src/locales/zh_CN/messages.json @@ -3862,7 +3862,7 @@ "description": "Browser extension/addon" }, "desktop": { - "message": "桌面版应用", + "message": "桌面端", "description": "Desktop app" }, "webVault": { @@ -4212,10 +4212,10 @@ } }, "userAcceptedTransfer": { - "message": "Accepted transfer to organization ownership." + "message": "接受了转移至组织所有权。" }, "userDeclinedTransfer": { - "message": "Revoked for declining transfer to organization ownership." + "message": "因拒绝转移至组织所有权而被撤销。" }, "invitedUserId": { "message": "邀请了用户 $ID$。", @@ -5195,7 +5195,7 @@ "message": "需要先修复您的密码库中的旧文件附件,然后才能轮换您账户的加密密钥。" }, "itemsTransferred": { - "message": "项目已传输" + "message": "项目已转移" }, "yourAccountsFingerprint": { "message": "您的账户指纹短语", @@ -6825,7 +6825,7 @@ "message": "密码库超时不在允许的范围内。" }, "disableExport": { - "message": "移除导出" + "message": "禁用导出" }, "disablePersonalVaultExportDescription": { "message": "不允许成员从个人密码库导出数据。" @@ -12406,7 +12406,7 @@ "message": "我该如何管理我的密码库?" }, "transferItemsToOrganizationTitle": { - "message": "传输项目到 $ORGANIZATION$", + "message": "转移项目到 $ORGANIZATION$", "placeholders": { "organization": { "content": "$1", @@ -12415,7 +12415,7 @@ } }, "transferItemsToOrganizationContent": { - "message": "出于安全和合规考虑,$ORGANIZATION$ 要求所有项目归组织所有。点击「接受」以传输您的项目的所有权。", + "message": "出于安全和合规考虑,$ORGANIZATION$ 要求所有项目归组织所有。点击「接受」以转移您的项目的所有权。", "placeholders": { "organization": { "content": "$1", @@ -12424,7 +12424,7 @@ } }, "acceptTransfer": { - "message": "接受传输" + "message": "接受转移" }, "declineAndLeave": { "message": "拒绝并退出" @@ -12439,7 +12439,7 @@ "message": "查看和管理您的高级版订阅" }, "youNeedToUpdateLicenseFile": { - "message": "您需要更新您的许可文件" + "message": "您需要更新您的许可证文件" }, "youNeedToUpdateLicenseFileDate": { "message": "$DATE$。", @@ -12469,13 +12469,13 @@ } }, "alreadyHaveSubscriptionQuestion": { - "message": "已经有一个订阅?" + "message": "已经有一个订阅了吗?" }, "alreadyHaveSubscriptionSelfHostedMessage": { - "message": "打开您的 Bitwarden 云账户上的订阅页面并下载您的许可证文件,然后返回此屏幕并上传。" + "message": "打开您的 Bitwarden 云账户中的订阅页面并下载您的许可证文件。然后返回此界面并在下方上传该文件。" }, "viewAllPlans": { - "message": "查看所有套餐" + "message": "查看所有方案" }, "planDescPremium": { "message": "全面的在线安全防护" From 47eb28be345f51db4e50812f74c0ae654262f78d Mon Sep 17 00:00:00 2001 From: Github Actions <actions@github.com> Date: Mon, 29 Dec 2025 14:59:06 +0000 Subject: [PATCH 162/188] 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/web/package.json | 2 +- package-lock.json | 6 +++--- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/browser/package.json b/apps/browser/package.json index cf2be624a22..7055aabf4fd 100644 --- a/apps/browser/package.json +++ b/apps/browser/package.json @@ -1,6 +1,6 @@ { "name": "@bitwarden/browser", - "version": "2025.12.0", + "version": "2025.12.1", "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 1651f616e03..26add57d1ae 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.12.0", + "version": "2025.12.1", "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 67399192b64..64d182ebd3d 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.12.0", + "version": "2025.12.1", "description": "__MSG_extDesc__", "default_locale": "en", "author": "Bitwarden Inc.", diff --git a/apps/cli/package.json b/apps/cli/package.json index ff74664ac76..5174e324586 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.12.0", + "version": "2025.12.1", "keywords": [ "bitwarden", "password", diff --git a/apps/web/package.json b/apps/web/package.json index a5399de920e..b92fc5f736a 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -1,6 +1,6 @@ { "name": "@bitwarden/web-vault", - "version": "2025.12.1", + "version": "2025.12.2", "scripts": { "build:oss": "webpack", "build:bit": "webpack -c ../../bitwarden_license/bit-web/webpack.config.js", diff --git a/package-lock.json b/package-lock.json index 014c291c38c..c40b5361cc8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -192,11 +192,11 @@ }, "apps/browser": { "name": "@bitwarden/browser", - "version": "2025.12.0" + "version": "2025.12.1" }, "apps/cli": { "name": "@bitwarden/cli", - "version": "2025.12.0", + "version": "2025.12.1", "license": "SEE LICENSE IN LICENSE.txt", "dependencies": { "@koa/multer": "4.0.0", @@ -491,7 +491,7 @@ }, "apps/web": { "name": "@bitwarden/web-vault", - "version": "2025.12.1" + "version": "2025.12.2" }, "libs/admin-console": { "name": "@bitwarden/admin-console", From d3701c38d14e62befc0ef3acfa88eec2b38827f3 Mon Sep 17 00:00:00 2001 From: neuronull <9162534+neuronull@users.noreply.github.com> Date: Mon, 29 Dec 2025 08:10:18 -0700 Subject: [PATCH 163/188] Desktop Autotype introduce strict type for keyboard input (#17141) * Desktop Autotype introduce strict type for keyboard input * cleanup * fix doc typo * unecessary into() * use str * propagate error * better var name * pass a slice * doc comment * napi fix * add ownership renovate for new dep * add code comment about modifier keys being released * fmt * remove keytar * fix input struct size compute * improve debug comment --- .github/renovate.json5 | 1 + apps/desktop/desktop_native/Cargo.lock | 16 ++ apps/desktop/desktop_native/Cargo.toml | 1 + .../desktop_native/autotype/Cargo.toml | 1 + .../desktop_native/autotype/src/lib.rs | 2 +- .../desktop_native/autotype/src/linux.rs | 2 +- .../desktop_native/autotype/src/macos.rs | 2 +- .../autotype/src/windows/mod.rs | 31 ++- .../autotype/src/windows/type_input.rs | 196 ++++++++++-------- .../autotype/src/windows/window_title.rs | 14 +- apps/desktop/desktop_native/napi/src/lib.rs | 5 +- 11 files changed, 162 insertions(+), 109 deletions(-) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index acd181310d6..c4c24799da1 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -157,6 +157,7 @@ "html-webpack-injector", "html-webpack-plugin", "interprocess", + "itertools", "json5", "keytar", "libc", diff --git a/apps/desktop/desktop_native/Cargo.lock b/apps/desktop/desktop_native/Cargo.lock index 5978659f21e..f5e5cf7ee18 100644 --- a/apps/desktop/desktop_native/Cargo.lock +++ b/apps/desktop/desktop_native/Cargo.lock @@ -329,6 +329,7 @@ name = "autotype" version = "0.0.0" dependencies = [ "anyhow", + "itertools", "mockall", "serial_test", "tracing", @@ -1026,6 +1027,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + [[package]] name = "elliptic-curve" version = "0.13.8" @@ -1617,6 +1624,15 @@ version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.15" diff --git a/apps/desktop/desktop_native/Cargo.toml b/apps/desktop/desktop_native/Cargo.toml index 26f791fd660..86eb507a6c1 100644 --- a/apps/desktop/desktop_native/Cargo.toml +++ b/apps/desktop/desktop_native/Cargo.toml @@ -39,6 +39,7 @@ futures = "=0.3.31" hex = "=0.4.3" homedir = "=0.3.6" interprocess = "=2.2.1" +itertools = "=0.14.0" libc = "=0.2.178" linux-keyutils = "=0.2.4" memsec = "=0.7.0" diff --git a/apps/desktop/desktop_native/autotype/Cargo.toml b/apps/desktop/desktop_native/autotype/Cargo.toml index 580df30e72d..6bf3218d98a 100644 --- a/apps/desktop/desktop_native/autotype/Cargo.toml +++ b/apps/desktop/desktop_native/autotype/Cargo.toml @@ -9,6 +9,7 @@ publish.workspace = true anyhow = { workspace = true } [target.'cfg(windows)'.dependencies] +itertools.workspace = true mockall = "=0.14.0" serial_test = "=3.2.0" tracing.workspace = true diff --git a/apps/desktop/desktop_native/autotype/src/lib.rs b/apps/desktop/desktop_native/autotype/src/lib.rs index c87fea23b60..4b9e65180e6 100644 --- a/apps/desktop/desktop_native/autotype/src/lib.rs +++ b/apps/desktop/desktop_native/autotype/src/lib.rs @@ -28,6 +28,6 @@ pub fn get_foreground_window_title() -> Result<String> { /// This function returns an `anyhow::Error` if there is any /// issue in typing the input. Detailed reasons will /// vary based on platform implementation. -pub fn type_input(input: Vec<u16>, keyboard_shortcut: Vec<String>) -> Result<()> { +pub fn type_input(input: &[u16], keyboard_shortcut: &[String]) -> Result<()> { windowing::type_input(input, keyboard_shortcut) } diff --git a/apps/desktop/desktop_native/autotype/src/linux.rs b/apps/desktop/desktop_native/autotype/src/linux.rs index 9fda0ed9e33..e7b0ee8117e 100644 --- a/apps/desktop/desktop_native/autotype/src/linux.rs +++ b/apps/desktop/desktop_native/autotype/src/linux.rs @@ -2,6 +2,6 @@ pub fn get_foreground_window_title() -> anyhow::Result<String> { todo!("Bitwarden does not yet support Linux autotype"); } -pub fn type_input(_input: Vec<u16>, _keyboard_shortcut: Vec<String>) -> anyhow::Result<()> { +pub fn type_input(_input: &[u16], _keyboard_shortcut: &[String]) -> anyhow::Result<()> { todo!("Bitwarden does not yet support Linux autotype"); } diff --git a/apps/desktop/desktop_native/autotype/src/macos.rs b/apps/desktop/desktop_native/autotype/src/macos.rs index c6681a3291e..56995a7f810 100644 --- a/apps/desktop/desktop_native/autotype/src/macos.rs +++ b/apps/desktop/desktop_native/autotype/src/macos.rs @@ -2,6 +2,6 @@ pub fn get_foreground_window_title() -> anyhow::Result<String> { todo!("Bitwarden does not yet support macOS autotype"); } -pub fn type_input(_input: Vec<u16>, _keyboard_shortcut: Vec<String>) -> anyhow::Result<()> { +pub fn type_input(_input: &[u16], _keyboard_shortcut: &[String]) -> anyhow::Result<()> { todo!("Bitwarden does not yet support macOS autotype"); } diff --git a/apps/desktop/desktop_native/autotype/src/windows/mod.rs b/apps/desktop/desktop_native/autotype/src/windows/mod.rs index 3ea63b2b8f4..9cd9bc0cbe5 100644 --- a/apps/desktop/desktop_native/autotype/src/windows/mod.rs +++ b/apps/desktop/desktop_native/autotype/src/windows/mod.rs @@ -1,6 +1,10 @@ use anyhow::Result; +use itertools::Itertools; use tracing::debug; -use windows::Win32::Foundation::{GetLastError, SetLastError, WIN32_ERROR}; +use windows::Win32::{ + Foundation::{GetLastError, SetLastError, WIN32_ERROR}, + UI::Input::KeyboardAndMouse::INPUT, +}; mod type_input; mod window_title; @@ -12,7 +16,7 @@ const WIN32_SUCCESS: WIN32_ERROR = WIN32_ERROR(0); /// win32 errors. #[cfg_attr(test, mockall::automock)] trait ErrorOperations { - /// https://learn.microsoft.com/en-us/windows/win32/api/errhandlingapi/nf-errhandlingapi-setlasterror + /// <https://learn.microsoft.com/en-us/windows/win32/api/errhandlingapi/nf-errhandlingapi-setlasterror> fn set_last_error(err: u32) { debug!(err, "Calling SetLastError"); unsafe { @@ -20,7 +24,7 @@ trait ErrorOperations { } } - /// https://learn.microsoft.com/en-us/windows/win32/api/errhandlingapi/nf-errhandlingapi-getlasterror + /// <https://learn.microsoft.com/en-us/windows/win32/api/errhandlingapi/nf-errhandlingapi-getlasterror> fn get_last_error() -> WIN32_ERROR { let last_err = unsafe { GetLastError() }; debug!("GetLastError(): {}", last_err.to_hresult().message()); @@ -36,6 +40,23 @@ pub fn get_foreground_window_title() -> Result<String> { window_title::get_foreground_window_title() } -pub fn type_input(input: Vec<u16>, keyboard_shortcut: Vec<String>) -> Result<()> { - type_input::type_input(input, keyboard_shortcut) +/// `KeyboardShortcutInput` is an `INPUT` of one of the valid shortcut keys: +/// - Control +/// - Alt +/// - Super +/// - Shift +/// - \[a-z\]\[A-Z\] +struct KeyboardShortcutInput(INPUT); + +pub fn type_input(input: &[u16], keyboard_shortcut: &[String]) -> Result<()> { + debug!(?keyboard_shortcut, "type_input() called."); + + // convert the raw string input to Windows input and error + // if any key is not a valid keyboard shortcut input + let keyboard_shortcut: Vec<KeyboardShortcutInput> = keyboard_shortcut + .iter() + .map(|s| KeyboardShortcutInput::try_from(s.as_str())) + .try_collect()?; + + type_input::type_input(input, &keyboard_shortcut) } diff --git a/apps/desktop/desktop_native/autotype/src/windows/type_input.rs b/apps/desktop/desktop_native/autotype/src/windows/type_input.rs index b2f4c6b82df..b62dd7290d1 100644 --- a/apps/desktop/desktop_native/autotype/src/windows/type_input.rs +++ b/apps/desktop/desktop_native/autotype/src/windows/type_input.rs @@ -5,7 +5,15 @@ use windows::Win32::UI::Input::KeyboardAndMouse::{ VIRTUAL_KEY, }; -use super::{ErrorOperations, Win32ErrorOperations}; +use super::{ErrorOperations, KeyboardShortcutInput, Win32ErrorOperations}; + +const SHIFT_KEY_STR: &str = "Shift"; +const CONTROL_KEY_STR: &str = "Control"; +const ALT_KEY_STR: &str = "Alt"; +const LEFT_WINDOWS_KEY_STR: &str = "Super"; + +const IS_VIRTUAL_KEY: bool = true; +const IS_REAL_KEY: bool = false; /// `InputOperations` provides an interface to Window32 API for /// working with inputs. @@ -13,7 +21,7 @@ use super::{ErrorOperations, Win32ErrorOperations}; trait InputOperations { /// Attempts to type the provided input wherever the user's cursor is. /// - /// https://learn.microsoft.com/en-in/windows/win32/api/winuser/nf-winuser-sendinput + /// <https://learn.microsoft.com/en-in/windows/win32/api/winuser/nf-winuser-sendinput> fn send_input(inputs: &[INPUT]) -> u32; } @@ -21,8 +29,11 @@ struct Win32InputOperations; impl InputOperations for Win32InputOperations { fn send_input(inputs: &[INPUT]) -> u32 { - const INPUT_STRUCT_SIZE: i32 = std::mem::size_of::<INPUT>() as i32; - let insert_count = unsafe { SendInput(inputs, INPUT_STRUCT_SIZE) }; + const INPUT_STRUCT_SIZE: usize = std::mem::size_of::<INPUT>(); + + let size = i32::try_from(INPUT_STRUCT_SIZE).expect("INPUT size to fit in i32"); + + let insert_count = unsafe { SendInput(inputs, size) }; debug!(insert_count, "SendInput() called."); @@ -33,40 +44,37 @@ impl InputOperations for Win32InputOperations { /// Attempts to type the input text wherever the user's cursor is. /// /// `input` must be a vector of utf-16 encoded characters to insert. -/// `keyboard_shortcut` must be a vector of Strings, where valid shortcut keys: Control, Alt, Super, -/// Shift, letters a - Z +/// `keyboard_shortcut` is a vector of valid shortcut keys. /// -/// https://learn.microsoft.com/en-in/windows/win32/api/winuser/nf-winuser-sendinput -pub(super) fn type_input(input: Vec<u16>, keyboard_shortcut: Vec<String>) -> Result<()> { +/// <https://learn.microsoft.com/en-in/windows/win32/api/winuser/nf-winuser-sendinput> +pub(super) fn type_input(input: &[u16], keyboard_shortcut: &[KeyboardShortcutInput]) -> Result<()> { // the length of this vec is always shortcut keys to release + (2x length of input chars) let mut keyboard_inputs: Vec<INPUT> = Vec::with_capacity(keyboard_shortcut.len() + (input.len() * 2)); - debug!(?keyboard_shortcut, "Converting keyboard shortcut to input."); - - // Add key "up" inputs for the shortcut - for key in keyboard_shortcut { - keyboard_inputs.push(convert_shortcut_key_to_up_input(key)?); + // insert the keyboard shortcut + for shortcut in keyboard_shortcut { + keyboard_inputs.push(shortcut.0); } - add_input(&input, &mut keyboard_inputs); + add_input(input, &mut keyboard_inputs); - send_input::<Win32InputOperations, Win32ErrorOperations>(keyboard_inputs) + send_input::<Win32InputOperations, Win32ErrorOperations>(&keyboard_inputs) } // Add key "down" and "up" inputs for the input // (currently in this form: {username}/t{password}) fn add_input(input: &[u16], keyboard_inputs: &mut Vec<INPUT>) { - const TAB_KEY: u8 = 9; + const TAB_KEY: u16 = 9; for i in input { - let next_down_input = if *i == TAB_KEY.into() { - build_virtual_key_input(InputKeyPress::Down, *i as u8) + let next_down_input = if *i == TAB_KEY { + build_virtual_key_input(InputKeyPress::Down, *i) } else { build_unicode_input(InputKeyPress::Down, *i) }; - let next_up_input = if *i == TAB_KEY.into() { - build_virtual_key_input(InputKeyPress::Up, *i as u8) + let next_up_input = if *i == TAB_KEY { + build_virtual_key_input(InputKeyPress::Up, *i) } else { build_unicode_input(InputKeyPress::Up, *i) }; @@ -76,26 +84,27 @@ fn add_input(input: &[u16], keyboard_inputs: &mut Vec<INPUT>) { } } -/// Converts a valid shortcut key to an "up" keyboard input. -/// -/// `input` must be a valid shortcut key: Control, Alt, Super, Shift, letters [a-z][A-Z] -fn convert_shortcut_key_to_up_input(key: String) -> Result<INPUT> { - const SHIFT_KEY: u8 = 0x10; - const SHIFT_KEY_STR: &str = "Shift"; - const CONTROL_KEY: u8 = 0x11; - const CONTROL_KEY_STR: &str = "Control"; - const ALT_KEY: u8 = 0x12; - const ALT_KEY_STR: &str = "Alt"; - const LEFT_WINDOWS_KEY: u8 = 0x5B; - const LEFT_WINDOWS_KEY_STR: &str = "Super"; +impl TryFrom<&str> for KeyboardShortcutInput { + type Error = anyhow::Error; - Ok(match key.as_str() { - SHIFT_KEY_STR => build_virtual_key_input(InputKeyPress::Up, SHIFT_KEY), - CONTROL_KEY_STR => build_virtual_key_input(InputKeyPress::Up, CONTROL_KEY), - ALT_KEY_STR => build_virtual_key_input(InputKeyPress::Up, ALT_KEY), - LEFT_WINDOWS_KEY_STR => build_virtual_key_input(InputKeyPress::Up, LEFT_WINDOWS_KEY), - _ => build_unicode_input(InputKeyPress::Up, get_alphabetic_hotkey(key)?), - }) + fn try_from(key: &str) -> std::result::Result<Self, Self::Error> { + const SHIFT_KEY: u16 = 0x10; + const CONTROL_KEY: u16 = 0x11; + const ALT_KEY: u16 = 0x12; + const LEFT_WINDOWS_KEY: u16 = 0x5B; + + // the modifier keys are using the Up keypress variant because the user has already + // pressed those keys in order to trigger the feature. + let input = match key { + SHIFT_KEY_STR => build_virtual_key_input(InputKeyPress::Up, SHIFT_KEY), + CONTROL_KEY_STR => build_virtual_key_input(InputKeyPress::Up, CONTROL_KEY), + ALT_KEY_STR => build_virtual_key_input(InputKeyPress::Up, ALT_KEY), + LEFT_WINDOWS_KEY_STR => build_virtual_key_input(InputKeyPress::Up, LEFT_WINDOWS_KEY), + _ => build_unicode_input(InputKeyPress::Up, get_alphabetic_hotkey(key)?), + }; + + Ok(KeyboardShortcutInput(input)) + } } /// Given a letter that is a String, get the utf16 encoded @@ -105,7 +114,7 @@ fn convert_shortcut_key_to_up_input(key: String) -> Result<INPUT> { /// Because we only accept [a-z][A-Z], the decimal u16 /// cast of the letter is safe because the unicode code point /// of these characters fits in a u16. -fn get_alphabetic_hotkey(letter: String) -> Result<u16> { +fn get_alphabetic_hotkey(letter: &str) -> Result<u16> { if letter.len() != 1 { error!( len = letter.len(), @@ -135,23 +144,28 @@ fn get_alphabetic_hotkey(letter: String) -> Result<u16> { } /// An input key can be either pressed (down), or released (up). +#[derive(Copy, Clone)] enum InputKeyPress { Down, Up, } -/// A function for easily building keyboard unicode INPUT structs used in SendInput(). -/// -/// Before modifying this function, make sure you read the SendInput() documentation: -/// https://learn.microsoft.com/en-in/windows/win32/api/winuser/nf-winuser-sendinput -fn build_unicode_input(key_press: InputKeyPress, character: u16) -> INPUT { +/// Before modifying this function, make sure you read the `SendInput()` documentation: +/// <https://learn.microsoft.com/en-in/windows/win32/api/winuser/nf-winuser-sendinput> +/// <https://learn.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes> +fn build_input(key_press: InputKeyPress, character: u16, is_virtual: bool) -> INPUT { + let (w_vk, w_scan) = if is_virtual { + (VIRTUAL_KEY(character), 0) + } else { + (VIRTUAL_KEY::default(), character) + }; match key_press { InputKeyPress::Down => INPUT { r#type: INPUT_KEYBOARD, Anonymous: INPUT_0 { ki: KEYBDINPUT { - wVk: Default::default(), - wScan: character, + wVk: w_vk, + wScan: w_scan, dwFlags: KEYEVENTF_UNICODE, time: 0, dwExtraInfo: 0, @@ -162,8 +176,8 @@ fn build_unicode_input(key_press: InputKeyPress, character: u16) -> INPUT { r#type: INPUT_KEYBOARD, Anonymous: INPUT_0 { ki: KEYBDINPUT { - wVk: Default::default(), - wScan: character, + wVk: w_vk, + wScan: w_scan, dwFlags: KEYEVENTF_KEYUP | KEYEVENTF_UNICODE, time: 0, dwExtraInfo: 0, @@ -173,53 +187,29 @@ fn build_unicode_input(key_press: InputKeyPress, character: u16) -> INPUT { } } -/// A function for easily building keyboard virtual-key INPUT structs used in SendInput(). -/// -/// Before modifying this function, make sure you read the SendInput() documentation: -/// https://learn.microsoft.com/en-in/windows/win32/api/winuser/nf-winuser-sendinput -/// https://learn.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes -fn build_virtual_key_input(key_press: InputKeyPress, virtual_key: u8) -> INPUT { - match key_press { - InputKeyPress::Down => INPUT { - r#type: INPUT_KEYBOARD, - Anonymous: INPUT_0 { - ki: KEYBDINPUT { - wVk: VIRTUAL_KEY(virtual_key as u16), - wScan: Default::default(), - dwFlags: Default::default(), - time: 0, - dwExtraInfo: 0, - }, - }, - }, - InputKeyPress::Up => INPUT { - r#type: INPUT_KEYBOARD, - Anonymous: INPUT_0 { - ki: KEYBDINPUT { - wVk: VIRTUAL_KEY(virtual_key as u16), - wScan: Default::default(), - dwFlags: KEYEVENTF_KEYUP, - time: 0, - dwExtraInfo: 0, - }, - }, - }, - } +/// A function for easily building keyboard unicode `INPUT` structs used in `SendInput()`. +fn build_unicode_input(key_press: InputKeyPress, character: u16) -> INPUT { + build_input(key_press, character, IS_REAL_KEY) } -fn send_input<I, E>(inputs: Vec<INPUT>) -> Result<()> +/// A function for easily building keyboard virtual-key `INPUT` structs used in `SendInput()`. +fn build_virtual_key_input(key_press: InputKeyPress, character: u16) -> INPUT { + build_input(key_press, character, IS_VIRTUAL_KEY) +} + +fn send_input<I, E>(inputs: &[INPUT]) -> Result<()> where I: InputOperations, E: ErrorOperations, { - let insert_count = I::send_input(&inputs); + let insert_count = I::send_input(inputs); if insert_count == 0 { let last_err = E::get_last_error().to_hresult().message(); error!(GetLastError = %last_err, "SendInput sent 0 inputs. Input was blocked by another thread."); return Err(anyhow!("SendInput sent 0 inputs. Input was blocked by another thread. GetLastError: {last_err}")); - } else if insert_count != inputs.len() as u32 { + } else if insert_count != u32::try_from(inputs.len()).expect("to convert inputs len to u32") { let last_err = E::get_last_error().to_hresult().message(); error!(sent = %insert_count, expected = inputs.len(), GetLastError = %last_err, "SendInput sent does not match expected." @@ -237,8 +227,9 @@ where mod tests { //! For the mocking of the traits that are static methods, we need to use the `serial_test` //! crate in order to mock those, since the mock expectations set have to be global in - //! absence of a `self`. More info: https://docs.rs/mockall/latest/mockall/#static-methods + //! absence of a `self`. More info: <https://docs.rs/mockall/latest/mockall/#static-methods> + use itertools::Itertools; use serial_test::serial; use windows::Win32::Foundation::WIN32_ERROR; @@ -249,7 +240,7 @@ mod tests { fn get_alphabetic_hot_key_succeeds() { for c in ('a'..='z').chain('A'..='Z') { let letter = c.to_string(); - let converted = get_alphabetic_hotkey(letter).unwrap(); + let converted = get_alphabetic_hotkey(&letter).unwrap(); assert_eq!(converted, c as u16); } } @@ -258,14 +249,14 @@ mod tests { #[should_panic = "Final keyboard shortcut key should be a single character: foo"] fn get_alphabetic_hot_key_fail_not_single_char() { let letter = String::from("foo"); - get_alphabetic_hotkey(letter).unwrap(); + get_alphabetic_hotkey(&letter).unwrap(); } #[test] #[should_panic = "Letter is not ASCII Alphabetic ([a-z][A-Z]): '}'"] fn get_alphabetic_hot_key_fail_not_alphabetic() { let letter = String::from("}"); - get_alphabetic_hotkey(letter).unwrap(); + get_alphabetic_hotkey(&letter).unwrap(); } #[test] @@ -275,7 +266,7 @@ mod tests { ctxi.checkpoint(); ctxi.expect().returning(|_| 1); - send_input::<MockInputOperations, MockErrorOperations>(vec![build_unicode_input( + send_input::<MockInputOperations, MockErrorOperations>(&[build_unicode_input( InputKeyPress::Up, 0, )]) @@ -284,6 +275,29 @@ mod tests { drop(ctxi); } + #[test] + #[serial] + fn keyboard_shortcut_conversion_succeeds() { + let keyboard_shortcut = [CONTROL_KEY_STR, SHIFT_KEY_STR, "B"]; + let _: Vec<KeyboardShortcutInput> = keyboard_shortcut + .iter() + .map(|s| KeyboardShortcutInput::try_from(*s)) + .try_collect() + .unwrap(); + } + + #[test] + #[serial] + #[should_panic = "Letter is not ASCII Alphabetic ([a-z][A-Z]): '1'"] + fn keyboard_shortcut_conversion_fails_invalid_key() { + let keyboard_shortcut = [CONTROL_KEY_STR, SHIFT_KEY_STR, "1"]; + let _: Vec<KeyboardShortcutInput> = keyboard_shortcut + .iter() + .map(|s| KeyboardShortcutInput::try_from(*s)) + .try_collect() + .unwrap(); + } + #[test] #[serial] #[should_panic( @@ -298,7 +312,7 @@ mod tests { ctxge.checkpoint(); ctxge.expect().returning(|| WIN32_ERROR(1)); - send_input::<MockInputOperations, MockErrorOperations>(vec![build_unicode_input( + send_input::<MockInputOperations, MockErrorOperations>(&[build_unicode_input( InputKeyPress::Up, 0, )]) @@ -320,7 +334,7 @@ mod tests { ctxge.checkpoint(); ctxge.expect().returning(|| WIN32_ERROR(1)); - send_input::<MockInputOperations, MockErrorOperations>(vec![build_unicode_input( + send_input::<MockInputOperations, MockErrorOperations>(&[build_unicode_input( InputKeyPress::Up, 0, )]) diff --git a/apps/desktop/desktop_native/autotype/src/windows/window_title.rs b/apps/desktop/desktop_native/autotype/src/windows/window_title.rs index 4fc0b3bb3ad..12e6501a7c5 100644 --- a/apps/desktop/desktop_native/autotype/src/windows/window_title.rs +++ b/apps/desktop/desktop_native/autotype/src/windows/window_title.rs @@ -11,10 +11,10 @@ use super::{ErrorOperations, Win32ErrorOperations, WIN32_SUCCESS}; #[cfg_attr(test, mockall::automock)] trait WindowHandleOperations { - // https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getwindowtextlengthw + // <https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getwindowtextlengthw> fn get_window_text_length_w(&self) -> Result<i32>; - // https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getwindowtextw + // <https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getwindowtextw> fn get_window_text_w(&self, buffer: &mut Vec<u16>) -> Result<i32>; } @@ -70,7 +70,7 @@ pub(super) fn get_foreground_window_title() -> Result<String> { /// Retrieves the foreground window handle and validates it. fn get_foreground_window_handle() -> Result<WindowHandle> { - // https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getforegroundwindow + // <https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getforegroundwindow> let handle = unsafe { GetForegroundWindow() }; debug!("GetForegroundWindow() called."); @@ -87,7 +87,7 @@ fn get_foreground_window_handle() -> Result<WindowHandle> { /// /// # Errors /// -/// - If the length zero and GetLastError() != 0, return the GetLastError() message. +/// - If the length zero and `GetLastError()` != 0, return the `GetLastError()` message. fn get_window_title_length<H, E>(window_handle: &H) -> Result<usize> where H: WindowHandleOperations, @@ -128,7 +128,7 @@ where /// # Errors /// /// - If the actual window title length (what the win32 API declares was written into the buffer), -/// is length zero and GetLastError() != 0 , return the GetLastError() message. +/// is length zero and `GetLastError()` != 0 , return the `GetLastError()` message. fn get_window_title<H, E>(window_handle: &H, expected_title_length: usize) -> Result<String> where H: WindowHandleOperations, @@ -140,7 +140,7 @@ where // The upstream will make a contains comparison on what we return, so an empty string // will not result on a match. warn!("Window title length is zero."); - return Ok(String::from("")); + return Ok(String::new()); } let mut buffer: Vec<u16> = vec![0; expected_title_length + 1]; // add extra space for the null character @@ -171,7 +171,7 @@ where mod tests { //! For the mocking of the traits that are static methods, we need to use the `serial_test` //! crate in order to mock those, since the mock expectations set have to be global in - //! absence of a `self`. More info: https://docs.rs/mockall/latest/mockall/#static-methods + //! absence of a `self`. More info: <https://docs.rs/mockall/latest/mockall/#static-methods> use mockall::predicate; use serial_test::serial; diff --git a/apps/desktop/desktop_native/napi/src/lib.rs b/apps/desktop/desktop_native/napi/src/lib.rs index fe084349501..588f757631c 100644 --- a/apps/desktop/desktop_native/napi/src/lib.rs +++ b/apps/desktop/desktop_native/napi/src/lib.rs @@ -1241,8 +1241,7 @@ pub mod autotype { input: Vec<u16>, keyboard_shortcut: Vec<String>, ) -> napi::Result<(), napi::Status> { - autotype::type_input(input, keyboard_shortcut).map_err(|_| { - napi::Error::from_reason("Autotype Error: failed to type input".to_string()) - }) + autotype::type_input(&input, &keyboard_shortcut) + .map_err(|e| napi::Error::from_reason(format!("Autotype Error: {e}"))) } } From 4e1cca132d5874b33e6a9699d12def0c474012e5 Mon Sep 17 00:00:00 2001 From: Daniel James Smith <2670567+djsmith85@users.noreply.github.com> Date: Mon, 29 Dec 2025 16:10:34 +0100 Subject: [PATCH 164/188] Bump year in copyright (#18132) Co-authored-by: Daniel James Smith <djsmith85@users.noreply.github.com> --- apps/browser/src/safari/desktop/Info.plist | 2 +- apps/browser/src/safari/safari/Info.plist | 2 +- apps/cli/stores/chocolatey/bitwarden-cli.nuspec | 2 +- apps/desktop/electron-builder.beta.json | 2 +- apps/desktop/electron-builder.json | 2 +- apps/desktop/stores/chocolatey/bitwarden.nuspec | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/browser/src/safari/desktop/Info.plist b/apps/browser/src/safari/desktop/Info.plist index b687d9d2f3a..94542609351 100644 --- a/apps/browser/src/safari/desktop/Info.plist +++ b/apps/browser/src/safari/desktop/Info.plist @@ -25,7 +25,7 @@ <key>LSMinimumSystemVersion</key> <string>$(MACOSX_DEPLOYMENT_TARGET)</string> <key>NSHumanReadableCopyright</key> - <string>Copyright © 2015-2025 Bitwarden Inc. All rights reserved.</string> + <string>Copyright © 2015-2026 Bitwarden Inc. All rights reserved.</string> <key>NSMainStoryboardFile</key> <string>Main</string> <key>NSPrincipalClass</key> diff --git a/apps/browser/src/safari/safari/Info.plist b/apps/browser/src/safari/safari/Info.plist index 95172846758..68b872610e9 100644 --- a/apps/browser/src/safari/safari/Info.plist +++ b/apps/browser/src/safari/safari/Info.plist @@ -30,7 +30,7 @@ <string>$(PRODUCT_MODULE_NAME).SafariWebExtensionHandler</string> </dict> <key>NSHumanReadableCopyright</key> - <string>Copyright © 2015-2025 Bitwarden Inc. All rights reserved.</string> + <string>Copyright © 2015-2026 Bitwarden Inc. All rights reserved.</string> <key>NSHumanReadableDescription</key> <string>A secure and free password manager for all of your devices.</string> <key>SFSafariAppExtensionBundleIdentifiersToReplace</key> diff --git a/apps/cli/stores/chocolatey/bitwarden-cli.nuspec b/apps/cli/stores/chocolatey/bitwarden-cli.nuspec index f7f86bc843f..9552ccc282c 100644 --- a/apps/cli/stores/chocolatey/bitwarden-cli.nuspec +++ b/apps/cli/stores/chocolatey/bitwarden-cli.nuspec @@ -10,7 +10,7 @@ <authors>Bitwarden Inc.</authors> <projectUrl>https://bitwarden.com/</projectUrl> <iconUrl>https://raw.githubusercontent.com/bitwarden/brand/master/icons/256x256.png</iconUrl> - <copyright>Copyright © 2015-2025 Bitwarden Inc.</copyright> + <copyright>Copyright © 2015-2026 Bitwarden Inc.</copyright> <projectSourceUrl>https://github.com/bitwarden/clients/</projectSourceUrl> <docsUrl>https://help.bitwarden.com/article/cli/</docsUrl> <bugTrackerUrl>https://github.com/bitwarden/clients/issues</bugTrackerUrl> diff --git a/apps/desktop/electron-builder.beta.json b/apps/desktop/electron-builder.beta.json index 630a956560d..0c95c7f01a6 100644 --- a/apps/desktop/electron-builder.beta.json +++ b/apps/desktop/electron-builder.beta.json @@ -5,7 +5,7 @@ "productName": "Bitwarden Beta", "appId": "com.bitwarden.desktop.beta", "buildDependenciesFromSource": true, - "copyright": "Copyright © 2015-2025 Bitwarden Inc.", + "copyright": "Copyright © 2015-2026 Bitwarden Inc.", "directories": { "buildResources": "resources", "output": "dist", diff --git a/apps/desktop/electron-builder.json b/apps/desktop/electron-builder.json index f979df81fd0..a4e1c44dc5b 100644 --- a/apps/desktop/electron-builder.json +++ b/apps/desktop/electron-builder.json @@ -5,7 +5,7 @@ "productName": "Bitwarden", "appId": "com.bitwarden.desktop", "buildDependenciesFromSource": true, - "copyright": "Copyright © 2015-2025 Bitwarden Inc.", + "copyright": "Copyright © 2015-2026 Bitwarden Inc.", "directories": { "buildResources": "resources", "output": "dist", diff --git a/apps/desktop/stores/chocolatey/bitwarden.nuspec b/apps/desktop/stores/chocolatey/bitwarden.nuspec index 450fa734736..567002d0d8c 100644 --- a/apps/desktop/stores/chocolatey/bitwarden.nuspec +++ b/apps/desktop/stores/chocolatey/bitwarden.nuspec @@ -10,7 +10,7 @@ <authors>Bitwarden Inc.</authors> <projectUrl>https://bitwarden.com/</projectUrl> <iconUrl>https://raw.githubusercontent.com/bitwarden/brand/master/icons/256x256.png</iconUrl> - <copyright>Copyright © 2015-2025 Bitwarden Inc.</copyright> + <copyright>Copyright © 2015-2026 Bitwarden Inc.</copyright> <projectSourceUrl>https://github.com/bitwarden/clients/</projectSourceUrl> <docsUrl>https://bitwarden.com/help/</docsUrl> <bugTrackerUrl>https://github.com/bitwarden/clients/issues</bugTrackerUrl> From e2a1cfcbe881c8eb0fb37e25ba403c8776583373 Mon Sep 17 00:00:00 2001 From: Jason Ng <jng@bitwarden.com> Date: Mon, 29 Dec 2025 10:11:12 -0500 Subject: [PATCH 165/188] [PM29951] add archive flag check to desktop vault-v2 (#18056) --- apps/desktop/src/vault/app/vault/vault-v2.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 6c4ebe13f14..730891f6dea 100644 --- a/apps/desktop/src/vault/app/vault/vault-v2.component.ts +++ b/apps/desktop/src/vault/app/vault/vault-v2.component.ts @@ -565,7 +565,7 @@ export class VaultV2Component<C extends CipherViewLike> } } - if (!cipher.organizationId && !cipher.isDeleted && !cipher.isArchived) { + if (userCanArchive && !cipher.isDeleted && !cipher.isArchived) { menu.push({ label: this.i18nService.t("archiveVerb"), click: async () => { From 146e2c0a12e6119aca5b807dba7de49ce6af0b14 Mon Sep 17 00:00:00 2001 From: Todd Martin <106564991+trmartin4@users.noreply.github.com> Date: Mon, 29 Dec 2025 11:35:56 -0500 Subject: [PATCH 166/188] chore(feature-flags): Remove notification on inactive and locked user feature flags --- libs/common/src/enums/feature-flag.enum.ts | 4 - ...ult-server-notifications.multiuser.spec.ts | 10 -- .../default-server-notifications.service.ts | 98 +++++-------------- 3 files changed, 26 insertions(+), 86 deletions(-) diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts index 837418f92cf..5a6eeebd001 100644 --- a/libs/common/src/enums/feature-flag.enum.ts +++ b/libs/common/src/enums/feature-flag.enum.ts @@ -70,8 +70,6 @@ export enum FeatureFlag { /* Platform */ IpcChannelFramework = "ipc-channel-framework", - InactiveUserServerNotification = "pm-25130-receive-push-notifications-for-inactive-users", - PushNotificationsWhenLocked = "pm-19388-push-notifications-when-locked", /* Innovation */ PM19148_InnovationArchive = "pm-19148-innovation-archive", @@ -157,8 +155,6 @@ export const DefaultFeatureFlagValue = { /* Platform */ [FeatureFlag.IpcChannelFramework]: FALSE, - [FeatureFlag.InactiveUserServerNotification]: FALSE, - [FeatureFlag.PushNotificationsWhenLocked]: FALSE, /* Innovation */ [FeatureFlag.PM19148_InnovationArchive]: FALSE, diff --git a/libs/common/src/platform/server-notifications/internal/default-server-notifications.multiuser.spec.ts b/libs/common/src/platform/server-notifications/internal/default-server-notifications.multiuser.spec.ts index 46178f62a07..7aacc783e65 100644 --- a/libs/common/src/platform/server-notifications/internal/default-server-notifications.multiuser.spec.ts +++ b/libs/common/src/platform/server-notifications/internal/default-server-notifications.multiuser.spec.ts @@ -5,7 +5,6 @@ import { BehaviorSubject, bufferCount, firstValueFrom, Subject, ObservedValueOf import { LogoutReason } from "@bitwarden/auth/common"; import { InternalPolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { AuthRequestAnsweringServiceAbstraction } from "@bitwarden/common/auth/abstractions/auth-request-answering/auth-request-answering.service.abstraction"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { mockAccountInfoWith } from "../../../../spec"; import { AccountService } from "../../../auth/abstractions/account.service"; @@ -130,15 +129,6 @@ describe("DefaultServerNotificationsService (multi-user)", () => { authRequestAnsweringService = mock<AuthRequestAnsweringServiceAbstraction>(); - configService = mock<ConfigService>(); - configService.getFeatureFlag$.mockImplementation((flag: FeatureFlag) => { - const flagValueByFlag: Partial<Record<FeatureFlag, boolean>> = { - [FeatureFlag.InactiveUserServerNotification]: true, - [FeatureFlag.PushNotificationsWhenLocked]: true, - }; - return new BehaviorSubject(flagValueByFlag[flag] ?? false) as any; - }); - policyService = mock<InternalPolicyService>(); defaultServerNotificationsService = new DefaultServerNotificationsService( diff --git a/libs/common/src/platform/server-notifications/internal/default-server-notifications.service.ts b/libs/common/src/platform/server-notifications/internal/default-server-notifications.service.ts index 5ee288351d5..5b026add1a2 100644 --- a/libs/common/src/platform/server-notifications/internal/default-server-notifications.service.ts +++ b/libs/common/src/platform/server-notifications/internal/default-server-notifications.service.ts @@ -71,48 +71,20 @@ export class DefaultServerNotificationsService implements ServerNotificationsSer private readonly configService: ConfigService, private readonly policyService: InternalPolicyService, ) { - this.notifications$ = this.configService - .getFeatureFlag$(FeatureFlag.InactiveUserServerNotification) - .pipe( - distinctUntilChanged(), - switchMap((inactiveUserServerNotificationEnabled) => { - if (inactiveUserServerNotificationEnabled) { - return this.accountService.accounts$.pipe( - map((accounts: Record<UserId, AccountInfo>): Set<UserId> => { - const validUserIds = Object.entries(accounts) - .filter( - ([_, accountInfo]) => accountInfo.email !== "" || accountInfo.emailVerified, - ) - .map(([userId, _]) => userId as UserId); - return new Set(validUserIds); - }), - trackedMerge((id: UserId) => { - return this.userNotifications$(id as UserId).pipe( - map( - (notification: NotificationResponse) => [notification, id as UserId] as const, - ), - ); - }), - ); - } - - return this.accountService.activeAccount$.pipe( - map((account) => account?.id), - distinctUntilChanged(), - switchMap((activeAccountId) => { - if (activeAccountId == null) { - // We don't emit server-notifications for inactive accounts currently - return EMPTY; - } - - return this.userNotifications$(activeAccountId).pipe( - map((notification) => [notification, activeAccountId] as const), - ); - }), - ); - }), - share(), // Multiple subscribers should only create a single connection to the server - ); + this.notifications$ = this.accountService.accounts$.pipe( + map((accounts: Record<UserId, AccountInfo>): Set<UserId> => { + const validUserIds = Object.entries(accounts) + .filter(([_, accountInfo]) => accountInfo.email !== "" || accountInfo.emailVerified) + .map(([userId, _]) => userId as UserId); + return new Set(validUserIds); + }), + trackedMerge((id: UserId) => { + return this.userNotifications$(id as UserId).pipe( + map((notification: NotificationResponse) => [notification, id as UserId] as const), + ); + }), + share(), // Multiple subscribers should only create a single connection to the server + ); } /** @@ -175,25 +147,13 @@ export class DefaultServerNotificationsService implements ServerNotificationsSer } private hasAccessToken$(userId: UserId) { - return this.configService.getFeatureFlag$(FeatureFlag.PushNotificationsWhenLocked).pipe( + return this.authService.authStatusFor$(userId).pipe( + map( + (authStatus) => + authStatus === AuthenticationStatus.Locked || + authStatus === AuthenticationStatus.Unlocked, + ), distinctUntilChanged(), - switchMap((featureFlagEnabled) => { - if (featureFlagEnabled) { - return this.authService.authStatusFor$(userId).pipe( - map( - (authStatus) => - authStatus === AuthenticationStatus.Locked || - authStatus === AuthenticationStatus.Unlocked, - ), - distinctUntilChanged(), - ); - } else { - return this.authService.authStatusFor$(userId).pipe( - map((authStatus) => authStatus === AuthenticationStatus.Unlocked), - distinctUntilChanged(), - ); - } - }), ); } @@ -208,19 +168,13 @@ export class DefaultServerNotificationsService implements ServerNotificationsSer return; } - if ( - await firstValueFrom( - this.configService.getFeatureFlag$(FeatureFlag.InactiveUserServerNotification), - ) - ) { - const activeAccountId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); + const activeAccountId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); - const isActiveUser = activeAccountId === userId; - if (!isActiveUser && !AllowedMultiUserNotificationTypes.has(notification.type)) { - return; - } + const notificationIsForActiveUser = activeAccountId === userId; + if (!notificationIsForActiveUser && !AllowedMultiUserNotificationTypes.has(notification.type)) { + return; } switch (notification.type) { From b7d2ce9d0eb8f8c2e1af1cc00c6a340f165fbf32 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 29 Dec 2025 12:03:32 -0500 Subject: [PATCH 167/188] [deps]: Update actions/checkout action to v6 (#17715) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Daniel James Smith <2670567+djsmith85@users.noreply.github.com> --- .../workflows/alert-ddg-files-modified.yml | 2 +- .github/workflows/auto-branch-updater.yml | 2 +- .github/workflows/build-browser.yml | 12 +++++------ .github/workflows/build-cli.yml | 8 ++++---- .github/workflows/build-desktop.yml | 20 +++++++++---------- .github/workflows/build-web.yml | 8 ++++---- .github/workflows/chromatic.yml | 2 +- .github/workflows/crowdin-pull.yml | 2 +- .github/workflows/lint-crowdin-config.yml | 2 +- .github/workflows/lint.yml | 4 ++-- .github/workflows/locales-lint.yml | 4 ++-- .github/workflows/nx.yml | 2 +- .github/workflows/publish-cli.yml | 6 +++--- .github/workflows/publish-desktop.yml | 6 +++--- .github/workflows/publish-web.yml | 4 ++-- .github/workflows/release-browser.yml | 4 ++-- .github/workflows/release-cli.yml | 2 +- .github/workflows/release-desktop.yml | 2 +- .github/workflows/release-web.yml | 2 +- .github/workflows/repository-management.yml | 4 ++-- .../workflows/sdk-breaking-change-check.yml | 2 +- .../workflows/test-browser-interactions.yml | 2 +- .github/workflows/test.yml | 8 ++++---- .github/workflows/version-auto-bump.yml | 2 +- 24 files changed, 56 insertions(+), 56 deletions(-) diff --git a/.github/workflows/alert-ddg-files-modified.yml b/.github/workflows/alert-ddg-files-modified.yml index 90c055a97b8..35eb0515c10 100644 --- a/.github/workflows/alert-ddg-files-modified.yml +++ b/.github/workflows/alert-ddg-files-modified.yml @@ -14,7 +14,7 @@ jobs: pull-requests: write steps: - name: Checkout code - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: fetch-depth: 0 persist-credentials: false diff --git a/.github/workflows/auto-branch-updater.yml b/.github/workflows/auto-branch-updater.yml index 02176b3169e..be9cd338e82 100644 --- a/.github/workflows/auto-branch-updater.yml +++ b/.github/workflows/auto-branch-updater.yml @@ -30,7 +30,7 @@ jobs: run: echo "branch=${GITHUB_REF#refs/heads/}" >> "$GITHUB_OUTPUT" - name: Checkout repo - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: ref: 'eu-web-${{ steps.setup.outputs.branch }}' fetch-depth: 0 diff --git a/.github/workflows/build-browser.yml b/.github/workflows/build-browser.yml index ab932c561ba..b5859516eaa 100644 --- a/.github/workflows/build-browser.yml +++ b/.github/workflows/build-browser.yml @@ -55,7 +55,7 @@ jobs: has_secrets: ${{ steps.check-secrets.outputs.has_secrets }} steps: - name: Check out repo - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: ref: ${{ github.event.pull_request.head.sha }} persist-credentials: false @@ -94,7 +94,7 @@ jobs: working-directory: apps/browser steps: - name: Check out repo - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: ref: ${{ github.event.pull_request.head.sha }} persist-credentials: false @@ -146,7 +146,7 @@ jobs: _NODE_VERSION: ${{ needs.setup.outputs.node_version }} steps: - name: Check out repo - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: ref: ${{ github.event.pull_request.head.sha }} persist-credentials: false @@ -254,7 +254,7 @@ jobs: artifact_name: "dist-opera-MV3" steps: - name: Check out repo - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: ref: ${{ github.event.pull_request.head.sha }} persist-credentials: false @@ -386,7 +386,7 @@ jobs: _NODE_VERSION: ${{ needs.setup.outputs.node_version }} steps: - name: Check out repo - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: ref: ${{ github.event.pull_request.head.sha }} persist-credentials: false @@ -542,7 +542,7 @@ jobs: - build-safari steps: - name: Check out repo - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: ref: ${{ github.event.pull_request.head.sha }} persist-credentials: false diff --git a/.github/workflows/build-cli.yml b/.github/workflows/build-cli.yml index 964cbc834c5..704a9810b27 100644 --- a/.github/workflows/build-cli.yml +++ b/.github/workflows/build-cli.yml @@ -59,7 +59,7 @@ jobs: has_secrets: ${{ steps.check-secrets.outputs.has_secrets }} steps: - name: Check out repo - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: ref: ${{ github.event.pull_request.head.sha }} persist-credentials: false @@ -114,7 +114,7 @@ jobs: steps: - name: Check out repo - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: ref: ${{ github.event.pull_request.head.sha }} persist-credentials: false @@ -311,7 +311,7 @@ jobs: _WIN_PKG_VERSION: 3.5 steps: - name: Check out repo - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: ref: ${{ github.event.pull_request.head.sha }} persist-credentials: false @@ -520,7 +520,7 @@ jobs: _PACKAGE_VERSION: ${{ needs.setup.outputs.package_version }} steps: - name: Check out repo - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: ref: ${{ github.event.pull_request.head.sha }} persist-credentials: false diff --git a/.github/workflows/build-desktop.yml b/.github/workflows/build-desktop.yml index 5a7703adb78..f3cdf80f710 100644 --- a/.github/workflows/build-desktop.yml +++ b/.github/workflows/build-desktop.yml @@ -55,7 +55,7 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Check out repo - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: ref: ${{ github.event.pull_request.head.sha }} persist-credentials: false @@ -88,7 +88,7 @@ jobs: working-directory: apps/desktop steps: - name: Check out repo - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: ref: ${{ github.event.pull_request.head.sha }} persist-credentials: true @@ -173,7 +173,7 @@ jobs: working-directory: apps/desktop steps: - name: Check out repo - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: fetch-depth: 1 ref: ${{ github.event.pull_request.head.sha }} @@ -343,7 +343,7 @@ jobs: working-directory: apps/desktop steps: - name: Check out repo - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: ref: ${{ github.event.pull_request.head.sha }} persist-credentials: false @@ -491,7 +491,7 @@ jobs: NODE_OPTIONS: --max_old_space_size=4096 steps: - name: Check out repo - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: ref: ${{ github.event.pull_request.head.sha }} persist-credentials: false @@ -759,7 +759,7 @@ jobs: NODE_OPTIONS: --max_old_space_size=4096 steps: - name: Check out repo - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: ref: ${{ github.event.pull_request.head.sha }} persist-credentials: false @@ -1004,7 +1004,7 @@ jobs: working-directory: apps/desktop steps: - name: Check out repo - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: ref: ${{ github.event.pull_request.head.sha }} persist-credentials: false @@ -1244,7 +1244,7 @@ jobs: working-directory: apps/desktop steps: - name: Check out repo - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: ref: ${{ github.event.pull_request.head.sha }} persist-credentials: false @@ -1519,7 +1519,7 @@ jobs: working-directory: apps/desktop steps: - name: Check out repo - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: ref: ${{ github.event.pull_request.head.sha }} persist-credentials: false @@ -1860,7 +1860,7 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Check out repo - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: ref: ${{ github.event.pull_request.head.sha }} persist-credentials: false diff --git a/.github/workflows/build-web.yml b/.github/workflows/build-web.yml index 02ab7727c24..7d302fb453b 100644 --- a/.github/workflows/build-web.yml +++ b/.github/workflows/build-web.yml @@ -64,7 +64,7 @@ jobs: has_secrets: ${{ steps.check-secrets.outputs.has_secrets }} steps: - name: Check out repo - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: ref: ${{ github.event.pull_request.head.sha }} persist-credentials: false @@ -144,7 +144,7 @@ jobs: _VERSION: ${{ needs.setup.outputs.version }} steps: - name: Check out repo - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: ref: ${{ github.event.pull_request.head.sha }} persist-credentials: false @@ -174,7 +174,7 @@ jobs: echo "server_ref=$SERVER_REF" >> "$GITHUB_OUTPUT" - name: Check out Server repo - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: path: server repository: bitwarden/server @@ -367,7 +367,7 @@ jobs: runs-on: ubuntu-24.04 steps: - name: Check out repo - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: ref: ${{ github.event.pull_request.head.sha }} persist-credentials: false diff --git a/.github/workflows/chromatic.yml b/.github/workflows/chromatic.yml index 44ea21276e2..c7d80b82baa 100644 --- a/.github/workflows/chromatic.yml +++ b/.github/workflows/chromatic.yml @@ -31,7 +31,7 @@ jobs: steps: - name: Check out repo - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: ref: ${{ github.event.pull_request.head.sha }} fetch-depth: 0 diff --git a/.github/workflows/crowdin-pull.yml b/.github/workflows/crowdin-pull.yml index 5475c4dd692..e99034c499a 100644 --- a/.github/workflows/crowdin-pull.yml +++ b/.github/workflows/crowdin-pull.yml @@ -58,7 +58,7 @@ jobs: permission-pull-requests: write # for generating pull requests - name: Checkout repo - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: token: ${{ steps.app-token.outputs.token }} persist-credentials: false diff --git a/.github/workflows/lint-crowdin-config.yml b/.github/workflows/lint-crowdin-config.yml index b0efeb50823..dff253a8da2 100644 --- a/.github/workflows/lint-crowdin-config.yml +++ b/.github/workflows/lint-crowdin-config.yml @@ -22,7 +22,7 @@ jobs: ] steps: - name: Check out repo - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: fetch-depth: 1 persist-credentials: false diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index b46204514b8..3aeb75dcbf6 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -31,7 +31,7 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Checkout repo - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: persist-credentials: false @@ -95,7 +95,7 @@ jobs: steps: - name: Checkout repo - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: persist-credentials: false diff --git a/.github/workflows/locales-lint.yml b/.github/workflows/locales-lint.yml index 8335d6aacad..e431854aea2 100644 --- a/.github/workflows/locales-lint.yml +++ b/.github/workflows/locales-lint.yml @@ -17,11 +17,11 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Checkout repo - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: persist-credentials: false - name: Checkout base branch repo - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: ref: ${{ github.event.pull_request.base.sha }} path: base diff --git a/.github/workflows/nx.yml b/.github/workflows/nx.yml index 0f01aa27899..1e23c31b033 100644 --- a/.github/workflows/nx.yml +++ b/.github/workflows/nx.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Checkout repository - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: fetch-depth: 0 persist-credentials: false diff --git a/.github/workflows/publish-cli.yml b/.github/workflows/publish-cli.yml index 8fcd1fe7c98..ef287b0de08 100644 --- a/.github/workflows/publish-cli.yml +++ b/.github/workflows/publish-cli.yml @@ -103,7 +103,7 @@ jobs: _PKG_VERSION: ${{ needs.setup.outputs.release_version }} steps: - name: Checkout repo - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: persist-credentials: false @@ -151,7 +151,7 @@ jobs: _PKG_VERSION: ${{ needs.setup.outputs.release_version }} steps: - name: Checkout repo - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: persist-credentials: false @@ -203,7 +203,7 @@ jobs: _PKG_VERSION: ${{ needs.setup.outputs.release_version }} steps: - name: Checkout repo - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: persist-credentials: false diff --git a/.github/workflows/publish-desktop.yml b/.github/workflows/publish-desktop.yml index 3d512d49559..f013abbbb3b 100644 --- a/.github/workflows/publish-desktop.yml +++ b/.github/workflows/publish-desktop.yml @@ -204,7 +204,7 @@ jobs: _RELEASE_TAG: ${{ needs.setup.outputs.tag_name }} steps: - name: Checkout Repo - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: persist-credentials: false @@ -258,7 +258,7 @@ jobs: _RELEASE_TAG: ${{ needs.setup.outputs.tag_name }} steps: - name: Checkout Repo - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: persist-credentials: false @@ -315,7 +315,7 @@ jobs: _RELEASE_TAG: ${{ needs.setup.outputs.tag_name }} steps: - name: Checkout repo - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: persist-credentials: false diff --git a/.github/workflows/publish-web.yml b/.github/workflows/publish-web.yml index fb1de5a1bc5..be0087800f7 100644 --- a/.github/workflows/publish-web.yml +++ b/.github/workflows/publish-web.yml @@ -28,7 +28,7 @@ jobs: contents: read steps: - name: Checkout repo - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: persist-credentials: false @@ -74,7 +74,7 @@ jobs: echo "Github Release Option: $_RELEASE_OPTION" - name: Checkout repo - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: persist-credentials: false diff --git a/.github/workflows/release-browser.yml b/.github/workflows/release-browser.yml index ff5fb669faf..f7e45919308 100644 --- a/.github/workflows/release-browser.yml +++ b/.github/workflows/release-browser.yml @@ -28,7 +28,7 @@ jobs: release_version: ${{ steps.version.outputs.version }} steps: - name: Checkout repo - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: persist-credentials: false @@ -61,7 +61,7 @@ jobs: contents: read steps: - name: Checkout repo - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: persist-credentials: false diff --git a/.github/workflows/release-cli.yml b/.github/workflows/release-cli.yml index 08045b8d3c7..3f7b7e326d9 100644 --- a/.github/workflows/release-cli.yml +++ b/.github/workflows/release-cli.yml @@ -29,7 +29,7 @@ jobs: release_version: ${{ steps.version.outputs.version }} steps: - name: Checkout repo - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: persist-credentials: false diff --git a/.github/workflows/release-desktop.yml b/.github/workflows/release-desktop.yml index 2239cb1268f..ec529d7b4d8 100644 --- a/.github/workflows/release-desktop.yml +++ b/.github/workflows/release-desktop.yml @@ -31,7 +31,7 @@ jobs: release_channel: ${{ steps.release_channel.outputs.channel }} steps: - name: Checkout repo - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: persist-credentials: false diff --git a/.github/workflows/release-web.yml b/.github/workflows/release-web.yml index fc0ac340234..f6feb3386a7 100644 --- a/.github/workflows/release-web.yml +++ b/.github/workflows/release-web.yml @@ -25,7 +25,7 @@ jobs: tag_version: ${{ steps.version.outputs.tag }} steps: - name: Checkout repo - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: persist-credentials: false diff --git a/.github/workflows/repository-management.yml b/.github/workflows/repository-management.yml index 0a343be878c..b2edf0171db 100644 --- a/.github/workflows/repository-management.yml +++ b/.github/workflows/repository-management.yml @@ -105,7 +105,7 @@ jobs: permission-contents: write # for committing and pushing to current branch - name: Check out branch - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: ref: ${{ github.ref }} token: ${{ steps.app-token.outputs.token }} @@ -471,7 +471,7 @@ jobs: permission-contents: write # for creating and pushing new branch - name: Check out target ref - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: ref: ${{ inputs.target_ref }} token: ${{ steps.app-token.outputs.token }} diff --git a/.github/workflows/sdk-breaking-change-check.yml b/.github/workflows/sdk-breaking-change-check.yml index 14547b3942f..ecc803ebd5c 100644 --- a/.github/workflows/sdk-breaking-change-check.yml +++ b/.github/workflows/sdk-breaking-change-check.yml @@ -64,7 +64,7 @@ jobs: uses: bitwarden/gh-actions/azure-logout@main - name: Check out clients repository - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: persist-credentials: false diff --git a/.github/workflows/test-browser-interactions.yml b/.github/workflows/test-browser-interactions.yml index c8f4c959c52..6e236f2352c 100644 --- a/.github/workflows/test-browser-interactions.yml +++ b/.github/workflows/test-browser-interactions.yml @@ -18,7 +18,7 @@ jobs: id-token: write steps: - name: Checkout code - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: fetch-depth: 0 persist-credentials: false diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e3ba6112b7d..e8f062ea345 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -24,7 +24,7 @@ jobs: steps: - name: Check out repo - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: persist-credentials: false @@ -103,7 +103,7 @@ jobs: sudo apt-get install -y gnome-keyring dbus-x11 - name: Check out repo - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: persist-credentials: false @@ -137,7 +137,7 @@ jobs: runs-on: macos-14 steps: - name: Checkout - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: persist-credentials: false @@ -173,7 +173,7 @@ jobs: - rust-coverage steps: - name: Check out repo - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: persist-credentials: false diff --git a/.github/workflows/version-auto-bump.yml b/.github/workflows/version-auto-bump.yml index 65f004149de..d66c48fcf58 100644 --- a/.github/workflows/version-auto-bump.yml +++ b/.github/workflows/version-auto-bump.yml @@ -39,7 +39,7 @@ jobs: permission-contents: write # for committing and pushing to the current branch - name: Check out target ref - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: ref: main token: ${{ steps.app-token.outputs.token }} From 2707811de8145ce00ea7d064efb49f4507a1faf8 Mon Sep 17 00:00:00 2001 From: Dave <3836813+enmande@users.noreply.github.com> Date: Mon, 29 Dec 2025 12:19:37 -0500 Subject: [PATCH 168/188] feat(2fa-webauthn) [PM-20109]: Increase 2FA WebAuthn Security Key Limit (#18040) * feat(2fa-webauthn) [PM-20109]: Update WebAuthN credential handling. * feat(messages) [PM-20109]: Add 'Unnamed key' translation. * refactor(2fa-webauthn) [PM-20109]: Refactor nextId for type safety. * refactor(2fa-webauthn) [PM-20109]: Clean up template comments. * fix(webauthn-2fa) [PM-3611]: Key name is required. --- .../two-factor-setup-webauthn.component.html | 43 ++++++------ .../two-factor-setup-webauthn.component.ts | 68 +++++++++++++------ apps/web/src/locales/en/messages.json | 3 + 3 files changed, 72 insertions(+), 42 deletions(-) diff --git a/apps/web/src/app/auth/settings/two-factor/two-factor-setup-webauthn.component.html b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-webauthn.component.html index c272a8e5b70..8a538cb961c 100644 --- a/apps/web/src/app/auth/settings/two-factor/two-factor-setup-webauthn.component.html +++ b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-webauthn.component.html @@ -16,27 +16,26 @@ <img class="tw-float-right tw-ml-5 mfaType7" alt="FIDO2 WebAuthn logo" /> <ul class="bwi-ul"> <li *ngFor="let k of keys; let i = index" #removeKeyBtn [appApiAction]="k.removePromise"> - <i class="bwi bwi-li bwi-key"></i> - <span *ngIf="!k.configured || !k.name" bitTypography="body1" class="tw-font-medium"> - {{ "webAuthnkeyX" | i18n: (i + 1).toString() }} - </span> - <span *ngIf="k.configured && k.name" bitTypography="body1" class="tw-font-medium"> - {{ k.name }} - </span> - <ng-container *ngIf="k.configured && !$any(removeKeyBtn).loading"> - <ng-container *ngIf="k.migrated"> - <span>{{ "webAuthnMigrated" | i18n }}</span> + <ng-container *ngIf="k.configured"> + <i class="bwi bwi-li bwi-key"></i> + <span *ngIf="k.configured" bitTypography="body1" class="tw-font-medium"> + {{ k.name || ("unnamedKey" | i18n) }} + </span> + <ng-container *ngIf="k.configured && !$any(removeKeyBtn).loading"> + <ng-container *ngIf="k.migrated"> + <span>{{ "webAuthnMigrated" | i18n }}</span> + </ng-container> + </ng-container> + <ng-container *ngIf="keysConfiguredCount > 1 && k.configured"> + <i + class="bwi bwi-spin bwi-spinner tw-text-muted bwi-fw" + title="{{ 'loading' | i18n }}" + *ngIf="$any(removeKeyBtn).loading" + aria-hidden="true" + ></i> + - + <a bitLink href="#" appStopClick (click)="remove(k)">{{ "remove" | i18n }}</a> </ng-container> - </ng-container> - <ng-container *ngIf="keysConfiguredCount > 1 && k.configured"> - <i - class="bwi bwi-spin bwi-spinner tw-text-muted bwi-fw" - title="{{ 'loading' | i18n }}" - *ngIf="$any(removeKeyBtn).loading" - aria-hidden="true" - ></i> - - - <a bitLink href="#" appStopClick (click)="remove(k)">{{ "remove" | i18n }}</a> </ng-container> </li> </ul> @@ -60,7 +59,9 @@ type="button" [bitAction]="readKey" buttonType="secondary" - [disabled]="$any(readKeyBtn).loading() || webAuthnListening || !keyIdAvailable" + [disabled]=" + $any(readKeyBtn).loading() || webAuthnListening || !keyIdAvailable || formGroup.invalid + " class="tw-mr-2" #readKeyBtn > diff --git a/apps/web/src/app/auth/settings/two-factor/two-factor-setup-webauthn.component.ts b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-webauthn.component.ts index 11ba5955902..57001acc4d2 100644 --- a/apps/web/src/app/auth/settings/two-factor/two-factor-setup-webauthn.component.ts +++ b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-webauthn.component.ts @@ -1,6 +1,6 @@ import { CommonModule } from "@angular/common"; import { Component, Inject, NgZone } from "@angular/core"; -import { FormControl, FormGroup, ReactiveFormsModule } from "@angular/forms"; +import { FormControl, FormGroup, ReactiveFormsModule, Validators } from "@angular/forms"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; @@ -99,7 +99,7 @@ export class TwoFactorSetupWebAuthnComponent extends TwoFactorSetupMethodBaseCom toastService, ); this.formGroup = new FormGroup({ - name: new FormControl({ value: "", disabled: false }), + name: new FormControl({ value: "", disabled: false }, Validators.required), }); this.auth(data); } @@ -213,7 +213,22 @@ export class TwoFactorSetupWebAuthnComponent extends TwoFactorSetupMethodBaseCom this.webAuthnListening = listening; } + private findNextAvailableKeyId(existingIds: Set<number>): number { + // Search for first gap, bounded by current key count + 1 + for (let i = 1; i <= existingIds.size + 1; i++) { + if (!existingIds.has(i)) { + return i; + } + } + + // This should never be reached due to loop bounds, but TypeScript requires a return + throw new Error("Unable to find next available key ID"); + } + private processResponse(response: TwoFactorWebAuthnResponse) { + if (!response.keys || response.keys.length === 0) { + response.keys = []; + } this.resetWebAuthn(); this.keys = []; this.keyIdAvailable = null; @@ -223,26 +238,37 @@ export class TwoFactorSetupWebAuthnComponent extends TwoFactorSetupMethodBaseCom nameControl.setValue(""); } this.keysConfiguredCount = 0; - for (let i = 1; i <= 5; i++) { - if (response.keys != null) { - const key = response.keys.filter((k) => k.id === i); - if (key.length > 0) { - this.keysConfiguredCount++; - this.keys.push({ - id: i, - name: key[0].name, - configured: true, - migrated: key[0].migrated, - removePromise: null, - }); - continue; - } - } - this.keys.push({ id: i, name: "", configured: false, removePromise: null }); - if (this.keyIdAvailable == null) { - this.keyIdAvailable = i; - } + + // Build configured keys + for (const key of response.keys) { + this.keysConfiguredCount++; + this.keys.push({ + id: key.id, + name: key.name, + configured: true, + migrated: key.migrated, + removePromise: null, + }); } + + // [PM-20109]: To accommodate the existing form logic with minimal changes, + // we need to have at least one unconfigured key slot available to the collection. + // Prior to PM-20109, both client and server had hard checks for IDs <= 5. + // While we don't have any technical constraints _at this time_, we should avoid + // unbounded growth of key IDs over time as users add/remove keys; + // this strategy gap-fills key IDs. + const existingIds = new Set(response.keys.map((k) => k.id)); + const nextId = this.findNextAvailableKeyId(existingIds); + + // Add unconfigured slot, which can be used to add a new key + this.keys.push({ + id: nextId, + name: "", + configured: false, + removePromise: null, + }); + this.keyIdAvailable = nextId; + this.enabled = response.enabled; this.onUpdated.emit(this.enabled); } diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index ac40f78e43f..4721c971dcc 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -2634,6 +2634,9 @@ "key": { "message": "Key" }, + "unnamedKey": { + "message": "Unnamed key" + }, "twoStepAuthenticatorEnterCodeV2": { "message": "Verification code" }, From f689fd88b76789ddf8a98951f0062a54f611ac88 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann <mail@quexten.com> Date: Mon, 29 Dec 2025 18:31:15 +0100 Subject: [PATCH 169/188] [PM-30285] Add soundness check to cipher and folder recovery step (#18120) * Add soundness check to cipher and folder recovery step * fix tests --------- Co-authored-by: Maciej Zieniuk <mzieniuk@bitwarden.com> --- .../data-recovery/steps/cipher-step.spec.ts | 36 ++++++++++++++++--- .../data-recovery/steps/cipher-step.ts | 6 +++- .../data-recovery/steps/folder-step.ts | 6 +++- 3 files changed, 42 insertions(+), 6 deletions(-) diff --git a/apps/web/src/app/key-management/data-recovery/steps/cipher-step.spec.ts b/apps/web/src/app/key-management/data-recovery/steps/cipher-step.spec.ts index a894fce0c41..9ae0600fb2a 100644 --- a/apps/web/src/app/key-management/data-recovery/steps/cipher-step.spec.ts +++ b/apps/web/src/app/key-management/data-recovery/steps/cipher-step.spec.ts @@ -132,7 +132,10 @@ describe("CipherStep", () => { userKey: null, encryptedPrivateKey: null, isPrivateKeyCorrupt: false, - ciphers: [{ id: "cipher-1", organizationId: null } as Cipher], + ciphers: [ + { id: "cipher-1", organizationId: null } as Cipher, + { id: "cipher-2", organizationId: null } as Cipher, + ], folders: [], }; @@ -144,14 +147,39 @@ describe("CipherStep", () => { expect(result).toBe(false); }); - it("returns true when there are undecryptable ciphers", async () => { + it("returns true when there are undecryptable ciphers but at least one decryptable cipher", async () => { const userId = "user-id" as UserId; const workingData: RecoveryWorkingData = { userId, userKey: null, encryptedPrivateKey: null, isPrivateKeyCorrupt: false, - ciphers: [{ id: "cipher-1", organizationId: null } as Cipher], + ciphers: [ + { id: "cipher-1", organizationId: null } as Cipher, + { id: "cipher-2", organizationId: null } as Cipher, + ], + folders: [], + }; + + cipherEncryptionService.decrypt.mockRejectedValueOnce(new Error("Decryption failed")); + + await cipherStep.runDiagnostics(workingData, logger); + const result = cipherStep.canRecover(workingData); + + expect(result).toBe(true); + }); + + it("returns false when all ciphers are undecryptable", async () => { + const userId = "user-id" as UserId; + const workingData: RecoveryWorkingData = { + userId, + userKey: null, + encryptedPrivateKey: null, + isPrivateKeyCorrupt: false, + ciphers: [ + { id: "cipher-1", organizationId: null } as Cipher, + { id: "cipher-2", organizationId: null } as Cipher, + ], folders: [], }; @@ -160,7 +188,7 @@ describe("CipherStep", () => { await cipherStep.runDiagnostics(workingData, logger); const result = cipherStep.canRecover(workingData); - expect(result).toBe(true); + expect(result).toBe(false); }); }); diff --git a/apps/web/src/app/key-management/data-recovery/steps/cipher-step.ts b/apps/web/src/app/key-management/data-recovery/steps/cipher-step.ts index b44e8afc54d..01c2d9bc2a1 100644 --- a/apps/web/src/app/key-management/data-recovery/steps/cipher-step.ts +++ b/apps/web/src/app/key-management/data-recovery/steps/cipher-step.ts @@ -10,6 +10,7 @@ export class CipherStep implements RecoveryStep { title = "recoveryStepCipherTitle"; private undecryptableCipherIds: string[] = []; + private decryptableCipherIds: string[] = []; constructor( private apiService: ApiService, @@ -31,18 +32,21 @@ export class CipherStep implements RecoveryStep { for (const cipher of userCiphers) { try { await this.cipherService.decrypt(cipher, workingData.userId); + this.decryptableCipherIds.push(cipher.id); } catch { logger.record(`Cipher ID ${cipher.id} was undecryptable`); this.undecryptableCipherIds.push(cipher.id); } } logger.record(`Found ${this.undecryptableCipherIds.length} undecryptable ciphers`); + logger.record(`Found ${this.decryptableCipherIds.length} decryptable ciphers`); return this.undecryptableCipherIds.length == 0; } canRecover(workingData: RecoveryWorkingData): boolean { - return this.undecryptableCipherIds.length > 0; + // If everything fails to decrypt, it's a deeper issue and we shouldn't offer recovery here. + return this.undecryptableCipherIds.length > 0 && this.decryptableCipherIds.length > 0; } async runRecovery(workingData: RecoveryWorkingData, logger: LogRecorder): Promise<void> { diff --git a/apps/web/src/app/key-management/data-recovery/steps/folder-step.ts b/apps/web/src/app/key-management/data-recovery/steps/folder-step.ts index bc0ae31efba..90e252ce6c3 100644 --- a/apps/web/src/app/key-management/data-recovery/steps/folder-step.ts +++ b/apps/web/src/app/key-management/data-recovery/steps/folder-step.ts @@ -11,6 +11,7 @@ export class FolderStep implements RecoveryStep { title = "recoveryStepFoldersTitle"; private undecryptableFolderIds: string[] = []; + private decryptableFolderIds: string[] = []; constructor( private folderService: FolderApiServiceAbstraction, @@ -36,18 +37,21 @@ export class FolderStep implements RecoveryStep { folder.name.encryptedString, workingData.userKey.toEncoded(), ); + this.decryptableFolderIds.push(folder.id); } catch { logger.record(`Folder name for folder ID ${folder.id} was undecryptable`); this.undecryptableFolderIds.push(folder.id); } } logger.record(`Found ${this.undecryptableFolderIds.length} undecryptable folders`); + logger.record(`Found ${this.decryptableFolderIds.length} decryptable folders`); return this.undecryptableFolderIds.length == 0; } canRecover(workingData: RecoveryWorkingData): boolean { - return this.undecryptableFolderIds.length > 0; + // If everything fails to decrypt, it's a deeper issue and we shouldn't offer recovery here. + return this.undecryptableFolderIds.length > 0 && this.decryptableFolderIds.length > 0; } async runRecovery(workingData: RecoveryWorkingData, logger: LogRecorder): Promise<void> { From 1c16b8edb92dea60fee93b8d912be34ec87691f3 Mon Sep 17 00:00:00 2001 From: shivam <hellrae@proton.me> Date: Mon, 29 Dec 2025 23:01:31 +0530 Subject: [PATCH 170/188] fix(ui): clean up unintended character on login page (#18101) --- apps/web/src/index.html | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/web/src/index.html b/apps/web/src/index.html index 06f7587a123..5e56df553fc 100644 --- a/apps/web/src/index.html +++ b/apps/web/src/index.html @@ -122,7 +122,6 @@ </clipPath> </defs> </svg> - `; </div> <div From 3beeab4414e8c1fe21186a07eaa3ce881abf4588 Mon Sep 17 00:00:00 2001 From: Jason Ng <jng@bitwarden.com> Date: Mon, 29 Dec 2025 13:49:00 -0500 Subject: [PATCH 171/188] [PM-29972] Update Vault Items List When Archiving Ciphers (#18102) * update default cipher service to use upsert, apply optional userId parameter --- .../src/vault/abstractions/cipher.service.ts | 6 +++++- .../src/vault/services/cipher.service.ts | 7 +++++-- .../default-cipher-archive.service.spec.ts | 20 +++++++++---------- .../default-cipher-archive.service.ts | 4 ++-- 4 files changed, 22 insertions(+), 15 deletions(-) diff --git a/libs/common/src/vault/abstractions/cipher.service.ts b/libs/common/src/vault/abstractions/cipher.service.ts index 8472a359c51..0d3a0b99fcb 100644 --- a/libs/common/src/vault/abstractions/cipher.service.ts +++ b/libs/common/src/vault/abstractions/cipher.service.ts @@ -207,9 +207,13 @@ export abstract class CipherService implements UserKeyRotationDataProvider<Ciphe * Update the local store of CipherData with the provided data. Values are upserted into the existing store. * * @param cipher The cipher data to upsert. Can be a single CipherData object or an array of CipherData objects. + * @param userId Optional user ID for whom the cipher data is being upserted. * @returns A promise that resolves to a record of updated cipher store, keyed by their cipher ID. Returns all ciphers, not just those updated */ - abstract upsert(cipher: CipherData | CipherData[]): Promise<Record<CipherId, CipherData>>; + abstract upsert( + cipher: CipherData | CipherData[], + userId?: UserId, + ): Promise<Record<CipherId, CipherData>>; abstract replace(ciphers: { [id: string]: CipherData }, userId: UserId): Promise<any>; abstract clear(userId?: string): Promise<void>; abstract moveManyWithServer(ids: string[], folderId: string, userId: UserId): Promise<any>; diff --git a/libs/common/src/vault/services/cipher.service.ts b/libs/common/src/vault/services/cipher.service.ts index 402b8ed1030..3c44b854de7 100644 --- a/libs/common/src/vault/services/cipher.service.ts +++ b/libs/common/src/vault/services/cipher.service.ts @@ -1196,12 +1196,15 @@ export class CipherService implements CipherServiceAbstraction { await this.encryptedCiphersState(userId).update(() => ciphers); } - async upsert(cipher: CipherData | CipherData[]): Promise<Record<CipherId, CipherData>> { + async upsert( + cipher: CipherData | CipherData[], + userId?: UserId, + ): Promise<Record<CipherId, CipherData>> { const ciphers = cipher instanceof CipherData ? [cipher] : cipher; const res = await this.updateEncryptedCipherState((current) => { ciphers.forEach((c) => (current[c.id as CipherId] = c)); return current; - }); + }, userId); // Some state storage providers (e.g. Electron) don't update the state immediately, wait for next tick // Otherwise, subscribers to cipherViews$ can get stale data await new Promise((resolve) => setTimeout(resolve, 0)); diff --git a/libs/common/src/vault/services/default-cipher-archive.service.spec.ts b/libs/common/src/vault/services/default-cipher-archive.service.spec.ts index 807311ca851..2f5e69d65ed 100644 --- a/libs/common/src/vault/services/default-cipher-archive.service.spec.ts +++ b/libs/common/src/vault/services/default-cipher-archive.service.spec.ts @@ -219,7 +219,7 @@ describe("DefaultCipherArchiveService", () => { } as any, }), ); - mockCipherService.replace.mockResolvedValue(undefined); + mockCipherService.upsert.mockResolvedValue(undefined); }); it("should archive single cipher", async () => { @@ -233,13 +233,13 @@ describe("DefaultCipherArchiveService", () => { true, ); expect(mockCipherService.ciphers$).toHaveBeenCalledWith(userId); - expect(mockCipherService.replace).toHaveBeenCalledWith( - expect.objectContaining({ - [cipherId]: expect.objectContaining({ + expect(mockCipherService.upsert).toHaveBeenCalledWith( + [ + expect.objectContaining({ archivedDate: "2024-01-15T10:30:00.000Z", revisionDate: "2024-01-15T10:31:00.000Z", }), - }), + ], userId, ); }); @@ -282,7 +282,7 @@ describe("DefaultCipherArchiveService", () => { } as any, }), ); - mockCipherService.replace.mockResolvedValue(undefined); + mockCipherService.upsert.mockResolvedValue(undefined); }); it("should unarchive single cipher", async () => { @@ -296,12 +296,12 @@ describe("DefaultCipherArchiveService", () => { true, ); expect(mockCipherService.ciphers$).toHaveBeenCalledWith(userId); - expect(mockCipherService.replace).toHaveBeenCalledWith( - expect.objectContaining({ - [cipherId]: expect.objectContaining({ + expect(mockCipherService.upsert).toHaveBeenCalledWith( + [ + expect.objectContaining({ revisionDate: "2024-01-15T10:31:00.000Z", }), - }), + ], userId, ); }); diff --git a/libs/common/src/vault/services/default-cipher-archive.service.ts b/libs/common/src/vault/services/default-cipher-archive.service.ts index 8076735c9e2..c1daade0dad 100644 --- a/libs/common/src/vault/services/default-cipher-archive.service.ts +++ b/libs/common/src/vault/services/default-cipher-archive.service.ts @@ -95,7 +95,7 @@ export class DefaultCipherArchiveService implements CipherArchiveService { localCipher.revisionDate = cipher.revisionDate; } - await this.cipherService.replace(currentCiphers, userId); + await this.cipherService.upsert(Object.values(currentCiphers), userId); } async unarchiveWithServer(ids: CipherId | CipherId[], userId: UserId): Promise<void> { @@ -116,6 +116,6 @@ export class DefaultCipherArchiveService implements CipherArchiveService { localCipher.revisionDate = cipher.revisionDate; } - await this.cipherService.replace(currentCiphers, userId); + await this.cipherService.upsert(Object.values(currentCiphers), userId); } } From ccb9a0b8a1675ad6292b17234fbe87dc745bbe84 Mon Sep 17 00:00:00 2001 From: Mark Youssef <141061617+mark-youssef-bitwarden@users.noreply.github.com> Date: Mon, 29 Dec 2025 11:08:33 -0800 Subject: [PATCH 172/188] [CL-132] Implement resizable side nav (#16533) Co-authored-by: Vicki League <vleague@bitwarden.com> --- apps/browser/src/_locales/en/messages.json | 3 + .../layout/desktop-layout.component.spec.ts | 8 ++ .../layout/desktop-side-nav.component.spec.ts | 8 ++ .../send-filters-nav.component.spec.ts | 8 ++ apps/desktop/src/locales/en/messages.json | 3 + .../navigation-switcher.component.spec.ts | 8 ++ .../navigation-switcher.stories.ts | 11 +- .../vault-items/vault-items.stories.ts | 7 +- apps/web/src/locales/en/messages.json | 3 + .../src/dialog/dialog.service.stories.ts | 7 +- libs/components/src/drawer/drawer.stories.ts | 13 ++- libs/components/src/item/item.stories.ts | 19 +++- libs/components/src/layout/layout.stories.ts | 12 +- libs/components/src/layout/mocks.ts | 1 + .../src/navigation/nav-group.stories.ts | 7 ++ .../src/navigation/nav-item.stories.ts | 13 ++- .../src/navigation/side-nav.component.html | 99 ++++++++++------- .../src/navigation/side-nav.component.ts | 32 +++++- .../src/navigation/side-nav.service.ts | 105 +++++++++++++++++- .../kitchen-sink/kitchen-sink.stories.ts | 7 ++ libs/components/src/table/table.stories.ts | 13 ++- libs/components/src/utils/index.ts | 1 + libs/components/src/utils/state-mock.ts | 48 ++++++++ libs/state/src/core/state-definitions.ts | 1 + 24 files changed, 381 insertions(+), 56 deletions(-) create mode 100644 libs/components/src/utils/state-mock.ts diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index 2b103555604..95d3f662994 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -6039,5 +6039,8 @@ }, "whyAmISeeingThis": { "message": "Why am I seeing this?" + }, + "resizeSideNavigation": { + "message": "Resize side navigation" } } diff --git a/apps/desktop/src/app/layout/desktop-layout.component.spec.ts b/apps/desktop/src/app/layout/desktop-layout.component.spec.ts index 74cddd02495..253444232e5 100644 --- a/apps/desktop/src/app/layout/desktop-layout.component.spec.ts +++ b/apps/desktop/src/app/layout/desktop-layout.component.spec.ts @@ -4,7 +4,9 @@ import { RouterModule } from "@angular/router"; import { mock } from "jest-mock-extended"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { FakeGlobalStateProvider } from "@bitwarden/common/spec"; import { NavigationModule } from "@bitwarden/components"; +import { GlobalStateProvider } from "@bitwarden/state"; import { SendFiltersNavComponent } from "../tools/send-v2/send-filters-nav.component"; @@ -36,6 +38,8 @@ describe("DesktopLayoutComponent", () => { let component: DesktopLayoutComponent; let fixture: ComponentFixture<DesktopLayoutComponent>; + const fakeGlobalStateProvider = new FakeGlobalStateProvider(); + beforeEach(async () => { await TestBed.configureTestingModule({ imports: [DesktopLayoutComponent, RouterModule.forRoot([]), NavigationModule], @@ -44,6 +48,10 @@ describe("DesktopLayoutComponent", () => { provide: I18nService, useValue: mock<I18nService>(), }, + { + provide: GlobalStateProvider, + useValue: fakeGlobalStateProvider, + }, ], }) .overrideComponent(DesktopLayoutComponent, { diff --git a/apps/desktop/src/app/layout/desktop-side-nav.component.spec.ts b/apps/desktop/src/app/layout/desktop-side-nav.component.spec.ts index 4d5c3a90253..9b99dbf09c2 100644 --- a/apps/desktop/src/app/layout/desktop-side-nav.component.spec.ts +++ b/apps/desktop/src/app/layout/desktop-side-nav.component.spec.ts @@ -2,7 +2,9 @@ import { ComponentFixture, TestBed } from "@angular/core/testing"; import { mock } from "jest-mock-extended"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { FakeGlobalStateProvider } from "@bitwarden/common/spec"; import { NavigationModule } from "@bitwarden/components"; +import { GlobalStateProvider } from "@bitwarden/state"; import { DesktopSideNavComponent } from "./desktop-side-nav.component"; @@ -24,6 +26,8 @@ describe("DesktopSideNavComponent", () => { let component: DesktopSideNavComponent; let fixture: ComponentFixture<DesktopSideNavComponent>; + const fakeGlobalStateProvider = new FakeGlobalStateProvider(); + beforeEach(async () => { await TestBed.configureTestingModule({ imports: [DesktopSideNavComponent, NavigationModule], @@ -32,6 +36,10 @@ describe("DesktopSideNavComponent", () => { provide: I18nService, useValue: mock<I18nService>(), }, + { + provide: GlobalStateProvider, + useValue: fakeGlobalStateProvider, + }, ], }).compileComponents(); diff --git a/apps/desktop/src/app/tools/send-v2/send-filters-nav.component.spec.ts b/apps/desktop/src/app/tools/send-v2/send-filters-nav.component.spec.ts index 95ba5c53e36..ab881e5b57b 100644 --- a/apps/desktop/src/app/tools/send-v2/send-filters-nav.component.spec.ts +++ b/apps/desktop/src/app/tools/send-v2/send-filters-nav.component.spec.ts @@ -5,9 +5,11 @@ import { RouterTestingHarness } from "@angular/router/testing"; import { BehaviorSubject } from "rxjs"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { FakeGlobalStateProvider } from "@bitwarden/common/spec"; import { SendType } from "@bitwarden/common/tools/send/enums/send-type"; import { NavigationModule } from "@bitwarden/components"; import { SendListFiltersService } from "@bitwarden/send-ui"; +import { GlobalStateProvider } from "@bitwarden/state"; import { SendFiltersNavComponent } from "./send-filters-nav.component"; @@ -35,6 +37,8 @@ describe("SendFiltersNavComponent", () => { let filterFormValueSubject: BehaviorSubject<{ sendType: SendType | null }>; let mockSendListFiltersService: Partial<SendListFiltersService>; + const fakeGlobalStateProvider = new FakeGlobalStateProvider(); + beforeEach(async () => { filterFormValueSubject = new BehaviorSubject<{ sendType: SendType | null }>({ sendType: null, @@ -72,6 +76,10 @@ describe("SendFiltersNavComponent", () => { t: jest.fn((key) => key), }, }, + { + provide: GlobalStateProvider, + useValue: fakeGlobalStateProvider, + }, ], }).compileComponents(); diff --git a/apps/desktop/src/locales/en/messages.json b/apps/desktop/src/locales/en/messages.json index d26a46a9efe..9be96a62589 100644 --- a/apps/desktop/src/locales/en/messages.json +++ b/apps/desktop/src/locales/en/messages.json @@ -4388,6 +4388,9 @@ "sessionTimeoutHeader": { "message": "Session timeout" }, + "resizeSideNavigation": { + "message": "Resize side navigation" + }, "sessionTimeoutSettingsManagedByOrganization": { "message": "This setting is managed by your organization." }, diff --git a/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.component.spec.ts b/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.component.spec.ts index 9f6c8f6b194..9a6de3ad9af 100644 --- a/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.component.spec.ts +++ b/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.component.spec.ts @@ -7,10 +7,12 @@ import { BehaviorSubject } from "rxjs"; import { I18nPipe } from "@bitwarden/angular/platform/pipes/i18n.pipe"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { FakeGlobalStateProvider } from "@bitwarden/common/spec"; import { IconButtonModule, NavigationModule } from "@bitwarden/components"; // FIXME: remove `src` and fix import // eslint-disable-next-line no-restricted-imports import { NavItemComponent } from "@bitwarden/components/src/navigation/nav-item.component"; +import { GlobalStateProvider } from "@bitwarden/state"; import { ProductSwitcherItem, ProductSwitcherService } from "../shared/product-switcher.service"; @@ -59,6 +61,8 @@ describe("NavigationProductSwitcherComponent", () => { productSwitcherService.shouldShowPremiumUpgradeButton$ = mockShouldShowPremiumUpgradeButton$; mockProducts$.next({ bento: [], other: [] }); + const fakeGlobalStateProvider = new FakeGlobalStateProvider(); + await TestBed.configureTestingModule({ imports: [RouterModule, NavigationModule, IconButtonModule, MockUpgradeNavButtonComponent], declarations: [NavigationProductSwitcherComponent, I18nPipe], @@ -72,6 +76,10 @@ describe("NavigationProductSwitcherComponent", () => { provide: ActivatedRoute, useValue: mock<ActivatedRoute>(), }, + { + provide: GlobalStateProvider, + useValue: fakeGlobalStateProvider, + }, ], }).compileComponents(); }); diff --git a/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.stories.ts b/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.stories.ts index 33c10309108..ba36063fb7b 100644 --- a/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.stories.ts +++ b/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.stories.ts @@ -16,10 +16,15 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { SyncService } from "@bitwarden/common/platform/sync"; import { UserId } from "@bitwarden/common/types/guid"; -import { LayoutComponent, NavigationModule } from "@bitwarden/components"; +import { + LayoutComponent, + NavigationModule, + StorybookGlobalStateProvider, +} from "@bitwarden/components"; // FIXME: remove `src` and fix import // eslint-disable-next-line no-restricted-imports import { I18nMockService } from "@bitwarden/components/src/utils/i18n-mock.service"; +import { GlobalStateProvider } from "@bitwarden/state"; import { I18nPipe } from "@bitwarden/ui-common"; import { ProductSwitcherService } from "../shared/product-switcher.service"; @@ -183,6 +188,10 @@ export default { }, ]), ), + { + provide: GlobalStateProvider, + useClass: StorybookGlobalStateProvider, + }, ], }), ], diff --git a/apps/web/src/app/vault/components/vault-items/vault-items.stories.ts b/apps/web/src/app/vault/components/vault-items/vault-items.stories.ts index a71427cf475..9c56df0db59 100644 --- a/apps/web/src/app/vault/components/vault-items/vault-items.stories.ts +++ b/apps/web/src/app/vault/components/vault-items/vault-items.stories.ts @@ -39,7 +39,8 @@ import { LoginView } from "@bitwarden/common/vault/models/view/login.view"; import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cipher-authorization.service"; import { RestrictedItemTypesService } from "@bitwarden/common/vault/services/restricted-item-types.service"; import { CipherViewLike } from "@bitwarden/common/vault/utils/cipher-view-like-utils"; -import { LayoutComponent } from "@bitwarden/components"; +import { LayoutComponent, StorybookGlobalStateProvider } from "@bitwarden/components"; +import { GlobalStateProvider } from "@bitwarden/state"; import { RoutedVaultFilterService } from "@bitwarden/web-vault/app/vault/individual-vault/vault-filter/services/routed-vault-filter.service"; import { GroupView } from "../../../admin-console/organizations/core"; @@ -168,6 +169,10 @@ export default { providers: [ importProvidersFrom(RouterModule.forRoot([], { useHash: true })), importProvidersFrom(PreloadedEnglishI18nModule), + { + provide: GlobalStateProvider, + useClass: StorybookGlobalStateProvider, + }, ], }), ], diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 4721c971dcc..98f847e1d36 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -12308,6 +12308,9 @@ "userVerificationFailed": { "message": "User verification failed." }, + "resizeSideNavigation": { + "message": "Resize side navigation" + }, "recoveryDeleteCiphersTitle": { "message": "Delete unrecoverable vault items" }, diff --git a/libs/components/src/dialog/dialog.service.stories.ts b/libs/components/src/dialog/dialog.service.stories.ts index 3b5bdc4d4e9..4e5c718e494 100644 --- a/libs/components/src/dialog/dialog.service.stories.ts +++ b/libs/components/src/dialog/dialog.service.stories.ts @@ -6,13 +6,14 @@ import { Meta, StoryObj, applicationConfig, moduleMetadata } from "@storybook/an import { getAllByRole, userEvent } from "storybook/test"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { GlobalStateProvider } from "@bitwarden/state"; import { ButtonModule } from "../button"; import { IconButtonModule } from "../icon-button"; import { LayoutComponent } from "../layout"; import { SharedModule } from "../shared"; import { positionFixedWrapperDecorator } from "../stories/storybook-decorators"; -import { I18nMockService } from "../utils/i18n-mock.service"; +import { I18nMockService, StorybookGlobalStateProvider } from "../utils"; import { DialogModule } from "./dialog.module"; import { DialogService } from "./dialog.service"; @@ -161,6 +162,10 @@ export default { }); }, }, + { + provide: GlobalStateProvider, + useClass: StorybookGlobalStateProvider, + }, ], }), ], diff --git a/libs/components/src/drawer/drawer.stories.ts b/libs/components/src/drawer/drawer.stories.ts index 727d16b5481..9904b77ee9f 100644 --- a/libs/components/src/drawer/drawer.stories.ts +++ b/libs/components/src/drawer/drawer.stories.ts @@ -1,7 +1,8 @@ import { RouterTestingModule } from "@angular/router/testing"; -import { Meta, StoryObj, moduleMetadata } from "@storybook/angular"; +import { Meta, StoryObj, applicationConfig, moduleMetadata } from "@storybook/angular"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { GlobalStateProvider } from "@bitwarden/state"; import { ButtonModule } from "../button"; import { CalloutModule } from "../callout"; @@ -9,7 +10,7 @@ import { LayoutComponent } from "../layout"; import { mockLayoutI18n } from "../layout/mocks"; import { positionFixedWrapperDecorator } from "../stories/storybook-decorators"; import { TypographyModule } from "../typography"; -import { I18nMockService } from "../utils"; +import { I18nMockService, StorybookGlobalStateProvider } from "../utils"; import { DrawerBodyComponent } from "./drawer-body.component"; import { DrawerHeaderComponent } from "./drawer-header.component"; @@ -47,6 +48,14 @@ export default { }, ], }), + applicationConfig({ + providers: [ + { + provide: GlobalStateProvider, + useClass: StorybookGlobalStateProvider, + }, + ], + }), ], } as Meta<DrawerComponent>; diff --git a/libs/components/src/item/item.stories.ts b/libs/components/src/item/item.stories.ts index d2c197d0088..9498c163da7 100644 --- a/libs/components/src/item/item.stories.ts +++ b/libs/components/src/item/item.stories.ts @@ -1,16 +1,23 @@ import { ScrollingModule } from "@angular/cdk/scrolling"; import { CommonModule } from "@angular/common"; import { RouterTestingModule } from "@angular/router/testing"; -import { Meta, StoryObj, componentWrapperDecorator, moduleMetadata } from "@storybook/angular"; +import { + Meta, + StoryObj, + applicationConfig, + componentWrapperDecorator, + moduleMetadata, +} from "@storybook/angular"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { GlobalStateProvider } from "@bitwarden/state"; import { AvatarModule } from "../avatar"; import { BadgeModule } from "../badge"; import { IconButtonModule } from "../icon-button"; import { LayoutComponent } from "../layout"; import { TypographyModule } from "../typography"; -import { I18nMockService } from "../utils/i18n-mock.service"; +import { I18nMockService, StorybookGlobalStateProvider } from "../utils"; import { ItemActionComponent } from "./item-action.component"; import { ItemContentComponent } from "./item-content.component"; @@ -50,6 +57,14 @@ export default { }, ], }), + applicationConfig({ + providers: [ + { + provide: GlobalStateProvider, + useClass: StorybookGlobalStateProvider, + }, + ], + }), componentWrapperDecorator((story) => `<div class="tw-bg-background-alt tw-p-2">${story}</div>`), ], parameters: { diff --git a/libs/components/src/layout/layout.stories.ts b/libs/components/src/layout/layout.stories.ts index a059fd61b92..59770c21d2e 100644 --- a/libs/components/src/layout/layout.stories.ts +++ b/libs/components/src/layout/layout.stories.ts @@ -1,13 +1,15 @@ import { RouterTestingModule } from "@angular/router/testing"; -import { Meta, StoryObj, moduleMetadata } from "@storybook/angular"; +import { Meta, StoryObj, applicationConfig, moduleMetadata } from "@storybook/angular"; import { userEvent } from "storybook/test"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { GlobalStateProvider } from "@bitwarden/state"; import { CalloutModule } from "../callout"; import { NavigationModule } from "../navigation"; import { positionFixedWrapperDecorator } from "../stories/storybook-decorators"; import { I18nMockService } from "../utils/i18n-mock.service"; +import { StorybookGlobalStateProvider } from "../utils/state-mock"; import { LayoutComponent } from "./layout.component"; import { mockLayoutI18n } from "./mocks"; @@ -28,6 +30,14 @@ export default { }, ], }), + applicationConfig({ + providers: [ + { + provide: GlobalStateProvider, + useClass: StorybookGlobalStateProvider, + }, + ], + }), ], parameters: { chromatic: { viewports: [640, 1280] }, diff --git a/libs/components/src/layout/mocks.ts b/libs/components/src/layout/mocks.ts index 8b001eb8fd1..15b126ca718 100644 --- a/libs/components/src/layout/mocks.ts +++ b/libs/components/src/layout/mocks.ts @@ -5,4 +5,5 @@ export const mockLayoutI18n = { submenu: "submenu", toggleCollapse: "toggle collapse", loading: "Loading", + resizeSideNavigation: "Resize side navigation", }; diff --git a/libs/components/src/navigation/nav-group.stories.ts b/libs/components/src/navigation/nav-group.stories.ts index c0111c23fc1..fa1cb06dbfe 100644 --- a/libs/components/src/navigation/nav-group.stories.ts +++ b/libs/components/src/navigation/nav-group.stories.ts @@ -3,11 +3,13 @@ import { RouterModule } from "@angular/router"; import { StoryObj, Meta, moduleMetadata, applicationConfig } from "@storybook/angular"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { GlobalStateProvider } from "@bitwarden/state"; import { LayoutComponent } from "../layout"; import { SharedModule } from "../shared/shared.module"; import { positionFixedWrapperDecorator } from "../stories/storybook-decorators"; import { I18nMockService } from "../utils/i18n-mock.service"; +import { StorybookGlobalStateProvider } from "../utils/state-mock"; import { NavGroupComponent } from "./nav-group.component"; import { NavigationModule } from "./navigation.module"; @@ -42,6 +44,7 @@ export default { toggleSideNavigation: "Toggle side navigation", skipToContent: "Skip to content", loading: "Loading", + resizeSideNavigation: "Resize side navigation", }); }, }, @@ -58,6 +61,10 @@ export default { { useHash: true }, ), ), + { + provide: GlobalStateProvider, + useClass: StorybookGlobalStateProvider, + }, ], }), ], diff --git a/libs/components/src/navigation/nav-item.stories.ts b/libs/components/src/navigation/nav-item.stories.ts index 131dacc8142..3036ab26348 100644 --- a/libs/components/src/navigation/nav-item.stories.ts +++ b/libs/components/src/navigation/nav-item.stories.ts @@ -1,12 +1,14 @@ import { RouterTestingModule } from "@angular/router/testing"; -import { StoryObj, Meta, moduleMetadata } from "@storybook/angular"; +import { StoryObj, Meta, moduleMetadata, applicationConfig } from "@storybook/angular"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { GlobalStateProvider } from "@bitwarden/state"; import { IconButtonModule } from "../icon-button"; import { LayoutComponent } from "../layout"; import { positionFixedWrapperDecorator } from "../stories/storybook-decorators"; import { I18nMockService } from "../utils/i18n-mock.service"; +import { StorybookGlobalStateProvider } from "../utils/state-mock"; import { NavItemComponent } from "./nav-item.component"; import { NavigationModule } from "./navigation.module"; @@ -31,11 +33,20 @@ export default { toggleSideNavigation: "Toggle side navigation", skipToContent: "Skip to content", loading: "Loading", + resizeSideNavigation: "Resize side navigation", }); }, }, ], }), + applicationConfig({ + providers: [ + { + provide: GlobalStateProvider, + useClass: StorybookGlobalStateProvider, + }, + ], + }), ], parameters: { design: { diff --git a/libs/components/src/navigation/side-nav.component.html b/libs/components/src/navigation/side-nav.component.html index c8b20ecba77..84c7e3e7298 100644 --- a/libs/components/src/navigation/side-nav.component.html +++ b/libs/components/src/navigation/side-nav.component.html @@ -5,47 +5,64 @@ }; as data ) { - <nav - id="bit-side-nav" - class="tw-sticky tw-inset-y-0 tw-left-0 tw-z-30 tw-flex tw-h-full tw-flex-col tw-overscroll-none tw-overflow-auto tw-bg-background-alt3 tw-outline-none" - [ngClass]="{ 'tw-w-60': data.open }" - [ngStyle]=" - variant() === 'secondary' && { - '--color-text-alt2': 'var(--color-text-main)', - '--color-background-alt3': 'var(--color-secondary-100)', - '--color-background-alt4': 'var(--color-secondary-300)', - '--color-hover-contrast': 'var(--color-hover-default)', - } - " - [cdkTrapFocus]="data.isOverlay" - [attr.role]="data.isOverlay ? 'dialog' : null" - [attr.aria-modal]="data.isOverlay ? 'true' : null" - (keydown)="handleKeyDown($event)" - > - <ng-content></ng-content> - <!-- 53rem = ~850px --> - <!-- This is a magic number. This number was selected by going to the UI and finding the number that felt the best to me and design. No real rhyme or reason :) --> - <div - class="[@media(min-height:53rem)]:tw-sticky tw-bottom-0 tw-left-0 tw-z-20 tw-mt-auto tw-w-full tw-bg-background-alt3" + <div class="tw-relative tw-h-full"> + <nav + id="bit-side-nav" + class="tw-sticky tw-inset-y-0 tw-left-0 tw-z-30 tw-flex tw-h-full tw-flex-col tw-overscroll-none tw-overflow-auto tw-bg-background-alt3 tw-outline-none" + [style.width.rem]="data.open ? (sideNavService.width$ | async) : undefined" + [ngStyle]=" + variant() === 'secondary' && { + '--color-text-alt2': 'var(--color-text-main)', + '--color-background-alt3': 'var(--color-secondary-100)', + '--color-background-alt4': 'var(--color-secondary-300)', + '--color-hover-contrast': 'var(--color-hover-default)', + } + " + [cdkTrapFocus]="data.isOverlay" + [attr.role]="data.isOverlay ? 'dialog' : null" + [attr.aria-modal]="data.isOverlay ? 'true' : null" + (keydown)="handleKeyDown($event)" > - <bit-nav-divider></bit-nav-divider> - @if (data.open) { - <ng-content select="[slot=footer]"></ng-content> - } - <div class="tw-mx-0.5 tw-my-4 tw-w-[3.75rem]"> - <button - #toggleButton - type="button" - class="tw-mx-auto tw-block tw-max-w-fit" - [bitIconButton]="data.open ? 'bwi-angle-left' : 'bwi-angle-right'" - buttonType="nav-contrast" - size="small" - (click)="sideNavService.toggle()" - [label]="'toggleSideNavigation' | i18n" - [attr.aria-expanded]="data.open" - aria-controls="bit-side-nav" - ></button> + <ng-content></ng-content> + <!-- 53rem = ~850px --> + <!-- This is a magic number. This number was selected by going to the UI and finding the number that felt the best to me and design. No real rhyme or reason :) --> + <div + class="[@media(min-height:53rem)]:tw-sticky tw-bottom-0 tw-left-0 tw-z-20 tw-mt-auto tw-w-full tw-bg-background-alt3" + > + <bit-nav-divider></bit-nav-divider> + @if (data.open) { + <ng-content select="[slot=footer]"></ng-content> + } + <div class="tw-mx-0.5 tw-my-4 tw-w-[3.75rem]"> + <button + #toggleButton + type="button" + class="tw-mx-auto tw-block tw-max-w-fit" + [bitIconButton]="data.open ? 'bwi-angle-left' : 'bwi-angle-right'" + buttonType="nav-contrast" + size="small" + (click)="sideNavService.toggle()" + [label]="'toggleSideNavigation' | i18n" + [attr.aria-expanded]="data.open" + aria-controls="bit-side-nav" + ></button> + </div> </div> - </div> - </nav> + </nav> + <div + cdkDrag + (cdkDragMoved)="onDragMoved($event)" + class="tw-absolute tw-top-0 -tw-right-0.5 tw-z-30 tw-h-full tw-w-1 tw-cursor-col-resize tw-transition-colors tw-duration-200 hover:tw-ease-in-out hover:tw-delay-500 hover:tw-bg-primary-300 focus:tw-outline-none focus-visible:tw-bg-primary-300 before:tw-content-[''] before:tw-absolute before:tw-block before:tw-inset-y-0 before:-tw-left-0.5 before:-tw-right-1" + [class.tw-hidden]="!data.open" + tabindex="0" + (keydown)="onKeydown($event)" + role="separator" + [attr.aria-valuenow]="sideNavService.width$ | async" + [attr.aria-valuemax]="sideNavService.MAX_OPEN_WIDTH" + [attr.aria-valuemin]="sideNavService.MIN_OPEN_WIDTH" + aria-orientation="vertical" + aria-controls="bit-side-nav" + [attr.aria-label]="'resizeSideNavigation' | i18n" + ></div> + </div> } diff --git a/libs/components/src/navigation/side-nav.component.ts b/libs/components/src/navigation/side-nav.component.ts index cf3d20762fe..b13920d9749 100644 --- a/libs/components/src/navigation/side-nav.component.ts +++ b/libs/components/src/navigation/side-nav.component.ts @@ -1,4 +1,5 @@ import { CdkTrapFocus } from "@angular/cdk/a11y"; +import { DragDropModule, CdkDragMove } from "@angular/cdk/drag-drop"; import { CommonModule } from "@angular/common"; import { Component, ElementRef, inject, input, viewChild } from "@angular/core"; @@ -16,16 +17,26 @@ export type SideNavVariant = "primary" | "secondary"; @Component({ selector: "bit-side-nav", templateUrl: "side-nav.component.html", - imports: [CommonModule, CdkTrapFocus, NavDividerComponent, BitIconButtonComponent, I18nPipe], + imports: [ + CommonModule, + CdkTrapFocus, + NavDividerComponent, + BitIconButtonComponent, + I18nPipe, + DragDropModule, + ], host: { class: "tw-block tw-h-full", }, }) export class SideNavComponent { + protected sideNavService = inject(SideNavService); + readonly variant = input<SideNavVariant>("primary"); private readonly toggleButton = viewChild("toggleButton", { read: ElementRef }); - protected sideNavService = inject(SideNavService); + + private elementRef = inject<ElementRef<HTMLElement>>(ElementRef); protected handleKeyDown = (event: KeyboardEvent) => { if (event.key === "Escape") { @@ -36,4 +47,21 @@ export class SideNavComponent { return true; }; + + protected onDragMoved(event: CdkDragMove) { + const rectX = this.elementRef.nativeElement.getBoundingClientRect().x; + const eventXPointer = event.pointerPosition.x; + + this.sideNavService.setWidthFromDrag(eventXPointer, rectX); + + // Fix for CDK applying a transform that can cause visual drifting + const element = event.source.element.nativeElement; + element.style.transform = "none"; + } + + protected onKeydown(event: KeyboardEvent) { + if (event.key === "ArrowRight" || event.key === "ArrowLeft") { + this.sideNavService.setWidthFromKeys(event.key); + } + } } diff --git a/libs/components/src/navigation/side-nav.service.ts b/libs/components/src/navigation/side-nav.service.ts index ce44811c7e0..63e54c81fe5 100644 --- a/libs/components/src/navigation/side-nav.service.ts +++ b/libs/components/src/navigation/side-nav.service.ts @@ -1,15 +1,37 @@ -import { Injectable } from "@angular/core"; +import { inject, Injectable } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; -import { BehaviorSubject, Observable, combineLatest, fromEvent, map, startWith } from "rxjs"; +import { + BehaviorSubject, + Observable, + combineLatest, + fromEvent, + map, + startWith, + debounceTime, + first, +} from "rxjs"; + +import { BIT_SIDE_NAV_DISK, GlobalStateProvider, KeyDefinition } from "@bitwarden/state"; import { BREAKPOINTS, isAtOrLargerThanBreakpoint } from "../utils/responsive-utils"; type CollapsePreference = "open" | "closed" | null; +const BIT_SIDE_NAV_WIDTH_KEY_DEF = new KeyDefinition<number>(BIT_SIDE_NAV_DISK, "side-nav-width", { + deserializer: (s) => s, +}); + @Injectable({ providedIn: "root", }) export class SideNavService { + // Units in rem + readonly DEFAULT_OPEN_WIDTH = 18; + readonly MIN_OPEN_WIDTH = 15; + readonly MAX_OPEN_WIDTH = 24; + + private rootFontSizePx: number; + private _open$ = new BehaviorSubject<boolean>(isAtOrLargerThanBreakpoint("md")); open$ = this._open$.asObservable(); @@ -21,7 +43,30 @@ export class SideNavService { map(([open, isLargeScreen]) => open && !isLargeScreen), ); + /** + * Local component state width + * + * This observable has immediate pixel-perfect updates for the sidebar display width to use + */ + private readonly _width$ = new BehaviorSubject<number>(this.DEFAULT_OPEN_WIDTH); + readonly width$ = this._width$.asObservable(); + + /** + * State provider width + * + * This observable is used to initialize the component state and will be periodically synced + * to the local _width$ state to avoid excessive writes + */ + private readonly widthState = inject(GlobalStateProvider).get(BIT_SIDE_NAV_WIDTH_KEY_DEF); + readonly widthState$ = this.widthState.state$.pipe( + map((width) => width ?? this.DEFAULT_OPEN_WIDTH), + ); + constructor() { + // Get computed root font size to support user-defined a11y font increases + this.rootFontSizePx = parseFloat(getComputedStyle(document.documentElement).fontSize || "16"); + + // Handle open/close state combineLatest([this.isLargeScreen$, this.userCollapsePreference$]) .pipe(takeUntilDestroyed()) .subscribe(([isLargeScreen, userCollapsePreference]) => { @@ -32,6 +77,16 @@ export class SideNavService { this.setOpen(); } }); + + // Initialize the resizable width from state provider + this.widthState$.pipe(first()).subscribe((width: number) => { + this._width$.next(width); + }); + + // Periodically sync to state provider when component state changes + this.width$.pipe(debounceTime(200), takeUntilDestroyed()).subscribe((width) => { + void this.widthState.update(() => width); + }); } get open() { @@ -46,6 +101,9 @@ export class SideNavService { this._open$.next(false); } + /** + * Toggle the open/close state of the side nav + */ toggle() { const curr = this._open$.getValue(); // Store user's preference based on what state they're toggling TO @@ -57,8 +115,51 @@ export class SideNavService { this.setOpen(); } } + + /** + * Set new side nav width from drag event coordinates + * + * @param eventXCoordinate x coordinate of the pointer's bounding client rect + * @param dragElementXCoordinate x coordinate of the drag element's bounding client rect + */ + setWidthFromDrag(eventXPointer: number, dragElementXCoordinate: number) { + const newWidthInPixels = eventXPointer - dragElementXCoordinate; + + const newWidthInRem = newWidthInPixels / this.rootFontSizePx; + + this._setWidthWithinMinMax(newWidthInRem); + } + + /** + * Set new side nav width from arrow key events + * + * @param key event key, must be either ArrowRight or ArrowLeft + */ + setWidthFromKeys(key: "ArrowRight" | "ArrowLeft") { + const currentWidth = this._width$.getValue(); + + const delta = key === "ArrowLeft" ? -1 : 1; + const newWidth = currentWidth + delta; + + this._setWidthWithinMinMax(newWidth); + } + + /** + * Calculate and set the new width, not going out of the min/max bounds + * @param newWidth desired new width: number + */ + private _setWidthWithinMinMax(newWidth: number) { + const width = Math.min(Math.max(newWidth, this.MIN_OPEN_WIDTH), this.MAX_OPEN_WIDTH); + + this._width$.next(width); + } } +/** + * Helper function for subscribing to media query events + * @param query media query to validate against + * @returns Observable<boolean> + */ export const media = (query: string): Observable<boolean> => { const mediaQuery = window.matchMedia(query); return fromEvent<MediaQueryList>(mediaQuery, "change").pipe( diff --git a/libs/components/src/stories/kitchen-sink/kitchen-sink.stories.ts b/libs/components/src/stories/kitchen-sink/kitchen-sink.stories.ts index fc6be00b0e0..08f4d875962 100644 --- a/libs/components/src/stories/kitchen-sink/kitchen-sink.stories.ts +++ b/libs/components/src/stories/kitchen-sink/kitchen-sink.stories.ts @@ -13,9 +13,11 @@ import { import { PasswordManagerLogo } from "@bitwarden/assets/svg"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { GlobalStateProvider } from "@bitwarden/state"; import { LayoutComponent } from "../../layout"; import { I18nMockService } from "../../utils/i18n-mock.service"; +import { StorybookGlobalStateProvider } from "../../utils/state-mock"; import { positionFixedWrapperDecorator } from "../storybook-decorators"; import { DialogVirtualScrollBlockComponent } from "./components/dialog-virtual-scroll-block.component"; @@ -65,9 +67,14 @@ export default { yes: "Yes", no: "No", loading: "Loading", + resizeSideNavigation: "Resize side navigation", }); }, }, + { + provide: GlobalStateProvider, + useClass: StorybookGlobalStateProvider, + }, ], }), ], diff --git a/libs/components/src/table/table.stories.ts b/libs/components/src/table/table.stories.ts index d696e6077dd..d20b5fd1cda 100644 --- a/libs/components/src/table/table.stories.ts +++ b/libs/components/src/table/table.stories.ts @@ -1,13 +1,14 @@ import { RouterTestingModule } from "@angular/router/testing"; -import { Meta, moduleMetadata, StoryObj } from "@storybook/angular"; +import { applicationConfig, Meta, moduleMetadata, StoryObj } from "@storybook/angular"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { GlobalStateProvider } from "@bitwarden/state"; import { countries } from "../form/countries"; import { LayoutComponent } from "../layout"; import { mockLayoutI18n } from "../layout/mocks"; import { positionFixedWrapperDecorator } from "../stories/storybook-decorators"; -import { I18nMockService } from "../utils"; +import { I18nMockService, StorybookGlobalStateProvider } from "../utils"; import { TableDataSource } from "./table-data-source"; import { TableModule } from "./table.module"; @@ -27,6 +28,14 @@ export default { }, ], }), + applicationConfig({ + providers: [ + { + provide: GlobalStateProvider, + useClass: StorybookGlobalStateProvider, + }, + ], + }), ], argTypes: { alignRowContent: { diff --git a/libs/components/src/utils/index.ts b/libs/components/src/utils/index.ts index 5ac6c6c0da0..c66d7761862 100644 --- a/libs/components/src/utils/index.ts +++ b/libs/components/src/utils/index.ts @@ -2,3 +2,4 @@ export * from "./aria-disable-element"; export * from "./function-to-observable"; export * from "./has-scrollable-content"; export * from "./i18n-mock.service"; +export * from "./state-mock"; diff --git a/libs/components/src/utils/state-mock.ts b/libs/components/src/utils/state-mock.ts new file mode 100644 index 00000000000..d82705f4d3b --- /dev/null +++ b/libs/components/src/utils/state-mock.ts @@ -0,0 +1,48 @@ +import { BehaviorSubject, Observable } from "rxjs"; + +import { + GlobalState, + StateUpdateOptions, + GlobalStateProvider, + KeyDefinition, +} from "@bitwarden/state"; + +export class StorybookGlobalState<T> implements GlobalState<T> { + private _state$ = new BehaviorSubject<T | null>(null); + + constructor(initialValue?: T | null) { + this._state$.next(initialValue ?? null); + } + + async update<TCombine>( + configureState: (state: T | null, dependency: TCombine) => T | null, + options?: Partial<StateUpdateOptions<T, TCombine>>, + ): Promise<T | null> { + const currentState = this._state$.value; + const newState = configureState(currentState, null as TCombine); + this._state$.next(newState); + return newState; + } + + get state$(): Observable<T | null> { + return this._state$.asObservable(); + } + + setValue(value: T | null): void { + this._state$.next(value); + } +} + +export class StorybookGlobalStateProvider implements GlobalStateProvider { + private states = new Map<string, StorybookGlobalState<any>>(); + + get<T>(keyDefinition: KeyDefinition<T>): GlobalState<T> { + const key = `${keyDefinition.fullName}_${keyDefinition.stateDefinition.defaultStorageLocation}`; + + if (!this.states.has(key)) { + this.states.set(key, new StorybookGlobalState<T>()); + } + + return this.states.get(key)!; + } +} diff --git a/libs/state/src/core/state-definitions.ts b/libs/state/src/core/state-definitions.ts index 156c03620b7..445e5fecde7 100644 --- a/libs/state/src/core/state-definitions.ts +++ b/libs/state/src/core/state-definitions.ts @@ -103,6 +103,7 @@ export const AUTOTYPE_SETTINGS_DISK = new StateDefinition("autotypeSettings", "d export const NEW_WEB_LAYOUT_BANNER_DISK = new StateDefinition("newWebLayoutBanner", "disk", { web: "disk-local", }); +export const BIT_SIDE_NAV_DISK = new StateDefinition("bitSideNav", "disk"); // DIRT From 32e0152cda9612c11e4d0c4f192b87c237c6c954 Mon Sep 17 00:00:00 2001 From: Daniel Riera <driera@livefront.com> Date: Mon, 29 Dec 2025 14:46:17 -0500 Subject: [PATCH 173/188] [PM-29514] Remove ts strict ignore in overlay notifications content overlay notifications content service (#17947) * early return on typedata if it is not present * use optional chaining on null checks * nullish coallescing operator on potentially undefined type * optional chaining to check both that the element exists and that contentWindow is not null before calling postMessage * add null check for this.currentNotificationBarType before calling * add a null check before appending notificationBarRootElement, ts cant track we set the iframe across method calls * added null checks before calling setElementStyles --- .../overlay-notifications-content.service.ts | 46 ++++++++++++------- 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/apps/browser/src/autofill/overlay/notifications/content/overlay-notifications-content.service.ts b/apps/browser/src/autofill/overlay/notifications/content/overlay-notifications-content.service.ts index ab3b8144426..5784fd7a73a 100644 --- a/apps/browser/src/autofill/overlay/notifications/content/overlay-notifications-content.service.ts +++ b/apps/browser/src/autofill/overlay/notifications/content/overlay-notifications-content.service.ts @@ -1,5 +1,3 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { EVENTS } from "@bitwarden/common/autofill/constants"; import { BrowserApi } from "../../../../platform/browser/browser-api"; @@ -84,11 +82,15 @@ export class OverlayNotificationsContentService implements OverlayNotificationsC } const { type, typeData, params } = message.data; + if (!typeData) { + return; + } + if (this.currentNotificationBarType && type !== this.currentNotificationBarType) { this.closeNotificationBar(); } - const initData = { + const initData: NotificationBarIframeInitData = { type: type as NotificationType, isVaultLocked: typeData.isVaultLocked, theme: typeData.theme, @@ -116,7 +118,9 @@ export class OverlayNotificationsContentService implements OverlayNotificationsC const closedByUser = typeof message.data?.closedByUser === "boolean" ? message.data.closedByUser : true; if (message.data?.fadeOutNotification) { - setElementStyles(this.notificationBarIframeElement, { opacity: "0" }, true); + if (this.notificationBarIframeElement) { + setElementStyles(this.notificationBarIframeElement, { opacity: "0" }, true); + } globalThis.setTimeout(() => this.closeNotificationBar(closedByUser), 150); return; } @@ -166,7 +170,9 @@ export class OverlayNotificationsContentService implements OverlayNotificationsC this.createNotificationBarElement(); this.setupInitNotificationBarMessageListener(initData); - globalThis.document.body.appendChild(this.notificationBarRootElement); + if (this.notificationBarRootElement) { + globalThis.document.body.appendChild(this.notificationBarRootElement); + } } } @@ -179,7 +185,7 @@ export class OverlayNotificationsContentService implements OverlayNotificationsC const isNotificationFresh = initData.launchTimestamp && Date.now() - initData.launchTimestamp < 250; - this.currentNotificationBarType = initData.type; + this.currentNotificationBarType = initData.type ?? null; this.notificationBarIframeElement = globalThis.document.createElement("iframe"); this.notificationBarIframeElement.id = "bit-notification-bar-iframe"; const parentOrigin = globalThis.location.origin; @@ -206,11 +212,13 @@ export class OverlayNotificationsContentService implements OverlayNotificationsC * This will animate the notification bar into view. */ private handleNotificationBarIframeOnLoad = () => { - setElementStyles( - this.notificationBarIframeElement, - { transform: "translateX(0)", opacity: "1" }, - true, - ); + if (this.notificationBarIframeElement) { + setElementStyles( + this.notificationBarIframeElement, + { transform: "translateX(0)", opacity: "1" }, + true, + ); + } this.notificationBarIframeElement?.removeEventListener( EVENTS.LOAD, @@ -252,6 +260,7 @@ export class OverlayNotificationsContentService implements OverlayNotificationsC const handleInitNotificationBarMessage = (event: MessageEvent) => { const { source, data } = event; if ( + !this.notificationBarIframeElement?.contentWindow || source !== this.notificationBarIframeElement.contentWindow || data?.command !== "initNotificationBar" ) { @@ -282,13 +291,14 @@ export class OverlayNotificationsContentService implements OverlayNotificationsC return; } - this.notificationBarIframeElement.remove(); + this.notificationBarIframeElement?.remove(); this.notificationBarIframeElement = null; - this.notificationBarElement.remove(); + this.notificationBarElement?.remove(); this.notificationBarElement = null; this.notificationBarShadowRoot = null; - this.notificationBarRootElement.remove(); + + this.notificationBarRootElement?.remove(); this.notificationBarRootElement = null; const removableNotificationTypes = new Set([ @@ -297,7 +307,11 @@ export class OverlayNotificationsContentService implements OverlayNotificationsC NotificationTypes.AtRiskPassword, ] as NotificationType[]); - if (closedByUserAction && removableNotificationTypes.has(this.currentNotificationBarType)) { + if ( + closedByUserAction && + this.currentNotificationBarType && + removableNotificationTypes.has(this.currentNotificationBarType) + ) { void sendExtensionMessage("bgRemoveTabFromNotificationQueue"); } @@ -310,7 +324,7 @@ export class OverlayNotificationsContentService implements OverlayNotificationsC * @param message - The message to send to the notification bar iframe. */ private sendMessageToNotificationBarIframe(message: Record<string, any>) { - if (this.notificationBarIframeElement) { + if (this.notificationBarIframeElement?.contentWindow) { this.notificationBarIframeElement.contentWindow.postMessage(message, this.extensionOrigin); } } From 1c6a83f31156b7df532008171748f5841c8ee83a Mon Sep 17 00:00:00 2001 From: Daniel James Smith <2670567+djsmith85@users.noreply.github.com> Date: Mon, 29 Dec 2025 21:28:33 +0100 Subject: [PATCH 174/188] [BEEEP][PM-29441] Introduce bitwarden-encrypted-json-importer (#17651) * Introduce bitwarden-encrypted-json-importer An effort to introduce type guards and split the logic between the differently protected bitwarden-json import-formats * Improved stricter types, but not quite ts-strict yet * Add guard to prevent passing password-protected exports to the wrong importer. * Only create one return object instead of multiple * Updated changes afer npm ci and npm run prettier --------- Co-authored-by: Daniel James Smith <djsmith85@users.noreply.github.com> --- .../bitwarden-encrypted-json-importer.ts | 201 ++++++++++++++++++ .../bitwarden/bitwarden-json-importer.ts | 195 ++++------------- ...warden-password-protected-importer.spec.ts | 11 +- .../bitwarden-password-protected-importer.ts | 23 +- .../src/types/bitwarden-json-export-types.ts | 85 ++++++-- 5 files changed, 328 insertions(+), 187 deletions(-) create mode 100644 libs/importer/src/importers/bitwarden/bitwarden-encrypted-json-importer.ts diff --git a/libs/importer/src/importers/bitwarden/bitwarden-encrypted-json-importer.ts b/libs/importer/src/importers/bitwarden/bitwarden-encrypted-json-importer.ts new file mode 100644 index 00000000000..4771f47b4c9 --- /dev/null +++ b/libs/importer/src/importers/bitwarden/bitwarden-encrypted-json-importer.ts @@ -0,0 +1,201 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore +import { filter, firstValueFrom } from "rxjs"; + +// 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 { Collection } from "@bitwarden/admin-console/common"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; +import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string"; +import { + CipherWithIdExport, + CollectionWithIdExport, + FolderWithIdExport, +} from "@bitwarden/common/models/export"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { OrgKey, UserKey } from "@bitwarden/common/types/key"; +import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; +import { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; +import { KeyService } from "@bitwarden/key-management"; +import { UserId } from "@bitwarden/user-core"; +import { + BitwardenEncryptedIndividualJsonExport, + BitwardenEncryptedJsonExport, + BitwardenEncryptedOrgJsonExport, + BitwardenJsonExport, + BitwardenPasswordProtectedFileFormat, + isOrgEncrypted, + isPasswordProtected, + isUnencrypted, +} from "@bitwarden/vault-export-core"; + +import { ImportResult } from "../../models/import-result"; +import { Importer } from "../importer"; + +import { BitwardenJsonImporter } from "./bitwarden-json-importer"; + +export class BitwardenEncryptedJsonImporter extends BitwardenJsonImporter implements Importer { + constructor( + protected keyService: KeyService, + protected encryptService: EncryptService, + protected i18nService: I18nService, + private cipherService: CipherService, + private accountService: AccountService, + ) { + super(); + } + + async parse(data: string): Promise<ImportResult> { + const results: BitwardenPasswordProtectedFileFormat | BitwardenJsonExport = JSON.parse(data); + + if (isPasswordProtected(results)) { + throw new Error( + "Data is password-protected. Use BitwardenPasswordProtectedImporter instead.", + ); + } + + if (results == null || results.items == null) { + const result = new ImportResult(); + result.success = false; + return result; + } + + if (isUnencrypted(results)) { + return super.parse(data); + } + + return await this.parseEncrypted(results); + } + + private async parseEncrypted(data: BitwardenEncryptedJsonExport): Promise<ImportResult> { + const result = new ImportResult(); + const account = await firstValueFrom(this.accountService.activeAccount$); + + if (this.isNullOrWhitespace(data.encKeyValidation_DO_NOT_EDIT)) { + result.success = false; + result.errorMessage = this.i18nService.t("importEncKeyError"); + return result; + } + + const orgKeys = await firstValueFrom(this.keyService.orgKeys$(account.id)); + let keyForDecryption: OrgKey | UserKey | null | undefined = orgKeys?.[this.organizationId]; + if (!keyForDecryption) { + keyForDecryption = await firstValueFrom(this.keyService.userKey$(account.id)); + } + + if (!keyForDecryption) { + result.success = false; + result.errorMessage = this.i18nService.t("importEncKeyError"); + return result; + } + const encKeyValidation = new EncString(data.encKeyValidation_DO_NOT_EDIT); + try { + await this.encryptService.decryptString(encKeyValidation, keyForDecryption); + } catch { + result.success = false; + result.errorMessage = this.i18nService.t("importEncKeyError"); + return result; + } + + let groupingsMap: Map<string, number> | null = null; + if (isOrgEncrypted(data)) { + groupingsMap = await this.parseEncryptedCollections(account.id, data, result); + } else { + groupingsMap = await this.parseEncryptedFolders(account.id, data, result); + } + + for (const c of data.items) { + const cipher = CipherWithIdExport.toDomain(c); + // reset ids in case they were set for some reason + cipher.id = null; + cipher.organizationId = this.organizationId; + cipher.collectionIds = null; + + // make sure password history is limited + if (cipher.passwordHistory != null && cipher.passwordHistory.length > 5) { + cipher.passwordHistory = cipher.passwordHistory.slice(0, 5); + } + + if (!this.organization && c.folderId != null && groupingsMap.has(c.folderId)) { + result.folderRelationships.push([result.ciphers.length, groupingsMap.get(c.folderId)]); + } else if (this.organization && c.collectionIds != null) { + c.collectionIds.forEach((cId) => { + if (groupingsMap.has(cId)) { + result.collectionRelationships.push([result.ciphers.length, groupingsMap.get(cId)]); + } + }); + } + + const view = await this.cipherService.decrypt(cipher, account.id); + this.cleanupCipher(view); + result.ciphers.push(view); + } + + result.success = true; + return result; + } + + private async parseEncryptedFolders( + userId: UserId, + data: BitwardenEncryptedIndividualJsonExport, + importResult: ImportResult, + ): Promise<Map<string, number>> { + const groupingsMap = new Map<string, number>(); + + if (data.folders == null) { + return groupingsMap; + } + + const userKey = await firstValueFrom(this.keyService.userKey$(userId)); + + for (const f of data.folders) { + let folderView: FolderView; + const folder = FolderWithIdExport.toDomain(f); + if (folder != null) { + folderView = await folder.decrypt(userKey); + } + + if (folderView != null) { + groupingsMap.set(f.id, importResult.folders.length); + importResult.folders.push(folderView); + } + } + return groupingsMap; + } + + private async parseEncryptedCollections( + userId: UserId, + data: BitwardenEncryptedOrgJsonExport, + importResult: ImportResult, + ): Promise<Map<string, number>> { + const groupingsMap = new Map<string, number>(); + if (data.collections == null) { + return groupingsMap; + } + + const orgKeys = await firstValueFrom( + this.keyService.orgKeys$(userId).pipe(filter((orgKeys) => orgKeys != null)), + ); + + for (const c of data.collections) { + const collection = CollectionWithIdExport.toDomain( + c, + new Collection({ + id: c.id, + name: new EncString(c.name), + organizationId: this.organizationId, + }), + ); + + const orgKey = orgKeys[c.organizationId]; + const collectionView = await collection.decrypt(orgKey, this.encryptService); + + if (collectionView != null) { + groupingsMap.set(c.id, importResult.collections.length); + importResult.collections.push(collectionView); + } + } + return groupingsMap; + } +} diff --git a/libs/importer/src/importers/bitwarden/bitwarden-json-importer.ts b/libs/importer/src/importers/bitwarden/bitwarden-json-importer.ts index 1f5be7f18ab..ddeba885f88 100644 --- a/libs/importer/src/importers/bitwarden/bitwarden-json-importer.ts +++ b/libs/importer/src/importers/bitwarden/bitwarden-json-importer.ts @@ -1,30 +1,17 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { filter, firstValueFrom } from "rxjs"; - -// 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 { Collection, CollectionView } from "@bitwarden/admin-console/common"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; -import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string"; import { CipherWithIdExport, CollectionWithIdExport, FolderWithIdExport, } from "@bitwarden/common/models/export"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; -import { UserId } from "@bitwarden/common/types/guid"; -import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; -import { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; -import { KeyService } from "@bitwarden/key-management"; import { - BitwardenEncryptedIndividualJsonExport, - BitwardenEncryptedOrgJsonExport, BitwardenJsonExport, BitwardenUnEncryptedIndividualJsonExport, + BitwardenUnEncryptedJsonExport, BitwardenUnEncryptedOrgJsonExport, + isOrgUnEncrypted, + isUnencrypted, } from "@bitwarden/vault-export-core"; import { ImportResult } from "../../models/import-result"; @@ -32,103 +19,30 @@ import { BaseImporter } from "../base-importer"; import { Importer } from "../importer"; export class BitwardenJsonImporter extends BaseImporter implements Importer { - private result: ImportResult; - - protected constructor( - protected keyService: KeyService, - protected encryptService: EncryptService, - protected i18nService: I18nService, - protected cipherService: CipherService, - protected accountService: AccountService, - ) { + protected constructor() { super(); } async parse(data: string): Promise<ImportResult> { - const account = await firstValueFrom(this.accountService.activeAccount$); - this.result = new ImportResult(); const results: BitwardenJsonExport = JSON.parse(data); if (results == null || results.items == null) { - this.result.success = false; - return this.result; + const result = new ImportResult(); + result.success = false; + return result; } - if (results.encrypted) { - await this.parseEncrypted(results as any, account.id); - } else { - await this.parseDecrypted(results as any, account.id); + if (!isUnencrypted(results)) { + throw new Error("Data is encrypted. Use BitwardenEncryptedJsonImporter instead."); } - - return this.result; + return await this.parseDecrypted(results); } - private async parseEncrypted( - results: BitwardenEncryptedIndividualJsonExport | BitwardenEncryptedOrgJsonExport, - userId: UserId, - ) { - if (results.encKeyValidation_DO_NOT_EDIT != null) { - const orgKeys = await firstValueFrom(this.keyService.orgKeys$(userId)); - let keyForDecryption: SymmetricCryptoKey = orgKeys?.[this.organizationId]; - if (keyForDecryption == null) { - keyForDecryption = await firstValueFrom(this.keyService.userKey$(userId)); - } - const encKeyValidation = new EncString(results.encKeyValidation_DO_NOT_EDIT); - try { - await this.encryptService.decryptString(encKeyValidation, keyForDecryption); - } catch { - this.result.success = false; - this.result.errorMessage = this.i18nService.t("importEncKeyError"); - return; - } - } + private async parseDecrypted(results: BitwardenUnEncryptedJsonExport): Promise<ImportResult> { + const importResult = new ImportResult(); - const groupingsMap = this.organization - ? await this.parseCollections(results as BitwardenEncryptedOrgJsonExport, userId) - : await this.parseFolders(results as BitwardenEncryptedIndividualJsonExport, userId); - - for (const c of results.items) { - const cipher = CipherWithIdExport.toDomain(c); - // reset ids in case they were set for some reason - cipher.id = null; - cipher.organizationId = this.organizationId; - cipher.collectionIds = null; - - // make sure password history is limited - if (cipher.passwordHistory != null && cipher.passwordHistory.length > 5) { - cipher.passwordHistory = cipher.passwordHistory.slice(0, 5); - } - - if (!this.organization && c.folderId != null && groupingsMap.has(c.folderId)) { - this.result.folderRelationships.push([ - this.result.ciphers.length, - groupingsMap.get(c.folderId), - ]); - } else if (this.organization && c.collectionIds != null) { - c.collectionIds.forEach((cId) => { - if (groupingsMap.has(cId)) { - this.result.collectionRelationships.push([ - this.result.ciphers.length, - groupingsMap.get(cId), - ]); - } - }); - } - - const view = await this.cipherService.decrypt(cipher, userId); - this.cleanupCipher(view); - this.result.ciphers.push(view); - } - - this.result.success = true; - } - - private async parseDecrypted( - results: BitwardenUnEncryptedIndividualJsonExport | BitwardenUnEncryptedOrgJsonExport, - userId: UserId, - ) { - const groupingsMap = this.organization - ? await this.parseCollections(results as BitwardenUnEncryptedOrgJsonExport, userId) - : await this.parseFolders(results as BitwardenUnEncryptedIndividualJsonExport, userId); + const groupingsMap = isOrgUnEncrypted(results) + ? await this.parseCollections(results, importResult) + : await this.parseFolders(results, importResult); results.items.forEach((c) => { const cipher = CipherWithIdExport.toView(c); @@ -143,15 +57,15 @@ export class BitwardenJsonImporter extends BaseImporter implements Importer { } if (!this.organization && c.folderId != null && groupingsMap.has(c.folderId)) { - this.result.folderRelationships.push([ - this.result.ciphers.length, + importResult.folderRelationships.push([ + importResult.ciphers.length, groupingsMap.get(c.folderId), ]); } else if (this.organization && c.collectionIds != null) { c.collectionIds.forEach((cId) => { if (groupingsMap.has(cId)) { - this.result.collectionRelationships.push([ - this.result.ciphers.length, + importResult.collectionRelationships.push([ + importResult.ciphers.length, groupingsMap.get(cId), ]); } @@ -159,79 +73,48 @@ export class BitwardenJsonImporter extends BaseImporter implements Importer { } this.cleanupCipher(cipher); - this.result.ciphers.push(cipher); + importResult.ciphers.push(cipher); }); - this.result.success = true; + importResult.success = true; + return importResult; } private async parseFolders( - data: BitwardenUnEncryptedIndividualJsonExport | BitwardenEncryptedIndividualJsonExport, - userId: UserId, - ): Promise<Map<string, number>> | null { + data: BitwardenUnEncryptedIndividualJsonExport, + importResult: ImportResult, + ): Promise<Map<string, number>> { + const groupingsMap = new Map<string, number>(); if (data.folders == null) { - return null; + return groupingsMap; } - const userKey = await firstValueFrom(this.keyService.userKey$(userId)); - - const groupingsMap = new Map<string, number>(); - for (const f of data.folders) { - let folderView: FolderView; - if (data.encrypted) { - const folder = FolderWithIdExport.toDomain(f); - if (folder != null) { - folderView = await folder.decrypt(userKey); - } - } else { - folderView = FolderWithIdExport.toView(f); - } - + const folderView = FolderWithIdExport.toView(f); if (folderView != null) { - groupingsMap.set(f.id, this.result.folders.length); - this.result.folders.push(folderView); + groupingsMap.set(f.id, importResult.folders.length); + importResult.folders.push(folderView); } } return groupingsMap; } private async parseCollections( - data: BitwardenUnEncryptedOrgJsonExport | BitwardenEncryptedOrgJsonExport, - userId: UserId, - ): Promise<Map<string, number>> | null { + data: BitwardenUnEncryptedOrgJsonExport, + importResult: ImportResult, + ): Promise<Map<string, number>> { + const groupingsMap = new Map<string, number>(); if (data.collections == null) { - return null; + return groupingsMap; } - const orgKeys = await firstValueFrom( - this.keyService.orgKeys$(userId).pipe(filter((orgKeys) => orgKeys != null)), - ); - - const groupingsMap = new Map<string, number>(); - for (const c of data.collections) { - let collectionView: CollectionView; - if (data.encrypted) { - const collection = CollectionWithIdExport.toDomain( - c, - new Collection({ - id: c.id, - name: new EncString(c.name), - organizationId: this.organizationId, - }), - ); - - const orgKey = orgKeys[c.organizationId]; - collectionView = await collection.decrypt(orgKey, this.encryptService); - } else { - collectionView = CollectionWithIdExport.toView(c); - collectionView.organizationId = null; - } + const collectionView = CollectionWithIdExport.toView(c); + collectionView.organizationId = null; if (collectionView != null) { - groupingsMap.set(c.id, this.result.collections.length); - this.result.collections.push(collectionView); + groupingsMap.set(c.id, importResult.collections.length); + importResult.collections.push(collectionView); } } return groupingsMap; 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 fdf92cac751..ff6e3692640 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 @@ -16,6 +16,7 @@ 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"; +import { BitwardenEncryptedJsonImporter } from "./bitwarden-encrypted-json-importer"; import { BitwardenJsonImporter } from "./bitwarden-json-importer"; import { BitwardenPasswordProtectedImporter } from "./bitwarden-password-protected-importer"; @@ -92,7 +93,7 @@ describe("BitwardenPasswordProtectedImporter", () => { describe("Account encrypted", () => { beforeAll(() => { - jest.spyOn(BitwardenJsonImporter.prototype, "parse"); + jest.spyOn(BitwardenEncryptedJsonImporter.prototype, "parse"); }); beforeEach(() => { @@ -114,9 +115,11 @@ describe("BitwardenPasswordProtectedImporter", () => { ); }); - it("Should call BitwardenJsonImporter", async () => { - expect((await importer.parse(emptyAccountEncrypted)).success).toEqual(true); - expect(BitwardenJsonImporter.prototype.parse).toHaveBeenCalledWith(emptyAccountEncrypted); + it("Should call BitwardenEncryptedJsonImporter", async () => { + expect((await importer.parse(emptyAccountEncrypted)).success).toEqual(false); + expect(BitwardenEncryptedJsonImporter.prototype.parse).toHaveBeenCalledWith( + emptyAccountEncrypted, + ); }); }); diff --git a/libs/importer/src/importers/bitwarden/bitwarden-password-protected-importer.ts b/libs/importer/src/importers/bitwarden/bitwarden-password-protected-importer.ts index b685ddf0fb5..cc38c420d9b 100644 --- a/libs/importer/src/importers/bitwarden/bitwarden-password-protected-importer.ts +++ b/libs/importer/src/importers/bitwarden/bitwarden-password-protected-importer.ts @@ -14,14 +14,21 @@ import { KeyService, KdfType, } from "@bitwarden/key-management"; -import { BitwardenPasswordProtectedFileFormat } from "@bitwarden/vault-export-core"; +import { + BitwardenJsonExport, + BitwardenPasswordProtectedFileFormat, + isPasswordProtected, +} from "@bitwarden/vault-export-core"; import { ImportResult } from "../../models/import-result"; import { Importer } from "../importer"; -import { BitwardenJsonImporter } from "./bitwarden-json-importer"; +import { BitwardenEncryptedJsonImporter } from "./bitwarden-encrypted-json-importer"; -export class BitwardenPasswordProtectedImporter extends BitwardenJsonImporter implements Importer { +export class BitwardenPasswordProtectedImporter + extends BitwardenEncryptedJsonImporter + implements Importer +{ private key: SymmetricCryptoKey; constructor( @@ -38,20 +45,14 @@ export class BitwardenPasswordProtectedImporter extends BitwardenJsonImporter im async parse(data: string): Promise<ImportResult> { const result = new ImportResult(); - const parsedData: BitwardenPasswordProtectedFileFormat = JSON.parse(data); + const parsedData: BitwardenPasswordProtectedFileFormat | BitwardenJsonExport = JSON.parse(data); if (!parsedData) { result.success = false; return result; } - // File is unencrypted - if (!parsedData?.encrypted) { - return await super.parse(data); - } - - // File is account-encrypted - if (!parsedData?.passwordProtected) { + if (!isPasswordProtected(parsedData)) { return await super.parse(data); } diff --git a/libs/tools/export/vault-export/vault-export-core/src/types/bitwarden-json-export-types.ts b/libs/tools/export/vault-export/vault-export-core/src/types/bitwarden-json-export-types.ts index ab2bcbb9f1f..fd33bf96923 100644 --- a/libs/tools/export/vault-export/vault-export-core/src/types/bitwarden-json-export-types.ts +++ b/libs/tools/export/vault-export/vault-export-core/src/types/bitwarden-json-export-types.ts @@ -5,42 +5,48 @@ import { } from "@bitwarden/common/models/export"; // Base -export type BitwardenJsonExport = { - encrypted: boolean; - items: CipherWithIdExport[]; -}; +export type BitwardenJsonExport = BitwardenUnEncryptedJsonExport | BitwardenEncryptedJsonExport; // Decrypted -export type BitwardenUnEncryptedJsonExport = BitwardenJsonExport & { - encrypted: false; -}; +export type BitwardenUnEncryptedJsonExport = + | BitwardenUnEncryptedIndividualJsonExport + | BitwardenUnEncryptedOrgJsonExport; -export type BitwardenUnEncryptedIndividualJsonExport = BitwardenUnEncryptedJsonExport & { +export type BitwardenUnEncryptedIndividualJsonExport = { + encrypted: false; + items: CipherWithIdExport[]; folders: FolderWithIdExport[]; }; -export type BitwardenUnEncryptedOrgJsonExport = BitwardenUnEncryptedJsonExport & { +export type BitwardenUnEncryptedOrgJsonExport = { + encrypted: false; + items: CipherWithIdExport[]; collections: CollectionWithIdExport[]; }; // Account-encrypted -export type BitwardenEncryptedJsonExport = BitwardenJsonExport & { +export type BitwardenEncryptedJsonExport = + | BitwardenEncryptedIndividualJsonExport + | BitwardenEncryptedOrgJsonExport; + +export type BitwardenEncryptedIndividualJsonExport = { encrypted: true; encKeyValidation_DO_NOT_EDIT: string; -}; - -export type BitwardenEncryptedIndividualJsonExport = BitwardenEncryptedJsonExport & { + items: CipherWithIdExport[]; folders: FolderWithIdExport[]; }; -export type BitwardenEncryptedOrgJsonExport = BitwardenEncryptedJsonExport & { +export type BitwardenEncryptedOrgJsonExport = { + encrypted: true; + encKeyValidation_DO_NOT_EDIT: string; + items: CipherWithIdExport[]; collections: CollectionWithIdExport[]; }; // Password-protected export type BitwardenPasswordProtectedFileFormat = { - encrypted: boolean; - passwordProtected: boolean; + encrypted: true; + passwordProtected: true; salt: string; kdfIterations: number; kdfMemory?: number; @@ -49,3 +55,50 @@ export type BitwardenPasswordProtectedFileFormat = { encKeyValidation_DO_NOT_EDIT: string; data: string; }; + +// Unencrypted type guards +export function isUnencrypted( + data: BitwardenJsonExport | null | undefined, +): data is BitwardenUnEncryptedJsonExport { + return data != null && (data as { encrypted?: unknown }).encrypted !== true; +} + +export function isIndividualUnEncrypted( + data: BitwardenJsonExport | null | undefined, +): data is BitwardenUnEncryptedIndividualJsonExport { + return isUnencrypted(data) && (data as { folders?: unknown }).folders != null; +} + +export function isOrgUnEncrypted( + data: BitwardenJsonExport | null | undefined, +): data is BitwardenUnEncryptedOrgJsonExport { + return isUnencrypted(data) && (data as { collections?: unknown }).collections != null; +} + +// Encrypted type guards +export function isEncrypted( + data: BitwardenJsonExport | null | undefined, +): data is BitwardenEncryptedJsonExport { + return data != null && (data as { encrypted?: unknown }).encrypted === true; +} +export function isPasswordProtected( + data: BitwardenPasswordProtectedFileFormat | BitwardenJsonExport | null | undefined, +): data is BitwardenPasswordProtectedFileFormat { + return ( + data != null && + (data as { encrypted?: unknown }).encrypted === true && + (data as { passwordProtected?: unknown }).passwordProtected === true + ); +} + +export function isIndividualEncrypted( + data: BitwardenJsonExport | null | undefined, +): data is BitwardenEncryptedIndividualJsonExport { + return isEncrypted(data) && (data as { folders?: unknown }).folders != null; +} + +export function isOrgEncrypted( + data: BitwardenJsonExport | null | undefined, +): data is BitwardenEncryptedOrgJsonExport { + return isEncrypted(data) && (data as { collections?: unknown }).collections != null; +} From 7853ac3d9f17e99937126f15cdd1788c04ace8df Mon Sep 17 00:00:00 2001 From: Daniel Riera <driera@livefront.com> Date: Mon, 29 Dec 2025 16:16:58 -0500 Subject: [PATCH 175/188] PM-29509 [LO IMPACT] Remove @ts-strict-ignore in fido2/content/messaging/messenger.ts (#17913) * PM-29509 [LO IMPACT] Remove @ts-strict-ignore in fido2/content/messaging/messenger.ts - 1 err, 137 LOC, 11.4 * strip metadata from message * preserve one way handler --- .../fido2/content/messaging/messenger.ts | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/apps/browser/src/autofill/fido2/content/messaging/messenger.ts b/apps/browser/src/autofill/fido2/content/messaging/messenger.ts index 257f7e9efd5..61ed7a8ed08 100644 --- a/apps/browser/src/autofill/fido2/content/messaging/messenger.ts +++ b/apps/browser/src/autofill/fido2/content/messaging/messenger.ts @@ -1,5 +1,3 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { Message, MessageTypes } from "./message"; const SENDER = "bitwarden-webauthn"; @@ -25,7 +23,7 @@ type Handler = ( * handling aborts and exceptions across separate execution contexts. */ export class Messenger { - private messageEventListener: (event: MessageEvent<MessageWithMetadata>) => void | null = null; + private messageEventListener: ((event: MessageEvent<MessageWithMetadata>) => void) | null = null; private onDestroy = new EventTarget(); /** @@ -60,6 +58,12 @@ export class Messenger { this.broadcastChannel.addEventListener(this.messageEventListener); } + private stripMetadata({ SENDER, senderId, ...message }: MessageWithMetadata): Message { + void SENDER; + void senderId; + return message; + } + /** * Sends a request to the content script and returns the response. * AbortController signals will be forwarded to the content script. @@ -74,7 +78,9 @@ export class Messenger { try { const promise = new Promise<Message>((resolve) => { - localPort.onmessage = (event: MessageEvent<MessageWithMetadata>) => resolve(event.data); + localPort.onmessage = (event: MessageEvent<MessageWithMetadata>) => { + resolve(this.stripMetadata(event.data)); + }; }); const abortListener = () => @@ -129,7 +135,9 @@ export class Messenger { try { const handlerResponse = await this.handler(message, abortController); - port.postMessage({ ...handlerResponse, SENDER }); + if (handlerResponse !== undefined) { + port.postMessage({ ...handlerResponse, SENDER }); + } } catch (error) { port.postMessage({ SENDER, From 696c53fac7fea2cd977155d0b1688277188c83e1 Mon Sep 17 00:00:00 2001 From: Shane Melton <smelton@bitwarden.com> Date: Mon, 29 Dec 2025 16:41:42 -0800 Subject: [PATCH 176/188] [PM-29209] Fix persistent browser settings berry (#18113) * [PM-29209] Introduce new autofill nudge service specific to the Browser client * [PM-29209] Cleanup redundant browser setting checks * [PM-29209] Ensure nudge is dismissed on nudge button click * [PM-29209] Add spec file for browser autofill nudge service * [PM-29209] Cleanup settings-v2 spec file --- .../popup/settings/autofill.component.html | 22 +-- .../popup/settings/autofill.component.ts | 4 + .../src/popup/services/services.module.ts | 8 + .../popup/settings/settings-v2.component.html | 12 +- .../settings/settings-v2.component.spec.ts | 46 +---- .../popup/settings/settings-v2.component.ts | 33 +--- .../browser-autofill-nudge.service.spec.ts | 157 ++++++++++++++++++ .../browser-autofill-nudge.service.ts | 37 +++++ libs/angular/src/vault/index.ts | 1 + .../services/custom-nudges-services/index.ts | 1 + .../noop-nudge.service.ts | 27 +++ .../vault/services/nudge-injection-tokens.ts | 7 + .../src/vault/services/nudges.service.ts | 10 +- 13 files changed, 272 insertions(+), 93 deletions(-) create mode 100644 apps/browser/src/vault/popup/services/browser-autofill-nudge.service.spec.ts create mode 100644 apps/browser/src/vault/popup/services/browser-autofill-nudge.service.ts create mode 100644 libs/angular/src/vault/services/custom-nudges-services/noop-nudge.service.ts create mode 100644 libs/angular/src/vault/services/nudge-injection-tokens.ts diff --git a/apps/browser/src/autofill/popup/settings/autofill.component.html b/apps/browser/src/autofill/popup/settings/autofill.component.html index 1153ad58719..085145adb19 100644 --- a/apps/browser/src/autofill/popup/settings/autofill.component.html +++ b/apps/browser/src/autofill/popup/settings/autofill.component.html @@ -6,16 +6,18 @@ </popup-header> <div class="tw-bg-background-alt"> - <div *ngIf="!defaultBrowserAutofillDisabled && (showSpotlightNudge$ | async)" class="tw-mb-6"> - <bit-spotlight - [title]="'autofillSpotlightTitle' | i18n" - [subtitle]="'autofillSpotlightDesc' | i18n" - [buttonText]="spotlightButtonText" - (onDismiss)="dismissSpotlight()" - (onButtonClick)="disableBrowserAutofillSettingsFromNudge($event)" - [buttonIcon]="spotlightButtonIcon" - ></bit-spotlight> - </div> + @if (showSpotlightNudge$ | async) { + <div class="tw-mb-6"> + <bit-spotlight + [title]="'autofillSpotlightTitle' | i18n" + [subtitle]="'autofillSpotlightDesc' | i18n" + [buttonText]="spotlightButtonText" + (onDismiss)="dismissSpotlight()" + (onButtonClick)="disableBrowserAutofillSettingsFromNudge($event)" + [buttonIcon]="spotlightButtonIcon" + ></bit-spotlight> + </div> + } <bit-section> <bit-section-header> <h2 bitTypography="h6">{{ "autofillSuggestionsSectionTitle" | i18n }}</h2> diff --git a/apps/browser/src/autofill/popup/settings/autofill.component.ts b/apps/browser/src/autofill/popup/settings/autofill.component.ts index 49be3104dc1..acb2aa7a970 100644 --- a/apps/browser/src/autofill/popup/settings/autofill.component.ts +++ b/apps/browser/src/autofill/popup/settings/autofill.component.ts @@ -611,6 +611,10 @@ export class AutofillComponent implements OnInit { if (this.canOverrideBrowserAutofillSetting) { this.defaultBrowserAutofillDisabled = true; await this.updateDefaultBrowserAutofillDisabled(); + await this.nudgesService.dismissNudge( + NudgeType.AutofillNudge, + await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)), + ); } else { await this.openURI(event, this.disablePasswordManagerURI); } diff --git a/apps/browser/src/popup/services/services.module.ts b/apps/browser/src/popup/services/services.module.ts index 739166ff6f8..7a2ded5bb83 100644 --- a/apps/browser/src/popup/services/services.module.ts +++ b/apps/browser/src/popup/services/services.module.ts @@ -23,6 +23,8 @@ import { WINDOW, } from "@bitwarden/angular/services/injection-tokens"; import { JslibServicesModule } from "@bitwarden/angular/services/jslib-services.module"; +import { AUTOFILL_NUDGE_SERVICE } from "@bitwarden/angular/vault"; +import { SingleNudgeService } from "@bitwarden/angular/vault/services/default-single-nudge.service"; import { LoginComponentService, TwoFactorAuthComponentService, @@ -208,6 +210,7 @@ import { } from "../../platform/system-notifications/browser-system-notification.service"; import { fromChromeRuntimeMessaging } from "../../platform/utils/from-chrome-runtime-messaging"; import { FilePopoutUtilsService } from "../../tools/popup/services/file-popout-utils.service"; +import { BrowserAutofillNudgeService } from "../../vault/popup/services/browser-autofill-nudge.service"; import { Fido2UserVerificationService } from "../../vault/services/fido2-user-verification.service"; import { ExtensionAnonLayoutWrapperDataService } from "../components/extension-anon-layout-wrapper/extension-anon-layout-wrapper-data.service"; @@ -756,6 +759,11 @@ const safeProviders: SafeProvider[] = [ MessagingServiceAbstraction, ], }), + safeProvider({ + provide: AUTOFILL_NUDGE_SERVICE as SafeInjectionToken<SingleNudgeService>, + useClass: BrowserAutofillNudgeService, + deps: [], + }), ]; @NgModule({ diff --git a/apps/browser/src/tools/popup/settings/settings-v2.component.html b/apps/browser/src/tools/popup/settings/settings-v2.component.html index 683b7d70ed6..06c89e15f59 100644 --- a/apps/browser/src/tools/popup/settings/settings-v2.component.html +++ b/apps/browser/src/tools/popup/settings/settings-v2.component.html @@ -34,13 +34,11 @@ <i slot="start" class="bwi bwi-check-circle" aria-hidden="true"></i> <div class="tw-flex tw-items-center tw-justify-center"> <p class="tw-pr-2">{{ "autofill" | i18n }}</p> - <span - *ngIf="!(isBrowserAutofillSettingOverridden$ | async) && (showAutofillBadge$ | async)" - bitBadge - variant="notification" - [attr.aria-label]="'nudgeBadgeAria' | i18n" - >1</span - > + @if (showAutofillBadge$ | async) { + <span bitBadge variant="notification" [attr.aria-label]="'nudgeBadgeAria' | i18n" + >1</span + > + } </div> <i slot="end" class="bwi bwi-angle-right" aria-hidden="true"></i> </a> diff --git a/apps/browser/src/tools/popup/settings/settings-v2.component.spec.ts b/apps/browser/src/tools/popup/settings/settings-v2.component.spec.ts index f51d514289e..4cc3ed0149c 100644 --- a/apps/browser/src/tools/popup/settings/settings-v2.component.spec.ts +++ b/apps/browser/src/tools/popup/settings/settings-v2.component.spec.ts @@ -148,31 +148,7 @@ describe("SettingsV2Component", () => { expect(openSpy).toHaveBeenCalledWith(dialogService); }); - it("isBrowserAutofillSettingOverridden$ emits the value from the AutofillBrowserSettingsService", async () => { - pushActiveAccount(); - - mockAutofillSettings.isBrowserAutofillSettingOverridden.mockResolvedValue(true); - - const fixture = TestBed.createComponent(SettingsV2Component); - const component = fixture.componentInstance; - fixture.detectChanges(); - await fixture.whenStable(); - - const value = await firstValueFrom(component["isBrowserAutofillSettingOverridden$"]); - expect(value).toBe(true); - - mockAutofillSettings.isBrowserAutofillSettingOverridden.mockResolvedValue(false); - - const fixture2 = TestBed.createComponent(SettingsV2Component); - const component2 = fixture2.componentInstance; - fixture2.detectChanges(); - await fixture2.whenStable(); - - const value2 = await firstValueFrom(component2["isBrowserAutofillSettingOverridden$"]); - expect(value2).toBe(false); - }); - - it("showAutofillBadge$ emits true when default autofill is NOT disabled and nudge is true", async () => { + it("showAutofillBadge$ emits true when showNudgeBadge is true", async () => { pushActiveAccount(); mockNudges.showNudgeBadge$.mockImplementation((type: NudgeType) => @@ -184,30 +160,10 @@ describe("SettingsV2Component", () => { fixture.detectChanges(); await fixture.whenStable(); - mockAutofillSettings.defaultBrowserAutofillDisabled$.next(false); - const value = await firstValueFrom(component.showAutofillBadge$); expect(value).toBe(true); }); - it("showAutofillBadge$ emits false when default autofill IS disabled even if nudge is true", async () => { - pushActiveAccount(); - - mockNudges.showNudgeBadge$.mockImplementation((type: NudgeType) => - of(type === NudgeType.AutofillNudge), - ); - - const fixture = TestBed.createComponent(SettingsV2Component); - const component = fixture.componentInstance; - fixture.detectChanges(); - await fixture.whenStable(); - - mockAutofillSettings.defaultBrowserAutofillDisabled$.next(true); - - const value = await firstValueFrom(component.showAutofillBadge$); - expect(value).toBe(false); - }); - it("dismissBadge dismisses when showVaultBadge$ emits true", async () => { const acct = pushActiveAccount(); diff --git a/apps/browser/src/tools/popup/settings/settings-v2.component.ts b/apps/browser/src/tools/popup/settings/settings-v2.component.ts index 95aeeb2f480..e10d41b9445 100644 --- a/apps/browser/src/tools/popup/settings/settings-v2.component.ts +++ b/apps/browser/src/tools/popup/settings/settings-v2.component.ts @@ -1,16 +1,7 @@ import { CommonModule } from "@angular/common"; import { ChangeDetectionStrategy, Component } from "@angular/core"; import { RouterModule } from "@angular/router"; -import { - combineLatest, - filter, - firstValueFrom, - from, - map, - Observable, - shareReplay, - switchMap, -} from "rxjs"; +import { filter, firstValueFrom, Observable, shareReplay, switchMap } from "rxjs"; import { PremiumUpgradeDialogComponent } from "@bitwarden/angular/billing/components"; import { JslibModule } from "@bitwarden/angular/jslib.module"; @@ -28,8 +19,6 @@ import { } from "@bitwarden/components"; import { CurrentAccountComponent } from "../../../auth/popup/account-switching/current-account.component"; -import { AutofillBrowserSettingsService } from "../../../autofill/services/autofill-browser-settings.service"; -import { BrowserApi } from "../../../platform/browser/browser-api"; import { PopOutComponent } from "../../../platform/popup/components/pop-out.component"; import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-header.component"; import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.component"; @@ -55,12 +44,6 @@ import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.co export class SettingsV2Component { NudgeType = NudgeType; - protected isBrowserAutofillSettingOverridden$ = from( - this.autofillBrowserSettingsService.isBrowserAutofillSettingOverridden( - BrowserApi.getBrowserClientVendor(window), - ), - ); - private authenticatedAccount$: Observable<Account> = this.accountService.activeAccount$.pipe( filter((account): account is Account => account !== null), shareReplay({ bufferSize: 1, refCount: true }), @@ -82,23 +65,13 @@ export class SettingsV2Component { ), ); - showAutofillBadge$: Observable<boolean> = combineLatest([ - this.autofillBrowserSettingsService.defaultBrowserAutofillDisabled$, - this.authenticatedAccount$, - ]).pipe( - switchMap(([defaultBrowserAutofillDisabled, account]) => - this.nudgesService.showNudgeBadge$(NudgeType.AutofillNudge, account.id).pipe( - map((badgeStatus) => { - return !defaultBrowserAutofillDisabled && badgeStatus; - }), - ), - ), + showAutofillBadge$: Observable<boolean> = this.authenticatedAccount$.pipe( + switchMap((account) => this.nudgesService.showNudgeBadge$(NudgeType.AutofillNudge, account.id)), ); constructor( private readonly nudgesService: NudgesService, private readonly accountService: AccountService, - private readonly autofillBrowserSettingsService: AutofillBrowserSettingsService, private readonly accountProfileStateService: BillingAccountProfileStateService, private readonly dialogService: DialogService, ) {} diff --git a/apps/browser/src/vault/popup/services/browser-autofill-nudge.service.spec.ts b/apps/browser/src/vault/popup/services/browser-autofill-nudge.service.spec.ts new file mode 100644 index 00000000000..40782760283 --- /dev/null +++ b/apps/browser/src/vault/popup/services/browser-autofill-nudge.service.spec.ts @@ -0,0 +1,157 @@ +import { TestBed } from "@angular/core/testing"; +import { mock, MockProxy } from "jest-mock-extended"; +import { firstValueFrom } from "rxjs"; + +import { NudgeStatus, NudgeType } from "@bitwarden/angular/vault"; +import { VaultProfileService } from "@bitwarden/angular/vault/services/vault-profile.service"; +import { BrowserClientVendors } from "@bitwarden/common/autofill/constants"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { StateProvider } from "@bitwarden/common/platform/state"; +import { UserId } from "@bitwarden/common/types/guid"; + +import { FakeStateProvider, mockAccountServiceWith } from "../../../../../../libs/common/spec"; +import { BrowserApi } from "../../../platform/browser/browser-api"; + +import { BrowserAutofillNudgeService } from "./browser-autofill-nudge.service"; + +describe("BrowserAutofillNudgeService", () => { + let service: BrowserAutofillNudgeService; + let vaultProfileService: MockProxy<VaultProfileService>; + let fakeStateProvider: FakeStateProvider; + + const userId = "test-user-id" as UserId; + const nudgeType = NudgeType.AutofillNudge; + + const notDismissedStatus: NudgeStatus = { + hasBadgeDismissed: false, + hasSpotlightDismissed: false, + }; + + const dismissedStatus: NudgeStatus = { + hasBadgeDismissed: true, + hasSpotlightDismissed: true, + }; + + // Set profile creation date to now (new account, within 30 days) + const recentProfileDate = new Date(); + + beforeEach(() => { + vaultProfileService = mock<VaultProfileService>(); + vaultProfileService.getProfileCreationDate.mockResolvedValue(recentProfileDate); + + fakeStateProvider = new FakeStateProvider(mockAccountServiceWith(userId)); + + TestBed.configureTestingModule({ + providers: [ + BrowserAutofillNudgeService, + { + provide: VaultProfileService, + useValue: vaultProfileService, + }, + { + provide: StateProvider, + useValue: fakeStateProvider, + }, + { + provide: LogService, + useValue: mock<LogService>(), + }, + ], + }); + + service = TestBed.inject(BrowserAutofillNudgeService); + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + describe("nudgeStatus$", () => { + it("returns parent status when browser client is Unknown", async () => { + jest + .spyOn(BrowserApi, "getBrowserClientVendor") + .mockReturnValue(BrowserClientVendors.Unknown); + jest.spyOn(BrowserApi, "browserAutofillSettingsOverridden").mockResolvedValue(true); + + const result = await firstValueFrom(service.nudgeStatus$(nudgeType, userId)); + + expect(result).toEqual(notDismissedStatus); + }); + + it("returns parent status when browser autofill is not overridden", async () => { + jest.spyOn(BrowserApi, "getBrowserClientVendor").mockReturnValue(BrowserClientVendors.Chrome); + jest.spyOn(BrowserApi, "browserAutofillSettingsOverridden").mockResolvedValue(false); + + const result = await firstValueFrom(service.nudgeStatus$(nudgeType, userId)); + + expect(result).toEqual(notDismissedStatus); + }); + + it("returns dismissed status when browser autofill is overridden", async () => { + jest.spyOn(BrowserApi, "getBrowserClientVendor").mockReturnValue(BrowserClientVendors.Chrome); + jest.spyOn(BrowserApi, "browserAutofillSettingsOverridden").mockResolvedValue(true); + + const result = await firstValueFrom(service.nudgeStatus$(nudgeType, userId)); + + expect(result).toEqual(dismissedStatus); + }); + + it("preserves parent dismissed status when account is older than 30 days", async () => { + // Set profile creation date to more than 30 days ago + const oldProfileDate = new Date(Date.now() - 31 * 24 * 60 * 60 * 1000); + vaultProfileService.getProfileCreationDate.mockResolvedValue(oldProfileDate); + + jest.spyOn(BrowserApi, "getBrowserClientVendor").mockReturnValue(BrowserClientVendors.Chrome); + jest.spyOn(BrowserApi, "browserAutofillSettingsOverridden").mockResolvedValue(false); + + const result = await firstValueFrom(service.nudgeStatus$(nudgeType, userId)); + + expect(result).toEqual(dismissedStatus); + }); + + it("combines parent dismissed and browser autofill overridden status", async () => { + // Set profile creation date to more than 30 days ago (parent dismisses) + const oldProfileDate = new Date(Date.now() - 31 * 24 * 60 * 60 * 1000); + vaultProfileService.getProfileCreationDate.mockResolvedValue(oldProfileDate); + + jest.spyOn(BrowserApi, "getBrowserClientVendor").mockReturnValue(BrowserClientVendors.Chrome); + jest.spyOn(BrowserApi, "browserAutofillSettingsOverridden").mockResolvedValue(true); + + const result = await firstValueFrom(service.nudgeStatus$(nudgeType, userId)); + + expect(result).toEqual(dismissedStatus); + }); + + it.each([ + BrowserClientVendors.Chrome, + BrowserClientVendors.Edge, + BrowserClientVendors.Opera, + BrowserClientVendors.Vivaldi, + ])("checks browser autofill settings for %s browser", async (browserVendor) => { + const getBrowserClientVendorSpy = jest + .spyOn(BrowserApi, "getBrowserClientVendor") + .mockReturnValue(browserVendor); + const browserAutofillSettingsOverriddenSpy = jest + .spyOn(BrowserApi, "browserAutofillSettingsOverridden") + .mockResolvedValue(true); + + await firstValueFrom(service.nudgeStatus$(nudgeType, userId)); + + expect(getBrowserClientVendorSpy).toHaveBeenCalledWith(window); + expect(browserAutofillSettingsOverriddenSpy).toHaveBeenCalled(); + }); + + it("does not check browser autofill settings for Unknown browser", async () => { + jest + .spyOn(BrowserApi, "getBrowserClientVendor") + .mockReturnValue(BrowserClientVendors.Unknown); + const browserAutofillSettingsOverriddenSpy = jest + .spyOn(BrowserApi, "browserAutofillSettingsOverridden") + .mockResolvedValue(true); + + await firstValueFrom(service.nudgeStatus$(nudgeType, userId)); + + expect(browserAutofillSettingsOverriddenSpy).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/apps/browser/src/vault/popup/services/browser-autofill-nudge.service.ts b/apps/browser/src/vault/popup/services/browser-autofill-nudge.service.ts new file mode 100644 index 00000000000..7fe5f527bcb --- /dev/null +++ b/apps/browser/src/vault/popup/services/browser-autofill-nudge.service.ts @@ -0,0 +1,37 @@ +import { Injectable } from "@angular/core"; +import { Observable, switchMap } from "rxjs"; + +import { NudgeStatus, NudgeType } from "@bitwarden/angular/vault"; +import { NewAccountNudgeService } from "@bitwarden/angular/vault/services/custom-nudges-services/new-account-nudge.service"; +import { BrowserClientVendors } from "@bitwarden/common/autofill/constants"; +import { UserId } from "@bitwarden/common/types/guid"; + +import { BrowserApi } from "../../../platform/browser/browser-api"; + +/** + * Browser-specific autofill nudge service. + * Extends NewAccountNudgeService (30-day account age check) and adds + * browser autofill setting detection. + * + * Nudge is dismissed if: + * - Account is older than 30 days (inherited from NewAccountNudgeService) + * - Browser's built-in password manager is already disabled via privacy settings + */ +@Injectable() +export class BrowserAutofillNudgeService extends NewAccountNudgeService { + override nudgeStatus$(nudgeType: NudgeType, userId: UserId): Observable<NudgeStatus> { + return super.nudgeStatus$(nudgeType, userId).pipe( + switchMap(async (status) => { + const browserClient = BrowserApi.getBrowserClientVendor(window); + const browserAutofillOverridden = + browserClient !== BrowserClientVendors.Unknown && + (await BrowserApi.browserAutofillSettingsOverridden()); + + return { + hasBadgeDismissed: status.hasBadgeDismissed || browserAutofillOverridden, + hasSpotlightDismissed: status.hasSpotlightDismissed || browserAutofillOverridden, + }; + }), + ); + } +} diff --git a/libs/angular/src/vault/index.ts b/libs/angular/src/vault/index.ts index cb43fadb3bc..b9131338a45 100644 --- a/libs/angular/src/vault/index.ts +++ b/libs/angular/src/vault/index.ts @@ -1,3 +1,4 @@ // Note: Nudge related code is exported from `libs/angular` because it is consumed by multiple // `libs/*` packages. Exporting from the `libs/vault` package creates circular dependencies. export { NudgesService, NudgeStatus, NudgeType } from "./services/nudges.service"; +export { AUTOFILL_NUDGE_SERVICE } from "./services/nudge-injection-tokens"; diff --git a/libs/angular/src/vault/services/custom-nudges-services/index.ts b/libs/angular/src/vault/services/custom-nudges-services/index.ts index f60592b9c71..d4bfe80a525 100644 --- a/libs/angular/src/vault/services/custom-nudges-services/index.ts +++ b/libs/angular/src/vault/services/custom-nudges-services/index.ts @@ -4,3 +4,4 @@ export * from "./empty-vault-nudge.service"; export * from "./vault-settings-import-nudge.service"; export * from "./new-item-nudge.service"; export * from "./new-account-nudge.service"; +export * from "./noop-nudge.service"; diff --git a/libs/angular/src/vault/services/custom-nudges-services/noop-nudge.service.ts b/libs/angular/src/vault/services/custom-nudges-services/noop-nudge.service.ts new file mode 100644 index 00000000000..eabf1d6fc4a --- /dev/null +++ b/libs/angular/src/vault/services/custom-nudges-services/noop-nudge.service.ts @@ -0,0 +1,27 @@ +import { Injectable } from "@angular/core"; +import { Observable, of } from "rxjs"; + +import { UserId } from "@bitwarden/common/types/guid"; + +import { SingleNudgeService } from "../default-single-nudge.service"; +import { NudgeStatus, NudgeType } from "../nudges.service"; + +/** + * A no-op nudge service that always returns dismissed status. + * Use this for nudges that should be completely ignored/hidden in certain clients. + * For example, browser-specific nudges can use this as the default in non-browser clients. + */ +@Injectable({ providedIn: "root" }) +export class NoOpNudgeService implements SingleNudgeService { + nudgeStatus$(_nudgeType: NudgeType, _userId: UserId): Observable<NudgeStatus> { + return of({ hasBadgeDismissed: true, hasSpotlightDismissed: true }); + } + + async setNudgeStatus( + _nudgeType: NudgeType, + _newStatus: NudgeStatus, + _userId: UserId, + ): Promise<void> { + // No-op: state changes are ignored + } +} diff --git a/libs/angular/src/vault/services/nudge-injection-tokens.ts b/libs/angular/src/vault/services/nudge-injection-tokens.ts new file mode 100644 index 00000000000..52a0838d356 --- /dev/null +++ b/libs/angular/src/vault/services/nudge-injection-tokens.ts @@ -0,0 +1,7 @@ +import { InjectionToken } from "@angular/core"; + +import { SingleNudgeService } from "./default-single-nudge.service"; + +export const AUTOFILL_NUDGE_SERVICE = new InjectionToken<SingleNudgeService>( + "AutofillNudgeService", +); diff --git a/libs/angular/src/vault/services/nudges.service.ts b/libs/angular/src/vault/services/nudges.service.ts index 05d565eb499..19acf690d32 100644 --- a/libs/angular/src/vault/services/nudges.service.ts +++ b/libs/angular/src/vault/services/nudges.service.ts @@ -12,8 +12,10 @@ import { NewItemNudgeService, AccountSecurityNudgeService, VaultSettingsImportNudgeService, + NoOpNudgeService, } from "./custom-nudges-services"; import { DefaultSingleNudgeService, SingleNudgeService } from "./default-single-nudge.service"; +import { AUTOFILL_NUDGE_SERVICE } from "./nudge-injection-tokens"; export type NudgeStatus = { hasBadgeDismissed: boolean; @@ -56,6 +58,12 @@ export class NudgesService { private newItemNudgeService = inject(NewItemNudgeService); private newAcctNudgeService = inject(NewAccountNudgeService); + // NoOp service that always returns dismissed + private noOpNudgeService = inject(NoOpNudgeService); + + // Optional Browser-specific service provided via injection token (not all clients have autofill) + private autofillNudgeService = inject(AUTOFILL_NUDGE_SERVICE, { optional: true }); + /** * Custom nudge services to use for specific nudge types * Each nudge type can have its own service to determine when to show the nudge @@ -66,7 +74,7 @@ export class NudgesService { [NudgeType.EmptyVaultNudge]: inject(EmptyVaultNudgeService), [NudgeType.VaultSettingsImportNudge]: inject(VaultSettingsImportNudgeService), [NudgeType.AccountSecurity]: inject(AccountSecurityNudgeService), - [NudgeType.AutofillNudge]: this.newAcctNudgeService, + [NudgeType.AutofillNudge]: this.autofillNudgeService ?? this.noOpNudgeService, [NudgeType.DownloadBitwarden]: this.newAcctNudgeService, [NudgeType.GeneratorNudgeStatus]: this.newAcctNudgeService, [NudgeType.NewLoginItemStatus]: this.newItemNudgeService, From cf1c3226c3f0d9002ec6454330e7f8d1366941c3 Mon Sep 17 00:00:00 2001 From: aj-bw <81774843+aj-bw@users.noreply.github.com> Date: Tue, 30 Dec 2025 09:07:32 +0000 Subject: [PATCH 177/188] replace inline removal with reusable workflow (#18144) --- .github/workflows/build-desktop.yml | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/.github/workflows/build-desktop.yml b/.github/workflows/build-desktop.yml index f3cdf80f710..45297a110a0 100644 --- a/.github/workflows/build-desktop.yml +++ b/.github/workflows/build-desktop.yml @@ -179,18 +179,8 @@ jobs: ref: ${{ github.event.pull_request.head.sha }} persist-credentials: false - - name: Free disk space for build - run: | - sudo rm -rf /usr/share/dotnet - sudo rm -rf /usr/share/swift - sudo rm -rf /usr/local/.ghcup - sudo rm -rf /usr/share/miniconda - sudo rm -rf /usr/share/az_* - sudo rm -rf /usr/local/julia* - sudo rm -rf /usr/lib/mono - sudo rm -rf /usr/lib/heroku - sudo rm -rf /usr/local/aws-cli - sudo rm -rf /usr/local/aws-sam-cli + - name: Free disk space + uses: bitwarden/gh-actions/free-disk-space@main - name: Set up Node uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0 From 8a6f9bfaeb84aa07b6c4d5cdd61305e4373d9b93 Mon Sep 17 00:00:00 2001 From: Daniel Riera <driera@livefront.com> Date: Tue, 30 Dec 2025 10:36:08 -0500 Subject: [PATCH 178/188] [PM-29515] Remove ts strict ignore in overlay inline menu iframe content autofill inline menu iframe service (#18030) * use optional chaining and make portkey optional to match the AutofillInlineMenuIframeExtensionMessage * make ariaAlertElement optional * tiemouts are set to null for clearing, updated type to match this * border color is conditionally applied, undefined is acceptable here * check if aria alerts exist before calling * return early if no styles exist for updateElementStyles or no position for updateIframePosition * initilaize timers to null * non null assert iframe since it is initialized in initMenuIframe which makes it safe to assert non null by lifecycle * remove optional chainning --- .../autofill-inline-menu-iframe.service.ts | 39 +++++++++++-------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/apps/browser/src/autofill/overlay/inline-menu/iframe-content/autofill-inline-menu-iframe.service.ts b/apps/browser/src/autofill/overlay/inline-menu/iframe-content/autofill-inline-menu-iframe.service.ts index 64ef7d180ed..ad1241e98d2 100644 --- a/apps/browser/src/autofill/overlay/inline-menu/iframe-content/autofill-inline-menu-iframe.service.ts +++ b/apps/browser/src/autofill/overlay/inline-menu/iframe-content/autofill-inline-menu-iframe.service.ts @@ -1,5 +1,3 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { EVENTS } from "@bitwarden/common/autofill/constants"; import { ThemeTypes } from "@bitwarden/common/platform/enums"; @@ -15,14 +13,17 @@ export class AutofillInlineMenuIframeService implements AutofillInlineMenuIframe private readonly setElementStyles = setElementStyles; private readonly sendExtensionMessage = sendExtensionMessage; private port: chrome.runtime.Port | null = null; - private portKey: string; + private portKey?: string; private readonly extensionOrigin: string; private iframeMutationObserver: MutationObserver; - private iframe: HTMLIFrameElement; - private ariaAlertElement: HTMLDivElement; - private ariaAlertTimeout: number | NodeJS.Timeout; - private delayedCloseTimeout: number | NodeJS.Timeout; - private fadeInTimeout: number | NodeJS.Timeout; + /** + * Initialized in initMenuIframe which makes it safe to assert non null by lifecycle. + */ + private iframe!: HTMLIFrameElement; + private ariaAlertElement?: HTMLDivElement; + private ariaAlertTimeout: number | NodeJS.Timeout | null = null; + private delayedCloseTimeout: number | NodeJS.Timeout | null = null; + private fadeInTimeout: number | NodeJS.Timeout | null = null; private readonly fadeInOpacityTransition = "opacity 125ms ease-out 0s"; private readonly fadeOutOpacityTransition = "opacity 65ms ease-out 0s"; private iframeStyles: Partial<CSSStyleDeclaration> = { @@ -50,7 +51,7 @@ export class AutofillInlineMenuIframeService implements AutofillInlineMenuIframe }; private foreignMutationsCount = 0; private mutationObserverIterations = 0; - private mutationObserverIterationsResetTimeout: number | NodeJS.Timeout; + private mutationObserverIterationsResetTimeout: number | NodeJS.Timeout | null = null; private readonly backgroundPortMessageHandlers: BackgroundPortMessageHandlers = { initAutofillInlineMenuButton: ({ message }) => this.initAutofillInlineMenu(message), initAutofillInlineMenuList: ({ message }) => this.initAutofillInlineMenu(message), @@ -134,7 +135,9 @@ export class AutofillInlineMenuIframeService implements AutofillInlineMenuIframe this.port.onDisconnect.addListener(this.handlePortDisconnect); this.port.onMessage.addListener(this.handlePortMessage); - this.announceAriaAlert(this.ariaAlert, 2000); + if (this.ariaAlert) { + this.announceAriaAlert(this.ariaAlert, 2000); + } }; /** @@ -155,7 +158,7 @@ export class AutofillInlineMenuIframeService implements AutofillInlineMenuIframe this.ariaAlertTimeout = globalThis.setTimeout(async () => { const isFieldFocused = await this.sendExtensionMessage("checkIsFieldCurrentlyFocused"); - if (isFieldFocused || triggeredByUser) { + if ((isFieldFocused || triggeredByUser) && this.ariaAlertElement) { this.shadow.appendChild(this.ariaAlertElement); } this.ariaAlertTimeout = null; @@ -242,7 +245,7 @@ export class AutofillInlineMenuIframeService implements AutofillInlineMenuIframe */ private initAutofillInlineMenuList(message: AutofillInlineMenuIframeExtensionMessage) { const { theme } = message; - let borderColor: string; + let borderColor: string | undefined; let verifiedTheme = theme; if (verifiedTheme === ThemeTypes.System) { verifiedTheme = globalThis.matchMedia("(prefers-color-scheme: dark)").matches @@ -274,8 +277,8 @@ export class AutofillInlineMenuIframeService implements AutofillInlineMenuIframe * * @param position - The position styles to apply to the iframe */ - private updateIframePosition(position: Partial<CSSStyleDeclaration>) { - if (!globalThis.document.hasFocus()) { + private updateIframePosition(position?: Partial<CSSStyleDeclaration>) { + if (!position || !globalThis.document.hasFocus()) { return; } @@ -295,7 +298,9 @@ export class AutofillInlineMenuIframeService implements AutofillInlineMenuIframe this.handleFadeInInlineMenuIframe(); } - this.announceAriaAlert(this.ariaAlert, 2000); + if (this.ariaAlert) { + this.announceAriaAlert(this.ariaAlert, 2000); + } } /** @@ -359,8 +364,8 @@ export class AutofillInlineMenuIframeService implements AutofillInlineMenuIframe * @param customElement - The element to update the styles for * @param styles - The styles to apply to the element */ - private updateElementStyles(customElement: HTMLElement, styles: Partial<CSSStyleDeclaration>) { - if (!customElement) { + private updateElementStyles(customElement: HTMLElement, styles?: Partial<CSSStyleDeclaration>) { + if (!customElement || !styles) { return; } From cee69f85c0da401c6ec572593e15b536cc6d78e9 Mon Sep 17 00:00:00 2001 From: Ben Brooks <56796209+bensbits91@users.noreply.github.com> Date: Tue, 30 Dec 2025 08:21:10 -0800 Subject: [PATCH 179/188] [pm-28077] Add input types to ignoredInputTypes (#17870) * [pm-28077] Add input types to ignoredInputTypes Signed-off-by: Ben Brooks <bbrooks@bitwarden.com> * Merge branch 'main' of github.com:bitwarden/clients into pm-28077-more-ignoredInputTypes-in-CollectAutofillContentService Signed-off-by: Ben Brooks <bbrooks@bitwarden.com> * [pm-28077] Remove month input type from ignored types Signed-off-by: Ben Brooks <bbrooks@bitwarden.com> * [pm-28077] Remove month radio and checkbox types from ignored types Signed-off-by: Ben Brooks <bbrooks@bitwarden.com> * Merge branch 'main' of github.com:bitwarden/clients into pm-28077-more-ignoredInputTypes-in-CollectAutofillContentService Signed-off-by: Ben Brooks <bbrooks@bitwarden.com> * [pm-28077] Fix prettier issues/conflicts Signed-off-by: Ben Brooks <bbrooks@bitwarden.com> * [pm-28077] Add comment regarding datetime depcrecation Signed-off-by: Ben Brooks <bbrooks@bitwarden.com> --------- Signed-off-by: Ben Brooks <bbrooks@bitwarden.com> --- .../services/collect-autofill-content.service.ts | 9 +++++++++ 1 file changed, 9 insertions(+) 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 367599f7ad0..18eb8e2baf8 100644 --- a/apps/browser/src/autofill/services/collect-autofill-content.service.ts +++ b/apps/browser/src/autofill/services/collect-autofill-content.service.ts @@ -60,6 +60,15 @@ export class CollectAutofillContentService implements CollectAutofillContentServ "button", "image", "file", + "search", + "url", + "date", + "time", + "datetime", // Note: datetime is deprecated in HTML5; keeping here for backwards compatibility + "datetime-local", + "week", + "color", + "range", ]); constructor( From 800a21d8a3b65369528610ac79517e7a5792e48a Mon Sep 17 00:00:00 2001 From: Leslie Tilton <23057410+Banrion@users.noreply.github.com> Date: Tue, 30 Dec 2025 11:06:30 -0600 Subject: [PATCH 180/188] [PM-28548] Phishing Blocker support links (#18070) * Change domain terminology to web addresses * Added phishing resource file * Finish renaming and adding runtime configuration for domains vs links setting * Update reference * Add matching functions per resource * correct URL matching logic for links-based detection Problem: The phishing link matcher was failing to detect known phishing URLs due to two issues: 1. Protocol mismatch: Entries in the phishing list use `http://` but users typically visit `https://` versions. The matcher was comparing full URLs including protocol, causing legitimate matches to fail. - List entry: `http://smartdapptradxx.pages.dev` - User visits: `https://smartdapptradxx.pages.dev/` - Result: No match (incorrect) 2. Hostname-only matching would have caused false positives: An earlier attempt to fix #1 included hostname-only comparison, which defeats the purpose of links-based detection. The goal of PM-28548 is precise URL matching to avoid blocking entire domains (like pages.dev, github.io) when only specific paths are malicious. Solution: - Always strip protocol (http:// or https://) from both entry and URL before comparison, treating them as equivalent - Remove hostname-only matching to maintain precision - Keep prefix matching for subpaths, query strings, and fragments --------- Co-authored-by: Alex <adewitt@bitwarden.com> --- .../phishing-detection/phishing-resources.ts | 98 ++++++++++++++++ .../services/phishing-data.service.spec.ts | 62 +++++----- .../services/phishing-data.service.ts | 107 +++++++++--------- .../services/phishing-detection.service.ts | 2 +- 4 files changed, 186 insertions(+), 83 deletions(-) create mode 100644 apps/browser/src/dirt/phishing-detection/phishing-resources.ts diff --git a/apps/browser/src/dirt/phishing-detection/phishing-resources.ts b/apps/browser/src/dirt/phishing-detection/phishing-resources.ts new file mode 100644 index 00000000000..262d6cf833b --- /dev/null +++ b/apps/browser/src/dirt/phishing-detection/phishing-resources.ts @@ -0,0 +1,98 @@ +export type PhishingResource = { + name?: string; + remoteUrl: string; + checksumUrl: string; + todayUrl: string; + /** Matcher used to decide whether a given URL matches an entry from this resource */ + match: (url: URL, entry: string) => boolean; +}; + +export const PhishingResourceType = Object.freeze({ + Domains: "domains", + Links: "links", +} as const); + +export type PhishingResourceType = (typeof PhishingResourceType)[keyof typeof PhishingResourceType]; + +export const PHISHING_RESOURCES: Record<PhishingResourceType, PhishingResource[]> = { + [PhishingResourceType.Domains]: [ + { + name: "Phishing.Database Domains", + remoteUrl: + "https://raw.githubusercontent.com/Phishing-Database/Phishing.Database/master/phishing-domains-ACTIVE.txt", + checksumUrl: + "https://raw.githubusercontent.com/Phishing-Database/checksums/refs/heads/master/phishing-domains-ACTIVE.txt.md5", + todayUrl: + "https://raw.githubusercontent.com/Phishing-Database/Phishing.Database/refs/heads/master/phishing-domains-NEW-today.txt", + match: (url: URL, entry: string) => { + if (!entry) { + return false; + } + const candidate = entry.trim().toLowerCase().replace(/\/$/, ""); + // If entry contains a scheme, strip it for comparison + const e = candidate.replace(/^https?:\/\//, ""); + // Compare against hostname or host+path + if (e === url.hostname.toLowerCase()) { + return true; + } + const urlNoProto = url.href + .toLowerCase() + .replace(/https?:\/\//, "") + .replace(/\/$/, ""); + return urlNoProto === e || urlNoProto.startsWith(e + "/"); + }, + }, + ], + [PhishingResourceType.Links]: [ + { + name: "Phishing.Database Links", + remoteUrl: + "https://raw.githubusercontent.com/Phishing-Database/Phishing.Database/master/phishing-links-ACTIVE.txt", + checksumUrl: + "https://raw.githubusercontent.com/Phishing-Database/checksums/refs/heads/master/phishing-links-ACTIVE.txt.md5", + todayUrl: + "https://raw.githubusercontent.com/Phishing-Database/Phishing.Database/refs/heads/master/phishing-links-NEW-today.txt", + match: (url: URL, entry: string) => { + if (!entry) { + return false; + } + // Basic HTML entity decode for common cases (the lists sometimes contain &amp;) + const decodeHtml = (s: string) => s.replace(/&amp;/g, "&"); + + const normalizedEntry = decodeHtml(entry.trim()).toLowerCase().replace(/\/$/, ""); + + // Normalize URL for comparison - always strip protocol for consistent matching + const normalizedUrl = decodeHtml(url.href).toLowerCase().replace(/\/$/, ""); + const urlNoProto = normalizedUrl.replace(/^https?:\/\//, ""); + + // Strip protocol from entry if present (http:// and https:// should be treated as equivalent) + const entryNoProto = normalizedEntry.replace(/^https?:\/\//, ""); + + // Compare full path (without protocol) - exact match + if (urlNoProto === entryNoProto) { + return true; + } + + // Check if URL starts with entry (prefix match for subpaths/query/hash) + // e.g., entry "site.com/phish" matches "site.com/phish/subpage" or "site.com/phish?id=1" + if ( + urlNoProto.startsWith(entryNoProto + "/") || + urlNoProto.startsWith(entryNoProto + "?") || + urlNoProto.startsWith(entryNoProto + "#") + ) { + return true; + } + + return false; + }, + }, + ], +}; + +export function getPhishingResources( + type: PhishingResourceType, + index = 0, +): PhishingResource | undefined { + const list = PHISHING_RESOURCES[type] ?? []; + return list[index]; +} diff --git a/apps/browser/src/dirt/phishing-detection/services/phishing-data.service.spec.ts b/apps/browser/src/dirt/phishing-detection/services/phishing-data.service.spec.ts index 94f3e99f8be..30aa947092d 100644 --- a/apps/browser/src/dirt/phishing-detection/services/phishing-data.service.spec.ts +++ b/apps/browser/src/dirt/phishing-detection/services/phishing-data.service.spec.ts @@ -25,7 +25,7 @@ describe("PhishingDataService", () => { }; let fetchChecksumSpy: jest.SpyInstance; - let fetchDomainsSpy: jest.SpyInstance; + let fetchWebAddressesSpy: jest.SpyInstance; beforeEach(() => { jest.useFakeTimers(); @@ -45,113 +45,113 @@ describe("PhishingDataService", () => { platformUtilsService, ); - fetchChecksumSpy = jest.spyOn(service as any, "fetchPhishingDomainsChecksum"); - fetchDomainsSpy = jest.spyOn(service as any, "fetchPhishingDomains"); + fetchChecksumSpy = jest.spyOn(service as any, "fetchPhishingChecksum"); + fetchWebAddressesSpy = jest.spyOn(service as any, "fetchPhishingWebAddresses"); }); - describe("isPhishingDomains", () => { - it("should detect a phishing domain", async () => { + describe("isPhishingWebAddress", () => { + it("should detect a phishing web address", async () => { setMockState({ - domains: ["phish.com", "badguy.net"], + webAddresses: ["phish.com", "badguy.net"], timestamp: Date.now(), checksum: "abc123", applicationVersion: "1.0.0", }); const url = new URL("http://phish.com"); - const result = await service.isPhishingDomain(url); + const result = await service.isPhishingWebAddress(url); expect(result).toBe(true); }); - it("should not detect a safe domain", async () => { + it("should not detect a safe web address", async () => { setMockState({ - domains: ["phish.com", "badguy.net"], + webAddresses: ["phish.com", "badguy.net"], timestamp: Date.now(), checksum: "abc123", applicationVersion: "1.0.0", }); const url = new URL("http://safe.com"); - const result = await service.isPhishingDomain(url); + const result = await service.isPhishingWebAddress(url); expect(result).toBe(false); }); - it("should match against root domain", async () => { + it("should match against root web address", async () => { setMockState({ - domains: ["phish.com", "badguy.net"], + webAddresses: ["phish.com", "badguy.net"], timestamp: Date.now(), checksum: "abc123", applicationVersion: "1.0.0", }); const url = new URL("http://phish.com/about"); - const result = await service.isPhishingDomain(url); + const result = await service.isPhishingWebAddress(url); expect(result).toBe(true); }); it("should not error on empty state", async () => { setMockState(undefined as any); const url = new URL("http://phish.com/about"); - const result = await service.isPhishingDomain(url); + const result = await service.isPhishingWebAddress(url); expect(result).toBe(false); }); }); - describe("getNextDomains", () => { - it("refetches all domains if applicationVersion has changed", async () => { + describe("getNextWebAddresses", () => { + it("refetches all web addresses if applicationVersion has changed", async () => { const prev: PhishingData = { - domains: ["a.com"], + webAddresses: ["a.com"], timestamp: Date.now() - 60000, checksum: "old", applicationVersion: "1.0.0", }; fetchChecksumSpy.mockResolvedValue("new"); - fetchDomainsSpy.mockResolvedValue(["d.com", "e.com"]); + fetchWebAddressesSpy.mockResolvedValue(["d.com", "e.com"]); platformUtilsService.getApplicationVersion.mockResolvedValue("2.0.0"); - const result = await service.getNextDomains(prev); + const result = await service.getNextWebAddresses(prev); - expect(result!.domains).toEqual(["d.com", "e.com"]); + expect(result!.webAddresses).toEqual(["d.com", "e.com"]); expect(result!.checksum).toBe("new"); expect(result!.applicationVersion).toBe("2.0.0"); }); it("only updates timestamp if checksum matches", async () => { const prev: PhishingData = { - domains: ["a.com"], + webAddresses: ["a.com"], timestamp: Date.now() - 60000, checksum: "abc", applicationVersion: "1.0.0", }; fetchChecksumSpy.mockResolvedValue("abc"); - const result = await service.getNextDomains(prev); - expect(result!.domains).toEqual(prev.domains); + const result = await service.getNextWebAddresses(prev); + expect(result!.webAddresses).toEqual(prev.webAddresses); expect(result!.checksum).toBe("abc"); expect(result!.timestamp).not.toBe(prev.timestamp); }); it("patches daily domains if cache is fresh", async () => { const prev: PhishingData = { - domains: ["a.com"], + webAddresses: ["a.com"], timestamp: Date.now() - 60000, checksum: "old", applicationVersion: "1.0.0", }; fetchChecksumSpy.mockResolvedValue("new"); - fetchDomainsSpy.mockResolvedValue(["b.com", "c.com"]); - const result = await service.getNextDomains(prev); - expect(result!.domains).toEqual(["a.com", "b.com", "c.com"]); + fetchWebAddressesSpy.mockResolvedValue(["b.com", "c.com"]); + const result = await service.getNextWebAddresses(prev); + expect(result!.webAddresses).toEqual(["a.com", "b.com", "c.com"]); expect(result!.checksum).toBe("new"); }); it("fetches all domains if cache is old", async () => { const prev: PhishingData = { - domains: ["a.com"], + webAddresses: ["a.com"], timestamp: Date.now() - 2 * 24 * 60 * 60 * 1000, checksum: "old", applicationVersion: "1.0.0", }; fetchChecksumSpy.mockResolvedValue("new"); - fetchDomainsSpy.mockResolvedValue(["d.com", "e.com"]); - const result = await service.getNextDomains(prev); - expect(result!.domains).toEqual(["d.com", "e.com"]); + fetchWebAddressesSpy.mockResolvedValue(["d.com", "e.com"]); + const result = await service.getNextWebAddresses(prev); + expect(result!.webAddresses).toEqual(["d.com", "e.com"]); expect(result!.checksum).toBe("new"); }); }); diff --git a/apps/browser/src/dirt/phishing-detection/services/phishing-data.service.ts b/apps/browser/src/dirt/phishing-detection/services/phishing-data.service.ts index 6e1bf07c647..21fe74f1873 100644 --- a/apps/browser/src/dirt/phishing-detection/services/phishing-data.service.ts +++ b/apps/browser/src/dirt/phishing-detection/services/phishing-data.service.ts @@ -20,14 +20,16 @@ import { ScheduledTaskNames, TaskSchedulerService } from "@bitwarden/common/plat import { LogService } from "@bitwarden/logging"; import { GlobalStateProvider, KeyDefinition, PHISHING_DETECTION_DISK } from "@bitwarden/state"; +import { getPhishingResources, PhishingResourceType } from "../phishing-resources"; + export type PhishingData = { - domains: string[]; + webAddresses: string[]; timestamp: number; checksum: string; /** * We store the application version to refetch the entire dataset on a new client release. - * This counteracts daily appends updates not removing inactive or false positive domains. + * This counteracts daily appends updates not removing inactive or false positive web addresses. */ applicationVersion: string; }; @@ -37,34 +39,27 @@ export const PHISHING_DOMAINS_KEY = new KeyDefinition<PhishingData>( "phishingDomains", { deserializer: (value: PhishingData) => - value ?? { domains: [], timestamp: 0, checksum: "", applicationVersion: "" }, + value ?? { webAddresses: [], timestamp: 0, checksum: "", applicationVersion: "" }, }, ); -/** Coordinates fetching, caching, and patching of known phishing domains */ +/** Coordinates fetching, caching, and patching of known phishing web addresses */ export class PhishingDataService { - private static readonly RemotePhishingDatabaseUrl = - "https://raw.githubusercontent.com/Phishing-Database/Phishing.Database/master/phishing-domains-ACTIVE.txt"; - private static readonly RemotePhishingDatabaseChecksumUrl = - "https://raw.githubusercontent.com/Phishing-Database/checksums/refs/heads/master/phishing-domains-ACTIVE.txt.md5"; - private static readonly RemotePhishingDatabaseTodayUrl = - "https://raw.githubusercontent.com/Phishing-Database/Phishing.Database/refs/heads/master/phishing-domains-NEW-today.txt"; - - private _testDomains = this.getTestDomains(); + private _testWebAddresses = this.getTestWebAddresses(); private _cachedState = this.globalStateProvider.get(PHISHING_DOMAINS_KEY); - private _domains$ = this._cachedState.state$.pipe( + private _webAddresses$ = this._cachedState.state$.pipe( map( (state) => new Set( - (state?.domains?.filter((line) => line.trim().length > 0) ?? []).concat( - this._testDomains, + (state?.webAddresses?.filter((line) => line.trim().length > 0) ?? []).concat( + this._testWebAddresses, "phishing.testcategory.com", // Included for QA to test in prod ), ), ), ); - // How often are new domains added to the remote? + // How often are new web addresses added to the remote? readonly UPDATE_INTERVAL_DURATION = 24 * 60 * 60 * 1000; // 24 hours private _triggerUpdate$ = new Subject<void>(); @@ -75,7 +70,7 @@ export class PhishingDataService { this._cachedState.state$.pipe( first(), // Only take the first value to avoid an infinite loop when updating the cache below switchMap(async (cachedState) => { - const next = await this.getNextDomains(cachedState); + const next = await this.getNextWebAddresses(cachedState); if (next) { await this._cachedState.update(() => next); this.logService.info(`[PhishingDataService] cache updated`); @@ -85,7 +80,7 @@ export class PhishingDataService { count: 3, delay: (err, count) => { this.logService.error( - `[PhishingDataService] Unable to update domains. Attempt ${count}.`, + `[PhishingDataService] Unable to update web addresses. Attempt ${count}.`, err, ); return timer(5 * 60 * 1000); // 5 minutes @@ -97,7 +92,7 @@ export class PhishingDataService { err: unknown /** Eslint actually crashed if you remove this type: https://github.com/cartant/eslint-plugin-rxjs/issues/122 */, ) => { this.logService.error( - "[PhishingDataService] Retries unsuccessful. Unable to update domains.", + "[PhishingDataService] Retries unsuccessful. Unable to update web addresses.", err, ); return EMPTY; @@ -114,6 +109,7 @@ export class PhishingDataService { private globalStateProvider: GlobalStateProvider, private logService: LogService, private platformUtilsService: PlatformUtilsService, + private resourceType: PhishingResourceType = PhishingResourceType.Links, ) { this.taskSchedulerService.registerTaskHandler(ScheduledTaskNames.phishingDomainUpdate, () => { this._triggerUpdate$.next(); @@ -125,22 +121,31 @@ export class PhishingDataService { } /** - * Checks if the given URL is a known phishing domain + * Checks if the given URL is a known phishing web address * * @param url The URL to check - * @returns True if the URL is a known phishing domain, false otherwise + * @returns True if the URL is a known phishing web address, false otherwise */ - async isPhishingDomain(url: URL): Promise<boolean> { - const domains = await firstValueFrom(this._domains$); - const result = domains.has(url.hostname); - if (result) { - return true; + async isPhishingWebAddress(url: URL): Promise<boolean> { + // Use domain (hostname) matching for domain resources, and link matching for links resources + const entries = await firstValueFrom(this._webAddresses$); + + const resource = getPhishingResources(this.resourceType); + if (resource && resource.match) { + for (const entry of entries) { + if (resource.match(url, entry)) { + return true; + } + } + return false; } - return false; + + // Default/domain behavior: exact hostname match as a fallback + return entries.has(url.hostname); } - async getNextDomains(prev: PhishingData | null): Promise<PhishingData | null> { - prev = prev ?? { domains: [], timestamp: 0, checksum: "", applicationVersion: "" }; + async getNextWebAddresses(prev: PhishingData | null): Promise<PhishingData | null> { + prev = prev ?? { webAddresses: [], timestamp: 0, checksum: "", applicationVersion: "" }; const timestamp = Date.now(); const prevAge = timestamp - prev.timestamp; this.logService.info(`[PhishingDataService] Cache age: ${prevAge}`); @@ -148,7 +153,7 @@ export class PhishingDataService { const applicationVersion = await this.platformUtilsService.getApplicationVersion(); // If checksum matches, return existing data with new timestamp & version - const remoteChecksum = await this.fetchPhishingDomainsChecksum(); + const remoteChecksum = await this.fetchPhishingChecksum(this.resourceType); if (remoteChecksum && prev.checksum === remoteChecksum) { this.logService.info( `[PhishingDataService] Remote checksum matches local checksum, updating timestamp only.`, @@ -157,66 +162,66 @@ export class PhishingDataService { } // Checksum is different, data needs to be updated. - // Approach 1: Fetch only new domains and append + // Approach 1: Fetch only new web addresses and append const isOneDayOldMax = prevAge <= this.UPDATE_INTERVAL_DURATION; if (isOneDayOldMax && applicationVersion === prev.applicationVersion) { - const dailyDomains: string[] = await this.fetchPhishingDomains( - PhishingDataService.RemotePhishingDatabaseTodayUrl, - ); + const webAddressesTodayUrl = getPhishingResources(this.resourceType)!.todayUrl; + const dailyWebAddresses: string[] = + await this.fetchPhishingWebAddresses(webAddressesTodayUrl); this.logService.info( - `[PhishingDataService] ${dailyDomains.length} new phishing domains added`, + `[PhishingDataService] ${dailyWebAddresses.length} new phishing web addresses added`, ); return { - domains: prev.domains.concat(dailyDomains), + webAddresses: prev.webAddresses.concat(dailyWebAddresses), checksum: remoteChecksum, timestamp, applicationVersion, }; } - // Approach 2: Fetch all domains - const domains = await this.fetchPhishingDomains(PhishingDataService.RemotePhishingDatabaseUrl); + // Approach 2: Fetch all web addresses + const remoteUrl = getPhishingResources(this.resourceType)!.remoteUrl; + const remoteWebAddresses = await this.fetchPhishingWebAddresses(remoteUrl); return { - domains, + webAddresses: remoteWebAddresses, timestamp, checksum: remoteChecksum, applicationVersion, }; } - private async fetchPhishingDomainsChecksum() { - const response = await this.apiService.nativeFetch( - new Request(PhishingDataService.RemotePhishingDatabaseChecksumUrl), - ); + private async fetchPhishingChecksum(type: PhishingResourceType = PhishingResourceType.Domains) { + const checksumUrl = getPhishingResources(type)!.checksumUrl; + const response = await this.apiService.nativeFetch(new Request(checksumUrl)); if (!response.ok) { throw new Error(`[PhishingDataService] Failed to fetch checksum: ${response.status}`); } return response.text(); } - private async fetchPhishingDomains(url: string) { + private async fetchPhishingWebAddresses(url: string) { const response = await this.apiService.nativeFetch(new Request(url)); if (!response.ok) { - throw new Error(`[PhishingDataService] Failed to fetch domains: ${response.status}`); + throw new Error(`[PhishingDataService] Failed to fetch web addresses: ${response.status}`); } return response.text().then((text) => text.split("\n")); } - private getTestDomains() { + private getTestWebAddresses() { const flag = devFlagEnabled("testPhishingUrls"); if (!flag) { return []; } - const domains = devFlagValue("testPhishingUrls") as unknown[]; - if (domains && domains instanceof Array) { + const webAddresses = devFlagValue("testPhishingUrls") as unknown[]; + if (webAddresses && webAddresses instanceof Array) { this.logService.debug( - "[PhishingDetectionService] Dev flag enabled for testing phishing detection. Adding test phishing domains:", - domains, + "[PhishingDetectionService] Dev flag enabled for testing phishing detection. Adding test phishing web addresses:", + webAddresses, ); - return domains as string[]; + return webAddresses as string[]; } return []; } 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 e04d08559ab..d90e872eef8 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 @@ -94,7 +94,7 @@ export class PhishingDetectionService { this._ignoredHostnames.delete(url.hostname); return; } - const isPhishing = await phishingDataService.isPhishingDomain(url); + const isPhishing = await phishingDataService.isPhishingWebAddress(url); if (!isPhishing) { return; } From 5b3e083af3013a1c68eafc819115f7de3f234df6 Mon Sep 17 00:00:00 2001 From: Mick Letofsky <mletofsky@bitwarden.com> Date: Tue, 30 Dec 2025 18:14:54 +0100 Subject: [PATCH 181/188] Review Code Triggered by labeled event (#18151) --- .github/workflows/review-code.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/review-code.yml b/.github/workflows/review-code.yml index 0e0597fccf0..908664209d8 100644 --- a/.github/workflows/review-code.yml +++ b/.github/workflows/review-code.yml @@ -2,7 +2,7 @@ name: Code Review on: pull_request: - types: [opened, synchronize, reopened, ready_for_review] + types: [opened, labeled] permissions: {} From 11b5342df789acf4fdb46aa0e98527148809b769 Mon Sep 17 00:00:00 2001 From: Alex Morask <144709477+amorask-bitwarden@users.noreply.github.com> Date: Tue, 30 Dec 2025 13:03:51 -0600 Subject: [PATCH 182/188] Remove circular invocation / have Account menu use new premium dialog (#17980) --- apps/desktop/src/app/app.component.ts | 4 +++- apps/desktop/src/app/app.module.ts | 10 +++++++++- .../desktop-premium-upgrade-prompt.service.spec.ts | 12 +++++------- .../desktop-premium-upgrade-prompt.service.ts | 6 +++--- 4 files changed, 20 insertions(+), 12 deletions(-) diff --git a/apps/desktop/src/app/app.component.ts b/apps/desktop/src/app/app.component.ts index 836328142b5..d75702ee8b8 100644 --- a/apps/desktop/src/app/app.component.ts +++ b/apps/desktop/src/app/app.component.ts @@ -70,6 +70,7 @@ import { SyncService } from "@bitwarden/common/platform/sync"; import { UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { InternalFolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; +import { PremiumUpgradePromptService } from "@bitwarden/common/vault/abstractions/premium-upgrade-prompt.service"; import { SearchService } from "@bitwarden/common/vault/abstractions/search.service"; import { CipherType } from "@bitwarden/common/vault/enums"; import { RestrictedItemTypesService } from "@bitwarden/common/vault/services/restricted-item-types.service"; @@ -198,6 +199,7 @@ export class AppComponent implements OnInit, OnDestroy { private readonly tokenService: TokenService, private desktopAutotypeDefaultSettingPolicy: DesktopAutotypeDefaultSettingPolicy, private readonly lockService: LockService, + private premiumUpgradePromptService: PremiumUpgradePromptService, ) { this.deviceTrustToastService.setupListeners$.pipe(takeUntilDestroyed()).subscribe(); @@ -305,7 +307,7 @@ export class AppComponent implements OnInit, OnDestroy { await this.openModal<SettingsComponent>(SettingsComponent, this.settingsRef); break; case "openPremium": - this.dialogService.open(PremiumComponent); + await this.premiumUpgradePromptService.promptForPremium(); break; case "showFingerprintPhrase": { const activeUserId = await firstValueFrom( diff --git a/apps/desktop/src/app/app.module.ts b/apps/desktop/src/app/app.module.ts index 31131c6202a..52a52ffd225 100644 --- a/apps/desktop/src/app/app.module.ts +++ b/apps/desktop/src/app/app.module.ts @@ -8,6 +8,7 @@ import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; import { ColorPasswordCountPipe } from "@bitwarden/angular/pipes/color-password-count.pipe"; import { ColorPasswordPipe } from "@bitwarden/angular/pipes/color-password.pipe"; +import { PremiumUpgradePromptService } from "@bitwarden/common/vault/abstractions/premium-upgrade-prompt.service"; import { CalloutModule, DialogModule } from "@bitwarden/components"; import { AssignCollectionsComponent } from "@bitwarden/vault"; @@ -15,6 +16,7 @@ import { DeleteAccountComponent } from "../auth/delete-account.component"; import { LoginModule } from "../auth/login/login.module"; import { SshAgentService } from "../autofill/services/ssh-agent.service"; import { PremiumComponent } from "../billing/app/accounts/premium.component"; +import { DesktopPremiumUpgradePromptService } from "../services/desktop-premium-upgrade-prompt.service"; import { VaultFilterModule } from "../vault/app/vault/vault-filter/vault-filter.module"; import { VaultV2Component } from "../vault/app/vault/vault-v2.component"; @@ -51,7 +53,13 @@ import { SharedModule } from "./shared/shared.module"; PremiumComponent, SearchComponent, ], - providers: [SshAgentService], + providers: [ + SshAgentService, + { + provide: PremiumUpgradePromptService, + useClass: DesktopPremiumUpgradePromptService, + }, + ], bootstrap: [AppComponent], }) export class AppModule {} diff --git a/apps/desktop/src/services/desktop-premium-upgrade-prompt.service.spec.ts b/apps/desktop/src/services/desktop-premium-upgrade-prompt.service.spec.ts index 1eee4cd54f6..3c36d648167 100644 --- a/apps/desktop/src/services/desktop-premium-upgrade-prompt.service.spec.ts +++ b/apps/desktop/src/services/desktop-premium-upgrade-prompt.service.spec.ts @@ -4,26 +4,24 @@ import { mock, MockProxy } from "jest-mock-extended"; import { PremiumUpgradeDialogComponent } from "@bitwarden/angular/billing/components"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; -import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { DialogService } from "@bitwarden/components"; +import { PremiumComponent } from "../billing/app/accounts/premium.component"; + import { DesktopPremiumUpgradePromptService } from "./desktop-premium-upgrade-prompt.service"; describe("DesktopPremiumUpgradePromptService", () => { let service: DesktopPremiumUpgradePromptService; - let messager: MockProxy<MessagingService>; let configService: MockProxy<ConfigService>; let dialogService: MockProxy<DialogService>; beforeEach(async () => { - messager = mock<MessagingService>(); configService = mock<ConfigService>(); dialogService = mock<DialogService>(); await TestBed.configureTestingModule({ providers: [ DesktopPremiumUpgradePromptService, - { provide: MessagingService, useValue: messager }, { provide: ConfigService, useValue: configService }, { provide: DialogService, useValue: dialogService }, ], @@ -52,10 +50,10 @@ describe("DesktopPremiumUpgradePromptService", () => { FeatureFlag.PM23713_PremiumBadgeOpensNewPremiumUpgradeDialog, ); expect(openSpy).toHaveBeenCalledWith(dialogService); - expect(messager.send).not.toHaveBeenCalled(); + expect(dialogService.open).not.toHaveBeenCalled(); }); - it("sends openPremium message when feature flag is disabled", async () => { + it("opens the PremiumComponent when feature flag is disabled", async () => { configService.getFeatureFlag.mockResolvedValue(false); await service.promptForPremium(); @@ -63,7 +61,7 @@ describe("DesktopPremiumUpgradePromptService", () => { expect(configService.getFeatureFlag).toHaveBeenCalledWith( FeatureFlag.PM23713_PremiumBadgeOpensNewPremiumUpgradeDialog, ); - expect(messager.send).toHaveBeenCalledWith("openPremium"); + expect(dialogService.open).toHaveBeenCalledWith(PremiumComponent); expect(openSpy).not.toHaveBeenCalled(); }); }); diff --git a/apps/desktop/src/services/desktop-premium-upgrade-prompt.service.ts b/apps/desktop/src/services/desktop-premium-upgrade-prompt.service.ts index 5004e5ed547..0161baba801 100644 --- a/apps/desktop/src/services/desktop-premium-upgrade-prompt.service.ts +++ b/apps/desktop/src/services/desktop-premium-upgrade-prompt.service.ts @@ -3,15 +3,15 @@ import { inject } from "@angular/core"; import { PremiumUpgradeDialogComponent } from "@bitwarden/angular/billing/components"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; -import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { PremiumUpgradePromptService } from "@bitwarden/common/vault/abstractions/premium-upgrade-prompt.service"; import { DialogService } from "@bitwarden/components"; +import { PremiumComponent } from "../billing/app/accounts/premium.component"; + /** * This class handles the premium upgrade process for the desktop. */ export class DesktopPremiumUpgradePromptService implements PremiumUpgradePromptService { - private messagingService = inject(MessagingService); private configService = inject(ConfigService); private dialogService = inject(DialogService); @@ -23,7 +23,7 @@ export class DesktopPremiumUpgradePromptService implements PremiumUpgradePromptS if (showNewDialog) { PremiumUpgradeDialogComponent.open(this.dialogService); } else { - this.messagingService.send("openPremium"); + this.dialogService.open(PremiumComponent); } } } From 2b5f474bf08881a710f06fa9944b9044e1d6b949 Mon Sep 17 00:00:00 2001 From: Maciej Zieniuk <167752252+mzieniukbw@users.noreply.github.com> Date: Wed, 31 Dec 2025 08:31:26 +0100 Subject: [PATCH 183/188] incorrectly serialized symmetric crypto key in session storage (#18150) --- apps/browser/src/background/main.background.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index 2d1da8510c1..e4fbe1f4edf 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -578,7 +578,7 @@ export default class MainBackground { "ephemeral", "bitwarden-ephemeral", ); - await sessionStorage.save("session-key", derivedKey); + await sessionStorage.save("session-key", derivedKey.toJSON()); return derivedKey; }); From 7fa1a6f07f7d921a3a561f4623627000daf50add Mon Sep 17 00:00:00 2001 From: Maciej Zieniuk <167752252+mzieniukbw@users.noreply.github.com> Date: Wed, 31 Dec 2025 11:04:54 +0100 Subject: [PATCH 184/188] [PM-27236] account registration v2 for key connector (#17951) * account registration v2 for key connector * explicit naming * test coverage * missing AccountCryptographicStateService and DI dependencies * redundant SdkLoadService.Ready * update sdk version --- .../browser/src/background/main.background.ts | 50 +- .../service-container/service-container.ts | 60 ++- .../src/services/jslib-services.module.ts | 4 + libs/common/src/enums/feature-flag.enum.ts | 2 + .../new-sso-user-key-connector-conversion.ts | 1 + .../services/key-connector.service.spec.ts | 430 +++++++++++++----- .../services/key-connector.service.ts | 130 +++++- package-lock.json | 16 +- package.json | 4 +- 9 files changed, 536 insertions(+), 161 deletions(-) diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index e4fbe1f4edf..3cd8b59aabc 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -126,6 +126,7 @@ import { FileUploadService as FileUploadServiceAbstraction } from "@bitwarden/co import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService as LogServiceAbstraction } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { RegisterSdkService } from "@bitwarden/common/platform/abstractions/sdk/register-sdk.service"; import { SdkLoadService } from "@bitwarden/common/platform/abstractions/sdk/sdk-load.service"; import { SdkService } from "@bitwarden/common/platform/abstractions/sdk/sdk.service"; import { StateService as StateServiceAbstraction } from "@bitwarden/common/platform/abstractions/state.service"; @@ -164,6 +165,7 @@ import { MigrationRunner } from "@bitwarden/common/platform/services/migration-r import { DefaultSdkClientFactory } from "@bitwarden/common/platform/services/sdk/default-sdk-client-factory"; import { DefaultSdkService } from "@bitwarden/common/platform/services/sdk/default-sdk.service"; import { NoopSdkClientFactory } from "@bitwarden/common/platform/services/sdk/noop-sdk-client-factory"; +import { DefaultRegisterSdkService } from "@bitwarden/common/platform/services/sdk/register-sdk.service"; import { SystemService } from "@bitwarden/common/platform/services/system.service"; import { UserAutoUnlockKeyService } from "@bitwarden/common/platform/services/user-auto-unlock-key.service"; import { PrimarySecondaryStorageService } from "@bitwarden/common/platform/storage/primary-secondary-storage.service"; @@ -463,6 +465,7 @@ export default class MainBackground { themeStateService: DefaultThemeStateService; autoSubmitLoginBackground: AutoSubmitLoginBackground; sdkService: SdkService; + registerSdkService: RegisterSdkService; sdkLoadService: SdkLoadService; cipherAuthorizationService: CipherAuthorizationService; endUserNotificationService: EndUserNotificationService; @@ -797,18 +800,6 @@ export default class MainBackground { this.apiService, this.accountService, ); - this.keyConnectorService = new KeyConnectorService( - this.accountService, - this.masterPasswordService, - this.keyService, - this.apiService, - this.tokenService, - this.logService, - this.organizationService, - this.keyGenerationService, - logoutCallback, - this.stateProvider, - ); this.authService = new AuthService( this.accountService, @@ -846,6 +837,37 @@ export default class MainBackground { this.configService, ); + this.registerSdkService = new DefaultRegisterSdkService( + sdkClientFactory, + this.environmentService, + this.platformUtilsService, + this.accountService, + this.apiService, + this.stateProvider, + this.configService, + ); + + this.accountCryptographicStateService = new DefaultAccountCryptographicStateService( + this.stateProvider, + ); + + this.keyConnectorService = new KeyConnectorService( + this.accountService, + this.masterPasswordService, + this.keyService, + this.apiService, + this.tokenService, + this.logService, + this.organizationService, + this.keyGenerationService, + logoutCallback, + this.stateProvider, + this.configService, + this.registerSdkService, + this.securityStateService, + this.accountCryptographicStateService, + ); + this.pinService = new PinService( this.encryptService, this.logService, @@ -1013,9 +1035,7 @@ export default class MainBackground { this.avatarService = new AvatarService(this.apiService, this.stateProvider); this.providerService = new ProviderService(this.stateProvider); - this.accountCryptographicStateService = new DefaultAccountCryptographicStateService( - this.stateProvider, - ); + this.syncService = new DefaultSyncService( this.masterPasswordService, this.accountService, diff --git a/apps/cli/src/service-container/service-container.ts b/apps/cli/src/service-container/service-container.ts index 2f1e92d14fc..d98b5f0a861 100644 --- a/apps/cli/src/service-container/service-container.ts +++ b/apps/cli/src/service-container/service-container.ts @@ -104,6 +104,7 @@ import { EnvironmentService, RegionConfig, } from "@bitwarden/common/platform/abstractions/environment.service"; +import { RegisterSdkService } from "@bitwarden/common/platform/abstractions/sdk/register-sdk.service"; import { SdkLoadService } from "@bitwarden/common/platform/abstractions/sdk/sdk-load.service"; import { SdkService } from "@bitwarden/common/platform/abstractions/sdk/sdk.service"; import { LogLevelType } from "@bitwarden/common/platform/enums"; @@ -124,6 +125,7 @@ import { MigrationRunner } from "@bitwarden/common/platform/services/migration-r import { DefaultSdkClientFactory } from "@bitwarden/common/platform/services/sdk/default-sdk-client-factory"; import { DefaultSdkService } from "@bitwarden/common/platform/services/sdk/default-sdk.service"; import { NoopSdkClientFactory } from "@bitwarden/common/platform/services/sdk/noop-sdk-client-factory"; +import { DefaultRegisterSdkService } from "@bitwarden/common/platform/services/sdk/register-sdk.service"; import { StorageServiceProvider } from "@bitwarden/common/platform/services/storage-service.provider"; import { UserAutoUnlockKeyService } from "@bitwarden/common/platform/services/user-auto-unlock-key.service"; import { SyncService } from "@bitwarden/common/platform/sync"; @@ -323,6 +325,7 @@ export class ServiceContainer { kdfConfigService: KdfConfigService; taskSchedulerService: TaskSchedulerService; sdkService: SdkService; + registerSdkService: RegisterSdkService; sdkLoadService: SdkLoadService; cipherAuthorizationService: CipherAuthorizationService; ssoUrlService: SsoUrlService; @@ -632,26 +635,10 @@ export class ServiceContainer { this.accountService, ); - this.keyConnectorService = new KeyConnectorService( - this.accountService, - this.masterPasswordService, - this.keyService, - this.apiService, - this.tokenService, - this.logService, - this.organizationService, - this.keyGenerationService, - logoutCallback, + this.accountCryptographicStateService = new DefaultAccountCryptographicStateService( this.stateProvider, ); - this.twoFactorService = new DefaultTwoFactorService( - this.i18nService, - this.platformUtilsService, - this.globalStateProvider, - this.twoFactorApiService, - ); - const sdkClientFactory = flagEnabled("sdk") ? new DefaultSdkClientFactory() : new NoopSdkClientFactory(); @@ -670,6 +657,41 @@ export class ServiceContainer { customUserAgent, ); + this.registerSdkService = new DefaultRegisterSdkService( + sdkClientFactory, + this.environmentService, + this.platformUtilsService, + this.accountService, + this.apiService, + this.stateProvider, + this.configService, + customUserAgent, + ); + + this.keyConnectorService = new KeyConnectorService( + this.accountService, + this.masterPasswordService, + this.keyService, + this.apiService, + this.tokenService, + this.logService, + this.organizationService, + this.keyGenerationService, + logoutCallback, + this.stateProvider, + this.configService, + this.registerSdkService, + this.securityStateService, + this.accountCryptographicStateService, + ); + + this.twoFactorService = new DefaultTwoFactorService( + this.i18nService, + this.platformUtilsService, + this.globalStateProvider, + this.twoFactorApiService, + ); + this.passwordStrengthService = new PasswordStrengthService(); this.passwordGenerationService = legacyPasswordGenerationServiceFactory( @@ -719,10 +741,6 @@ export class ServiceContainer { this.accountService, ); - this.accountCryptographicStateService = new DefaultAccountCryptographicStateService( - this.stateProvider, - ); - this.loginStrategyService = new LoginStrategyService( this.accountService, this.masterPasswordService, diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index ab6ca7295e3..7899ff5281a 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -1146,6 +1146,10 @@ const safeProviders: SafeProvider[] = [ KeyGenerationService, LOGOUT_CALLBACK, StateProvider, + ConfigService, + RegisterSdkService, + SecurityStateService, + AccountCryptographicStateService, ], }), safeProvider({ diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts index 5a6eeebd001..e5c29636585 100644 --- a/libs/common/src/enums/feature-flag.enum.ts +++ b/libs/common/src/enums/feature-flag.enum.ts @@ -45,6 +45,7 @@ export enum FeatureFlag { DataRecoveryTool = "pm-28813-data-recovery-tool", ConsolidatedSessionTimeoutComponent = "pm-26056-consolidated-session-timeout-component", PM27279_V2RegistrationTdeJit = "pm-27279-v2-registration-tde-jit", + EnableAccountEncryptionV2KeyConnectorRegistration = "enable-account-encryption-v2-key-connector-registration", /* Tools */ DesktopSendUIRefresh = "desktop-send-ui-refresh", @@ -152,6 +153,7 @@ export const DefaultFeatureFlagValue = { [FeatureFlag.DataRecoveryTool]: FALSE, [FeatureFlag.ConsolidatedSessionTimeoutComponent]: FALSE, [FeatureFlag.PM27279_V2RegistrationTdeJit]: FALSE, + [FeatureFlag.EnableAccountEncryptionV2KeyConnectorRegistration]: FALSE, /* Platform */ [FeatureFlag.IpcChannelFramework]: FALSE, diff --git a/libs/common/src/key-management/key-connector/models/new-sso-user-key-connector-conversion.ts b/libs/common/src/key-management/key-connector/models/new-sso-user-key-connector-conversion.ts index 12996747c96..3d686697a4e 100644 --- a/libs/common/src/key-management/key-connector/models/new-sso-user-key-connector-conversion.ts +++ b/libs/common/src/key-management/key-connector/models/new-sso-user-key-connector-conversion.ts @@ -5,5 +5,6 @@ import { KdfConfig } from "@bitwarden/key-management"; export interface NewSsoUserKeyConnectorConversion { kdfConfig: KdfConfig; keyConnectorUrl: string; + // SSO organization identifier, not UUID organizationId: string; } diff --git a/libs/common/src/key-management/key-connector/services/key-connector.service.spec.ts b/libs/common/src/key-management/key-connector/services/key-connector.service.spec.ts index 45b4f5e4ac6..b8ee5d7df64 100644 --- a/libs/common/src/key-management/key-connector/services/key-connector.service.spec.ts +++ b/libs/common/src/key-management/key-connector/services/key-connector.service.spec.ts @@ -7,7 +7,8 @@ import { SetKeyConnectorKeyRequest } from "@bitwarden/common/key-management/key- import { KeysRequest } from "@bitwarden/common/models/request/keys.request"; // 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 { Argon2KdfConfig, PBKDF2KdfConfig, KeyService, KdfType } from "@bitwarden/key-management"; +import { Argon2KdfConfig, KdfType, KeyService, PBKDF2KdfConfig } from "@bitwarden/key-management"; +import { BitwardenClient } from "@bitwarden/sdk-internal"; import { FakeAccountService, FakeStateProvider, mockAccountServiceWith } from "../../../../spec"; import { ApiService } from "../../../abstractions/api.service"; @@ -16,21 +17,26 @@ import { Organization } from "../../../admin-console/models/domain/organization" import { ProfileOrganizationResponse } from "../../../admin-console/models/response/profile-organization.response"; import { KeyConnectorUserKeyResponse } from "../../../auth/models/response/key-connector-user-key.response"; import { TokenService } from "../../../auth/services/token.service"; +import { ConfigService } from "../../../platform/abstractions/config/config.service"; import { LogService } from "../../../platform/abstractions/log.service"; +import { RegisterSdkService } from "../../../platform/abstractions/sdk/register-sdk.service"; +import { Rc } from "../../../platform/misc/reference-counting/rc"; import { Utils } from "../../../platform/misc/utils"; import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key"; import { OrganizationId, UserId } from "../../../types/guid"; import { MasterKey, UserKey } from "../../../types/key"; +import { AccountCryptographicStateService } from "../../account-cryptography/account-cryptographic-state.service"; import { KeyGenerationService } from "../../crypto"; import { EncString } from "../../crypto/models/enc-string"; import { FakeMasterPasswordService } from "../../master-password/services/fake-master-password.service"; +import { SecurityStateService } from "../../security-state/abstractions/security-state.service"; import { KeyConnectorUserKeyRequest } from "../models/key-connector-user-key.request"; import { NewSsoUserKeyConnectorConversion } from "../models/new-sso-user-key-connector-conversion"; import { - USES_KEY_CONNECTOR, - NEW_SSO_USER_KEY_CONNECTOR_CONVERSION, KeyConnectorService, + NEW_SSO_USER_KEY_CONNECTOR_CONVERSION, + USES_KEY_CONNECTOR, } from "./key-connector.service"; describe("KeyConnectorService", () => { @@ -43,6 +49,10 @@ describe("KeyConnectorService", () => { const organizationService = mock<OrganizationService>(); const keyGenerationService = mock<KeyGenerationService>(); const logoutCallback = jest.fn(); + const configService = mock<ConfigService>(); + const registerSdkService = mock<RegisterSdkService>(); + const securityStateService = mock<SecurityStateService>(); + const accountCryptographicStateService = mock<AccountCryptographicStateService>(); let stateProvider: FakeStateProvider; @@ -50,6 +60,7 @@ describe("KeyConnectorService", () => { let masterPasswordService: FakeMasterPasswordService; const mockUserId = Utils.newGuid() as UserId; + const mockSsoOrgIdentifier = "test-sso-org-id"; const mockOrgId = Utils.newGuid() as OrganizationId; const mockMasterKeyResponse: KeyConnectorUserKeyResponse = new KeyConnectorUserKeyResponse({ @@ -61,7 +72,7 @@ describe("KeyConnectorService", () => { const conversion: NewSsoUserKeyConnectorConversion = { kdfConfig: new PBKDF2KdfConfig(600_000), keyConnectorUrl, - organizationId: mockOrgId, + organizationId: mockSsoOrgIdentifier, }; beforeEach(() => { @@ -82,6 +93,10 @@ describe("KeyConnectorService", () => { keyGenerationService, logoutCallback, stateProvider, + configService, + registerSdkService, + securityStateService, + accountCryptographicStateService, ); }); @@ -419,44 +434,52 @@ describe("KeyConnectorService", () => { }); describe("convertNewSsoUserToKeyConnector", () => { - const passwordKey = new SymmetricCryptoKey(new Uint8Array(64)); - const mockUserKey = new SymmetricCryptoKey(new Uint8Array(64)) as UserKey; - const mockEmail = "test@example.com"; - const mockMasterKey = getMockMasterKey(); - const mockKeyPair = ["mockPubKey", new EncString("mockEncryptedPrivKey")] as [ - string, - EncString, - ]; - let mockMakeUserKeyResult: [UserKey, EncString]; + describe("V2", () => { + const mockKeyConnectorKey = Utils.fromBufferToB64(new Uint8Array(64)); + const mockUserKeyString = Utils.fromBufferToB64(new Uint8Array(64)); + const mockPrivateKey = "mockPrivateKey789"; + const mockKeyConnectorKeyWrappedUserKey = "2.mockWrappedUserKey"; + const mockSigningKey = "mockSigningKey"; + const mockSignedPublicKey = "mockSignedPublicKey"; + const mockSecurityState = "mockSecurityState"; - beforeEach(() => { - const mockUserKey = new SymmetricCryptoKey(new Uint8Array(64)) as UserKey; - const encString = new EncString("mockEncryptedString"); - mockMakeUserKeyResult = [mockUserKey, encString] as [UserKey, EncString]; + let mockSdkRef: any; + let mockSdk: any; - keyGenerationService.createKey.mockResolvedValue(passwordKey); - keyService.makeMasterKey.mockResolvedValue(mockMasterKey); - keyService.makeUserKey.mockResolvedValue(mockMakeUserKeyResult); - keyService.makeKeyPair.mockResolvedValue(mockKeyPair); - tokenService.getEmail.mockResolvedValue(mockEmail); - }); + beforeEach(() => { + configService.getFeatureFlag$.mockReturnValue(of(true)); - it.each([ - [KdfType.PBKDF2_SHA256, 700_000, undefined, undefined], - [KdfType.Argon2id, 11, 65, 5], - ])( - "sets up a new SSO user with key connector", - async (kdfType, kdfIterations, kdfMemory, kdfParallelism) => { - const expectedKdfConfig = - kdfType == KdfType.PBKDF2_SHA256 - ? new PBKDF2KdfConfig(kdfIterations) - : new Argon2KdfConfig(kdfIterations, kdfMemory, kdfParallelism); - - const conversion: NewSsoUserKeyConnectorConversion = { - kdfConfig: expectedKdfConfig, - keyConnectorUrl: keyConnectorUrl, - organizationId: mockOrgId, + mockSdkRef = { + value: { + auth: jest.fn().mockReturnValue({ + registration: jest.fn().mockReturnValue({ + post_keys_for_key_connector_registration: jest.fn().mockResolvedValue({ + key_connector_key: mockKeyConnectorKey, + user_key: mockUserKeyString, + key_connector_key_wrapped_user_key: mockKeyConnectorKeyWrappedUserKey, + account_cryptographic_state: { + V2: { + private_key: mockPrivateKey, + signing_key: mockSigningKey, + signed_public_key: mockSignedPublicKey, + security_state: mockSecurityState, + }, + }, + }), + }), + }), + }, + [Symbol.dispose]: jest.fn(), }; + + mockSdk = { + take: jest.fn().mockReturnValue(mockSdkRef), + }; + + registerSdkService.registerClient$.mockReturnValue(of(mockSdk)); + }); + + it("should set up a new SSO user with key connector using V2", async () => { const conversionState = stateProvider.singleUser.getFake( mockUserId, NEW_SSO_USER_KEY_CONNECTOR_CONVERSION, @@ -465,11 +488,253 @@ describe("KeyConnectorService", () => { await keyConnectorService.convertNewSsoUserToKeyConnector(mockUserId); + expect(registerSdkService.registerClient$).toHaveBeenCalledWith(mockUserId); + expect(mockSdk.take).toHaveBeenCalled(); + expect(mockSdkRef.value.auth).toHaveBeenCalled(); + + const mockRegistration = mockSdkRef.value + .auth() + .registration().post_keys_for_key_connector_registration; + expect(mockRegistration).toHaveBeenCalledWith( + keyConnectorUrl, + mockSsoOrgIdentifier, + mockUserId, + ); + + expect(masterPasswordService.mock.setMasterKey).toHaveBeenCalledWith( + expect.any(SymmetricCryptoKey), + mockUserId, + ); + expect(keyService.setUserKey).toHaveBeenCalledWith( + expect.any(SymmetricCryptoKey), + mockUserId, + ); + expect(masterPasswordService.mock.setMasterKeyEncryptedUserKey).toHaveBeenCalledWith( + expect.any(EncString), + mockUserId, + ); + expect(accountCryptographicStateService.setAccountCryptographicState).toHaveBeenCalledWith( + { + V2: { + private_key: mockPrivateKey, + signing_key: mockSigningKey, + signed_public_key: mockSignedPublicKey, + security_state: mockSecurityState, + }, + }, + mockUserId, + ); + expect(keyService.setPrivateKey).toHaveBeenCalledWith(mockPrivateKey, mockUserId); + expect(keyService.setUserSigningKey).toHaveBeenCalledWith(mockSigningKey, mockUserId); + expect(securityStateService.setAccountSecurityState).toHaveBeenCalledWith( + mockSecurityState, + mockUserId, + ); + expect(keyService.setSignedPublicKey).toHaveBeenCalledWith(mockSignedPublicKey, mockUserId); + + expect(await firstValueFrom(conversionState.state$)).toBeNull(); + }); + + it("should throw error when SDK is not available", async () => { + registerSdkService.registerClient$.mockReturnValue( + of(null as unknown as Rc<BitwardenClient>), + ); + + const conversionState = stateProvider.singleUser.getFake( + mockUserId, + NEW_SSO_USER_KEY_CONNECTOR_CONVERSION, + ); + conversionState.nextState(conversion); + + await expect( + keyConnectorService.convertNewSsoUserToKeyConnector(mockUserId), + ).rejects.toThrow("SDK not available"); + + expect(await firstValueFrom(conversionState.state$)).toEqual(conversion); + expect(masterPasswordService.mock.setMasterKey).not.toHaveBeenCalled(); + expect(keyService.setUserKey).not.toHaveBeenCalled(); + expect(masterPasswordService.mock.setMasterKeyEncryptedUserKey).not.toHaveBeenCalled(); + expect( + accountCryptographicStateService.setAccountCryptographicState, + ).not.toHaveBeenCalled(); + expect(keyService.setPrivateKey).not.toHaveBeenCalled(); + expect(keyService.setUserSigningKey).not.toHaveBeenCalled(); + expect(securityStateService.setAccountSecurityState).not.toHaveBeenCalled(); + expect(keyService.setSignedPublicKey).not.toHaveBeenCalled(); + }); + + it("should throw error when account cryptographic state is not V2", async () => { + mockSdkRef.value + .auth() + .registration() + .post_keys_for_key_connector_registration.mockResolvedValue({ + key_connector_key: mockKeyConnectorKey, + user_key: mockUserKeyString, + key_connector_key_wrapped_user_key: mockKeyConnectorKeyWrappedUserKey, + account_cryptographic_state: { + V1: { + private_key: mockPrivateKey, + }, + }, + }); + + const conversionState = stateProvider.singleUser.getFake( + mockUserId, + NEW_SSO_USER_KEY_CONNECTOR_CONVERSION, + ); + conversionState.nextState(conversion); + + await expect( + keyConnectorService.convertNewSsoUserToKeyConnector(mockUserId), + ).rejects.toThrow("Unexpected account cryptographic state version"); + + expect(await firstValueFrom(conversionState.state$)).toEqual(conversion); + expect(masterPasswordService.mock.setMasterKey).not.toHaveBeenCalled(); + expect(keyService.setUserKey).not.toHaveBeenCalled(); + expect(masterPasswordService.mock.setMasterKeyEncryptedUserKey).not.toHaveBeenCalled(); + expect( + accountCryptographicStateService.setAccountCryptographicState, + ).not.toHaveBeenCalled(); + expect(keyService.setPrivateKey).not.toHaveBeenCalled(); + expect(keyService.setUserSigningKey).not.toHaveBeenCalled(); + expect(securityStateService.setAccountSecurityState).not.toHaveBeenCalled(); + expect(keyService.setSignedPublicKey).not.toHaveBeenCalled(); + }); + + it("should throw error when post_keys_for_key_connector_registration fails", async () => { + const sdkError = new Error("Key Connector registration failed"); + mockSdkRef.value + .auth() + .registration() + .post_keys_for_key_connector_registration.mockRejectedValue(sdkError); + + const conversionState = stateProvider.singleUser.getFake( + mockUserId, + NEW_SSO_USER_KEY_CONNECTOR_CONVERSION, + ); + conversionState.nextState(conversion); + + await expect( + keyConnectorService.convertNewSsoUserToKeyConnector(mockUserId), + ).rejects.toThrow("Key Connector registration failed"); + + expect(await firstValueFrom(conversionState.state$)).toEqual(conversion); + expect(masterPasswordService.mock.setMasterKey).not.toHaveBeenCalled(); + expect(keyService.setUserKey).not.toHaveBeenCalled(); + expect(masterPasswordService.mock.setMasterKeyEncryptedUserKey).not.toHaveBeenCalled(); + expect( + accountCryptographicStateService.setAccountCryptographicState, + ).not.toHaveBeenCalled(); + expect(keyService.setPrivateKey).not.toHaveBeenCalled(); + expect(keyService.setUserSigningKey).not.toHaveBeenCalled(); + expect(securityStateService.setAccountSecurityState).not.toHaveBeenCalled(); + expect(keyService.setSignedPublicKey).not.toHaveBeenCalled(); + }); + }); + + describe("V1", () => { + const passwordKey = new SymmetricCryptoKey(new Uint8Array(64)); + const mockUserKey = new SymmetricCryptoKey(new Uint8Array(64)) as UserKey; + const mockEmail = "test@example.com"; + const mockMasterKey = getMockMasterKey(); + const mockKeyPair = ["mockPubKey", new EncString("mockEncryptedPrivKey")] as [ + string, + EncString, + ]; + let mockMakeUserKeyResult: [UserKey, EncString]; + + beforeEach(() => { + const mockUserKey = new SymmetricCryptoKey(new Uint8Array(64)) as UserKey; + const encString = new EncString("mockEncryptedString"); + mockMakeUserKeyResult = [mockUserKey, encString] as [UserKey, EncString]; + + keyGenerationService.createKey.mockResolvedValue(passwordKey); + keyService.makeMasterKey.mockResolvedValue(mockMasterKey); + keyService.makeUserKey.mockResolvedValue(mockMakeUserKeyResult); + keyService.makeKeyPair.mockResolvedValue(mockKeyPair); + tokenService.getEmail.mockResolvedValue(mockEmail); + configService.getFeatureFlag$.mockReturnValue(of(false)); + }); + + it.each([ + [KdfType.PBKDF2_SHA256, 700_000, undefined, undefined], + [KdfType.Argon2id, 11, 65, 5], + ])( + "sets up a new SSO user with key connector", + async (kdfType, kdfIterations, kdfMemory, kdfParallelism) => { + const expectedKdfConfig = + kdfType == KdfType.PBKDF2_SHA256 + ? new PBKDF2KdfConfig(kdfIterations) + : new Argon2KdfConfig(kdfIterations, kdfMemory, kdfParallelism); + + const conversion: NewSsoUserKeyConnectorConversion = { + kdfConfig: expectedKdfConfig, + keyConnectorUrl: keyConnectorUrl, + organizationId: mockSsoOrgIdentifier, + }; + const conversionState = stateProvider.singleUser.getFake( + mockUserId, + NEW_SSO_USER_KEY_CONNECTOR_CONVERSION, + ); + conversionState.nextState(conversion); + + await keyConnectorService.convertNewSsoUserToKeyConnector(mockUserId); + + expect(keyGenerationService.createKey).toHaveBeenCalledWith(512); + expect(keyService.makeMasterKey).toHaveBeenCalledWith( + passwordKey.keyB64, + mockEmail, + expectedKdfConfig, + ); + expect(masterPasswordService.mock.setMasterKey).toHaveBeenCalledWith( + mockMasterKey, + mockUserId, + ); + expect(keyService.makeUserKey).toHaveBeenCalledWith(mockMasterKey); + expect(keyService.setUserKey).toHaveBeenCalledWith(mockUserKey, mockUserId); + expect(masterPasswordService.mock.setMasterKeyEncryptedUserKey).toHaveBeenCalledWith( + mockMakeUserKeyResult[1], + mockUserId, + ); + expect(keyService.makeKeyPair).toHaveBeenCalledWith(mockMakeUserKeyResult[0]); + expect(apiService.postUserKeyToKeyConnector).toHaveBeenCalledWith( + keyConnectorUrl, + new KeyConnectorUserKeyRequest( + Utils.fromBufferToB64(mockMasterKey.inner().encryptionKey), + ), + ); + expect(apiService.postSetKeyConnectorKey).toHaveBeenCalledWith( + new SetKeyConnectorKeyRequest( + mockMakeUserKeyResult[1].encryptedString!, + expectedKdfConfig, + mockSsoOrgIdentifier, + new KeysRequest(mockKeyPair[0], mockKeyPair[1].encryptedString!), + ), + ); + + // Verify that conversion data is cleared from conversionState + expect(await firstValueFrom(conversionState.state$)).toBeNull(); + }, + ); + + it("handles api error", async () => { + apiService.postUserKeyToKeyConnector.mockRejectedValue(new Error("API error")); + + const conversionState = stateProvider.singleUser.getFake( + mockUserId, + NEW_SSO_USER_KEY_CONNECTOR_CONVERSION, + ); + conversionState.nextState(conversion); + + await expect( + keyConnectorService.convertNewSsoUserToKeyConnector(mockUserId), + ).rejects.toThrow(new Error("Key Connector error")); + expect(keyGenerationService.createKey).toHaveBeenCalledWith(512); expect(keyService.makeMasterKey).toHaveBeenCalledWith( passwordKey.keyB64, mockEmail, - expectedKdfConfig, + new PBKDF2KdfConfig(600_000), ); expect(masterPasswordService.mock.setMasterKey).toHaveBeenCalledWith( mockMasterKey, @@ -488,76 +753,29 @@ describe("KeyConnectorService", () => { Utils.fromBufferToB64(mockMasterKey.inner().encryptionKey), ), ); - expect(apiService.postSetKeyConnectorKey).toHaveBeenCalledWith( - new SetKeyConnectorKeyRequest( - mockMakeUserKeyResult[1].encryptedString!, - expectedKdfConfig, - mockOrgId, - new KeysRequest(mockKeyPair[0], mockKeyPair[1].encryptedString!), - ), + expect(apiService.postSetKeyConnectorKey).not.toHaveBeenCalled(); + expect(await firstValueFrom(conversionState.state$)).toEqual(conversion); + + expect(logoutCallback).toHaveBeenCalledWith("keyConnectorError"); + }); + + it("should throw error when conversion data is null", async () => { + const conversionState = stateProvider.singleUser.getFake( + mockUserId, + NEW_SSO_USER_KEY_CONNECTOR_CONVERSION, ); + conversionState.nextState(null); - // Verify that conversion data is cleared from conversionState - expect(await firstValueFrom(conversionState.state$)).toBeNull(); - }, - ); + await expect( + keyConnectorService.convertNewSsoUserToKeyConnector(mockUserId), + ).rejects.toThrow(new Error("Key Connector conversion not found")); - it("handles api error", async () => { - apiService.postUserKeyToKeyConnector.mockRejectedValue(new Error("API error")); - - const conversionState = stateProvider.singleUser.getFake( - mockUserId, - NEW_SSO_USER_KEY_CONNECTOR_CONVERSION, - ); - conversionState.nextState(conversion); - - await expect(keyConnectorService.convertNewSsoUserToKeyConnector(mockUserId)).rejects.toThrow( - new Error("Key Connector error"), - ); - - expect(keyGenerationService.createKey).toHaveBeenCalledWith(512); - expect(keyService.makeMasterKey).toHaveBeenCalledWith( - passwordKey.keyB64, - mockEmail, - new PBKDF2KdfConfig(600_000), - ); - expect(masterPasswordService.mock.setMasterKey).toHaveBeenCalledWith( - mockMasterKey, - mockUserId, - ); - expect(keyService.makeUserKey).toHaveBeenCalledWith(mockMasterKey); - expect(keyService.setUserKey).toHaveBeenCalledWith(mockUserKey, mockUserId); - expect(masterPasswordService.mock.setMasterKeyEncryptedUserKey).toHaveBeenCalledWith( - mockMakeUserKeyResult[1], - mockUserId, - ); - expect(keyService.makeKeyPair).toHaveBeenCalledWith(mockMakeUserKeyResult[0]); - expect(apiService.postUserKeyToKeyConnector).toHaveBeenCalledWith( - keyConnectorUrl, - new KeyConnectorUserKeyRequest(Utils.fromBufferToB64(mockMasterKey.inner().encryptionKey)), - ); - expect(apiService.postSetKeyConnectorKey).not.toHaveBeenCalled(); - expect(await firstValueFrom(conversionState.state$)).toEqual(conversion); - - expect(logoutCallback).toHaveBeenCalledWith("keyConnectorError"); - }); - - it("should throw error when conversion data is null", async () => { - const conversionState = stateProvider.singleUser.getFake( - mockUserId, - NEW_SSO_USER_KEY_CONNECTOR_CONVERSION, - ); - conversionState.nextState(null); - - await expect(keyConnectorService.convertNewSsoUserToKeyConnector(mockUserId)).rejects.toThrow( - new Error("Key Connector conversion not found"), - ); - - // Verify that no key generation or API calls were made - expect(keyGenerationService.createKey).not.toHaveBeenCalled(); - expect(keyService.makeMasterKey).not.toHaveBeenCalled(); - expect(apiService.postUserKeyToKeyConnector).not.toHaveBeenCalled(); - expect(apiService.postSetKeyConnectorKey).not.toHaveBeenCalled(); + // Verify that no key generation or API calls were made + expect(keyGenerationService.createKey).not.toHaveBeenCalled(); + expect(keyService.makeMasterKey).not.toHaveBeenCalled(); + expect(apiService.postUserKeyToKeyConnector).not.toHaveBeenCalled(); + expect(apiService.postSetKeyConnectorKey).not.toHaveBeenCalled(); + }); }); }); diff --git a/libs/common/src/key-management/key-connector/services/key-connector.service.ts b/libs/common/src/key-management/key-connector/services/key-connector.service.ts index 8a75034cae1..751f1ec8594 100644 --- a/libs/common/src/key-management/key-connector/services/key-connector.service.ts +++ b/libs/common/src/key-management/key-connector/services/key-connector.service.ts @@ -9,22 +9,36 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv import { NewSsoUserKeyConnectorConversion } from "@bitwarden/common/key-management/key-connector/models/new-sso-user-key-connector-conversion"; // 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 { Argon2KdfConfig, KdfType, KeyService, PBKDF2KdfConfig } from "@bitwarden/key-management"; +import { + Argon2KdfConfig, + KdfConfig, + KdfType, + KeyService, + PBKDF2KdfConfig, +} from "@bitwarden/key-management"; +import { LogService } from "@bitwarden/logging"; import { ApiService } from "../../../abstractions/api.service"; import { OrganizationService } from "../../../admin-console/abstractions/organization/organization.service.abstraction"; import { OrganizationUserType } from "../../../admin-console/enums"; import { Organization } from "../../../admin-console/models/domain/organization"; import { TokenService } from "../../../auth/abstractions/token.service"; +import { FeatureFlag } from "../../../enums/feature-flag.enum"; import { KeysRequest } from "../../../models/request/keys.request"; -import { LogService } from "../../../platform/abstractions/log.service"; +import { ConfigService } from "../../../platform/abstractions/config/config.service"; +import { RegisterSdkService } from "../../../platform/abstractions/sdk/register-sdk.service"; +import { asUuid } from "../../../platform/abstractions/sdk/sdk.service"; import { Utils } from "../../../platform/misc/utils"; import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key"; import { KEY_CONNECTOR_DISK, StateProvider, UserKeyDefinition } from "../../../platform/state"; import { UserId } from "../../../types/guid"; -import { MasterKey } from "../../../types/key"; +import { MasterKey, UserKey } from "../../../types/key"; +import { AccountCryptographicStateService } from "../../account-cryptography/account-cryptographic-state.service"; import { KeyGenerationService } from "../../crypto"; +import { EncString } from "../../crypto/models/enc-string"; import { InternalMasterPasswordServiceAbstraction } from "../../master-password/abstractions/master-password.service.abstraction"; +import { SecurityStateService } from "../../security-state/abstractions/security-state.service"; +import { SignedPublicKey, SignedSecurityState, WrappedSigningKey } from "../../types"; import { KeyConnectorService as KeyConnectorServiceAbstraction } from "../abstractions/key-connector.service"; import { KeyConnectorDomainConfirmation } from "../models/key-connector-domain-confirmation"; import { KeyConnectorUserKeyRequest } from "../models/key-connector-user-key.request"; @@ -75,6 +89,10 @@ export class KeyConnectorService implements KeyConnectorServiceAbstraction { private keyGenerationService: KeyGenerationService, private logoutCallback: (logoutReason: LogoutReason, userId?: string) => Promise<void>, private stateProvider: StateProvider, + private configService: ConfigService, + private registerSdkService: RegisterSdkService, + private securityStateService: SecurityStateService, + private accountCryptographicStateService: AccountCryptographicStateService, ) { this.convertAccountRequired$ = accountService.activeAccount$.pipe( filter((account) => account != null), @@ -152,8 +170,106 @@ export class KeyConnectorService implements KeyConnectorServiceAbstraction { throw new Error("Key Connector conversion not found"); } - const { kdfConfig, keyConnectorUrl, organizationId } = conversion; + const { kdfConfig, keyConnectorUrl, organizationId: ssoOrganizationIdentifier } = conversion; + if ( + await firstValueFrom( + this.configService.getFeatureFlag$( + FeatureFlag.EnableAccountEncryptionV2KeyConnectorRegistration, + ), + ) + ) { + await this.convertNewSsoUserToKeyConnectorV2( + userId, + keyConnectorUrl, + ssoOrganizationIdentifier, + ); + } else { + await this.convertNewSsoUserToKeyConnectorV1( + userId, + kdfConfig, + keyConnectorUrl, + ssoOrganizationIdentifier, + ); + } + + await this.stateProvider + .getUser(userId, NEW_SSO_USER_KEY_CONNECTOR_CONVERSION) + .update(() => null); + } + + async convertNewSsoUserToKeyConnectorV2( + userId: UserId, + keyConnectorUrl: string, + ssoOrganizationIdentifier: string, + ) { + const result = await firstValueFrom( + this.registerSdkService.registerClient$(userId).pipe( + map((sdk) => { + if (!sdk) { + throw new Error("SDK not available"); + } + + using ref = sdk.take(); + + return ref.value + .auth() + .registration() + .post_keys_for_key_connector_registration( + keyConnectorUrl, + ssoOrganizationIdentifier, + asUuid(userId), + ); + }), + ), + ); + + if (!("V2" in result.account_cryptographic_state)) { + const version = Object.keys(result.account_cryptographic_state); + throw new Error(`Unexpected account cryptographic state version ${version}`); + } + + await this.masterPasswordService.setMasterKey( + SymmetricCryptoKey.fromString(result.key_connector_key) as MasterKey, + userId, + ); + await this.keyService.setUserKey( + SymmetricCryptoKey.fromString(result.user_key) as UserKey, + userId, + ); + await this.masterPasswordService.setMasterKeyEncryptedUserKey( + new EncString(result.key_connector_key_wrapped_user_key), + userId, + ); + + await this.accountCryptographicStateService.setAccountCryptographicState( + result.account_cryptographic_state, + userId, + ); + // Legacy states + await this.keyService.setPrivateKey(result.account_cryptographic_state.V2.private_key, userId); + await this.keyService.setUserSigningKey( + result.account_cryptographic_state.V2.signing_key as WrappedSigningKey, + userId, + ); + await this.securityStateService.setAccountSecurityState( + result.account_cryptographic_state.V2.security_state as SignedSecurityState, + userId, + ); + if (result.account_cryptographic_state.V2.signed_public_key != null) { + await this.keyService.setSignedPublicKey( + result.account_cryptographic_state.V2.signed_public_key as SignedPublicKey, + userId, + ); + } + } + + async convertNewSsoUserToKeyConnectorV1( + userId: UserId, + kdfConfig: KdfConfig, + keyConnectorUrl: string, + ssoOrganizationIdentifier: string, + ) { const password = await this.keyGenerationService.createKey(512); const masterKey = await this.keyService.makeMasterKey( @@ -182,14 +298,10 @@ export class KeyConnectorService implements KeyConnectorServiceAbstraction { const setPasswordRequest = new SetKeyConnectorKeyRequest( userKey[1].encryptedString, kdfConfig, - organizationId, + ssoOrganizationIdentifier, keys, ); await this.apiService.postSetKeyConnectorKey(setPasswordRequest); - - await this.stateProvider - .getUser(userId, NEW_SSO_USER_KEY_CONNECTOR_CONVERSION) - .update(() => null); } async setNewSsoUserKeyConnectorConversionData( diff --git a/package-lock.json b/package-lock.json index c40b5361cc8..e2556015113 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,8 +23,8 @@ "@angular/platform-browser": "20.3.15", "@angular/platform-browser-dynamic": "20.3.15", "@angular/router": "20.3.15", - "@bitwarden/commercial-sdk-internal": "0.2.0-main.439", - "@bitwarden/sdk-internal": "0.2.0-main.439", + "@bitwarden/commercial-sdk-internal": "0.2.0-main.450", + "@bitwarden/sdk-internal": "0.2.0-main.450", "@electron/fuses": "1.8.0", "@emotion/css": "11.13.5", "@koa/multer": "4.0.0", @@ -4973,9 +4973,9 @@ "link": true }, "node_modules/@bitwarden/commercial-sdk-internal": { - "version": "0.2.0-main.439", - "resolved": "https://registry.npmjs.org/@bitwarden/commercial-sdk-internal/-/commercial-sdk-internal-0.2.0-main.439.tgz", - "integrity": "sha512-Wujtym00U7XMEsf9zJ3/0Ggw9WmMcIpE9hMtcLryloX182118vnzkEQbEldqtywpMHiDsD9VmP6RiZ725nnUIQ==", + "version": "0.2.0-main.450", + "resolved": "https://registry.npmjs.org/@bitwarden/commercial-sdk-internal/-/commercial-sdk-internal-0.2.0-main.450.tgz", + "integrity": "sha512-WCihR6ykpIfaqJBHl4Wou4xDB8mp+5UPi94eEKYUdkx/9/19YyX33SX9H56zEriOuOMCD8l2fymhzAFjAAB++g==", "license": "BITWARDEN SOFTWARE DEVELOPMENT KIT LICENSE AGREEMENT", "dependencies": { "type-fest": "^4.41.0" @@ -5078,9 +5078,9 @@ "link": true }, "node_modules/@bitwarden/sdk-internal": { - "version": "0.2.0-main.439", - "resolved": "https://registry.npmjs.org/@bitwarden/sdk-internal/-/sdk-internal-0.2.0-main.439.tgz", - "integrity": "sha512-uvIS8erGmzgWCZom7Kt78C4n4tbjfZuTCn7+y2+E8BTtLBqIZNtl4kC0tNh8c4GUWsmoIYlbQyz+HymWQ7J+QA==", + "version": "0.2.0-main.450", + "resolved": "https://registry.npmjs.org/@bitwarden/sdk-internal/-/sdk-internal-0.2.0-main.450.tgz", + "integrity": "sha512-XRhrBN0uoo66ONx7dYo9glhe9N451+VhwtC/oh3wo3j3qYxbPwf9yE98szlQ52u3iUExLisiYJY7sQNzhZrbZw==", "license": "GPL-3.0", "dependencies": { "type-fest": "^4.41.0" diff --git a/package.json b/package.json index 29ee9683464..be6658964a0 100644 --- a/package.json +++ b/package.json @@ -162,8 +162,8 @@ "@angular/platform-browser": "20.3.15", "@angular/platform-browser-dynamic": "20.3.15", "@angular/router": "20.3.15", - "@bitwarden/sdk-internal": "0.2.0-main.439", - "@bitwarden/commercial-sdk-internal": "0.2.0-main.439", + "@bitwarden/sdk-internal": "0.2.0-main.450", + "@bitwarden/commercial-sdk-internal": "0.2.0-main.450", "@electron/fuses": "1.8.0", "@emotion/css": "11.13.5", "@koa/multer": "4.0.0", From c6b02080d85b3469e45918650edeaaeac2df142a Mon Sep 17 00:00:00 2001 From: Mick Letofsky <mletofsky@bitwarden.com> Date: Wed, 31 Dec 2025 17:17:41 +0100 Subject: [PATCH 185/188] Revert review Code Triggered by labeled event (#18165) --- .github/workflows/review-code.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/review-code.yml b/.github/workflows/review-code.yml index 908664209d8..000f4020961 100644 --- a/.github/workflows/review-code.yml +++ b/.github/workflows/review-code.yml @@ -2,7 +2,7 @@ name: Code Review on: pull_request: - types: [opened, labeled] + types: [opened, synchronize, reopened] permissions: {} From 966f9a0c52bf10c5d05a5eb4ce344bc070249032 Mon Sep 17 00:00:00 2001 From: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com> Date: Wed, 31 Dec 2025 12:53:57 -0600 Subject: [PATCH 186/188] [PM-29928] Fix biometrics status check when native messaging permission is missing (#18154) * Dont check biometrics status when nativeMessaging permission isn't granted * Increase polling interval and add unit tests --- .../account-security.component.spec.ts | 130 +++++++++++++++++- .../settings/account-security.component.ts | 13 +- 2 files changed, 137 insertions(+), 6 deletions(-) diff --git a/apps/browser/src/auth/popup/settings/account-security.component.spec.ts b/apps/browser/src/auth/popup/settings/account-security.component.spec.ts index 0f799fe7d4d..ebabbadf71c 100644 --- a/apps/browser/src/auth/popup/settings/account-security.component.spec.ts +++ b/apps/browser/src/auth/popup/settings/account-security.component.spec.ts @@ -1,5 +1,5 @@ import { Component } from "@angular/core"; -import { ComponentFixture, TestBed } from "@angular/core/testing"; +import { ComponentFixture, fakeAsync, TestBed, tick } from "@angular/core/testing"; import { By } from "@angular/platform-browser"; import { ActivatedRoute } from "@angular/router"; import { mock } from "jest-mock-extended"; @@ -37,7 +37,12 @@ import { UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { DialogService, ToastService } from "@bitwarden/components"; import { newGuid } from "@bitwarden/guid"; -import { BiometricStateService, BiometricsService, KeyService } from "@bitwarden/key-management"; +import { + BiometricStateService, + BiometricsService, + BiometricsStatus, + KeyService, +} from "@bitwarden/key-management"; import { BrowserApi } from "../../../platform/browser/browser-api"; import BrowserPopupUtils from "../../../platform/browser/browser-popup-utils"; @@ -64,6 +69,7 @@ describe("AccountSecurityComponent", () => { const apiService = mock<ApiService>(); const billingService = mock<BillingAccountProfileStateService>(); const biometricStateService = mock<BiometricStateService>(); + const biometricsService = mock<BiometricsService>(); const configService = mock<ConfigService>(); const dialogService = mock<DialogService>(); const keyService = mock<KeyService>(); @@ -75,6 +81,7 @@ describe("AccountSecurityComponent", () => { const validationService = mock<ValidationService>(); const vaultNudgesService = mock<NudgesService>(); const vaultTimeoutSettingsService = mock<VaultTimeoutSettingsService>(); + const mockI18nService = mock<I18nService>(); // Mock subjects to control the phishing detection observables let phishingAvailableSubject: BehaviorSubject<boolean>; @@ -91,14 +98,14 @@ describe("AccountSecurityComponent", () => { provide: BillingAccountProfileStateService, useValue: billingService, }, - { provide: BiometricsService, useValue: mock<BiometricsService>() }, + { provide: BiometricsService, useValue: biometricsService }, { provide: BiometricStateService, useValue: biometricStateService }, { provide: CipherService, useValue: mock<CipherService>() }, { provide: CollectionService, useValue: mock<CollectionService>() }, { provide: ConfigService, useValue: configService }, { provide: DialogService, useValue: dialogService }, { provide: EnvironmentService, useValue: mock<EnvironmentService>() }, - { provide: I18nService, useValue: mock<I18nService>() }, + { provide: I18nService, useValue: mockI18nService }, { provide: KeyService, useValue: keyService }, { provide: LockService, useValue: lockService }, { provide: LogService, useValue: mock<LogService>() }, @@ -153,6 +160,7 @@ describe("AccountSecurityComponent", () => { pinServiceAbstraction.isPinSet.mockResolvedValue(false); configService.getFeatureFlag$.mockReturnValue(of(false)); billingService.hasPremiumPersonally$.mockReturnValue(of(true)); + mockI18nService.t.mockImplementation((key) => `${key}-used-i18n`); policyService.policiesByType$.mockReturnValue(of([null])); @@ -459,4 +467,118 @@ describe("AccountSecurityComponent", () => { }); }); }); + + describe("biometrics polling timer", () => { + let browserApiSpy: jest.SpyInstance; + + beforeEach(() => { + browserApiSpy = jest.spyOn(BrowserApi, "permissionsGranted"); + }); + + afterEach(() => { + component.ngOnDestroy(); + }); + + it("disables biometric control when canEnableBiometricUnlock is false", fakeAsync(async () => { + biometricsService.canEnableBiometricUnlock.mockResolvedValue(false); + + await component.ngOnInit(); + tick(); + + expect(component.form.controls.biometric.disabled).toBe(true); + })); + + it("enables biometric control when canEnableBiometricUnlock is true", fakeAsync(async () => { + biometricsService.canEnableBiometricUnlock.mockResolvedValue(true); + + await component.ngOnInit(); + tick(); + + expect(component.form.controls.biometric.disabled).toBe(false); + })); + + it("skips status check when nativeMessaging permission is not granted and not Safari", fakeAsync(async () => { + biometricsService.canEnableBiometricUnlock.mockResolvedValue(true); + browserApiSpy.mockResolvedValue(false); + platformUtilsService.isSafari.mockReturnValue(false); + + await component.ngOnInit(); + tick(); + + expect(biometricsService.getBiometricsStatusForUser).not.toHaveBeenCalled(); + expect(component.biometricUnavailabilityReason).toBeUndefined(); + })); + + it("checks biometrics status when nativeMessaging permission is granted", fakeAsync(async () => { + biometricsService.canEnableBiometricUnlock.mockResolvedValue(true); + browserApiSpy.mockResolvedValue(true); + platformUtilsService.isSafari.mockReturnValue(false); + biometricsService.getBiometricsStatusForUser.mockResolvedValue( + BiometricsStatus.DesktopDisconnected, + ); + + await component.ngOnInit(); + tick(); + + expect(biometricsService.getBiometricsStatusForUser).toHaveBeenCalledWith(mockUserId); + })); + + it("should check status on Safari", fakeAsync(async () => { + biometricsService.canEnableBiometricUnlock.mockResolvedValue(true); + browserApiSpy.mockResolvedValue(false); + platformUtilsService.isSafari.mockReturnValue(true); + biometricsService.getBiometricsStatusForUser.mockResolvedValue( + BiometricsStatus.DesktopDisconnected, + ); + + await component.ngOnInit(); + tick(); + + expect(biometricsService.getBiometricsStatusForUser).toHaveBeenCalledWith(mockUserId); + })); + + test.each([ + [ + BiometricsStatus.DesktopDisconnected, + "biometricsStatusHelptextDesktopDisconnected-used-i18n", + ], + [ + BiometricsStatus.NotEnabledInConnectedDesktopApp, + "biometricsStatusHelptextNotEnabledInDesktop-used-i18n", + ], + [ + BiometricsStatus.HardwareUnavailable, + "biometricsStatusHelptextHardwareUnavailable-used-i18n", + ], + ])( + "sets expected unavailability reason for %s status when biometric not available", + fakeAsync(async (biometricStatus: BiometricsStatus, expected: string) => { + biometricsService.canEnableBiometricUnlock.mockResolvedValue(false); + browserApiSpy.mockResolvedValue(true); + platformUtilsService.isSafari.mockReturnValue(false); + biometricsService.getBiometricsStatusForUser.mockResolvedValue(biometricStatus); + + await component.ngOnInit(); + tick(); + + expect(component.biometricUnavailabilityReason).toBe(expected); + }), + ); + + it("should not set unavailability reason for error statuses when biometric is available", fakeAsync(async () => { + biometricsService.canEnableBiometricUnlock.mockResolvedValue(true); + browserApiSpy.mockResolvedValue(true); + platformUtilsService.isSafari.mockReturnValue(false); + biometricsService.getBiometricsStatusForUser.mockResolvedValue( + BiometricsStatus.DesktopDisconnected, + ); + + await component.ngOnInit(); + tick(); + + // Status is DesktopDisconnected but biometric IS available, so don't show error + expect(component.biometricUnavailabilityReason).toBe(""); + component.ngOnDestroy(); + })); + }); }); diff --git a/apps/browser/src/auth/popup/settings/account-security.component.ts b/apps/browser/src/auth/popup/settings/account-security.component.ts index 7c36754c894..6a3378670bf 100644 --- a/apps/browser/src/auth/popup/settings/account-security.component.ts +++ b/apps/browser/src/auth/popup/settings/account-security.component.ts @@ -149,6 +149,7 @@ export class AccountSecurityComponent implements OnInit, OnDestroy { protected refreshTimeoutSettings$ = new BehaviorSubject<void>(undefined); private destroy$ = new Subject<void>(); + private readonly BIOMETRICS_POLLING_INTERVAL = 2000; constructor( private accountService: AccountService, @@ -264,10 +265,9 @@ export class AccountSecurityComponent implements OnInit, OnDestroy { }; this.form.patchValue(initialValues, { emitEvent: false }); - timer(0, 1000) + timer(0, this.BIOMETRICS_POLLING_INTERVAL) .pipe( switchMap(async () => { - const status = await this.biometricsService.getBiometricsStatusForUser(activeAccount.id); const biometricSettingAvailable = await this.biometricsService.canEnableBiometricUnlock(); if (!biometricSettingAvailable) { this.form.controls.biometric.disable({ emitEvent: false }); @@ -275,6 +275,15 @@ export class AccountSecurityComponent implements OnInit, OnDestroy { this.form.controls.biometric.enable({ emitEvent: false }); } + // Biometrics status shouldn't be checked if permissions are needed. + const needsPermissionPrompt = + !(await BrowserApi.permissionsGranted(["nativeMessaging"])) && + !this.platformUtilsService.isSafari(); + if (needsPermissionPrompt) { + return; + } + + const status = await this.biometricsService.getBiometricsStatusForUser(activeAccount.id); if (status === BiometricsStatus.DesktopDisconnected && !biometricSettingAvailable) { this.biometricUnavailabilityReason = this.i18nService.t( "biometricsStatusHelptextDesktopDisconnected", From 528f59875e338504340fb77de2c5deb16f990559 Mon Sep 17 00:00:00 2001 From: Todd Martin <106564991+trmartin4@users.noreply.github.com> Date: Wed, 31 Dec 2025 14:01:26 -0500 Subject: [PATCH 187/188] chore(deps): Include Cargo dependencies in dep-ownership lint check * Added Cardo dep ownership. * Fixed file paths. * Moved aes-gcm from KM to Tools. --- .github/renovate.json5 | 12 +++++++++ scripts/dep-ownership.ts | 57 +++++++++++++++++++++++++++++++++++----- 2 files changed, 63 insertions(+), 6 deletions(-) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index c4c24799da1..b402d01e209 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -132,6 +132,7 @@ "@yao-pkg/pkg", "anyhow", "arboard", + "ashpd", "babel-loader", "base64-loader", "base64", @@ -142,6 +143,7 @@ "core-foundation", "copy-webpack-plugin", "css-loader", + "ctor", "dirs", "electron", "electron-builder", @@ -179,6 +181,7 @@ "sass", "sass-loader", "scopeguard", + "secmem-proc", "security-framework", "security-framework-sys", "semver", @@ -187,6 +190,7 @@ "simplelog", "style-loader", "sysinfo", + "thiserror", "tokio", "tokio-util", "tracing", @@ -210,6 +214,7 @@ "windows-registry", "zbus", "zbus_polkit", + "zeroizing-alloc", ], description: "Platform owned dependencies", commitMessagePrefix: "[deps] Platform:", @@ -285,6 +290,7 @@ "@types/jsdom", "@types/papaparse", "@types/zxcvbn", + "aes-gcm", "async-trait", "clap", "jsdom", @@ -337,6 +343,7 @@ "aes", "big-integer", "cbc", + "chacha20poly1305", "linux-keyutils", "memsec", "node-forge", @@ -445,6 +452,7 @@ matchPackageNames: [ "anyhow", "arboard", + "ashpd", "babel-loader", "base64-loader", "base64", @@ -454,6 +462,7 @@ "core-foundation", "copy-webpack-plugin", "css-loader", + "ctor", "dirs", "electron-builder", "electron-log", @@ -488,6 +497,7 @@ "sass", "sass-loader", "scopeguard", + "secmem-proc", "security-framework", "security-framework-sys", "semver", @@ -496,6 +506,7 @@ "simplelog", "style-loader", "sysinfo", + "thiserror", "tokio", "tokio-util", "tracing", @@ -517,6 +528,7 @@ "windows-registry", "zbus", "zbus_polkit", + "zeroizing-alloc", ], matchUpdateTypes: ["minor", "patch"], dependencyDashboardApproval: true, diff --git a/scripts/dep-ownership.ts b/scripts/dep-ownership.ts index f0bcb1f7dd8..ae1a19bb170 100644 --- a/scripts/dep-ownership.ts +++ b/scripts/dep-ownership.ts @@ -1,6 +1,6 @@ /* eslint-disable no-console */ -/// Ensure that all dependencies in package.json have an owner in the renovate.json file. +/// Ensure that all dependencies in package.json and Cargo.toml have an owner in the renovate.json5 file. import fs from "fs"; import path from "path"; @@ -11,22 +11,67 @@ const renovateConfig = JSON5.parse( fs.readFileSync(path.join(__dirname, "..", "..", ".github", "renovate.json5"), "utf8"), ); +// Extract all packages with owners from renovate config const packagesWithOwners = renovateConfig.packageRules .flatMap((rule: any) => rule.matchPackageNames) .filter((packageName: string) => packageName != null); +function hasOwner(packageName: string): boolean { + return packagesWithOwners.includes(packageName); +} + +// Collect npm dependencies const packageJson = JSON.parse( fs.readFileSync(path.join(__dirname, "..", "..", "package.json"), "utf8"), ); -const dependencies = Object.keys(packageJson.dependencies).concat( - Object.keys(packageJson.devDependencies), +const npmDependencies = [ + ...Object.keys(packageJson.dependencies || {}), + ...Object.keys(packageJson.devDependencies || {}), +]; + +// Collect Cargo dependencies from workspace Cargo.toml +const cargoTomlPath = path.join( + __dirname, + "..", + "..", + "apps", + "desktop", + "desktop_native", + "Cargo.toml", ); +const cargoTomlContent = fs.existsSync(cargoTomlPath) ? fs.readFileSync(cargoTomlPath, "utf8") : ""; -const missingOwners = dependencies.filter((dep) => !packagesWithOwners.includes(dep)); +const cargoDependencies = new Set<string>(); -if (missingOwners.length > 0) { +// Extract dependency names from [workspace.dependencies] section by +// extracting everything between [workspace.dependencies] and the next section start +// (indicated by a "\n["). +const workspaceSection = + cargoTomlContent.split("[workspace.dependencies]")[1]?.split(/\n\[/)[0] ?? ""; + +// Process each line to extract dependency names +workspaceSection + .split("\n") // Process each line + .map((line) => line.match(/^([a-zA-Z0-9_-]+)\s*=/)?.[1]) // Find the dependency name + .filter((depName): depName is string => depName != null && !depName.startsWith("bitwarden")) // Make sure it's not an empty line or a Bitwarden dependency + .forEach((depName) => cargoDependencies.add(depName)); + +// Check for missing owners +const missingNpmOwners = npmDependencies.filter((dep) => !hasOwner(dep)); +const missingCargoOwners = Array.from(cargoDependencies).filter((dep) => !hasOwner(dep)); + +const allMissing = [...missingNpmOwners, ...missingCargoOwners]; + +if (allMissing.length > 0) { console.error("Missing owners for the following dependencies:"); - console.error(missingOwners.join("\n")); + if (missingNpmOwners.length > 0) { + console.error("\nNPM dependencies:"); + console.error(missingNpmOwners.join("\n")); + } + if (missingCargoOwners.length > 0) { + console.error("\nCargo dependencies:"); + console.error(missingCargoOwners.join("\n")); + } process.exit(1); } From 2665a29f2df824118614371ba9aa107fd0645818 Mon Sep 17 00:00:00 2001 From: Addison Beck <github@addisonbeck.com> Date: Wed, 31 Dec 2025 14:34:52 -0500 Subject: [PATCH 188/188] fix(desktop): restore explicit target to Linux builds (#18169) Commit a3e654d (https://github.com/bitwarden/clients/pull/16053) removed explicit --target flags from the Linux desktop build workflow when removing musl support. This change inadvertently broke the build.js binary distribution logic, which only copies the desktop_proxy binary to the dist directory when an explicit target is specified. Without this binary in the expected location (/opt/Bitwarden/desktop_proxy), browser integration fails on Linux. This fix restores explicit --target flags using gnu triplets instead of the previously removed musl triplets. The x64 build now uses --target=x86_64-unknown-linux-gnu and the arm64 build uses --target=aarch64-unknown-linux-gnu, ensuring build.js properly distributes the desktop_proxy binary and restoring browser integration functionality on Linux desktop. --- .github/workflows/build-desktop.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-desktop.yml b/.github/workflows/build-desktop.yml index 45297a110a0..8e43127770c 100644 --- a/.github/workflows/build-desktop.yml +++ b/.github/workflows/build-desktop.yml @@ -255,7 +255,7 @@ jobs: # Note: It is important that we use the release build because some compute heavy # operations such as key derivation for oo7 on linux are too slow in debug mode run: | - node build.js --release + node build.js --target=x86_64-unknown-linux-gnu --release - name: Build application run: npm run dist:lin @@ -418,7 +418,7 @@ jobs: # Note: It is important that we use the release build because some compute heavy # operations such as key derivation for oo7 on linux are too slow in debug mode run: | - node build.js --release + node build.js --target=aarch64-unknown-linux-gnu --release - name: Check index.d.ts generated if: github.event_name == 'pull_request' && steps.cache.outputs.cache-hit != 'true'