From 514e2ca6644a41236469c8c217fad0772d9d7203 Mon Sep 17 00:00:00 2001
From: Jordan Aasen <166539328+jaasen-livefront@users.noreply.github.com>
Date: Tue, 18 Mar 2025 08:51:11 -0700
Subject: [PATCH 001/113] [PM-17673] - fix loading state for vault popup items
service (#13711)
* fix loading state for vault popup items service
* fix tests
* add fallback in collectPageDetailsFromTab$
* add pageDetailsTimeout to autofill service
---
.../src/autofill/services/autofill.service.ts | 27 ++++++++++++++++---
1 file changed, 24 insertions(+), 3 deletions(-)
diff --git a/apps/browser/src/autofill/services/autofill.service.ts b/apps/browser/src/autofill/services/autofill.service.ts
index e833420b859..ed8b2033209 100644
--- a/apps/browser/src/autofill/services/autofill.service.ts
+++ b/apps/browser/src/autofill/services/autofill.service.ts
@@ -1,7 +1,16 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
-import { filter, firstValueFrom, merge, Observable, ReplaySubject, scan, startWith } from "rxjs";
-import { pairwise } from "rxjs/operators";
+import {
+ filter,
+ firstValueFrom,
+ merge,
+ Observable,
+ ReplaySubject,
+ scan,
+ startWith,
+ timer,
+} from "rxjs";
+import { map, pairwise, share, takeUntil } from "rxjs/operators";
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
import { AccountInfo, AccountService } from "@bitwarden/common/auth/abstractions/account.service";
@@ -146,7 +155,19 @@ export default class AutofillService implements AutofillServiceInterface {
pageDetailsFallback$.next([]);
}
- return merge(pageDetailsFromTab$, pageDetailsFallback$);
+ // Share the pageDetailsFromTab$ observable so that multiple subscribers don't trigger multiple executions.
+ const sharedPageDetailsFromTab$ = pageDetailsFromTab$.pipe(share());
+
+ // Create a timeout observable that emits an empty array if pageDetailsFromTab$ hasn't emitted within 1 second.
+ const pageDetailsTimeout$ = timer(1000).pipe(
+ map(() => []),
+ takeUntil(sharedPageDetailsFromTab$),
+ );
+
+ // Merge the responses so that if pageDetailsFromTab$ emits, that value is used.
+ // Otherwise, if it doesn't emit in time, the timeout observable emits an empty array.
+ // Also, pageDetailsFallback$ will emit in error cases.
+ return merge(sharedPageDetailsFromTab$, pageDetailsFallback$, pageDetailsTimeout$);
}
/**
From 30057fea33a6996c45ec19a89614435d9862a889 Mon Sep 17 00:00:00 2001
From: Jordan Aasen <166539328+jaasen-livefront@users.noreply.github.com>
Date: Tue, 18 Mar 2025 09:03:03 -0700
Subject: [PATCH 002/113] [PM-18669] - Moving item to org in desktop app does
not update until sync (#13754)
* refresh vault item list after sharing cipher
* refresh list after save
* fix vault sync issue
* update autofill a11y
* Revert "update autofill a11y"
This reverts commit 1a3aa0d0f0d29b3aed6df1c2c6f872f00345afde.
---
apps/desktop/src/vault/app/vault/vault.component.ts | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/apps/desktop/src/vault/app/vault/vault.component.ts b/apps/desktop/src/vault/app/vault/vault.component.ts
index 2c669e388f8..d8be360170a 100644
--- a/apps/desktop/src/vault/app/vault/vault.component.ts
+++ b/apps/desktop/src/vault/app/vault/vault.component.ts
@@ -493,12 +493,14 @@ export class VaultComponent implements OnInit, OnDestroy {
}
async savedCipher(cipher: CipherView) {
- this.cipherId = cipher.id;
+ this.cipherId = null;
this.action = "view";
await this.vaultItemsComponent.refresh();
+ this.cipherId = cipher.id;
await this.cipherService.clearCache(this.activeUserId);
- await this.viewComponent.load();
+ await this.vaultItemsComponent.load(this.activeFilter.buildFilter());
this.go();
+ await this.vaultItemsComponent.refresh();
}
async deletedCipher(cipher: CipherView) {
@@ -572,6 +574,8 @@ export class VaultComponent implements OnInit, OnDestroy {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.viewCipher(cipher);
await this.vaultItemsComponent.refresh();
+ await this.cipherService.clearCache(this.activeUserId);
+ await this.vaultItemsComponent.load(this.activeFilter.buildFilter());
});
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
this.modal.onClosed.subscribe(async () => {
From 70cfd33d9670a4e4f4d16cfd4e237595b9f3e90b Mon Sep 17 00:00:00 2001
From: Jordan Aasen <166539328+jaasen-livefront@users.noreply.github.com>
Date: Tue, 18 Mar 2025 09:18:20 -0700
Subject: [PATCH 003/113] Revert "collapse collections initially" (#13845)
This reverts commit 2e90a6af12f4e22c83606e8b5cc179d32d27a62c.
---
.../components/collection-filter.component.ts | 13 ++-----------
1 file changed, 2 insertions(+), 11 deletions(-)
diff --git a/libs/angular/src/vault/vault-filter/components/collection-filter.component.ts b/libs/angular/src/vault/vault-filter/components/collection-filter.component.ts
index 168afbdd72a..d104026f2f6 100644
--- a/libs/angular/src/vault/vault-filter/components/collection-filter.component.ts
+++ b/libs/angular/src/vault/vault-filter/components/collection-filter.component.ts
@@ -1,6 +1,6 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
-import { Directive, EventEmitter, Input, OnInit, Output } from "@angular/core";
+import { Directive, EventEmitter, Input, Output } from "@angular/core";
import { CollectionView } from "@bitwarden/admin-console/common";
import { ITreeNodeObject } from "@bitwarden/common/vault/models/domain/tree-node";
@@ -10,7 +10,7 @@ import { TopLevelTreeNode } from "../models/top-level-tree-node.model";
import { VaultFilter } from "../models/vault-filter.model";
@Directive()
-export class CollectionFilterComponent implements OnInit {
+export class CollectionFilterComponent {
@Input() hide = false;
@Input() collapsedFilterNodes: Set;
@Input() collectionNodes: DynamicTreeNode;
@@ -51,13 +51,4 @@ export class CollectionFilterComponent implements OnInit {
async toggleCollapse(node: ITreeNodeObject) {
this.onNodeCollapseStateChange.emit(node);
}
-
- ngOnInit() {
- // Populate the set with all node IDs so all nodes are collapsed initially.
- if (this.collectionNodes?.fullList) {
- this.collectionNodes.fullList.forEach((node) => {
- this.collapsedFilterNodes.add(node.id);
- });
- }
- }
}
From ac127c1512b53e076930f9edeec2eaa453dd771b Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Tue, 18 Mar 2025 14:23:48 -0400
Subject: [PATCH 004/113] [deps] Vault: Update https-proxy-agent to v7.0.6
(#12301)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
apps/cli/package.json | 2 +-
package-lock.json | 73 ++++++++++++++++++++++++++++++++-----------
package.json | 2 +-
3 files changed, 56 insertions(+), 21 deletions(-)
diff --git a/apps/cli/package.json b/apps/cli/package.json
index f8f1c8a02d9..7d9f4af0ffe 100644
--- a/apps/cli/package.json
+++ b/apps/cli/package.json
@@ -68,7 +68,7 @@
"chalk": "4.1.2",
"commander": "11.1.0",
"form-data": "4.0.1",
- "https-proxy-agent": "7.0.5",
+ "https-proxy-agent": "7.0.6",
"inquirer": "8.2.6",
"jsdom": "26.0.0",
"jszip": "3.10.1",
diff --git a/package-lock.json b/package-lock.json
index 023b36afadc..52d525f08a5 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -43,7 +43,7 @@
"commander": "11.1.0",
"core-js": "3.40.0",
"form-data": "4.0.1",
- "https-proxy-agent": "7.0.5",
+ "https-proxy-agent": "7.0.6",
"inquirer": "8.2.6",
"jquery": "3.7.1",
"jsdom": "26.0.0",
@@ -204,7 +204,7 @@
"chalk": "4.1.2",
"commander": "11.1.0",
"form-data": "4.0.1",
- "https-proxy-agent": "7.0.5",
+ "https-proxy-agent": "7.0.6",
"inquirer": "8.2.6",
"jsdom": "26.0.0",
"jszip": "3.10.1",
@@ -759,6 +759,16 @@
"@types/send": "*"
}
},
+ "node_modules/@angular-devkit/build-angular/node_modules/agent-base": {
+ "version": "7.1.3",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz",
+ "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 14"
+ }
+ },
"node_modules/@angular-devkit/build-angular/node_modules/babel-loader": {
"version": "9.1.3",
"resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.1.3.tgz",
@@ -880,6 +890,20 @@
"node": ">= 6"
}
},
+ "node_modules/@angular-devkit/build-angular/node_modules/https-proxy-agent": {
+ "version": "7.0.5",
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz",
+ "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "^7.0.2",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
"node_modules/@angular-devkit/build-angular/node_modules/immutable": {
"version": "4.3.7",
"resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.7.tgz",
@@ -2090,6 +2114,16 @@
"semver": "bin/semver.js"
}
},
+ "node_modules/@angular/build/node_modules/agent-base": {
+ "version": "7.1.3",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz",
+ "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 14"
+ }
+ },
"node_modules/@angular/build/node_modules/chokidar": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
@@ -2135,6 +2169,20 @@
"node": ">= 6"
}
},
+ "node_modules/@angular/build/node_modules/https-proxy-agent": {
+ "version": "7.0.5",
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz",
+ "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "^7.0.2",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
"node_modules/@angular/build/node_modules/immutable": {
"version": "4.3.7",
"resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.7.tgz",
@@ -21272,12 +21320,12 @@
}
},
"node_modules/https-proxy-agent": {
- "version": "7.0.5",
- "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz",
- "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==",
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
+ "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
"license": "MIT",
"dependencies": {
- "agent-base": "^7.0.2",
+ "agent-base": "^7.1.2",
"debug": "4"
},
"engines": {
@@ -24154,19 +24202,6 @@
"node": ">= 14"
}
},
- "node_modules/jsdom/node_modules/https-proxy-agent": {
- "version": "7.0.6",
- "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
- "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
- "license": "MIT",
- "dependencies": {
- "agent-base": "^7.1.2",
- "debug": "4"
- },
- "engines": {
- "node": ">= 14"
- }
- },
"node_modules/jsdom/node_modules/tough-cookie": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.1.tgz",
diff --git a/package.json b/package.json
index 571ec82fcda..4886fe9a90d 100644
--- a/package.json
+++ b/package.json
@@ -173,7 +173,7 @@
"commander": "11.1.0",
"core-js": "3.40.0",
"form-data": "4.0.1",
- "https-proxy-agent": "7.0.5",
+ "https-proxy-agent": "7.0.6",
"inquirer": "8.2.6",
"jquery": "3.7.1",
"jsdom": "26.0.0",
From 0cb77b24f6a57ae14aabd1171a48958034cd8a49 Mon Sep 17 00:00:00 2001
From: Justin Baur <19896123+justindbaur@users.noreply.github.com>
Date: Tue, 18 Mar 2025 14:51:32 -0400
Subject: [PATCH 005/113] Update `@bitwarden/sdk-internal` version (#13883)
* Update `@bitwarden/sdk-internal` version
* Update package-lock file
---
package-lock.json | 9 ++++-----
package.json | 2 +-
2 files changed, 5 insertions(+), 6 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index 52d525f08a5..eaf5c0f24ed 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -24,7 +24,7 @@
"@angular/platform-browser": "18.2.13",
"@angular/platform-browser-dynamic": "18.2.13",
"@angular/router": "18.2.13",
- "@bitwarden/sdk-internal": "0.2.0-main.107",
+ "@bitwarden/sdk-internal": "0.2.0-main.124",
"@electron/fuses": "1.8.0",
"@emotion/css": "11.13.5",
"@koa/multer": "3.0.2",
@@ -4698,10 +4698,9 @@
"link": true
},
"node_modules/@bitwarden/sdk-internal": {
- "version": "0.2.0-main.107",
- "resolved": "https://registry.npmjs.org/@bitwarden/sdk-internal/-/sdk-internal-0.2.0-main.107.tgz",
- "integrity": "sha512-xpOF6NAS0/em3jFBv4FI1ASy1Nuc7I1v41TVmG56wS+80y+NH1RnfGjp+a+XiO7Xxh3jssrxmjzihJjWQQA0rg==",
- "license": "GPL-3.0"
+ "version": "0.2.0-main.124",
+ "resolved": "https://registry.npmjs.org/@bitwarden/sdk-internal/-/sdk-internal-0.2.0-main.124.tgz",
+ "integrity": "sha512-7F+DlPFng/thT4EVIQk2tRC7kff6G2B7alHAIxBdioJc9vE64Z5R5pviUyMZzqLnA5e9y8EnQdtWsQzUkHxisQ=="
},
"node_modules/@bitwarden/send-ui": {
"resolved": "libs/tools/send/send-ui",
diff --git a/package.json b/package.json
index 4886fe9a90d..943fde5fcf3 100644
--- a/package.json
+++ b/package.json
@@ -154,7 +154,7 @@
"@angular/platform-browser": "18.2.13",
"@angular/platform-browser-dynamic": "18.2.13",
"@angular/router": "18.2.13",
- "@bitwarden/sdk-internal": "0.2.0-main.107",
+ "@bitwarden/sdk-internal": "0.2.0-main.124",
"@electron/fuses": "1.8.0",
"@emotion/css": "11.13.5",
"@koa/multer": "3.0.2",
From 17a60d0226fd0067507867806d6bb5193ade229d Mon Sep 17 00:00:00 2001
From: Vince Grassia <593223+vgrassia@users.noreply.github.com>
Date: Tue, 18 Mar 2025 13:08:41 -0700
Subject: [PATCH 006/113] Update Dockerfile (#13888)
---
apps/web/Dockerfile | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/apps/web/Dockerfile b/apps/web/Dockerfile
index 41be86e584e..09dac7f4c48 100644
--- a/apps/web/Dockerfile
+++ b/apps/web/Dockerfile
@@ -1,4 +1,4 @@
-FROM bitwarden/server
+FROM ghcr.io/bitwarden/server
LABEL com.bitwarden.product="bitwarden"
From 69b2972e56f4865788d951229d79f038cfceb768 Mon Sep 17 00:00:00 2001
From: Brandon Treston
Date: Tue, 18 Mar 2025 16:42:02 -0400
Subject: [PATCH 007/113] [PM-19244] add missing null check, fix feature flag
logic, cleanup (#13850)
* add missing null check, fix feature flag logic, cleanup
* add null check
---
.../vault-header/vault-header.component.ts | 25 ++++++++++---------
1 file changed, 13 insertions(+), 12 deletions(-)
diff --git a/apps/web/src/app/vault/individual-vault/vault-header/vault-header.component.ts b/apps/web/src/app/vault/individual-vault/vault-header/vault-header.component.ts
index 489f42649f9..e9122864447 100644
--- a/apps/web/src/app/vault/individual-vault/vault-header/vault-header.component.ts
+++ b/apps/web/src/app/vault/individual-vault/vault-header/vault-header.component.ts
@@ -219,23 +219,24 @@ export class VaultHeaderComponent implements OnInit {
}
async addCollection(): Promise {
- const organization = this.organizations?.find(
- (org) => org.productTierType === ProductTierType.Free,
- );
const isBreadcrumbEventLogsEnabled = await firstValueFrom(
this.configService.getFeatureFlag$(FeatureFlag.PM12276_BreadcrumbEventLogs),
);
- if (
- this.organizations.length == 1 &&
- organization.productTierType === ProductTierType.Free &&
- isBreadcrumbEventLogsEnabled
- ) {
- const collections = await this.collectionAdminService.getAll(organization.id);
- if (collections.length === organization.maxCollections) {
- await this.showFreeOrgUpgradeDialog(organization);
- return;
+
+ if (isBreadcrumbEventLogsEnabled) {
+ const organization = this.organizations?.find(
+ (org) => org.productTierType === ProductTierType.Free,
+ );
+
+ if (this.organizations?.length == 1 && !!organization) {
+ const collections = await this.collectionAdminService.getAll(organization.id);
+ if (collections.length === organization.maxCollections) {
+ await this.showFreeOrgUpgradeDialog(organization);
+ return;
+ }
}
}
+
this.onAddCollection.emit();
}
From 7c0af6c8fbb7f8c7010548163993f422572f5879 Mon Sep 17 00:00:00 2001
From: rr-bw <102181210+rr-bw@users.noreply.github.com>
Date: Tue, 18 Mar 2025 14:41:43 -0700
Subject: [PATCH 008/113] fix(service): [Auth/PM-17648] Fix
MasterPasswordApiService injection (#13886)
---
libs/angular/src/services/jslib-services.module.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts
index 04bcb42aa53..9cb39a35856 100644
--- a/libs/angular/src/services/jslib-services.module.ts
+++ b/libs/angular/src/services/jslib-services.module.ts
@@ -1352,7 +1352,7 @@ const safeProviders: SafeProvider[] = [
useClass: DefaultSetPasswordJitService,
deps: [
ApiServiceAbstraction,
- MasterPasswordApiService,
+ MasterPasswordApiServiceAbstraction,
KeyService,
EncryptService,
I18nServiceAbstraction,
From 5c279186096495f350af4a118c3863f6707653b0 Mon Sep 17 00:00:00 2001
From: Jonathan Prusik
Date: Wed, 19 Mar 2025 10:49:15 -0400
Subject: [PATCH 009/113] [PM-18785] Build dropdown button selection components
(#13692)
* create standalone option selection button component
* create option selection components
* replace dropdown-menu component with option selection in button row
* create folder and vault selection components
* add story for option-selection and update button row component
* update options selection component behaviour and styling
* add shared icon typing
* move Options to common types
* refactor option selection component to handle options better
* rework notification footer and button row components to handle expected data props
* add optional selection option menu label
* set max-height with scroll handling on option selection menu
* fix menu item spacing
* avoid displaying the dropdown menu horizontally outside of the viewport
* update dropdown menu style
* update button content spacing
* allow overriding default scrollbar colors
* update options items menu typography
* fix eslint exception
* refine some prop names
---
.../components/buttons/action-button.ts | 10 +-
.../content/components/buttons/edit-button.ts | 6 +-
.../buttons/option-selection-button.ts | 104 +++++++++++++
.../cipher/cipher-indicator-icons.ts | 12 +-
.../content/components/common-types.ts | 28 ++++
.../content/components/constants/styles.ts | 15 +-
.../content/components/dropdown-menu.ts | 121 ---------------
.../content/components/icons/angle-down.ts | 13 +-
.../content/components/icons/angle-up.ts | 23 +++
.../content/components/icons/business.ts | 13 +-
.../content/components/icons/close.ts | 13 +-
.../components/icons/exclamation-triangle.ts | 13 +-
.../content/components/icons/family.ts | 13 +-
.../content/components/icons/folder.ts | 17 +--
.../content/components/icons/globe.ts | 13 +-
.../content/components/icons/index.ts | 1 +
.../content/components/icons/party-horn.ts | 6 +-
.../content/components/icons/pencil-square.ts | 13 +-
.../content/components/icons/shield.ts | 5 +-
.../autofill/content/components/icons/user.ts | 17 +--
.../.lit-docs/cipher-indicator-icon.mdx | 20 +--
.../buttons/action-button.lit-stories.ts | 6 +-
.../cipher-indicator-icon.lit-stories.ts | 12 +-
.../components/lit-stories/mock-data.ts | 68 +++++++++
.../notification/footer.lit-stories.ts | 31 ++--
.../option-selection.lit-stories.ts | 81 ++++++++++
.../rows/button-row.lit-stories.ts | 58 +++++---
.../components/notification/button-row.ts | 109 ++++++++++++++
.../content/components/notification/footer.ts | 38 +++--
.../option-selection/option-item.ts | 81 ++++++++++
.../option-selection/option-items.ts | 90 ++++++++++++
.../option-selection/option-selection.ts | 138 ++++++++++++++++++
.../content/components/rows/button-row.ts | 85 +++++------
33 files changed, 918 insertions(+), 355 deletions(-)
create mode 100644 apps/browser/src/autofill/content/components/buttons/option-selection-button.ts
create mode 100644 apps/browser/src/autofill/content/components/common-types.ts
delete mode 100644 apps/browser/src/autofill/content/components/dropdown-menu.ts
create mode 100644 apps/browser/src/autofill/content/components/icons/angle-up.ts
create mode 100644 apps/browser/src/autofill/content/components/lit-stories/mock-data.ts
create mode 100644 apps/browser/src/autofill/content/components/lit-stories/options-selection/option-selection.lit-stories.ts
create mode 100644 apps/browser/src/autofill/content/components/notification/button-row.ts
create mode 100644 apps/browser/src/autofill/content/components/option-selection/option-item.ts
create mode 100644 apps/browser/src/autofill/content/components/option-selection/option-items.ts
create mode 100644 apps/browser/src/autofill/content/components/option-selection/option-selection.ts
diff --git a/apps/browser/src/autofill/content/components/buttons/action-button.ts b/apps/browser/src/autofill/content/components/buttons/action-button.ts
index a9b4742b448..f0642d4233a 100644
--- a/apps/browser/src/autofill/content/components/buttons/action-button.ts
+++ b/apps/browser/src/autofill/content/components/buttons/action-button.ts
@@ -6,27 +6,27 @@ import { Theme } from "@bitwarden/common/platform/enums";
import { border, themes, typography, spacing } from "../constants/styles";
export function ActionButton({
- buttonAction,
buttonText,
disabled = false,
theme,
+ handleClick,
}: {
- buttonAction: (e: Event) => void;
buttonText: string;
disabled?: boolean;
theme: Theme;
+ handleClick: (e: Event) => void;
}) {
const handleButtonClick = (event: Event) => {
if (!disabled) {
- buttonAction(event);
+ handleClick(event);
}
};
return html`
${buttonText}
diff --git a/apps/browser/src/autofill/content/components/buttons/edit-button.ts b/apps/browser/src/autofill/content/components/buttons/edit-button.ts
index cacd2b59f0e..67221f5be18 100644
--- a/apps/browser/src/autofill/content/components/buttons/edit-button.ts
+++ b/apps/browser/src/autofill/content/components/buttons/edit-button.ts
@@ -23,9 +23,9 @@ export function EditButton({
title=${buttonText}
class=${editButtonStyles({ disabled, theme })}
@click=${(event: Event) => {
- // FIXME: Remove when updating file. Eslint update
- // eslint-disable-next-line @typescript-eslint/no-unused-expressions
- !disabled && buttonAction(event);
+ if (!disabled) {
+ buttonAction(event);
+ }
}}
>
${PencilSquare({ disabled, theme })}
diff --git a/apps/browser/src/autofill/content/components/buttons/option-selection-button.ts b/apps/browser/src/autofill/content/components/buttons/option-selection-button.ts
new file mode 100644
index 00000000000..cf9a561ee39
--- /dev/null
+++ b/apps/browser/src/autofill/content/components/buttons/option-selection-button.ts
@@ -0,0 +1,104 @@
+import { css } from "@emotion/css";
+import { html, nothing } from "lit";
+
+import { Theme } from "@bitwarden/common/platform/enums";
+
+import { IconProps, Option } from "../common-types";
+import { border, spacing, themes, typography } from "../constants/styles";
+import { AngleUp, AngleDown } from "../icons";
+
+export type OptionSelectionButtonProps = {
+ disabled: boolean;
+ icon?: Option["icon"];
+ text?: string;
+ theme: Theme;
+ toggledOn: boolean;
+ handleButtonClick: (e: Event) => void;
+};
+
+export function OptionSelectionButton({
+ disabled,
+ icon,
+ text,
+ theme,
+ toggledOn,
+ handleButtonClick,
+}: OptionSelectionButtonProps) {
+ const selectedOptionIconProps: IconProps = { color: themes[theme].text.muted, theme };
+
+ const buttonIcon = icon?.(selectedOptionIconProps);
+
+ return html`
+
+ ${buttonIcon ?? nothing}
+ ${text ? html`${text} ` : nothing}
+ ${toggledOn
+ ? AngleUp({ color: themes[theme].text.muted, theme })
+ : AngleDown({ color: themes[theme].text.muted, theme })}
+
+ `;
+}
+
+const iconSize = "15px";
+
+const selectionButtonStyles = ({
+ disabled,
+ toggledOn,
+ theme,
+}: {
+ disabled: boolean;
+ toggledOn: boolean;
+ theme: Theme;
+}) => css`
+ ${typography.body2}
+
+ gap: ${spacing["1.5"]};
+ user-select: none;
+ display: flex;
+ flex-direction: row;
+ flex-wrap: nowrap;
+ align-items: center;
+ justify-content: space-between;
+ columns: ${iconSize} max-content ${iconSize};
+ border-radius: ${border.radius.full};
+ padding: ${spacing["1"]} ${spacing["2"]};
+ max-height: fit-content;
+ overflow: hidden;
+ text-align: center;
+ text-overflow: ellipsis;
+ font-weight: 400;
+
+ ${disabled
+ ? `
+ border: 1px solid ${themes[theme].secondary["300"]};
+ background-color: ${themes[theme].secondary["300"]};
+ cursor: not-allowed;
+ color: ${themes[theme].text.muted};
+ `
+ : `
+ border: 1px solid ${themes[theme].text.muted};
+ background-color: ${toggledOn ? themes[theme].secondary["100"] : "transparent"};
+ cursor: pointer;
+ color: ${themes[theme].text.muted};
+
+ :hover {
+ border-color: ${themes[theme].secondary["700"]};
+ background-color: ${themes[theme].secondary["100"]};
+ }
+ `}
+
+ > svg {
+ max-width: ${iconSize};
+ height: fit-content;
+ }
+`;
+
+const dropdownButtonTextStyles = css`
+ overflow-x: hidden;
+ text-overflow: ellipsis;
+`;
diff --git a/apps/browser/src/autofill/content/components/cipher/cipher-indicator-icons.ts b/apps/browser/src/autofill/content/components/cipher/cipher-indicator-icons.ts
index 38b4292f8e5..39d4dd28f24 100644
--- a/apps/browser/src/autofill/content/components/cipher/cipher-indicator-icons.ts
+++ b/apps/browser/src/autofill/content/components/cipher/cipher-indicator-icons.ts
@@ -9,17 +9,17 @@ import { Business, Family } from "../../../content/components/icons";
// @TODO connect data source to icon checks
// @TODO support other indicator types (attachments, etc)
export function CipherInfoIndicatorIcons({
- isBusinessOrg,
- isFamilyOrg,
+ showBusinessIcon,
+ showFamilyIcon,
theme,
}: {
- isBusinessOrg?: boolean;
- isFamilyOrg?: boolean;
+ showBusinessIcon?: boolean;
+ showFamilyIcon?: boolean;
theme: Theme;
}) {
const indicatorIcons = [
- ...(isBusinessOrg ? [Business({ color: themes[theme].text.muted, theme })] : []),
- ...(isFamilyOrg ? [Family({ color: themes[theme].text.muted, theme })] : []),
+ ...(showBusinessIcon ? [Business({ color: themes[theme].text.muted, theme })] : []),
+ ...(showFamilyIcon ? [Family({ color: themes[theme].text.muted, theme })] : []),
];
return indicatorIcons.length
diff --git a/apps/browser/src/autofill/content/components/common-types.ts b/apps/browser/src/autofill/content/components/common-types.ts
new file mode 100644
index 00000000000..df11e140d70
--- /dev/null
+++ b/apps/browser/src/autofill/content/components/common-types.ts
@@ -0,0 +1,28 @@
+import { TemplateResult } from "lit";
+
+import { ProductTierType } from "@bitwarden/common/billing/enums";
+import { Theme } from "@bitwarden/common/platform/enums";
+
+export type IconProps = {
+ color?: string;
+ disabled?: boolean;
+ theme: Theme;
+};
+
+export type Option = {
+ default?: boolean;
+ icon?: (props: IconProps) => TemplateResult;
+ text?: string;
+ value: any;
+};
+
+export type FolderView = {
+ id: string;
+ name: string;
+};
+
+export type OrgView = {
+ id: string;
+ name: string;
+ productTierType?: ProductTierType;
+};
diff --git a/apps/browser/src/autofill/content/components/constants/styles.ts b/apps/browser/src/autofill/content/components/constants/styles.ts
index cdf8f1ead53..f7c9ffd4d92 100644
--- a/apps/browser/src/autofill/content/components/constants/styles.ts
+++ b/apps/browser/src/autofill/content/components/constants/styles.ts
@@ -174,14 +174,17 @@ export const buildIconColorRule = (color: string, rule: RuleName = ruleNames.fil
${rule}: ${color};
`;
-export function scrollbarStyles(theme: Theme) {
+export function scrollbarStyles(theme: Theme, color?: { thumb?: string; track?: string }) {
+ const thumbColor = color?.thumb || themes[theme].secondary["500"];
+ const trackColor = color?.track || themes[theme].background.alt;
+
return {
+ /* FireFox & Chrome support */
default: `
- /* FireFox & Chrome support */
- scrollbar-color: ${themes[theme].secondary["500"]} ${themes[theme].background.alt};
+ scrollbar-color: ${thumbColor} ${trackColor};
`,
+ /* Safari Support */
safari: `
- /* Safari Support */
::-webkit-scrollbar {
overflow: auto;
}
@@ -191,10 +194,10 @@ export function scrollbarStyles(theme: Theme) {
border-radius: 0.5rem;
border-color: transparent;
background-clip: content-box;
- background-color: ${themes[theme].secondary["500"]};
+ background-color: ${thumbColor};
}
::-webkit-scrollbar-track {
- ${themes[theme].background.alt};
+ ${trackColor};
}
::-webkit-scrollbar-thumb:hover {
${themes[theme].secondary["600"]};
diff --git a/apps/browser/src/autofill/content/components/dropdown-menu.ts b/apps/browser/src/autofill/content/components/dropdown-menu.ts
deleted file mode 100644
index 3e3874b37d7..00000000000
--- a/apps/browser/src/autofill/content/components/dropdown-menu.ts
+++ /dev/null
@@ -1,121 +0,0 @@
-import { css } from "@emotion/css";
-import { html, TemplateResult } from "lit";
-
-import { Theme } from "@bitwarden/common/platform/enums";
-
-import { border, themes, typography, spacing } from "./constants/styles";
-import { AngleDown } from "./icons";
-
-export function DropdownMenu({
- buttonText,
- icon,
- disabled = false,
- selectAction,
- theme,
-}: {
- selectAction?: (e: Event) => void;
- buttonText: string;
- icon?: TemplateResult;
- disabled?: boolean;
- theme: Theme;
-}) {
- // @TODO placeholder/will not work; make stateful
- const showDropdown = false;
- const handleButtonClick = (event: Event) => {
- // if (!disabled) {
- // // show dropdown
- // showDropdown = !showDropdown;
- // this.requestUpdate();
- // }
- };
-
- const dropdownMenuItems: TemplateResult[] = [];
-
- return html`
-
-
- ${icon ?? null}
- ${buttonText}
- ${AngleDown({ color: themes[theme].text.muted, theme })}
-
- ${showDropdown
- ? html` `
- : null}
-
- `;
-}
-
-const iconSize = "15px";
-
-const dropdownContainerStyles = css`
- display: flex;
-
- > div,
- > button {
- width: 100%;
- }
-`;
-
-const dropdownButtonStyles = ({ disabled, theme }: { disabled: boolean; theme: Theme }) => css`
- ${typography.body2}
-
- font-weight: 400;
- gap: ${spacing["1.5"]};
- user-select: none;
- display: flex;
- flex-direction: row;
- flex-wrap: nowrap;
- align-items: center;
- justify-content: space-between;
- border-radius: ${border.radius.full};
- padding: ${spacing["1"]} ${spacing["2"]};
- max-height: fit-content;
- overflow: hidden;
- text-align: center;
- text-overflow: ellipsis;
-
- > svg {
- max-width: ${iconSize};
- height: fit-content;
- }
-
- ${disabled
- ? `
- border: 1px solid ${themes[theme].secondary["300"]};
- background-color: ${themes[theme].secondary["300"]};
- color: ${themes[theme].text.muted};
- `
- : `
- border: 1px solid ${themes[theme].text.muted};
- background-color: transparent;
- cursor: pointer;
- color: ${themes[theme].text.muted};
-
- :hover {
- border-color: ${themes[theme].secondary["700"]};
- background-color: ${themes[theme].secondary["100"]};
- }
- `}
-`;
-
-const dropdownButtonTextStyles = css`
- max-width: calc(100% - ${iconSize} - ${iconSize});
- overflow-x: hidden;
- text-overflow: ellipsis;
-`;
-
-const dropdownMenuStyles = ({ theme }: { theme: Theme }) => css`
- color: ${themes[theme].text.main};
- border: 1px solid ${themes[theme].secondary["500"]};
- border-radius: 0.5rem;
- background-clip: padding-box;
- background-color: ${themes[theme].background.DEFAULT};
- padding: 0.25rem 0.75rem;
- position: absolute;
- overflow-y: auto;
-`;
diff --git a/apps/browser/src/autofill/content/components/icons/angle-down.ts b/apps/browser/src/autofill/content/components/icons/angle-down.ts
index 4b85319c18a..db5275aafa9 100644
--- a/apps/browser/src/autofill/content/components/icons/angle-down.ts
+++ b/apps/browser/src/autofill/content/components/icons/angle-down.ts
@@ -1,19 +1,10 @@
import { css } from "@emotion/css";
import { html } from "lit";
-import { Theme } from "@bitwarden/common/platform/enums";
-
+import { IconProps } from "../common-types";
import { buildIconColorRule, ruleNames, themes } from "../constants/styles";
-export function AngleDown({
- color,
- disabled,
- theme,
-}: {
- color?: string;
- disabled?: boolean;
- theme: Theme;
-}) {
+export function AngleDown({ color, disabled, theme }: IconProps) {
const shapeColor = disabled ? themes[theme].secondary["300"] : color || themes[theme].text.main;
return html`
diff --git a/apps/browser/src/autofill/content/components/icons/angle-up.ts b/apps/browser/src/autofill/content/components/icons/angle-up.ts
new file mode 100644
index 00000000000..7344123d5ad
--- /dev/null
+++ b/apps/browser/src/autofill/content/components/icons/angle-up.ts
@@ -0,0 +1,23 @@
+import { css } from "@emotion/css";
+import { html } from "lit";
+
+import { IconProps } from "../common-types";
+import { buildIconColorRule, ruleNames, themes } from "../constants/styles";
+
+export function AngleUp({ color, disabled, theme }: IconProps) {
+ const shapeColor = disabled ? themes[theme].secondary["300"] : color || themes[theme].text.main;
+
+ return html`
+
+
+
+ `;
+}
diff --git a/apps/browser/src/autofill/content/components/icons/business.ts b/apps/browser/src/autofill/content/components/icons/business.ts
index 547ee82b547..ef8e082c21f 100644
--- a/apps/browser/src/autofill/content/components/icons/business.ts
+++ b/apps/browser/src/autofill/content/components/icons/business.ts
@@ -1,19 +1,10 @@
import { css } from "@emotion/css";
import { html } from "lit";
-import { Theme } from "@bitwarden/common/platform/enums";
-
+import { IconProps } from "../common-types";
import { buildIconColorRule, ruleNames, themes } from "../constants/styles";
-export function Business({
- color,
- disabled,
- theme,
-}: {
- color?: string;
- disabled?: boolean;
- theme: Theme;
-}) {
+export function Business({ color, disabled, theme }: IconProps) {
const shapeColor = disabled ? themes[theme].secondary["300"] : color || themes[theme].text.main;
return html`
diff --git a/apps/browser/src/autofill/content/components/icons/close.ts b/apps/browser/src/autofill/content/components/icons/close.ts
index c94a4b20a6a..c9d9286ca3f 100644
--- a/apps/browser/src/autofill/content/components/icons/close.ts
+++ b/apps/browser/src/autofill/content/components/icons/close.ts
@@ -1,19 +1,10 @@
import { css } from "@emotion/css";
import { html } from "lit";
-import { Theme } from "@bitwarden/common/platform/enums";
-
+import { IconProps } from "../common-types";
import { buildIconColorRule, ruleNames, themes } from "../constants/styles";
-export function Close({
- color,
- disabled,
- theme,
-}: {
- color?: string;
- disabled?: boolean;
- theme: Theme;
-}) {
+export function Close({ color, disabled, theme }: IconProps) {
const shapeColor = disabled ? themes[theme].secondary["300"] : color || themes[theme].text.main;
return html`
diff --git a/apps/browser/src/autofill/content/components/icons/exclamation-triangle.ts b/apps/browser/src/autofill/content/components/icons/exclamation-triangle.ts
index bcc7b3d5432..d87d5621e30 100644
--- a/apps/browser/src/autofill/content/components/icons/exclamation-triangle.ts
+++ b/apps/browser/src/autofill/content/components/icons/exclamation-triangle.ts
@@ -1,19 +1,10 @@
import { css } from "@emotion/css";
import { html } from "lit";
-import { Theme } from "@bitwarden/common/platform/enums";
-
+import { IconProps } from "../common-types";
import { buildIconColorRule, ruleNames, themes } from "../constants/styles";
-export function ExclamationTriangle({
- color,
- disabled,
- theme,
-}: {
- color?: string;
- disabled?: boolean;
- theme: Theme;
-}) {
+export function ExclamationTriangle({ color, disabled, theme }: IconProps) {
const shapeColor = disabled ? themes[theme].secondary["300"] : color || themes[theme].text.main;
return html`
diff --git a/apps/browser/src/autofill/content/components/icons/family.ts b/apps/browser/src/autofill/content/components/icons/family.ts
index 33e2e422ced..9870c5d37c0 100644
--- a/apps/browser/src/autofill/content/components/icons/family.ts
+++ b/apps/browser/src/autofill/content/components/icons/family.ts
@@ -1,19 +1,10 @@
import { css } from "@emotion/css";
import { html } from "lit";
-import { Theme } from "@bitwarden/common/platform/enums";
-
+import { IconProps } from "../common-types";
import { buildIconColorRule, ruleNames, themes } from "../constants/styles";
-export function Family({
- color,
- disabled,
- theme,
-}: {
- color?: string;
- disabled?: boolean;
- theme: Theme;
-}) {
+export function Family({ color, disabled, theme }: IconProps) {
const shapeColor = disabled ? themes[theme].secondary["300"] : color || themes[theme].text.main;
return html`
diff --git a/apps/browser/src/autofill/content/components/icons/folder.ts b/apps/browser/src/autofill/content/components/icons/folder.ts
index 7e1f8f197f6..84577aef820 100644
--- a/apps/browser/src/autofill/content/components/icons/folder.ts
+++ b/apps/browser/src/autofill/content/components/icons/folder.ts
@@ -1,26 +1,17 @@
import { css } from "@emotion/css";
import { html } from "lit";
-import { Theme } from "@bitwarden/common/platform/enums";
-
+import { IconProps } from "../common-types";
import { buildIconColorRule, ruleNames, themes } from "../constants/styles";
-export function Folder({
- color,
- disabled,
- theme,
-}: {
- color?: string;
- disabled?: boolean;
- theme: Theme;
-}) {
+export function Folder({ color, disabled, theme }: IconProps) {
const shapeColor = disabled ? themes[theme].secondary["300"] : color || themes[theme].text.main;
return html`
-
+
`;
diff --git a/apps/browser/src/autofill/content/components/icons/globe.ts b/apps/browser/src/autofill/content/components/icons/globe.ts
index 6697fa93b70..fc0a975284d 100644
--- a/apps/browser/src/autofill/content/components/icons/globe.ts
+++ b/apps/browser/src/autofill/content/components/icons/globe.ts
@@ -1,19 +1,10 @@
import { css } from "@emotion/css";
import { html } from "lit";
-import { Theme } from "@bitwarden/common/platform/enums";
-
+import { IconProps } from "../common-types";
import { buildIconColorRule, ruleNames, themes } from "../constants/styles";
-export function Globe({
- color,
- disabled,
- theme,
-}: {
- color?: string;
- disabled?: boolean;
- theme: Theme;
-}) {
+export function Globe({ color, disabled, theme }: IconProps) {
const shapeColor = disabled ? themes[theme].secondary["300"] : color || themes[theme].text.main;
return html`
diff --git a/apps/browser/src/autofill/content/components/icons/index.ts b/apps/browser/src/autofill/content/components/icons/index.ts
index 6cc56e079d4..c4769a0e69d 100644
--- a/apps/browser/src/autofill/content/components/icons/index.ts
+++ b/apps/browser/src/autofill/content/components/icons/index.ts
@@ -1,4 +1,5 @@
export { AngleDown } from "./angle-down";
+export { AngleUp } from "./angle-up";
export { BrandIconContainer } from "./brand-icon-container";
export { Business } from "./business";
export { Close } from "./close";
diff --git a/apps/browser/src/autofill/content/components/icons/party-horn.ts b/apps/browser/src/autofill/content/components/icons/party-horn.ts
index e807df1d86e..439d60a79de 100644
--- a/apps/browser/src/autofill/content/components/icons/party-horn.ts
+++ b/apps/browser/src/autofill/content/components/icons/party-horn.ts
@@ -1,9 +1,11 @@
import { html } from "lit";
-import { Theme, ThemeTypes } from "@bitwarden/common/platform/enums";
+import { ThemeTypes } from "@bitwarden/common/platform/enums";
+
+import { IconProps } from "../common-types";
// This icon has static multi-colors for each theme
-export function PartyHorn({ theme }: { theme: Theme }) {
+export function PartyHorn({ theme }: IconProps) {
if (theme === ThemeTypes.Dark) {
return html`
+
`;
diff --git a/apps/browser/src/autofill/content/components/lit-stories/.lit-docs/cipher-indicator-icon.mdx b/apps/browser/src/autofill/content/components/lit-stories/.lit-docs/cipher-indicator-icon.mdx
index 9bac07afe26..6c338276c02 100644
--- a/apps/browser/src/autofill/content/components/lit-stories/.lit-docs/cipher-indicator-icon.mdx
+++ b/apps/browser/src/autofill/content/components/lit-stories/.lit-docs/cipher-indicator-icon.mdx
@@ -15,11 +15,11 @@ dynamically based on the provided theme.
## Props
-| **Prop** | **Type** | **Required** | **Description** |
-| --------------- | --------- | ------------ | ----------------------------------------------------------------------- |
-| `isBusinessOrg` | `boolean` | No | Displays the business organization icon when set to `true`. |
-| `isFamilyOrg` | `boolean` | No | Displays the family organization icon when set to `true`. |
-| `theme` | `Theme` | Yes | Defines the theme used to style the icons. Must match the `Theme` enum. |
+| **Prop** | **Type** | **Required** | **Description** |
+| ------------------ | --------- | ------------ | ----------------------------------------------------------------------- |
+| `showBusinessIcon` | `boolean` | No | Displays the business organization icon when set to `true`. |
+| `showFamilyIcon` | `boolean` | No | Displays the family organization icon when set to `true`. |
+| `theme` | `Theme` | Yes | Defines the theme used to style the icons. Must match the `Theme` enum. |
## Installation and Setup
@@ -29,8 +29,8 @@ dynamically based on the provided theme.
- `@emotion/css`: Used for styling.
2. Pass the required props when using the component:
- - `isBusinessOrg`: A boolean that, when `true`, displays the business icon.
- - `isFamilyOrg`: A boolean that, when `true`, displays the family icon.
+ - `showBusinessIcon`: A boolean that, when `true`, displays the business icon.
+ - `showFamilyIcon`: A boolean that, when `true`, displays the family icon.
- `theme`: Specifies the theme for styling the icons.
## Accessibility (WCAG) Compliance
@@ -57,8 +57,8 @@ import { CipherInfoIndicatorIcons } from "./cipher-info-indicator-icons";
import { ThemeTypes } from "@bitwarden/common/platform/enums/theme-type.enum";
;
```
@@ -77,5 +77,5 @@ family organization icon.
### Notes
-- If neither isBusinessOrg nor isFamilyOrg is set to true, the component renders nothing. This
+- If neither showBusinessIcon nor showFamilyIcon is set to true, the component renders nothing. This
behavior should be handled by the parent component.
diff --git a/apps/browser/src/autofill/content/components/lit-stories/buttons/action-button.lit-stories.ts b/apps/browser/src/autofill/content/components/lit-stories/buttons/action-button.lit-stories.ts
index 4e0efd5955c..2aa61c627b7 100644
--- a/apps/browser/src/autofill/content/components/lit-stories/buttons/action-button.lit-stories.ts
+++ b/apps/browser/src/autofill/content/components/lit-stories/buttons/action-button.lit-stories.ts
@@ -8,7 +8,7 @@ type Args = {
buttonText: string;
disabled: boolean;
theme: Theme;
- buttonAction: (e: Event) => void;
+ handleClick: (e: Event) => void;
};
export default {
@@ -17,13 +17,13 @@ export default {
buttonText: { control: "text" },
disabled: { control: "boolean" },
theme: { control: "select", options: [...Object.values(ThemeTypes)] },
- buttonAction: { control: false },
+ handleClick: { control: false },
},
args: {
buttonText: "Click Me",
disabled: false,
theme: ThemeTypes.Light,
- buttonAction: () => alert("Clicked"),
+ handleClick: () => alert("Clicked"),
},
parameters: {
design: {
diff --git a/apps/browser/src/autofill/content/components/lit-stories/ciphers/cipher-indicator-icon.lit-stories.ts b/apps/browser/src/autofill/content/components/lit-stories/ciphers/cipher-indicator-icon.lit-stories.ts
index 2d031fa3afd..89c3ecbcb1c 100644
--- a/apps/browser/src/autofill/content/components/lit-stories/ciphers/cipher-indicator-icon.lit-stories.ts
+++ b/apps/browser/src/autofill/content/components/lit-stories/ciphers/cipher-indicator-icon.lit-stories.ts
@@ -6,22 +6,22 @@ import { Theme, ThemeTypes } from "@bitwarden/common/platform/enums/theme-type.e
import { CipherInfoIndicatorIcons } from "../../cipher/cipher-indicator-icons";
type Args = {
- isBusinessOrg?: boolean;
- isFamilyOrg?: boolean;
+ showBusinessIcon?: boolean;
+ showFamilyIcon?: boolean;
theme: Theme;
};
export default {
title: "Components/Ciphers/Cipher Indicator Icon",
argTypes: {
- isBusinessOrg: { control: "boolean" },
- isFamilyOrg: { control: "boolean" },
+ showBusinessIcon: { control: "boolean" },
+ showFamilyIcon: { control: "boolean" },
theme: { control: "select", options: [...Object.values(ThemeTypes)] },
},
args: {
theme: ThemeTypes.Light,
- isBusinessOrg: true,
- isFamilyOrg: false,
+ showBusinessIcon: true,
+ showFamilyIcon: false,
},
} as Meta;
diff --git a/apps/browser/src/autofill/content/components/lit-stories/mock-data.ts b/apps/browser/src/autofill/content/components/lit-stories/mock-data.ts
new file mode 100644
index 00000000000..024ac9e22b7
--- /dev/null
+++ b/apps/browser/src/autofill/content/components/lit-stories/mock-data.ts
@@ -0,0 +1,68 @@
+import { ProductTierType } from "@bitwarden/common/billing/enums";
+
+export const mockFolderData = [
+ {
+ id: "unique-id1",
+ name: "A folder",
+ },
+ {
+ id: "unique-id2",
+ name: "Another folder",
+ },
+ {
+ id: "unique-id3",
+ name: "One more folder",
+ },
+ {
+ id: "unique-id4",
+ name: "Definitely not a folder",
+ },
+ {
+ id: "unique-id5",
+ name: "Yet another folder",
+ },
+ {
+ id: "unique-id6",
+ name: "Something else entirely, with an essence being completely unfolder-like in all the unimportant ways and none of the important ones",
+ },
+ {
+ id: "unique-id7",
+ name: 'A "folder"',
+ },
+ {
+ id: "unique-id8",
+ name: "Two folders",
+ },
+];
+
+export const mockOrganizationData = [
+ {
+ id: "unique-id0",
+ name: "Another personal vault",
+ },
+ {
+ id: "unique-id1",
+ name: "Acme, inc",
+ productTierType: ProductTierType.Teams,
+ },
+ {
+ id: "unique-id2",
+ name: "A Really Long Business Name That Just Kinda Goes On For A Really Long Time",
+ productTierType: ProductTierType.TeamsStarter,
+ },
+ {
+ id: "unique-id3",
+ name: "Family Vault",
+ productTierType: ProductTierType.Families,
+ },
+ {
+ id: "unique-id4",
+ name: "Family Vault Trial",
+ productTierType: ProductTierType.Free,
+ },
+ {
+ id: "unique-id5",
+ name: "Exciting Enterprises, LLC",
+ productTierType: ProductTierType.Enterprise,
+ },
+];
diff --git a/apps/browser/src/autofill/content/components/lit-stories/notification/footer.lit-stories.ts b/apps/browser/src/autofill/content/components/lit-stories/notification/footer.lit-stories.ts
index 5ad0a5a2aca..ea2bbdc2e15 100644
--- a/apps/browser/src/autofill/content/components/lit-stories/notification/footer.lit-stories.ts
+++ b/apps/browser/src/autofill/content/components/lit-stories/notification/footer.lit-stories.ts
@@ -1,33 +1,29 @@
import { Meta, StoryObj } from "@storybook/web-components";
+import { html } from "lit";
-import { Theme, ThemeTypes } from "@bitwarden/common/platform/enums/theme-type.enum";
+import { ThemeTypes } from "@bitwarden/common/platform/enums/theme-type.enum";
-import { NotificationType } from "../../../../notification/abstractions/notification-bar";
-import { NotificationFooter } from "../../notification/footer";
-
-type Args = {
- notificationType: NotificationType;
- theme: Theme;
- handleSaveAction: (e: Event) => void;
- i18n: { [key: string]: string };
-};
+import { NotificationFooter, NotificationFooterProps } from "../../notification/footer";
+import { mockFolderData, mockOrganizationData } from "../mock-data";
export default {
title: "Components/Notifications/Notification Footer",
argTypes: {
- theme: { control: "select", options: [...Object.values(ThemeTypes)] },
notificationType: {
control: "select",
options: ["add", "change", "unlock", "fileless-import"],
},
+ theme: { control: "select", options: [...Object.values(ThemeTypes)] },
},
args: {
- theme: ThemeTypes.Light,
- notificationType: "add",
+ folders: mockFolderData,
i18n: {
- saveAsNewLoginAction: "Save as New Login",
saveAction: "Save",
+ saveAsNewLoginAction: "Save as New Login",
},
+ notificationType: "add",
+ organizations: mockOrganizationData,
+ theme: ThemeTypes.Light,
handleSaveAction: () => alert("Save action triggered"),
},
parameters: {
@@ -36,10 +32,11 @@ export default {
url: "https://www.figma.com/design/LEhqLAcBPY8uDKRfU99n9W/Autofill-notification-redesign?node-id=32-4949&m=dev",
},
},
-} as Meta;
+} as Meta;
-const Template = (args: Args) => NotificationFooter({ ...args });
+const Template = (args: NotificationFooterProps) =>
+ html`${NotificationFooter({ ...args })}
`;
-export const Default: StoryObj = {
+export const Default: StoryObj = {
render: Template,
};
diff --git a/apps/browser/src/autofill/content/components/lit-stories/options-selection/option-selection.lit-stories.ts b/apps/browser/src/autofill/content/components/lit-stories/options-selection/option-selection.lit-stories.ts
new file mode 100644
index 00000000000..2e8b1653546
--- /dev/null
+++ b/apps/browser/src/autofill/content/components/lit-stories/options-selection/option-selection.lit-stories.ts
@@ -0,0 +1,81 @@
+import { Meta, StoryObj } from "@storybook/web-components";
+import { html } from "lit";
+
+import { Theme, ThemeTypes } from "@bitwarden/common/platform/enums/theme-type.enum";
+
+import { Option } from "../../common-types";
+import { themes } from "../../constants/styles";
+import { User, Business } from "../../icons";
+import "../../option-selection/option-selection";
+import { mockOrganizationData } from "../mock-data";
+
+const mockOptions: Option[] = [
+ { icon: User, text: "My Vault", value: "0" },
+ ...mockOrganizationData.map(({ id, name }) => ({ icon: Business, text: name, value: id })),
+];
+
+type ComponentProps = {
+ disabled?: boolean;
+ label?: string;
+ options: Option[];
+ theme: Theme;
+};
+
+export default {
+ title: "Components/Option Selection",
+ argTypes: {
+ disabled: { control: "boolean" },
+ label: { control: "text" },
+ options: { control: "object" },
+ theme: { control: "select", options: [ThemeTypes.Light, ThemeTypes.Dark] },
+ },
+ args: {
+ disabled: false,
+ label: undefined,
+ options: mockOptions,
+ theme: ThemeTypes.Light,
+ },
+} as Meta;
+
+const BaseComponent = ({ disabled, label, options, theme }: ComponentProps) => {
+ return html`
+
+ `;
+};
+
+export const Light: StoryObj = {
+ render: BaseComponent,
+ argTypes: {
+ theme: { control: "radio", options: [ThemeTypes.Light] },
+ },
+ args: {
+ theme: ThemeTypes.Light,
+ },
+ parameters: {
+ backgrounds: {
+ values: [{ name: "Light", value: themes.light.background.alt }],
+ default: "Light",
+ },
+ },
+};
+
+export const Dark: StoryObj = {
+ render: BaseComponent,
+ argTypes: {
+ theme: { control: "radio", options: [ThemeTypes.Dark] },
+ },
+ args: {
+ theme: ThemeTypes.Dark,
+ },
+ parameters: {
+ backgrounds: {
+ values: [{ name: "Dark", value: themes.dark.background.alt }],
+ default: "Dark",
+ },
+ },
+};
diff --git a/apps/browser/src/autofill/content/components/lit-stories/rows/button-row.lit-stories.ts b/apps/browser/src/autofill/content/components/lit-stories/rows/button-row.lit-stories.ts
index 7f833f2a1f6..83b498df7cb 100644
--- a/apps/browser/src/autofill/content/components/lit-stories/rows/button-row.lit-stories.ts
+++ b/apps/browser/src/autofill/content/components/lit-stories/rows/button-row.lit-stories.ts
@@ -1,29 +1,53 @@
import { Meta, StoryObj } from "@storybook/web-components";
-import { Theme, ThemeTypes } from "@bitwarden/common/platform/enums/theme-type.enum";
+import { ThemeTypes } from "@bitwarden/common/platform/enums/theme-type.enum";
-import { ButtonRow } from "../../rows/button-row";
-
-type Args = {
- theme: Theme;
- buttonAction: (e: Event) => void;
- buttonText: string;
-};
+import { themes } from "../../constants/styles";
+import { ButtonRow, ButtonRowProps } from "../../rows/button-row";
export default {
title: "Components/Rows/Button Row",
+ argTypes: {},
+ args: {
+ primaryButton: {
+ text: "Action",
+ handlePrimaryButtonClick: (e: Event) => {
+ window.alert("Button clicked!");
+ },
+ },
+ },
+} as Meta;
+
+const Component = (args: ButtonRowProps) => ButtonRow({ ...args });
+
+export const Light: StoryObj = {
+ render: Component,
argTypes: {
- theme: { control: "select", options: [...Object.values(ThemeTypes)] },
- buttonText: { control: "text" },
+ theme: { control: "radio", options: [ThemeTypes.Light] },
},
args: {
theme: ThemeTypes.Light,
- buttonText: "Action",
},
-} as Meta;
-
-const Template = (args: Args) => ButtonRow({ ...args });
-
-export const Default: StoryObj = {
- render: Template,
+ parameters: {
+ backgrounds: {
+ values: [{ name: "Light", value: themes.light.background.alt }],
+ default: "Light",
+ },
+ },
+};
+
+export const Dark: StoryObj = {
+ render: Component,
+ argTypes: {
+ theme: { control: "radio", options: [ThemeTypes.Dark] },
+ },
+ args: {
+ theme: ThemeTypes.Dark,
+ },
+ parameters: {
+ backgrounds: {
+ values: [{ name: "Dark", value: themes.dark.background.alt }],
+ default: "Dark",
+ },
+ },
};
diff --git a/apps/browser/src/autofill/content/components/notification/button-row.ts b/apps/browser/src/autofill/content/components/notification/button-row.ts
new file mode 100644
index 00000000000..1eb0a4ac5f4
--- /dev/null
+++ b/apps/browser/src/autofill/content/components/notification/button-row.ts
@@ -0,0 +1,109 @@
+import { html } from "lit";
+
+import { ProductTierType } from "@bitwarden/common/billing/enums";
+import { Theme } from "@bitwarden/common/platform/enums";
+
+import { Option, OrgView, FolderView } from "../common-types";
+import { Business, Family, Folder, User } from "../icons";
+import { ButtonRow } from "../rows/button-row";
+
+function getVaultIconByProductTier(productTierType?: ProductTierType): Option["icon"] {
+ switch (productTierType) {
+ case ProductTierType.Free:
+ case ProductTierType.Families:
+ return Family;
+ case ProductTierType.Teams:
+ case ProductTierType.Enterprise:
+ case ProductTierType.TeamsStarter:
+ return Business;
+ default:
+ return User;
+ }
+}
+
+export type NotificationButtonRowProps = {
+ theme: Theme;
+ primaryButton: {
+ text: string;
+ handlePrimaryButtonClick: (args: any) => void;
+ };
+ folders?: FolderView[];
+ organizations?: OrgView[];
+};
+
+export function NotificationButtonRow({
+ folders,
+ organizations,
+ primaryButton,
+ theme,
+}: NotificationButtonRowProps) {
+ const currentUserVaultOption: Option = {
+ icon: User,
+ default: true,
+ text: "My vault", // @TODO localize
+ value: "0",
+ };
+ const organizationOptions: Option[] = organizations?.length
+ ? organizations.reduce(
+ (options, { id, name, productTierType }: OrgView) => {
+ const icon = getVaultIconByProductTier(productTierType);
+ return [
+ ...options,
+ {
+ icon,
+ text: name,
+ value: id,
+ },
+ ];
+ },
+ [currentUserVaultOption],
+ )
+ : ([] as Option[]);
+
+ const noFolderOption: Option = {
+ default: true,
+ icon: Folder,
+ text: "No folder", // @TODO localize
+ value: "0",
+ };
+ const folderOptions: Option[] = folders?.length
+ ? folders.reduce(
+ (options, { id, name }: FolderView) => [
+ ...options,
+ {
+ icon: Folder,
+ text: name,
+ value: id,
+ },
+ ],
+ [noFolderOption],
+ )
+ : [];
+
+ return html`
+ ${ButtonRow({
+ theme,
+ primaryButton,
+ selectButtons: [
+ ...(organizationOptions.length > 1
+ ? [
+ {
+ id: "organization",
+ label: "Vault", // @TODO localize
+ options: organizationOptions,
+ },
+ ]
+ : []),
+ ...(folderOptions.length > 1
+ ? [
+ {
+ id: "folder",
+ label: "Folder", // @TODO localize
+ options: folderOptions,
+ },
+ ]
+ : []),
+ ],
+ })}
+ `;
+}
diff --git a/apps/browser/src/autofill/content/components/notification/footer.ts b/apps/browser/src/autofill/content/components/notification/footer.ts
index c604fcd196f..8ed69a96ad9 100644
--- a/apps/browser/src/autofill/content/components/notification/footer.ts
+++ b/apps/browser/src/autofill/content/components/notification/footer.ts
@@ -7,27 +7,43 @@ import {
NotificationType,
NotificationTypes,
} from "../../../notification/abstractions/notification-bar";
+import { OrgView, FolderView } from "../common-types";
import { spacing, themes } from "../constants/styles";
-import { ButtonRow } from "../rows/button-row";
-export function NotificationFooter({
- handleSaveAction,
- notificationType,
- theme,
- i18n,
-}: {
- handleSaveAction: (e: Event) => void;
+import { NotificationButtonRow } from "./button-row";
+
+export type NotificationFooterProps = {
+ folders?: FolderView[];
i18n: { [key: string]: string };
notificationType?: NotificationType;
+ organizations?: OrgView[];
theme: Theme;
-}) {
+ handleSaveAction: (e: Event) => void;
+};
+
+export function NotificationFooter({
+ folders,
+ i18n,
+ notificationType,
+ organizations,
+ theme,
+ handleSaveAction,
+}: NotificationFooterProps) {
const isChangeNotification = notificationType === NotificationTypes.Change;
- const buttonText = i18n.saveAction;
+ const primaryButtonText = i18n.saveAction;
return html`
`;
diff --git a/apps/browser/src/autofill/content/components/option-selection/option-item.ts b/apps/browser/src/autofill/content/components/option-selection/option-item.ts
new file mode 100644
index 00000000000..619d77e63d3
--- /dev/null
+++ b/apps/browser/src/autofill/content/components/option-selection/option-item.ts
@@ -0,0 +1,81 @@
+import createEmotion from "@emotion/css/create-instance";
+import { html, nothing } from "lit";
+
+import { Theme } from "@bitwarden/common/platform/enums";
+
+import { IconProps, Option } from "../common-types";
+import { themes, spacing } from "../constants/styles";
+
+export const optionItemTagName = "option-item";
+
+const { css } = createEmotion({
+ key: optionItemTagName,
+});
+
+export function OptionItem({
+ icon,
+ text,
+ value,
+ theme,
+ handleSelection,
+}: Option & {
+ theme: Theme;
+ handleSelection: () => void;
+}) {
+ const handleSelectionKeyUpProxy = (event: KeyboardEvent) => {
+ const listenedForKeys = new Set(["Enter", "Space"]);
+ if (listenedForKeys.has(event.code) && event.target instanceof Element) {
+ handleSelection();
+ }
+
+ return;
+ };
+
+ const iconProps: IconProps = { color: themes[theme].text.main, theme };
+ const itemIcon = icon?.(iconProps);
+
+ return html`
+ ${itemIcon ? html`
${itemIcon}
` : nothing}
+
${text || value}
+
`;
+}
+
+export const optionItemIconWidth = 16;
+const optionItemGap = spacing["2"];
+
+const optionItemStyles = css`
+ gap: ${optionItemGap};
+ user-select: none;
+ display: flex;
+ flex-direction: row;
+ flex-wrap: nowrap;
+ align-items: center;
+ justify-content: flex-start;
+ cursor: pointer;
+`;
+
+const optionItemIconContainerStyles = css`
+ flex-grow: 1;
+ flex-shrink: 1;
+ width: ${optionItemIconWidth}px;
+ height: ${optionItemIconWidth}px;
+
+ > svg {
+ width: 100%;
+ height: fit-content;
+ }
+`;
+
+const optionItemTextStyles = css`
+ flex: 1 1 calc(100% - ${optionItemIconWidth}px - ${optionItemGap});
+ overflow-x: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+`;
diff --git a/apps/browser/src/autofill/content/components/option-selection/option-items.ts b/apps/browser/src/autofill/content/components/option-selection/option-items.ts
new file mode 100644
index 00000000000..b87eea2a3a5
--- /dev/null
+++ b/apps/browser/src/autofill/content/components/option-selection/option-items.ts
@@ -0,0 +1,90 @@
+import createEmotion from "@emotion/css/create-instance";
+import { html, nothing } from "lit";
+
+import { Theme } from "@bitwarden/common/platform/enums";
+
+import { Option } from "../common-types";
+import { themes, typography, scrollbarStyles, spacing } from "../constants/styles";
+
+import { OptionItem, optionItemTagName } from "./option-item";
+
+export const optionItemsTagName = "option-items";
+
+const { css } = createEmotion({
+ key: optionItemsTagName,
+});
+
+export function OptionItems({
+ theme,
+ topOffset,
+ label,
+ options,
+ handleOptionSelection,
+}: {
+ theme: Theme;
+ topOffset: number;
+ label?: string;
+ options: Option[];
+ handleOptionSelection: (selectedOption: Option) => void;
+}) {
+ // @TODO get client vendor from context
+ const isSafari = false;
+
+ return html`
+
+ ${label ? html`
${label}
` : nothing}
+
+ ${options.map((option) =>
+ OptionItem({ ...option, theme, handleSelection: () => handleOptionSelection(option) }),
+ )}
+
+
+ `;
+}
+
+const optionsStyles = ({ theme, topOffset }: { theme: Theme; topOffset: number }) => css`
+ ${typography.body1}
+
+ -webkit-font-smoothing: antialiased;
+ position: absolute;
+ /* top offset + line-height of dropdown button + top and bottom padding of button + border-width */
+ top: calc(${topOffset}px + 20px + ${spacing["1"]} + ${spacing["1"]} + 1px);
+ border: 1px solid ${themes[theme].secondary["500"]};
+ border-radius: 0.5rem;
+ background-clip: padding-box;
+ background-color: ${themes[theme].background.DEFAULT};
+ padding: 0.25rem 0;
+ max-width: fit-content;
+ overflow-y: hidden;
+ color: ${themes[theme].text.main};
+`;
+
+const optionsLabelStyles = ({ theme }: { theme: Theme }) => css`
+ ${typography.helperMedium}
+
+ user-select: none;
+ padding: 0.375rem ${spacing["3"]};
+ color: ${themes[theme].text.muted};
+ font-weight: 600;
+`;
+
+export const optionsMenuItemMaxWidth = 260;
+export const optionsMenuItemsMaxHeight = 114;
+
+const optionsWrapper = ({ isSafari, theme }: { isSafari: boolean; theme: Theme }) => css`
+ max-height: ${optionsMenuItemsMaxHeight}px;
+ overflow-y: auto;
+
+ > [class*="${optionItemTagName}-"] {
+ padding: ${spacing["1.5"]} ${spacing["3"]};
+ max-width: ${optionsMenuItemMaxWidth}px;
+
+ :hover {
+ background-color: ${themes[theme].primary["100"]};
+ }
+ }
+
+ ${isSafari
+ ? scrollbarStyles(theme, { track: "transparent" }).safari
+ : scrollbarStyles(theme, { track: "transparent" }).default}
+`;
diff --git a/apps/browser/src/autofill/content/components/option-selection/option-selection.ts b/apps/browser/src/autofill/content/components/option-selection/option-selection.ts
new file mode 100644
index 00000000000..5f43e7a0256
--- /dev/null
+++ b/apps/browser/src/autofill/content/components/option-selection/option-selection.ts
@@ -0,0 +1,138 @@
+import createEmotion from "@emotion/css/create-instance";
+import { html, LitElement, nothing } from "lit";
+import { property, state } from "lit/decorators.js";
+
+import { Theme, ThemeTypes } from "@bitwarden/common/platform/enums";
+
+import { OptionSelectionButton } from "../buttons/option-selection-button";
+import { Option } from "../common-types";
+
+import { optionItemIconWidth } from "./option-item";
+import { OptionItems, optionsMenuItemMaxWidth } from "./option-items";
+
+export const optionSelectionTagName = "option-selection";
+
+const { css } = createEmotion({
+ key: optionSelectionTagName,
+});
+
+export class OptionSelection extends LitElement {
+ @property()
+ disabled: boolean = false;
+
+ @property()
+ label?: string;
+
+ @property({ type: Array })
+ options: Option[] = [];
+
+ @property()
+ theme: Theme = ThemeTypes.Light;
+
+ @property({ type: (selectedOption: Option["value"]) => selectedOption })
+ handleSelectionUpdate?: (args: any) => void;
+
+ @state()
+ private showMenu = false;
+
+ @state()
+ private menuTopOffset: number = 0;
+
+ // Determines if the opened menu should be "anchored" to the right or left side of the opening button
+ @state()
+ private menuIsEndJustified: boolean = false;
+
+ @state()
+ private selection?: Option;
+
+ private handleButtonClick = (event: Event) => {
+ if (!this.disabled) {
+ // Menu is about to be shown
+ if (!this.showMenu) {
+ this.menuTopOffset = this.offsetTop;
+
+ // Distance from right edge of button to left edge of the viewport
+ // Assumes no enclosing frames between the intended host frame and the component
+ const boundingClientRect = this.getBoundingClientRect();
+
+ // Width of the client (minus scrollbar)
+ const documentWidth = document.documentElement.clientWidth;
+
+ // Distance between left edge of the button and right edge of the viewport
+ // (e.g. the max space the menu can use when left-aligned)
+ const distanceFromViewportRightEdge = documentWidth - boundingClientRect.left;
+
+ // The full width the option menu can take up
+ // (base + icon + border + gap + padding)
+ const maxDifferenceThreshold =
+ optionsMenuItemMaxWidth + optionItemIconWidth + 2 + 8 + 12 * 2;
+
+ this.menuIsEndJustified = distanceFromViewportRightEdge < maxDifferenceThreshold;
+ }
+
+ this.showMenu = !this.showMenu;
+ }
+ };
+
+ private handleOptionSelection = (selectedOption: Option) => {
+ this.showMenu = false;
+ this.selection = selectedOption;
+
+ // Any side-effects that should occur from the selection
+ this.handleSelectionUpdate?.(selectedOption.value);
+ };
+
+ protected createRenderRoot() {
+ return this;
+ }
+
+ render() {
+ if (!this.selection) {
+ this.selection = getDefaultOption(this.options);
+ }
+
+ return html`
+
+ ${OptionSelectionButton({
+ disabled: this.disabled,
+ icon: this.selection?.icon,
+ text: this.selection?.text,
+ theme: this.theme,
+ toggledOn: this.showMenu,
+ handleButtonClick: this.handleButtonClick,
+ })}
+ ${this.showMenu
+ ? OptionItems({
+ label: this.label,
+ options: this.options,
+ theme: this.theme,
+ topOffset: this.menuTopOffset,
+ handleOptionSelection: this.handleOptionSelection,
+ })
+ : nothing}
+
+ `;
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ [optionSelectionTagName]: OptionSelection;
+ }
+}
+
+export default customElements.define(optionSelectionTagName, OptionSelection);
+
+function getDefaultOption(options: Option[] = []) {
+ return options.find((option: Option) => option.default) || options[0];
+}
+
+const optionSelectionStyles = ({ menuIsEndJustified }: { menuIsEndJustified: boolean }) => css`
+ display: flex;
+ justify-content: ${menuIsEndJustified ? "flex-end" : "flex-start"};
+
+ > div,
+ > button {
+ width: 100%;
+ }
+`;
diff --git a/apps/browser/src/autofill/content/components/rows/button-row.ts b/apps/browser/src/autofill/content/components/rows/button-row.ts
index ed0ed5aac7d..80dcd0de125 100644
--- a/apps/browser/src/autofill/content/components/rows/button-row.ts
+++ b/apps/browser/src/autofill/content/components/rows/button-row.ts
@@ -1,54 +1,53 @@
import { css } from "@emotion/css";
-import { html, TemplateResult } from "lit";
+import { html, nothing } from "lit";
import { Theme } from "@bitwarden/common/platform/enums";
import { ActionButton } from "../../../content/components/buttons/action-button";
-import { spacing, themes } from "../../../content/components/constants/styles";
-import { Folder, User } from "../../../content/components/icons";
-import { DropdownMenu } from "../dropdown-menu";
+import { spacing } from "../../../content/components/constants/styles";
+import { Option } from "../common-types";
+import { optionSelectionTagName } from "../option-selection/option-selection";
-export function ButtonRow({
- theme,
- buttonAction,
- buttonText,
-}: {
+export type ButtonRowProps = {
theme: Theme;
- buttonAction: (e: Event) => void;
- buttonText: string;
-}) {
+ primaryButton: {
+ text: string;
+ handlePrimaryButtonClick: (args: any) => void;
+ };
+ selectButtons?: {
+ id: string;
+ label?: string;
+ options: Option[];
+ handleSelectionUpdate?: (args: any) => void;
+ }[];
+};
+
+export function ButtonRow({ theme, primaryButton, selectButtons }: ButtonRowProps) {
return html`
`;
}
-function DropdownContainer({ children }: { children: TemplateResult[] }) {
- return html` ${children}
`;
-}
-
const buttonRowStyles = css`
gap: 16px;
display: flex;
@@ -69,14 +68,16 @@ const buttonRowStyles = css`
}
`;
-const dropdownContainerStyles = css`
- gap: 8px;
+const optionSelectionsStyles = css`
+ gap: ${spacing["2"]};
display: flex;
align-items: center;
justify-content: flex-end;
overflow: hidden;
- > div {
- min-width: calc(50% - ${spacing["1.5"]});
+ > ${optionSelectionTagName} {
+ /* assumes two option selections */
+ max-width: calc(50% - ${spacing["1.5"]});
+ min-width: 120px;
}
`;
From a238b7e2971136bbafe07d4c6c13660e0538a0e0 Mon Sep 17 00:00:00 2001
From: "bw-ghapp[bot]" <178206702+bw-ghapp[bot]@users.noreply.github.com>
Date: Wed, 19 Mar 2025 18:10:32 +0000
Subject: [PATCH 010/113] Autosync the updated translations (#13903)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
---
apps/browser/src/_locales/ar/messages.json | 9 ----
apps/browser/src/_locales/az/messages.json | 9 ----
apps/browser/src/_locales/be/messages.json | 9 ----
apps/browser/src/_locales/bg/messages.json | 9 ----
apps/browser/src/_locales/bn/messages.json | 9 ----
apps/browser/src/_locales/bs/messages.json | 9 ----
apps/browser/src/_locales/ca/messages.json | 9 ----
apps/browser/src/_locales/cs/messages.json | 9 ----
apps/browser/src/_locales/cy/messages.json | 9 ----
apps/browser/src/_locales/da/messages.json | 9 ----
apps/browser/src/_locales/de/messages.json | 33 +++++--------
apps/browser/src/_locales/el/messages.json | 9 ----
apps/browser/src/_locales/en_GB/messages.json | 9 ----
apps/browser/src/_locales/en_IN/messages.json | 9 ----
apps/browser/src/_locales/es/messages.json | 9 ----
apps/browser/src/_locales/et/messages.json | 9 ----
apps/browser/src/_locales/eu/messages.json | 9 ----
apps/browser/src/_locales/fa/messages.json | 9 ----
apps/browser/src/_locales/fi/messages.json | 9 ----
apps/browser/src/_locales/fil/messages.json | 9 ----
apps/browser/src/_locales/fr/messages.json | 9 ----
apps/browser/src/_locales/gl/messages.json | 9 ----
apps/browser/src/_locales/he/messages.json | 35 +++++---------
apps/browser/src/_locales/hi/messages.json | 9 ----
apps/browser/src/_locales/hr/messages.json | 9 ----
apps/browser/src/_locales/hu/messages.json | 9 ----
apps/browser/src/_locales/id/messages.json | 9 ----
apps/browser/src/_locales/it/messages.json | 9 ----
apps/browser/src/_locales/ja/messages.json | 9 ----
apps/browser/src/_locales/ka/messages.json | 9 ----
apps/browser/src/_locales/km/messages.json | 9 ----
apps/browser/src/_locales/kn/messages.json | 9 ----
apps/browser/src/_locales/ko/messages.json | 9 ----
apps/browser/src/_locales/lt/messages.json | 9 ----
apps/browser/src/_locales/lv/messages.json | 9 ----
apps/browser/src/_locales/ml/messages.json | 9 ----
apps/browser/src/_locales/mr/messages.json | 9 ----
apps/browser/src/_locales/my/messages.json | 9 ----
apps/browser/src/_locales/nb/messages.json | 9 ----
apps/browser/src/_locales/ne/messages.json | 9 ----
apps/browser/src/_locales/nl/messages.json | 9 ----
apps/browser/src/_locales/nn/messages.json | 9 ----
apps/browser/src/_locales/or/messages.json | 9 ----
apps/browser/src/_locales/pl/messages.json | 9 ----
apps/browser/src/_locales/pt_BR/messages.json | 9 ----
apps/browser/src/_locales/pt_PT/messages.json | 9 ----
apps/browser/src/_locales/ro/messages.json | 9 ----
apps/browser/src/_locales/ru/messages.json | 47 ++++++++-----------
apps/browser/src/_locales/si/messages.json | 9 ----
apps/browser/src/_locales/sk/messages.json | 9 ----
apps/browser/src/_locales/sl/messages.json | 9 ----
apps/browser/src/_locales/sr/messages.json | 9 ----
apps/browser/src/_locales/sv/messages.json | 9 ----
apps/browser/src/_locales/te/messages.json | 9 ----
apps/browser/src/_locales/th/messages.json | 9 ----
apps/browser/src/_locales/tr/messages.json | 9 ----
apps/browser/src/_locales/uk/messages.json | 9 ----
apps/browser/src/_locales/vi/messages.json | 9 ----
apps/browser/src/_locales/zh_CN/messages.json | 11 +----
apps/browser/src/_locales/zh_TW/messages.json | 9 ----
60 files changed, 45 insertions(+), 585 deletions(-)
diff --git a/apps/browser/src/_locales/ar/messages.json b/apps/browser/src/_locales/ar/messages.json
index 916c6cb6a67..4c051d455ac 100644
--- a/apps/browser/src/_locales/ar/messages.json
+++ b/apps/browser/src/_locales/ar/messages.json
@@ -4733,15 +4733,6 @@
"nothingSelected": {
"message": "You have not selected anything."
},
- "movedItemsToOrg": {
- "message": "Selected items moved to $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Items moved to $ORGNAME$",
"placeholders": {
diff --git a/apps/browser/src/_locales/az/messages.json b/apps/browser/src/_locales/az/messages.json
index 007428ed073..6f3ace453ae 100644
--- a/apps/browser/src/_locales/az/messages.json
+++ b/apps/browser/src/_locales/az/messages.json
@@ -4733,15 +4733,6 @@
"nothingSelected": {
"message": "Heç nə seçməmisiniz."
},
- "movedItemsToOrg": {
- "message": "Seçilən elementlər $ORGNAME$ təşkilatına daşınıldı",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Elementlər bura daşındı: $ORGNAME$",
"placeholders": {
diff --git a/apps/browser/src/_locales/be/messages.json b/apps/browser/src/_locales/be/messages.json
index 9e5449f7bd4..905f6b6ac1d 100644
--- a/apps/browser/src/_locales/be/messages.json
+++ b/apps/browser/src/_locales/be/messages.json
@@ -4733,15 +4733,6 @@
"nothingSelected": {
"message": "Вы пакуль нічога не выбралі."
},
- "movedItemsToOrg": {
- "message": "Выбраныя элементы перамешчаны ў $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Items moved to $ORGNAME$",
"placeholders": {
diff --git a/apps/browser/src/_locales/bg/messages.json b/apps/browser/src/_locales/bg/messages.json
index 703007d2565..226e63e32cb 100644
--- a/apps/browser/src/_locales/bg/messages.json
+++ b/apps/browser/src/_locales/bg/messages.json
@@ -4733,15 +4733,6 @@
"nothingSelected": {
"message": "Не сте избрали нищо."
},
- "movedItemsToOrg": {
- "message": "Избраните записи бяха преместени в $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Елементите са преместени в $ORGNAME$",
"placeholders": {
diff --git a/apps/browser/src/_locales/bn/messages.json b/apps/browser/src/_locales/bn/messages.json
index f3af2810d3c..983b9fadde4 100644
--- a/apps/browser/src/_locales/bn/messages.json
+++ b/apps/browser/src/_locales/bn/messages.json
@@ -4733,15 +4733,6 @@
"nothingSelected": {
"message": "You have not selected anything."
},
- "movedItemsToOrg": {
- "message": "Selected items moved to $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Items moved to $ORGNAME$",
"placeholders": {
diff --git a/apps/browser/src/_locales/bs/messages.json b/apps/browser/src/_locales/bs/messages.json
index 7dd00c873f3..08fedb6f10a 100644
--- a/apps/browser/src/_locales/bs/messages.json
+++ b/apps/browser/src/_locales/bs/messages.json
@@ -4733,15 +4733,6 @@
"nothingSelected": {
"message": "You have not selected anything."
},
- "movedItemsToOrg": {
- "message": "Selected items moved to $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Items moved to $ORGNAME$",
"placeholders": {
diff --git a/apps/browser/src/_locales/ca/messages.json b/apps/browser/src/_locales/ca/messages.json
index 4513104a1b9..14e4a577440 100644
--- a/apps/browser/src/_locales/ca/messages.json
+++ b/apps/browser/src/_locales/ca/messages.json
@@ -4733,15 +4733,6 @@
"nothingSelected": {
"message": "No heu seleccionat res."
},
- "movedItemsToOrg": {
- "message": "Els elements seleccionats s'han desplaçat a $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "S'han desplaçat elements a $ORGNAME$",
"placeholders": {
diff --git a/apps/browser/src/_locales/cs/messages.json b/apps/browser/src/_locales/cs/messages.json
index 78ccaad05a0..e106d371d57 100644
--- a/apps/browser/src/_locales/cs/messages.json
+++ b/apps/browser/src/_locales/cs/messages.json
@@ -4733,15 +4733,6 @@
"nothingSelected": {
"message": "Nevybrali jste žádné položky."
},
- "movedItemsToOrg": {
- "message": "Vybrané položky přesunuty do $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Položky přesunuty do $ORGNAME$",
"placeholders": {
diff --git a/apps/browser/src/_locales/cy/messages.json b/apps/browser/src/_locales/cy/messages.json
index 0eff6912205..83d09d13273 100644
--- a/apps/browser/src/_locales/cy/messages.json
+++ b/apps/browser/src/_locales/cy/messages.json
@@ -4733,15 +4733,6 @@
"nothingSelected": {
"message": "You have not selected anything."
},
- "movedItemsToOrg": {
- "message": "Selected items moved to $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Items moved to $ORGNAME$",
"placeholders": {
diff --git a/apps/browser/src/_locales/da/messages.json b/apps/browser/src/_locales/da/messages.json
index 0ff62a56ee1..69c8b28d29a 100644
--- a/apps/browser/src/_locales/da/messages.json
+++ b/apps/browser/src/_locales/da/messages.json
@@ -4733,15 +4733,6 @@
"nothingSelected": {
"message": "Ingenting er valgt."
},
- "movedItemsToOrg": {
- "message": "Valgte emner flyttet til $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Emner flyttet til $ORGNAME$",
"placeholders": {
diff --git a/apps/browser/src/_locales/de/messages.json b/apps/browser/src/_locales/de/messages.json
index 581b7611391..4df2fd25b3c 100644
--- a/apps/browser/src/_locales/de/messages.json
+++ b/apps/browser/src/_locales/de/messages.json
@@ -380,7 +380,7 @@
"message": "Ordner bearbeiten"
},
"editFolderWithName": {
- "message": "Edit folder: $FOLDERNAME$",
+ "message": "Ordner bearbeiten: $FOLDERNAME$",
"placeholders": {
"foldername": {
"content": "$1",
@@ -1668,7 +1668,7 @@
"message": "Zum Sortieren ziehen"
},
"dragToReorder": {
- "message": "Drag to reorder"
+ "message": "Ziehen zum umsortieren"
},
"cfTypeText": {
"message": "Text"
@@ -4671,7 +4671,7 @@
}
},
"reorderWebsiteUriButton": {
- "message": "Reorder website URI. Use arrow key to move item up or down."
+ "message": "Website-URI umsortieren. Verwende die Pfeiltasten, um den Eintrag nach oben oder unten zu bewegen."
},
"reorderFieldUp": {
"message": "$LABEL$ nach oben verschoben, Position $INDEX$ von $LENGTH$",
@@ -4733,15 +4733,6 @@
"nothingSelected": {
"message": "Du hast nichts ausgewählt."
},
- "movedItemsToOrg": {
- "message": "Ausgewählte Einträge in $ORGNAME$ verschoben",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Einträge verschoben nach $ORGNAME$",
"placeholders": {
@@ -5105,31 +5096,31 @@
"message": "Extra breit"
},
"sshKeyWrongPassword": {
- "message": "The password you entered is incorrect."
+ "message": "Dein eingegebenes Passwort ist falsch."
},
"importSshKey": {
- "message": "Import"
+ "message": "Importieren"
},
"confirmSshKeyPassword": {
- "message": "Confirm password"
+ "message": "Passwort bestätigen"
},
"enterSshKeyPasswordDesc": {
- "message": "Enter the password for the SSH key."
+ "message": "Gib das Passwort für den SSH-Schlüssel ein."
},
"enterSshKeyPassword": {
- "message": "Enter password"
+ "message": "Passwort eingeben"
},
"invalidSshKey": {
- "message": "The SSH key is invalid"
+ "message": "Der SSH-Schlüssel ist ungültig"
},
"sshKeyTypeUnsupported": {
- "message": "The SSH key type is not supported"
+ "message": "Der SSH-Schlüsseltyp wird nicht unterstützt"
},
"importSshKeyFromClipboard": {
- "message": "Import key from clipboard"
+ "message": "Schlüssel aus Zwischenablage importieren"
},
"sshKeyImported": {
- "message": "SSH key imported successfully"
+ "message": "SSH-Schlüssel erfolgreich importiert"
},
"cannotRemoveViewOnlyCollections": {
"message": "Du kannst Sammlungen mit Leseberechtigung nicht entfernen: $COLLECTIONS$",
diff --git a/apps/browser/src/_locales/el/messages.json b/apps/browser/src/_locales/el/messages.json
index 59206965632..fc07f12a48f 100644
--- a/apps/browser/src/_locales/el/messages.json
+++ b/apps/browser/src/_locales/el/messages.json
@@ -4733,15 +4733,6 @@
"nothingSelected": {
"message": "Δεν έχετε επιλέξει τίποτα."
},
- "movedItemsToOrg": {
- "message": "Τα επιλεγμένα αντικείμενα μετακινήθηκαν στο $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Τα αντικείμενα μεταφέρθηκαν στο $ORGNAME$",
"placeholders": {
diff --git a/apps/browser/src/_locales/en_GB/messages.json b/apps/browser/src/_locales/en_GB/messages.json
index 5513d11ad18..1381afbdc6e 100644
--- a/apps/browser/src/_locales/en_GB/messages.json
+++ b/apps/browser/src/_locales/en_GB/messages.json
@@ -4733,15 +4733,6 @@
"nothingSelected": {
"message": "You have not selected anything."
},
- "movedItemsToOrg": {
- "message": "Selected items moved to $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Items moved to $ORGNAME$",
"placeholders": {
diff --git a/apps/browser/src/_locales/en_IN/messages.json b/apps/browser/src/_locales/en_IN/messages.json
index d26a68f0088..6b14a358148 100644
--- a/apps/browser/src/_locales/en_IN/messages.json
+++ b/apps/browser/src/_locales/en_IN/messages.json
@@ -4733,15 +4733,6 @@
"nothingSelected": {
"message": "You have not selected anything."
},
- "movedItemsToOrg": {
- "message": "Selected items moved to $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Items moved to $ORGNAME$",
"placeholders": {
diff --git a/apps/browser/src/_locales/es/messages.json b/apps/browser/src/_locales/es/messages.json
index abe8453e7b5..4d430d23337 100644
--- a/apps/browser/src/_locales/es/messages.json
+++ b/apps/browser/src/_locales/es/messages.json
@@ -4733,15 +4733,6 @@
"nothingSelected": {
"message": "You have not selected anything."
},
- "movedItemsToOrg": {
- "message": "Selected items moved to $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Items moved to $ORGNAME$",
"placeholders": {
diff --git a/apps/browser/src/_locales/et/messages.json b/apps/browser/src/_locales/et/messages.json
index 4a4ee1fb4ae..2daf5f9d7f0 100644
--- a/apps/browser/src/_locales/et/messages.json
+++ b/apps/browser/src/_locales/et/messages.json
@@ -4733,15 +4733,6 @@
"nothingSelected": {
"message": "You have not selected anything."
},
- "movedItemsToOrg": {
- "message": "Selected items moved to $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Items moved to $ORGNAME$",
"placeholders": {
diff --git a/apps/browser/src/_locales/eu/messages.json b/apps/browser/src/_locales/eu/messages.json
index f3a775e11c3..b1e1ca3526b 100644
--- a/apps/browser/src/_locales/eu/messages.json
+++ b/apps/browser/src/_locales/eu/messages.json
@@ -4733,15 +4733,6 @@
"nothingSelected": {
"message": "You have not selected anything."
},
- "movedItemsToOrg": {
- "message": "Selected items moved to $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Items moved to $ORGNAME$",
"placeholders": {
diff --git a/apps/browser/src/_locales/fa/messages.json b/apps/browser/src/_locales/fa/messages.json
index ac15eb203ae..2b3d66ad104 100644
--- a/apps/browser/src/_locales/fa/messages.json
+++ b/apps/browser/src/_locales/fa/messages.json
@@ -4733,15 +4733,6 @@
"nothingSelected": {
"message": "You have not selected anything."
},
- "movedItemsToOrg": {
- "message": "Selected items moved to $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Items moved to $ORGNAME$",
"placeholders": {
diff --git a/apps/browser/src/_locales/fi/messages.json b/apps/browser/src/_locales/fi/messages.json
index 0175aeea535..a37bd0235c1 100644
--- a/apps/browser/src/_locales/fi/messages.json
+++ b/apps/browser/src/_locales/fi/messages.json
@@ -4733,15 +4733,6 @@
"nothingSelected": {
"message": "Et ole valinnut mitään."
},
- "movedItemsToOrg": {
- "message": "Valitut kohteet siirrettiin organisaatiolle $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Kohteet siirrettiin organisaatiolle $ORGNAME$",
"placeholders": {
diff --git a/apps/browser/src/_locales/fil/messages.json b/apps/browser/src/_locales/fil/messages.json
index a186f0ad66a..9f991843f4f 100644
--- a/apps/browser/src/_locales/fil/messages.json
+++ b/apps/browser/src/_locales/fil/messages.json
@@ -4733,15 +4733,6 @@
"nothingSelected": {
"message": "You have not selected anything."
},
- "movedItemsToOrg": {
- "message": "Selected items moved to $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Items moved to $ORGNAME$",
"placeholders": {
diff --git a/apps/browser/src/_locales/fr/messages.json b/apps/browser/src/_locales/fr/messages.json
index f694c0bb4dd..32b6dd0296b 100644
--- a/apps/browser/src/_locales/fr/messages.json
+++ b/apps/browser/src/_locales/fr/messages.json
@@ -4733,15 +4733,6 @@
"nothingSelected": {
"message": "Vous n'avez rien sélectionné."
},
- "movedItemsToOrg": {
- "message": "Les éléments sélectionnés ont été déplacés vers $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Éléments déplacés vers $ORGNAME$",
"placeholders": {
diff --git a/apps/browser/src/_locales/gl/messages.json b/apps/browser/src/_locales/gl/messages.json
index 83133349aee..6c0b9cce87b 100644
--- a/apps/browser/src/_locales/gl/messages.json
+++ b/apps/browser/src/_locales/gl/messages.json
@@ -4733,15 +4733,6 @@
"nothingSelected": {
"message": "Non tes nada seleccionado."
},
- "movedItemsToOrg": {
- "message": "Entradas seleccionadas transferidas a $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Entradas transferidas a $ORGNAME$",
"placeholders": {
diff --git a/apps/browser/src/_locales/he/messages.json b/apps/browser/src/_locales/he/messages.json
index 86b6239d9b4..035bf9da48e 100644
--- a/apps/browser/src/_locales/he/messages.json
+++ b/apps/browser/src/_locales/he/messages.json
@@ -123,7 +123,7 @@
"message": "הגדרות"
},
"currentTab": {
- "message": "כרטיסייה נוכחית"
+ "message": "כרטיסיה נוכחית"
},
"copyPassword": {
"message": "העתק סיסמה"
@@ -796,7 +796,7 @@
"message": "החשבון החדש שלך נוצר!"
},
"youHaveBeenLoggedIn": {
- "message": "נכנסת!"
+ "message": "אתה נכנסת!"
},
"youSuccessfullyLoggedIn": {
"message": "נכנסת בהצלחה"
@@ -1217,7 +1217,7 @@
"message": "הקובץ מכיל את פרטי הכספת שלך בפורמט לא מוצפן. מומלץ להעביר את הקובץ רק בדרכים מוצפנות, ומאוד לא מומלץ לשמור או לשלוח את הקובץ הזה בדרכים לא מוצפנות (כדוגמת סתם אימייל). מחק את הקובץ מיד לאחר שסיימת את השימוש בו."
},
"encExportKeyWarningDesc": {
- "message": "ייצוא זה מצפין את הנתונים שלך באמצעות מפתח ההצפנה של חשבונך. אם אי פעם תבצע סיבוב (רוטציה) למפתח ההצפנה של חשבונך, תצטרך לייצא שוב משום שלא תוכל לפענח קובץ ייצוא זה."
+ "message": "ייצוא זה מצפין את הנתונים שלך באמצעות מפתח ההצפנה של חשבונך. אם אי פעם תבצע סיבוב למפתח ההצפנה של חשבונך, תצטרך לייצא שוב משום שלא תוכל לפענח קובץ ייצוא זה."
},
"encExportAccountWarningDesc": {
"message": "מפתחות הצפנת חשבון הם ייחודים לכל חשבון משתמש של Bitwarden, לכן אינך יכול לייבא ייצוא מוצפן אל תוך חשבון אחר."
@@ -1444,13 +1444,13 @@
"message": "הכנס את מפתח האבטחה שלך אל כניסת ה-USB במחשבך. אם יש לו כפתור, לחץ עליו."
},
"webAuthnNewTab": {
- "message": "על מנת להתחיל אימות WebAuthn דו־שלבי. לחץ על הלחצן למטה כדי לפתוח כרטיסייה חדשה ועקוב אחר ההוראות המסופקת בכרטיסייה החדשה."
+ "message": "על מנת להתחיל אימות WebAuthn דו־שלבי. לחץ על הלחצן למטה כדי לפתוח כרטיסיה חדשה ועקוב אחר ההוראות המסופקת בכרטיסיה החדשה."
},
"webAuthnNewTabOpen": {
- "message": "פתח כרטיסייה חדשה"
+ "message": "פתח כרטיסיה חדשה"
},
"openInNewTab": {
- "message": "פתח בכרטיסייה חדשה"
+ "message": "פתח בכרטיסיה חדשה"
},
"webAuthnAuthenticate": {
"message": "אמת WebAuthn"
@@ -1477,7 +1477,7 @@
"message": "בחר שיטת כניסה דו־שלבית"
},
"recoveryCodeDesc": {
- "message": "איבדת גישה לכל הספקים הדו־גורמיים שלך? השתמש בקוד השחזור שלך כדי להשבית את כל הספקים הדו־גורמיים בחשבון שלך."
+ "message": "איבדת גישה לכל הספקים הדו־גורמיים שלך? השתמש בקוד השחזור שלך כדי לכבות את כל הספקים הדו־גורמיים מהחשבון שלך."
},
"recoveryCodeTitle": {
"message": "קוד שחזור"
@@ -2834,7 +2834,7 @@
"message": "עדכן סיסמה ראשית"
},
"updateMasterPasswordWarning": {
- "message": "הסיסמה הראשית שלך שונתה לאחרונה על ידי מנהל הארגון שלך. כדי לגשת לכספת, עליך לעדכן אותה כעת. המשך התהליך יוציא אותך מההפעלה הנוכחית שלך ותידרש להיכנס חזרה. הפעלות פעילות במכשירים אחרים עלולות להישאר פעילות למשך עד שעה אחת."
+ "message": "הסיסמה הראשית שלך שונתה לאחרונה על ידי מנהל הארגון שלך. כדי לגשת לכספת, עליך לעדכן אותה כעת. המשך התהליך יוציא אותך מההפעלה הנוכחית שלך, מה שידרוש ממך להיכנס חזרה. הפעלות פעילות במכשירים אחרים עלולות להישאר פעילות למשך עד שעה אחת."
},
"updateWeakMasterPasswordWarning": {
"message": "הסיסמה הראשית שלך אינה עומדת באחת או יותר מפוליסות הארגון שלך. כדי לגשת לכספת, אתה מוכרח לעדכן את הסיסמה הראשית שלך עכשיו. בהמשך תנותק מההפעלה הנוכחית שלך, מה שידרוש ממך להיכנס חזרה. הפעלות פעילות במכשירים אחרים עלולות להישאר פעילות למשך עד שעה אחת."
@@ -3665,7 +3665,7 @@
"description": "Message appearing below the autofill on load message when master password reprompt is set for a vault item."
},
"toggleSideNavigation": {
- "message": "פתח או סגור ניווט צדדי"
+ "message": "החלף מצב ניווט צדדי"
},
"skipToContent": {
"message": "דלג לתוכן"
@@ -3926,7 +3926,7 @@
"message": "אשר סיסמת קובץ"
},
"exportSuccess": {
- "message": "נתוני כספת יוצאו"
+ "message": "נתוני הכספת יוצאו"
},
"typePasskey": {
"message": "מפתח גישה"
@@ -3935,7 +3935,7 @@
"message": "ניגש אל"
},
"loggedInExclamation": {
- "message": "מחובר!"
+ "message": "נכנסת!"
},
"passkeyNotCopied": {
"message": "מפתח גישה לא יועתק"
@@ -4310,7 +4310,7 @@
"message": "העתק כתובת"
},
"adminConsole": {
- "message": "מסוף ניהול"
+ "message": "מסוף מנהל"
},
"accountSecurity": {
"message": "אבטחת החשבון"
@@ -4733,15 +4733,6 @@
"nothingSelected": {
"message": "לא בחרת כלום."
},
- "movedItemsToOrg": {
- "message": "פריטים נבחרים הועברו אל $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "פריטים הועברו אל $ORGNAME$",
"placeholders": {
@@ -4841,7 +4832,7 @@
"message": "תוכן נוסף זמין"
},
"fileSavedToDevice": {
- "message": "קובץ נשמר למכשיר. נהל מהורדות המכשיר שלך."
+ "message": "הקובץ נשמר למכשיר. נהל מהורדות המכשיר שלך."
},
"showCharacterCount": {
"message": "הצג מונה תווים"
diff --git a/apps/browser/src/_locales/hi/messages.json b/apps/browser/src/_locales/hi/messages.json
index 3a7a5fdf591..748d9eb966b 100644
--- a/apps/browser/src/_locales/hi/messages.json
+++ b/apps/browser/src/_locales/hi/messages.json
@@ -4733,15 +4733,6 @@
"nothingSelected": {
"message": "You have not selected anything."
},
- "movedItemsToOrg": {
- "message": "Selected items moved to $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Items moved to $ORGNAME$",
"placeholders": {
diff --git a/apps/browser/src/_locales/hr/messages.json b/apps/browser/src/_locales/hr/messages.json
index b0c865e6321..400e70b864f 100644
--- a/apps/browser/src/_locales/hr/messages.json
+++ b/apps/browser/src/_locales/hr/messages.json
@@ -4733,15 +4733,6 @@
"nothingSelected": {
"message": "Ništa nije odabrano."
},
- "movedItemsToOrg": {
- "message": "Odabrane stavke premještene u $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Stavke premještene u $ORGNAME$",
"placeholders": {
diff --git a/apps/browser/src/_locales/hu/messages.json b/apps/browser/src/_locales/hu/messages.json
index 066bb669552..80b1dbf095d 100644
--- a/apps/browser/src/_locales/hu/messages.json
+++ b/apps/browser/src/_locales/hu/messages.json
@@ -4733,15 +4733,6 @@
"nothingSelected": {
"message": "You have not selected anything."
},
- "movedItemsToOrg": {
- "message": "Selected items moved to $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Items moved to $ORGNAME$",
"placeholders": {
diff --git a/apps/browser/src/_locales/id/messages.json b/apps/browser/src/_locales/id/messages.json
index 4056b5ca09b..33f44c60579 100644
--- a/apps/browser/src/_locales/id/messages.json
+++ b/apps/browser/src/_locales/id/messages.json
@@ -4733,15 +4733,6 @@
"nothingSelected": {
"message": "You have not selected anything."
},
- "movedItemsToOrg": {
- "message": "Selected items moved to $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Items moved to $ORGNAME$",
"placeholders": {
diff --git a/apps/browser/src/_locales/it/messages.json b/apps/browser/src/_locales/it/messages.json
index 813da3582f9..cb8e414ca37 100644
--- a/apps/browser/src/_locales/it/messages.json
+++ b/apps/browser/src/_locales/it/messages.json
@@ -4733,15 +4733,6 @@
"nothingSelected": {
"message": "Non hai selezionato nulla."
},
- "movedItemsToOrg": {
- "message": "Elementi selezionati spostati in $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Elementi spostati su $ORGNAME$",
"placeholders": {
diff --git a/apps/browser/src/_locales/ja/messages.json b/apps/browser/src/_locales/ja/messages.json
index 7043070d899..b67d0b62a6e 100644
--- a/apps/browser/src/_locales/ja/messages.json
+++ b/apps/browser/src/_locales/ja/messages.json
@@ -4733,15 +4733,6 @@
"nothingSelected": {
"message": "何も選択されていません。"
},
- "movedItemsToOrg": {
- "message": "選択したアイテムを $ORGNAME$ に移動しました",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "アイテムを $ORGNAME$ に移動しました",
"placeholders": {
diff --git a/apps/browser/src/_locales/ka/messages.json b/apps/browser/src/_locales/ka/messages.json
index 930ef958484..818d7cdcd19 100644
--- a/apps/browser/src/_locales/ka/messages.json
+++ b/apps/browser/src/_locales/ka/messages.json
@@ -4733,15 +4733,6 @@
"nothingSelected": {
"message": "You have not selected anything."
},
- "movedItemsToOrg": {
- "message": "Selected items moved to $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Items moved to $ORGNAME$",
"placeholders": {
diff --git a/apps/browser/src/_locales/km/messages.json b/apps/browser/src/_locales/km/messages.json
index a9a7c75ecac..c9c29611deb 100644
--- a/apps/browser/src/_locales/km/messages.json
+++ b/apps/browser/src/_locales/km/messages.json
@@ -4733,15 +4733,6 @@
"nothingSelected": {
"message": "You have not selected anything."
},
- "movedItemsToOrg": {
- "message": "Selected items moved to $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Items moved to $ORGNAME$",
"placeholders": {
diff --git a/apps/browser/src/_locales/kn/messages.json b/apps/browser/src/_locales/kn/messages.json
index 2d22e32fa86..c8aff3a6488 100644
--- a/apps/browser/src/_locales/kn/messages.json
+++ b/apps/browser/src/_locales/kn/messages.json
@@ -4733,15 +4733,6 @@
"nothingSelected": {
"message": "You have not selected anything."
},
- "movedItemsToOrg": {
- "message": "Selected items moved to $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Items moved to $ORGNAME$",
"placeholders": {
diff --git a/apps/browser/src/_locales/ko/messages.json b/apps/browser/src/_locales/ko/messages.json
index 304124dacb0..cd54ac47506 100644
--- a/apps/browser/src/_locales/ko/messages.json
+++ b/apps/browser/src/_locales/ko/messages.json
@@ -4733,15 +4733,6 @@
"nothingSelected": {
"message": "아무것도 선택하지 않았습니다."
},
- "movedItemsToOrg": {
- "message": "선택한 항목이 $ORGNAME$(으)로 이동됨",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "항목들이 $ORGNAME$로 이동했습니다",
"placeholders": {
diff --git a/apps/browser/src/_locales/lt/messages.json b/apps/browser/src/_locales/lt/messages.json
index a504f44cf59..8bf8a8f7518 100644
--- a/apps/browser/src/_locales/lt/messages.json
+++ b/apps/browser/src/_locales/lt/messages.json
@@ -4733,15 +4733,6 @@
"nothingSelected": {
"message": "You have not selected anything."
},
- "movedItemsToOrg": {
- "message": "Selected items moved to $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Items moved to $ORGNAME$",
"placeholders": {
diff --git a/apps/browser/src/_locales/lv/messages.json b/apps/browser/src/_locales/lv/messages.json
index b41c0968770..7b5f077f69d 100644
--- a/apps/browser/src/_locales/lv/messages.json
+++ b/apps/browser/src/_locales/lv/messages.json
@@ -4733,15 +4733,6 @@
"nothingSelected": {
"message": "Nekas nav atlasīts."
},
- "movedItemsToOrg": {
- "message": "Atzīmētie vienumi pārvietoti uz $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Vienumi pārvietoti uz $ORGNAME$",
"placeholders": {
diff --git a/apps/browser/src/_locales/ml/messages.json b/apps/browser/src/_locales/ml/messages.json
index 3ed24789099..24a096db0ef 100644
--- a/apps/browser/src/_locales/ml/messages.json
+++ b/apps/browser/src/_locales/ml/messages.json
@@ -4733,15 +4733,6 @@
"nothingSelected": {
"message": "You have not selected anything."
},
- "movedItemsToOrg": {
- "message": "Selected items moved to $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Items moved to $ORGNAME$",
"placeholders": {
diff --git a/apps/browser/src/_locales/mr/messages.json b/apps/browser/src/_locales/mr/messages.json
index da46837ab94..9a49998d3d9 100644
--- a/apps/browser/src/_locales/mr/messages.json
+++ b/apps/browser/src/_locales/mr/messages.json
@@ -4733,15 +4733,6 @@
"nothingSelected": {
"message": "You have not selected anything."
},
- "movedItemsToOrg": {
- "message": "Selected items moved to $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Items moved to $ORGNAME$",
"placeholders": {
diff --git a/apps/browser/src/_locales/my/messages.json b/apps/browser/src/_locales/my/messages.json
index a9a7c75ecac..c9c29611deb 100644
--- a/apps/browser/src/_locales/my/messages.json
+++ b/apps/browser/src/_locales/my/messages.json
@@ -4733,15 +4733,6 @@
"nothingSelected": {
"message": "You have not selected anything."
},
- "movedItemsToOrg": {
- "message": "Selected items moved to $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Items moved to $ORGNAME$",
"placeholders": {
diff --git a/apps/browser/src/_locales/nb/messages.json b/apps/browser/src/_locales/nb/messages.json
index 6df109cadd7..0ff4ed9486a 100644
--- a/apps/browser/src/_locales/nb/messages.json
+++ b/apps/browser/src/_locales/nb/messages.json
@@ -4733,15 +4733,6 @@
"nothingSelected": {
"message": "Du har ikke valgt noe."
},
- "movedItemsToOrg": {
- "message": "De valgte gjenstandene ble flyttet til $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Gjenstandene ble flyttet til $ORGNAME$",
"placeholders": {
diff --git a/apps/browser/src/_locales/ne/messages.json b/apps/browser/src/_locales/ne/messages.json
index a9a7c75ecac..c9c29611deb 100644
--- a/apps/browser/src/_locales/ne/messages.json
+++ b/apps/browser/src/_locales/ne/messages.json
@@ -4733,15 +4733,6 @@
"nothingSelected": {
"message": "You have not selected anything."
},
- "movedItemsToOrg": {
- "message": "Selected items moved to $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Items moved to $ORGNAME$",
"placeholders": {
diff --git a/apps/browser/src/_locales/nl/messages.json b/apps/browser/src/_locales/nl/messages.json
index 4b099100bb3..65d8d6f740f 100644
--- a/apps/browser/src/_locales/nl/messages.json
+++ b/apps/browser/src/_locales/nl/messages.json
@@ -4733,15 +4733,6 @@
"nothingSelected": {
"message": "Je hebt niets geselecteerd."
},
- "movedItemsToOrg": {
- "message": "Geselecteerde items verplaatst naar $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Items verplaatst naar $ORGNAME$",
"placeholders": {
diff --git a/apps/browser/src/_locales/nn/messages.json b/apps/browser/src/_locales/nn/messages.json
index a9a7c75ecac..c9c29611deb 100644
--- a/apps/browser/src/_locales/nn/messages.json
+++ b/apps/browser/src/_locales/nn/messages.json
@@ -4733,15 +4733,6 @@
"nothingSelected": {
"message": "You have not selected anything."
},
- "movedItemsToOrg": {
- "message": "Selected items moved to $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Items moved to $ORGNAME$",
"placeholders": {
diff --git a/apps/browser/src/_locales/or/messages.json b/apps/browser/src/_locales/or/messages.json
index a9a7c75ecac..c9c29611deb 100644
--- a/apps/browser/src/_locales/or/messages.json
+++ b/apps/browser/src/_locales/or/messages.json
@@ -4733,15 +4733,6 @@
"nothingSelected": {
"message": "You have not selected anything."
},
- "movedItemsToOrg": {
- "message": "Selected items moved to $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Items moved to $ORGNAME$",
"placeholders": {
diff --git a/apps/browser/src/_locales/pl/messages.json b/apps/browser/src/_locales/pl/messages.json
index fbca7ef1988..f4c1303bd18 100644
--- a/apps/browser/src/_locales/pl/messages.json
+++ b/apps/browser/src/_locales/pl/messages.json
@@ -4733,15 +4733,6 @@
"nothingSelected": {
"message": "Nie zaznaczyłeś żadnych elementów."
},
- "movedItemsToOrg": {
- "message": "Zaznaczone elementy zostały przeniesione do $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Elementy przeniesione do $ORGNAME$",
"placeholders": {
diff --git a/apps/browser/src/_locales/pt_BR/messages.json b/apps/browser/src/_locales/pt_BR/messages.json
index d4ce34de17c..0b5d569f443 100644
--- a/apps/browser/src/_locales/pt_BR/messages.json
+++ b/apps/browser/src/_locales/pt_BR/messages.json
@@ -4733,15 +4733,6 @@
"nothingSelected": {
"message": "Você selecionou nada."
},
- "movedItemsToOrg": {
- "message": "Itens selecionados movidos para $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Itens movidos para $ORGNAME$",
"placeholders": {
diff --git a/apps/browser/src/_locales/pt_PT/messages.json b/apps/browser/src/_locales/pt_PT/messages.json
index a9ab857ae67..c1f1f51609c 100644
--- a/apps/browser/src/_locales/pt_PT/messages.json
+++ b/apps/browser/src/_locales/pt_PT/messages.json
@@ -4733,15 +4733,6 @@
"nothingSelected": {
"message": "Não selecionou nada."
},
- "movedItemsToOrg": {
- "message": "Itens selecionados movidos para $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Itens movidos para $ORGNAME$",
"placeholders": {
diff --git a/apps/browser/src/_locales/ro/messages.json b/apps/browser/src/_locales/ro/messages.json
index 402a2e82559..5ce73eb2e49 100644
--- a/apps/browser/src/_locales/ro/messages.json
+++ b/apps/browser/src/_locales/ro/messages.json
@@ -4733,15 +4733,6 @@
"nothingSelected": {
"message": "You have not selected anything."
},
- "movedItemsToOrg": {
- "message": "Selected items moved to $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Items moved to $ORGNAME$",
"placeholders": {
diff --git a/apps/browser/src/_locales/ru/messages.json b/apps/browser/src/_locales/ru/messages.json
index e2a1faf9885..11d8de4acbe 100644
--- a/apps/browser/src/_locales/ru/messages.json
+++ b/apps/browser/src/_locales/ru/messages.json
@@ -878,7 +878,7 @@
"message": "Нажмите на YubiKey для аутентификации"
},
"duoTwoFactorRequiredPageSubtitle": {
- "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in."
+ "message": "Для вашего аккаунта требуется двухэтапная аутентификация Duo. Выполните следующие действия, чтобы завершить авторизацию."
},
"followTheStepsBelowToFinishLoggingIn": {
"message": "Следуйте указаниям ниже, чтобы завершить авторизацию."
@@ -1040,7 +1040,7 @@
"message": "Кликните элементы для автозаполнения в режиме просмотра хранилища"
},
"clickToAutofill": {
- "message": "Click items in autofill suggestion to fill"
+ "message": "Выберите элементы в предложении автозаполнения для вставки"
},
"clearClipboard": {
"message": "Очистить буфер обмена",
@@ -2164,7 +2164,7 @@
"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": "Действие по тайм-ауту хранилища"
@@ -2476,7 +2476,7 @@
"message": "Пароли, подверженные риску"
},
"atRiskPasswordDescSingleOrg": {
- "message": "$ORGANIZATION$ запрашивает смену одного пароля, так как он находится под угрозой.",
+ "message": "$ORGANIZATION$ запрашивает смену одного пароля, так как он подвержен риску.",
"placeholders": {
"organization": {
"content": "$1",
@@ -2485,7 +2485,7 @@
}
},
"atRiskPasswordsDescSingleOrgPlural": {
- "message": "$ORGANIZATION$ запрашивает смену $COUNT$ паролей, так как они находятся под угрозой.",
+ "message": "$ORGANIZATION$ запрашивает смену $COUNT$ паролей, так как они подвержены риску.",
"placeholders": {
"organization": {
"content": "$1",
@@ -2498,7 +2498,7 @@
}
},
"atRiskPasswordsDescMultiOrgPlural": {
- "message": "Ваша организация запрашивает смену $COUNT$ паролей, так как они находятся под угрозой.",
+ "message": "Ваша организация запрашивает смену $COUNT$ паролей, так как они подвержены риску.",
"placeholders": {
"count": {
"content": "$1",
@@ -2507,10 +2507,10 @@
}
},
"reviewAndChangeAtRiskPassword": {
- "message": "Review and change one at-risk password"
+ "message": "Проверить и изменить один пароль, подверженный риску"
},
"reviewAndChangeAtRiskPasswordsPlural": {
- "message": "Review and change $COUNT$ at-risk passwords",
+ "message": "Проверить и изменить $COUNT$ паролей, подверженных риску",
"placeholders": {
"count": {
"content": "$1",
@@ -2519,26 +2519,26 @@
}
},
"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": "Обзор логинов, находящихся под угрозой"
+ "message": "Обзор логинов, подверженных риску"
},
"reviewAtRiskPasswords": {
- "message": "Обзор паролей, находящихся под угрозой"
+ "message": "Обзор паролей, подверженных риску"
},
"reviewAtRiskLoginsSlideDesc": {
- "message": "Пароли вашей организации находятся под угрозой, потому что они слабые, повторно используются и/или раскрыты.",
+ "message": "Пароли вашей организации подвержены риску, потому что они слабые, повторно используются и/или раскрыты.",
"description": "Description of the review at-risk login slide on the at-risk password page carousel"
},
"reviewAtRiskLoginSlideImgAlt": {
- "message": "Иллюстрация списка логинов, которые находятся под угрозой"
+ "message": "Иллюстрация списка логинов, которые подвержены риску"
},
"generatePasswordSlideDesc": {
- "message": "Быстро сгенерируйте надежный уникальный пароль с помощью меню автозаполнения Bitwarden на сайте, находящемся под угрозой.",
+ "message": "Быстро сгенерируйте надежный уникальный пароль с помощью меню автозаполнения Bitwarden на сайте, подверженном риску.",
"description": "Description of the generate password slide on the at-risk password page carousel"
},
"generatePasswordSlideImgAlt": {
@@ -2555,13 +2555,13 @@
"message": "Иллюстрация уведомления Bitwarden, предлагающего пользователю обновить логин"
},
"turnOnAutofill": {
- "message": "Turn on autofill"
+ "message": "Включить автозаполнение"
},
"turnedOnAutofill": {
- "message": "Turned on autofill"
+ "message": "Автозаполнение включено"
},
"dismiss": {
- "message": "Dismiss"
+ "message": "Отклонить"
},
"websiteItemLabel": {
"message": "Сайт $number$ (URI)",
@@ -4733,15 +4733,6 @@
"nothingSelected": {
"message": "Вы ничего не выбрали."
},
- "movedItemsToOrg": {
- "message": "Выбранные элементы перемещены в $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Элементы перемещены в $ORGNAME$",
"placeholders": {
@@ -5147,6 +5138,6 @@
"message": "Чтобы использовать биометрическую разблокировку, обновите приложение для компьютера или отключите разблокировку по отпечатку пальца в настройках компьютера."
},
"changeAtRiskPassword": {
- "message": "Изменить пароль, находящийся под угрозой"
+ "message": "Изменить пароль, подверженный риску"
}
}
diff --git a/apps/browser/src/_locales/si/messages.json b/apps/browser/src/_locales/si/messages.json
index 25a5029e159..721d16a2eee 100644
--- a/apps/browser/src/_locales/si/messages.json
+++ b/apps/browser/src/_locales/si/messages.json
@@ -4733,15 +4733,6 @@
"nothingSelected": {
"message": "You have not selected anything."
},
- "movedItemsToOrg": {
- "message": "Selected items moved to $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Items moved to $ORGNAME$",
"placeholders": {
diff --git a/apps/browser/src/_locales/sk/messages.json b/apps/browser/src/_locales/sk/messages.json
index 90d325693ce..897db9f3176 100644
--- a/apps/browser/src/_locales/sk/messages.json
+++ b/apps/browser/src/_locales/sk/messages.json
@@ -4733,15 +4733,6 @@
"nothingSelected": {
"message": "Nič ste nevybrali."
},
- "movedItemsToOrg": {
- "message": "Vybraté položky boli presunuté do $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Položky presunuté do $ORGNAME$",
"placeholders": {
diff --git a/apps/browser/src/_locales/sl/messages.json b/apps/browser/src/_locales/sl/messages.json
index 1faf75ec920..f6a543ea5ea 100644
--- a/apps/browser/src/_locales/sl/messages.json
+++ b/apps/browser/src/_locales/sl/messages.json
@@ -4733,15 +4733,6 @@
"nothingSelected": {
"message": "You have not selected anything."
},
- "movedItemsToOrg": {
- "message": "Selected items moved to $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Items moved to $ORGNAME$",
"placeholders": {
diff --git a/apps/browser/src/_locales/sr/messages.json b/apps/browser/src/_locales/sr/messages.json
index 1bd6222eb82..3c7bef94c13 100644
--- a/apps/browser/src/_locales/sr/messages.json
+++ b/apps/browser/src/_locales/sr/messages.json
@@ -4733,15 +4733,6 @@
"nothingSelected": {
"message": "Нисте ништа изабрали."
},
- "movedItemsToOrg": {
- "message": "Одабране ставке премештене у $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Ставке премештене у $ORGNAME$",
"placeholders": {
diff --git a/apps/browser/src/_locales/sv/messages.json b/apps/browser/src/_locales/sv/messages.json
index a47cc398fa5..6db7a22490e 100644
--- a/apps/browser/src/_locales/sv/messages.json
+++ b/apps/browser/src/_locales/sv/messages.json
@@ -4733,15 +4733,6 @@
"nothingSelected": {
"message": "You have not selected anything."
},
- "movedItemsToOrg": {
- "message": "Selected items moved to $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Items moved to $ORGNAME$",
"placeholders": {
diff --git a/apps/browser/src/_locales/te/messages.json b/apps/browser/src/_locales/te/messages.json
index a9a7c75ecac..c9c29611deb 100644
--- a/apps/browser/src/_locales/te/messages.json
+++ b/apps/browser/src/_locales/te/messages.json
@@ -4733,15 +4733,6 @@
"nothingSelected": {
"message": "You have not selected anything."
},
- "movedItemsToOrg": {
- "message": "Selected items moved to $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Items moved to $ORGNAME$",
"placeholders": {
diff --git a/apps/browser/src/_locales/th/messages.json b/apps/browser/src/_locales/th/messages.json
index 409c4e4925f..c545f802d64 100644
--- a/apps/browser/src/_locales/th/messages.json
+++ b/apps/browser/src/_locales/th/messages.json
@@ -4733,15 +4733,6 @@
"nothingSelected": {
"message": "You have not selected anything."
},
- "movedItemsToOrg": {
- "message": "Selected items moved to $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Items moved to $ORGNAME$",
"placeholders": {
diff --git a/apps/browser/src/_locales/tr/messages.json b/apps/browser/src/_locales/tr/messages.json
index 275db28e89c..e69b33d63af 100644
--- a/apps/browser/src/_locales/tr/messages.json
+++ b/apps/browser/src/_locales/tr/messages.json
@@ -4733,15 +4733,6 @@
"nothingSelected": {
"message": "Hiçbir şey seçmediniz."
},
- "movedItemsToOrg": {
- "message": "Selected items moved to $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Kayıtlar $ORGNAME$ kuruluşuna taşındı",
"placeholders": {
diff --git a/apps/browser/src/_locales/uk/messages.json b/apps/browser/src/_locales/uk/messages.json
index e214154212a..d18d90babff 100644
--- a/apps/browser/src/_locales/uk/messages.json
+++ b/apps/browser/src/_locales/uk/messages.json
@@ -4733,15 +4733,6 @@
"nothingSelected": {
"message": "Ви нічого не вибрали."
},
- "movedItemsToOrg": {
- "message": "Вибрані записи переміщено до $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Записи переміщено до $ORGNAME$",
"placeholders": {
diff --git a/apps/browser/src/_locales/vi/messages.json b/apps/browser/src/_locales/vi/messages.json
index 7de0316d682..db0da3b5874 100644
--- a/apps/browser/src/_locales/vi/messages.json
+++ b/apps/browser/src/_locales/vi/messages.json
@@ -4733,15 +4733,6 @@
"nothingSelected": {
"message": "Bạn chưa chọn gì."
},
- "movedItemsToOrg": {
- "message": "Đã chuyển các mục được chọn đến $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Các mục đã được chuyển tới $ORGNAME$",
"placeholders": {
diff --git a/apps/browser/src/_locales/zh_CN/messages.json b/apps/browser/src/_locales/zh_CN/messages.json
index f3d5527e713..4595b20ddbf 100644
--- a/apps/browser/src/_locales/zh_CN/messages.json
+++ b/apps/browser/src/_locales/zh_CN/messages.json
@@ -3543,7 +3543,7 @@
"message": "搜索"
},
"inputMinLength": {
- "message": "至少输入 $COUNT$ 个字符。",
+ "message": "输入长度不能低于 $COUNT$ 个字符。",
"placeholders": {
"count": {
"content": "$1",
@@ -4733,15 +4733,6 @@
"nothingSelected": {
"message": "您尚未选择任何内容。"
},
- "movedItemsToOrg": {
- "message": "所选项目已移动到 $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "项目已移动到 $ORGNAME$",
"placeholders": {
diff --git a/apps/browser/src/_locales/zh_TW/messages.json b/apps/browser/src/_locales/zh_TW/messages.json
index eb7d996b0f6..37e450c8fd2 100644
--- a/apps/browser/src/_locales/zh_TW/messages.json
+++ b/apps/browser/src/_locales/zh_TW/messages.json
@@ -4733,15 +4733,6 @@
"nothingSelected": {
"message": "您沒有選擇任何項目。"
},
- "movedItemsToOrg": {
- "message": "將已選取項目移動至 $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "項目已移到 $ORGNAME$",
"placeholders": {
From ce5e734130638045bf00b78b321de1a2adee1358 Mon Sep 17 00:00:00 2001
From: "bw-ghapp[bot]" <178206702+bw-ghapp[bot]@users.noreply.github.com>
Date: Wed, 19 Mar 2025 18:11:01 +0000
Subject: [PATCH 011/113] Autosync the updated translations (#13902)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
---
apps/desktop/src/locales/de/messages.json | 2 +-
apps/desktop/src/locales/he/messages.json | 14 +++++++-------
apps/desktop/src/locales/ru/messages.json | 12 ++++++------
apps/desktop/src/locales/zh_CN/messages.json | 2 +-
4 files changed, 15 insertions(+), 15 deletions(-)
diff --git a/apps/desktop/src/locales/de/messages.json b/apps/desktop/src/locales/de/messages.json
index c91444947e5..79f61475ab0 100644
--- a/apps/desktop/src/locales/de/messages.json
+++ b/apps/desktop/src/locales/de/messages.json
@@ -3518,7 +3518,7 @@
"message": "Schlüssel aus Zwischenablage importieren"
},
"sshKeyImported": {
- "message": "SSH key imported successfully"
+ "message": "SSH-Schlüssel erfolgreich importiert"
},
"fileSavedToDevice": {
"message": "Datei auf Gerät gespeichert. Greife darauf über die Downloads deines Geräts zu."
diff --git a/apps/desktop/src/locales/he/messages.json b/apps/desktop/src/locales/he/messages.json
index e673f3c8f1f..7f98ab977ae 100644
--- a/apps/desktop/src/locales/he/messages.json
+++ b/apps/desktop/src/locales/he/messages.json
@@ -532,7 +532,7 @@
"description": "Label for the password generator lowercase character checkbox"
},
"numbersDescription": {
- "message": "העתק מספרים",
+ "message": "כלול מספרים",
"description": "Full description for the password generator numbers checkbox"
},
"numbersLabel": {
@@ -825,7 +825,7 @@
"message": "האימות בוטל או לקח זמן רב מדי. נא לנסות שוב."
},
"openInNewTab": {
- "message": "פתח בכרטיסייה חדשה"
+ "message": "פתח בכרטיסיה חדשה"
},
"invalidVerificationCode": {
"message": "קוד אימות שגוי"
@@ -2305,7 +2305,7 @@
"message": "עדכון סיסמה ראשית"
},
"updateMasterPasswordWarning": {
- "message": "הסיסמה הראשית שלך שונתה לאחרונה על ידי מנהל הארגון שלך. כדי לגשת לכספת, עליך לעדכן אותה כעת. המשך התהליך יוציא אותך מההפעלה הנוכחית שלך ותידרש להיכנס חזרה. הפעלות פעילות במכשירים אחרים עלולות להישאר פעילות למשך עד שעה אחת."
+ "message": "הסיסמה הראשית שלך שונתה לאחרונה על ידי מנהל הארגון שלך. כדי לגשת לכספת, עליך לעדכן אותה כעת. המשך התהליך יוציא אותך מההפעלה הנוכחית שלך, מה שידרוש ממך להיכנס חזרה. הפעלות פעילות במכשירים אחרים עלולות להישאר פעילות למשך עד שעה אחת."
},
"updateWeakMasterPasswordWarning": {
"message": "הסיסמה הראשית שלך אינה עומדת באחת או יותר מפוליסות הארגון שלך. כדי לגשת לכספת, אתה מוכרח לעדכן את הסיסמה הראשית שלך עכשיו. בהמשך תנותק מההפעלה הנוכחית שלך, מה שידרוש ממך להיכנס חזרה. הפעלות פעילות במכשירים אחרים עלולות להישאר פעילות למשך עד שעה אחת."
@@ -3155,7 +3155,7 @@
"message": "תפריט משנה"
},
"toggleSideNavigation": {
- "message": "פתח או סגור ניווט צדדי"
+ "message": "החלף מצב ניווט צדדי"
},
"skipToContent": {
"message": "דלג לתוכן"
@@ -3249,7 +3249,7 @@
"message": "למד על אפשרויות הייבוא שלך"
},
"selectImportFolder": {
- "message": "בחר תיקיה"
+ "message": "בחר תיקייה"
},
"selectImportCollection": {
"message": "בחר אוסף"
@@ -3302,7 +3302,7 @@
"message": "אשר סיסמת קובץ"
},
"exportSuccess": {
- "message": "נתוני כספת יוצאו"
+ "message": "נתוני הכספת יוצאו"
},
"multifactorAuthenticationCancelled": {
"message": "אימות רב־גורמי בוטל"
@@ -3521,7 +3521,7 @@
"message": "מפתח SSH יובא בהצלחה"
},
"fileSavedToDevice": {
- "message": "קובץ נשמר למכשיר. נהל מהורדות המכשיר שלך."
+ "message": "הקובץ נשמר למכשיר. נהל מהורדות המכשיר שלך."
},
"importantNotice": {
"message": "הודעה חשובה"
diff --git a/apps/desktop/src/locales/ru/messages.json b/apps/desktop/src/locales/ru/messages.json
index 4178f3a6739..ea5b8c6d526 100644
--- a/apps/desktop/src/locales/ru/messages.json
+++ b/apps/desktop/src/locales/ru/messages.json
@@ -3222,7 +3222,7 @@
"message": "Для вашего аккаунта требуется двухэтапная аутентификация Duo."
},
"duoTwoFactorRequiredPageSubtitle": {
- "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in."
+ "message": "Для вашего аккаунта требуется двухэтапная аутентификация Duo. Выполните следующие действия, чтобы завершить авторизацию."
},
"followTheStepsBelowToFinishLoggingIn": {
"message": "Следуйте указаниям ниже, чтобы завершить авторизацию."
@@ -3488,7 +3488,7 @@
"message": "Предупреждение: Переадресация агента"
},
"agentForwardingWarningText": {
- "message": "Этот запрос поступает с удаленного устройства, на которое вы вошли"
+ "message": "Этот запрос поступает с удаленного устройства, на котором вы авторизовались"
},
"sshkeyApprovalMessageInfix": {
"message": "запрашивает доступ к"
@@ -3500,10 +3500,10 @@
"message": "авторизоваться на сервере"
},
"sshActionSign": {
- "message": "sign a message"
+ "message": "подписать сообщение"
},
"sshActionGitSign": {
- "message": "sign a git commit"
+ "message": "подписать git коммит"
},
"unknownApplication": {
"message": "Приложение"
@@ -3569,7 +3569,7 @@
"message": "Окно подтверждения остается видимым"
},
"confirmWindowStillVisibleContent": {
- "message": "Убедитесь, что окно по-прежнему видно."
+ "message": "Пожалуйста, подтвердите, что окно все еще видно."
},
"updateBrowserOrDisableFingerprintDialogTitle": {
"message": "Необходимо обновить расширение"
@@ -3578,6 +3578,6 @@
"message": "Используемое вами расширение браузера устарело. Пожалуйста, обновите его или отключите проверку интеграции браузера с помощью отпечатка пальца в настройках приложения для компьютера."
},
"changeAtRiskPassword": {
- "message": "Изменить пароль, находящийся под угрозой"
+ "message": "Изменить пароль, подверженный риску"
}
}
diff --git a/apps/desktop/src/locales/zh_CN/messages.json b/apps/desktop/src/locales/zh_CN/messages.json
index 9e9abaf48a2..8bf4d16c474 100644
--- a/apps/desktop/src/locales/zh_CN/messages.json
+++ b/apps/desktop/src/locales/zh_CN/messages.json
@@ -3064,7 +3064,7 @@
"message": "搜索"
},
"inputMinLength": {
- "message": "至少输入 $COUNT$ 个字符。",
+ "message": "输入长度不能低于 $COUNT$ 个字符。",
"placeholders": {
"count": {
"content": "$1",
From c9f7bb38a47777632d5a01b54a332bd3310090fa Mon Sep 17 00:00:00 2001
From: "bw-ghapp[bot]" <178206702+bw-ghapp[bot]@users.noreply.github.com>
Date: Wed, 19 Mar 2025 18:11:32 +0000
Subject: [PATCH 012/113] Autosync the updated translations (#13904)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
---
apps/web/src/locales/af/messages.json | 32 -
apps/web/src/locales/ar/messages.json | 32 -
apps/web/src/locales/az/messages.json | 32 -
apps/web/src/locales/be/messages.json | 32 -
apps/web/src/locales/bg/messages.json | 32 -
apps/web/src/locales/bn/messages.json | 32 -
apps/web/src/locales/bs/messages.json | 32 -
apps/web/src/locales/ca/messages.json | 32 -
apps/web/src/locales/cs/messages.json | 32 -
apps/web/src/locales/cy/messages.json | 32 -
apps/web/src/locales/da/messages.json | 32 -
apps/web/src/locales/de/messages.json | 68 +-
apps/web/src/locales/el/messages.json | 32 -
apps/web/src/locales/en_GB/messages.json | 32 -
apps/web/src/locales/en_IN/messages.json | 32 -
apps/web/src/locales/eo/messages.json | 32 -
apps/web/src/locales/es/messages.json | 32 -
apps/web/src/locales/et/messages.json | 32 -
apps/web/src/locales/eu/messages.json | 32 -
apps/web/src/locales/fa/messages.json | 32 -
apps/web/src/locales/fi/messages.json | 32 -
apps/web/src/locales/fil/messages.json | 32 -
apps/web/src/locales/fr/messages.json | 32 -
apps/web/src/locales/gl/messages.json | 32 -
apps/web/src/locales/he/messages.json | 1956 +++++++++++-----------
apps/web/src/locales/hi/messages.json | 32 -
apps/web/src/locales/hr/messages.json | 32 -
apps/web/src/locales/hu/messages.json | 32 -
apps/web/src/locales/id/messages.json | 144 +-
apps/web/src/locales/it/messages.json | 294 ++--
apps/web/src/locales/ja/messages.json | 32 -
apps/web/src/locales/ka/messages.json | 32 -
apps/web/src/locales/km/messages.json | 32 -
apps/web/src/locales/kn/messages.json | 32 -
apps/web/src/locales/ko/messages.json | 32 -
apps/web/src/locales/lv/messages.json | 32 -
apps/web/src/locales/ml/messages.json | 32 -
apps/web/src/locales/mr/messages.json | 32 -
apps/web/src/locales/my/messages.json | 32 -
apps/web/src/locales/nb/messages.json | 32 -
apps/web/src/locales/ne/messages.json | 32 -
apps/web/src/locales/nl/messages.json | 32 -
apps/web/src/locales/nn/messages.json | 32 -
apps/web/src/locales/or/messages.json | 32 -
apps/web/src/locales/pl/messages.json | 32 -
apps/web/src/locales/pt_BR/messages.json | 32 -
apps/web/src/locales/pt_PT/messages.json | 32 -
apps/web/src/locales/ro/messages.json | 32 -
apps/web/src/locales/ru/messages.json | 62 +-
apps/web/src/locales/si/messages.json | 32 -
apps/web/src/locales/sk/messages.json | 32 -
apps/web/src/locales/sl/messages.json | 32 -
apps/web/src/locales/sr/messages.json | 32 -
apps/web/src/locales/sr_CS/messages.json | 32 -
apps/web/src/locales/sv/messages.json | 32 -
apps/web/src/locales/te/messages.json | 32 -
apps/web/src/locales/th/messages.json | 32 -
apps/web/src/locales/tr/messages.json | 32 -
apps/web/src/locales/uk/messages.json | 72 +-
apps/web/src/locales/vi/messages.json | 32 -
apps/web/src/locales/zh_CN/messages.json | 52 +-
apps/web/src/locales/zh_TW/messages.json | 32 -
62 files changed, 1212 insertions(+), 3196 deletions(-)
diff --git a/apps/web/src/locales/af/messages.json b/apps/web/src/locales/af/messages.json
index 8afd28eb559..3b9168b29b2 100644
--- a/apps/web/src/locales/af/messages.json
+++ b/apps/web/src/locales/af/messages.json
@@ -879,9 +879,6 @@
"filter": {
"message": "Filter"
},
- "moveSelectedToOrg": {
- "message": "Skuif seleksie na organisasie"
- },
"deleteSelected": {
"message": "Skrap seleksie"
},
@@ -937,15 +934,6 @@
}
}
},
- "movedItemsToOrg": {
- "message": "Gekose items is na $ORGNAME$ geskuif",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Items moved to $ORGNAME$",
"placeholders": {
@@ -1571,9 +1559,6 @@
"moveToOrgDesc": {
"message": "Kies ’n organisasie waarheen u hierdie item wil skuif. Deur te skuif kry die organisasie die einaarskap van die item. U is dan nie meer die direkte eienaar van die item wanneer dit geskuif is nie."
},
- "moveManyToOrgDesc": {
- "message": "Kies ’n organisasie waarheen u hierdie items wil skuif. Deur te skuif kry die organisasie die einaarskap van die items. U is dan nie meer die direkte eienaar van die items wanneer dit geskuif is nie."
- },
"collectionsDesc": {
"message": "Edit the collections that this item is being shared with. Only organization users with access to these collections will be able to see this item."
},
@@ -1607,23 +1592,6 @@
}
}
},
- "moveSelectedItemsCountDesc": {
- "message": "U het $COUNT$ item(s) gekies. $MOVEABLE_COUNT$ item(s) kan na ’n organisasie geskuif word, $NONMOVEABLE_COUNT$ kan nie.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "10"
- },
- "moveable_count": {
- "content": "$2",
- "example": "8"
- },
- "nonmoveable_count": {
- "content": "$3",
- "example": "2"
- }
- }
- },
"verificationCodeTotp": {
"message": "Bevestigingskode (TOTP)"
},
diff --git a/apps/web/src/locales/ar/messages.json b/apps/web/src/locales/ar/messages.json
index eccc53e4ef8..13777f942de 100644
--- a/apps/web/src/locales/ar/messages.json
+++ b/apps/web/src/locales/ar/messages.json
@@ -879,9 +879,6 @@
"filter": {
"message": "تصفية"
},
- "moveSelectedToOrg": {
- "message": "نقل العناصر المختارة إلى منظمة"
- },
"deleteSelected": {
"message": "حذف العناصر المختارة"
},
@@ -937,15 +934,6 @@
}
}
},
- "movedItemsToOrg": {
- "message": "تم نقل العناصر المحددة إلى $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Items moved to $ORGNAME$",
"placeholders": {
@@ -1571,9 +1559,6 @@
"moveToOrgDesc": {
"message": "Choose an organization that you wish to move this item to. Moving to an organization transfers ownership of the item to that organization. You will no longer be the direct owner of this item once it has been moved."
},
- "moveManyToOrgDesc": {
- "message": "Choose an organization that you wish to move these items to. Moving to an organization transfers ownership of the items to that organization. You will no longer be the direct owner of these items once they have been moved."
- },
"collectionsDesc": {
"message": "Edit the collections that this item is being shared with. Only organization users with access to these collections will be able to see this item."
},
@@ -1607,23 +1592,6 @@
}
}
},
- "moveSelectedItemsCountDesc": {
- "message": "You have selected $COUNT$ item(s). $MOVEABLE_COUNT$ item(s) can be moved to an organization, $NONMOVEABLE_COUNT$ cannot.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "10"
- },
- "moveable_count": {
- "content": "$2",
- "example": "8"
- },
- "nonmoveable_count": {
- "content": "$3",
- "example": "2"
- }
- }
- },
"verificationCodeTotp": {
"message": "رمز التحقق (TOTP)"
},
diff --git a/apps/web/src/locales/az/messages.json b/apps/web/src/locales/az/messages.json
index ef6d1bbf121..f092620b00a 100644
--- a/apps/web/src/locales/az/messages.json
+++ b/apps/web/src/locales/az/messages.json
@@ -879,9 +879,6 @@
"filter": {
"message": "Filtr"
},
- "moveSelectedToOrg": {
- "message": "Seçiləni təşkilata daşı"
- },
"deleteSelected": {
"message": "Seçiləni sil"
},
@@ -937,15 +934,6 @@
}
}
},
- "movedItemsToOrg": {
- "message": "Seçilən elementlər $ORGNAME$ təşkilatına daşınıldı",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Elementlər bura daşındı: $ORGNAME$",
"placeholders": {
@@ -1571,9 +1559,6 @@
"moveToOrgDesc": {
"message": "Bu elementi daşımaq istədiyiniz təşkilatı seçin. Bir təşkilata daşımaq, elementin sahibliyini də həmin təşkilata daşıyacaq. Daşıdıqdan sonra bu elementə birbaşa sahibliyiniz olmayacaq."
},
- "moveManyToOrgDesc": {
- "message": "Bu elementləri daşımaq istədiyiniz təşkilatı seçin. Bir təşkilata daşımaq, elementin sahibliyini də həmin təşkilata daşıyacaq. Daşıdıqdan sonra bu elementlərə birbaşa sahibliyiniz olmayacaq."
- },
"collectionsDesc": {
"message": "Bu elementin paylaşıldığı kolleksiyalara düzəliş edin. Yalnız bu kolleksiyalara müraciəti olan təşkilat istifadəçiləri bu elementi görə bilər."
},
@@ -1607,23 +1592,6 @@
}
}
},
- "moveSelectedItemsCountDesc": {
- "message": "$COUNT$ element seçdiniz. $MOVEABLE_COUNT$ element bir təşkilata daşınıla bilər, $NONMOVEABLE_COUNT$ ədədi isə daşınıla bilməz.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "10"
- },
- "moveable_count": {
- "content": "$2",
- "example": "8"
- },
- "nonmoveable_count": {
- "content": "$3",
- "example": "2"
- }
- }
- },
"verificationCodeTotp": {
"message": "Doğrulama kodu (TOTP)"
},
diff --git a/apps/web/src/locales/be/messages.json b/apps/web/src/locales/be/messages.json
index bf7eb88efc8..2e5aeabff32 100644
--- a/apps/web/src/locales/be/messages.json
+++ b/apps/web/src/locales/be/messages.json
@@ -879,9 +879,6 @@
"filter": {
"message": "Фільтр"
},
- "moveSelectedToOrg": {
- "message": "Перамясціць выбранае ў арганізацыю"
- },
"deleteSelected": {
"message": "Выдаліць выбраныя"
},
@@ -937,15 +934,6 @@
}
}
},
- "movedItemsToOrg": {
- "message": "Выбраныя элементы перамешчаны ў $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Items moved to $ORGNAME$",
"placeholders": {
@@ -1571,9 +1559,6 @@
"moveToOrgDesc": {
"message": "Выберыце арганізацыю, у якую вы хочаце перамясціць гэты элемент. Пры перамяшчэнні ў арганізацыю ўсе правы ўласнасці на дадзены элемент пяройдуць да гэтай арганізацыі. Вы больш не будзеце адзіным уласнікам гэтага элемента пасля яго перамяшчэння."
},
- "moveManyToOrgDesc": {
- "message": "Выберыце арганізацыю, у якую вы хочаце перамясціць гэтыя элементы. Пры перамяшчэнні ў арганізацыю ўсе правы ўласнасці на дадзеныя элементы пяройдуць да гэтай арганізацыі. Вы больш не будзеце адзіным уласнікам гэтых элементаў пасля іх перамяшчэння."
- },
"collectionsDesc": {
"message": "Рэдагуйце калекцыі, з якімі гэты элемент знаходзіцца ў агульным доступе. Толькі карыстальнікі арганізацыі з доступам да гэтых калекцый змогуць бачыць гэты элемент."
},
@@ -1607,23 +1592,6 @@
}
}
},
- "moveSelectedItemsCountDesc": {
- "message": "Вы выбралі наступную колькасць элементаў: $COUNT$ шт. З іх будуць перамешчаны ў арганізацыю: $MOVEABLE_COUNT$ шт. Не перамешчанымі застануцца: $NONMOVEABLE_COUNT$ шт.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "10"
- },
- "moveable_count": {
- "content": "$2",
- "example": "8"
- },
- "nonmoveable_count": {
- "content": "$3",
- "example": "2"
- }
- }
- },
"verificationCodeTotp": {
"message": "Праверачны код (TOTP)"
},
diff --git a/apps/web/src/locales/bg/messages.json b/apps/web/src/locales/bg/messages.json
index fc1e154b2b0..04d4e25bb7c 100644
--- a/apps/web/src/locales/bg/messages.json
+++ b/apps/web/src/locales/bg/messages.json
@@ -879,9 +879,6 @@
"filter": {
"message": "Филтриране"
},
- "moveSelectedToOrg": {
- "message": "Преместване на избраните в организация"
- },
"deleteSelected": {
"message": "Изтриване на избраното"
},
@@ -937,15 +934,6 @@
}
}
},
- "movedItemsToOrg": {
- "message": "Избраните записи бяха преместени в $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Елементите са преместени в $ORGNAME$",
"placeholders": {
@@ -1571,9 +1559,6 @@
"moveToOrgDesc": {
"message": "Изберете организацията, в която искате да преместите записа. Преместването прехвърля собствеността му към новата организация. След това няма вече директно да го притежавате."
},
- "moveManyToOrgDesc": {
- "message": "Изберете организацията, в която искате да преместите избраните записи. Преместването прехвърля собствеността им към новата организация. След това няма вече директно да ги притежавате."
- },
"collectionsDesc": {
"message": "Редактиране на колекциите, с които записът е споделен. Само потребителите на организациите, с които колекцията е споделена, ще виждат записа."
},
@@ -1607,23 +1592,6 @@
}
}
},
- "moveSelectedItemsCountDesc": {
- "message": "Избрали сте $COUNT$ запис(а). От тях могат да се преместят: $MOVEABLE_COUNT$, не могат да се преместят: $NONMOVEABLE_COUNT$.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "10"
- },
- "moveable_count": {
- "content": "$2",
- "example": "8"
- },
- "nonmoveable_count": {
- "content": "$3",
- "example": "2"
- }
- }
- },
"verificationCodeTotp": {
"message": "Код за потвърждаване (TOTP)"
},
diff --git a/apps/web/src/locales/bn/messages.json b/apps/web/src/locales/bn/messages.json
index afc2c7faac2..8c082c57e30 100644
--- a/apps/web/src/locales/bn/messages.json
+++ b/apps/web/src/locales/bn/messages.json
@@ -879,9 +879,6 @@
"filter": {
"message": "Filter"
},
- "moveSelectedToOrg": {
- "message": "Move selected to organization"
- },
"deleteSelected": {
"message": "Delete selected"
},
@@ -937,15 +934,6 @@
}
}
},
- "movedItemsToOrg": {
- "message": "Selected items moved to $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Items moved to $ORGNAME$",
"placeholders": {
@@ -1571,9 +1559,6 @@
"moveToOrgDesc": {
"message": "Choose an organization that you wish to move this item to. Moving to an organization transfers ownership of the item to that organization. You will no longer be the direct owner of this item once it has been moved."
},
- "moveManyToOrgDesc": {
- "message": "Choose an organization that you wish to move these items to. Moving to an organization transfers ownership of the items to that organization. You will no longer be the direct owner of these items once they have been moved."
- },
"collectionsDesc": {
"message": "Edit the collections that this item is being shared with. Only organization users with access to these collections will be able to see this item."
},
@@ -1607,23 +1592,6 @@
}
}
},
- "moveSelectedItemsCountDesc": {
- "message": "You have selected $COUNT$ item(s). $MOVEABLE_COUNT$ item(s) can be moved to an organization, $NONMOVEABLE_COUNT$ cannot.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "10"
- },
- "moveable_count": {
- "content": "$2",
- "example": "8"
- },
- "nonmoveable_count": {
- "content": "$3",
- "example": "2"
- }
- }
- },
"verificationCodeTotp": {
"message": "Verification code (TOTP)"
},
diff --git a/apps/web/src/locales/bs/messages.json b/apps/web/src/locales/bs/messages.json
index 017e27fd772..7efb47bd86c 100644
--- a/apps/web/src/locales/bs/messages.json
+++ b/apps/web/src/locales/bs/messages.json
@@ -879,9 +879,6 @@
"filter": {
"message": "Filter"
},
- "moveSelectedToOrg": {
- "message": "Premjestite Odabrano u Organizaciju"
- },
"deleteSelected": {
"message": "Obrišite Odabrano"
},
@@ -937,15 +934,6 @@
}
}
},
- "movedItemsToOrg": {
- "message": "Odabrane stavke premještene u $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Items moved to $ORGNAME$",
"placeholders": {
@@ -1571,9 +1559,6 @@
"moveToOrgDesc": {
"message": "Choose an organization that you wish to move this item to. Moving to an organization transfers ownership of the item to that organization. You will no longer be the direct owner of this item once it has been moved."
},
- "moveManyToOrgDesc": {
- "message": "Choose an organization that you wish to move these items to. Moving to an organization transfers ownership of the items to that organization. You will no longer be the direct owner of these items once they have been moved."
- },
"collectionsDesc": {
"message": "Edit the collections that this item is being shared with. Only organization users with access to these collections will be able to see this item."
},
@@ -1607,23 +1592,6 @@
}
}
},
- "moveSelectedItemsCountDesc": {
- "message": "You have selected $COUNT$ item(s). $MOVEABLE_COUNT$ item(s) can be moved to an organization, $NONMOVEABLE_COUNT$ cannot.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "10"
- },
- "moveable_count": {
- "content": "$2",
- "example": "8"
- },
- "nonmoveable_count": {
- "content": "$3",
- "example": "2"
- }
- }
- },
"verificationCodeTotp": {
"message": "Verification code (TOTP)"
},
diff --git a/apps/web/src/locales/ca/messages.json b/apps/web/src/locales/ca/messages.json
index cd79ad9727a..a4f2aeb7f55 100644
--- a/apps/web/src/locales/ca/messages.json
+++ b/apps/web/src/locales/ca/messages.json
@@ -879,9 +879,6 @@
"filter": {
"message": "Filtre"
},
- "moveSelectedToOrg": {
- "message": "Desplaça la selecció a l'organització"
- },
"deleteSelected": {
"message": "Suprimeix selecció"
},
@@ -937,15 +934,6 @@
}
}
},
- "movedItemsToOrg": {
- "message": "Els elements seleccionats s'han desplaçat a $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "S'han desplaçat elements a $ORGNAME$",
"placeholders": {
@@ -1571,9 +1559,6 @@
"moveToOrgDesc": {
"message": "Trieu una organització a la qual vulgueu desplaçar aquest element. El trasllat a una organització transfereix la propietat de l'element a aquesta organització. Ja no sereu el propietari directe d'aquest element una vegada s'haja mogut."
},
- "moveManyToOrgDesc": {
- "message": "Trieu una organització a la qual vulgueu desplaçar aquests elements. El trasllat a una organització transfereix la propietat dels elements a aquesta organització. Ja no sereu el propietari directe d'aquests elements una vegada s'hagen mogut."
- },
"collectionsDesc": {
"message": "Editeu les col·leccions amb les què es comparteix aquest element. Només els usuaris de l'organització que tinguen accés a aquestes col·leccions podran veure'l."
},
@@ -1607,23 +1592,6 @@
}
}
},
- "moveSelectedItemsCountDesc": {
- "message": "Heu seleccionat $COUNT$ elements. $MOVEABLE_COUNT$ elements es poden desplaçar a una organització, $NONMOVEABLE_COUNT$ no.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "10"
- },
- "moveable_count": {
- "content": "$2",
- "example": "8"
- },
- "nonmoveable_count": {
- "content": "$3",
- "example": "2"
- }
- }
- },
"verificationCodeTotp": {
"message": "Codi de verificació (TOTP)"
},
diff --git a/apps/web/src/locales/cs/messages.json b/apps/web/src/locales/cs/messages.json
index 9028a2a05dc..46b510a6459 100644
--- a/apps/web/src/locales/cs/messages.json
+++ b/apps/web/src/locales/cs/messages.json
@@ -879,9 +879,6 @@
"filter": {
"message": "Filtr"
},
- "moveSelectedToOrg": {
- "message": "Přesunout vybrané do organizace"
- },
"deleteSelected": {
"message": "Smazat vybrané"
},
@@ -937,15 +934,6 @@
}
}
},
- "movedItemsToOrg": {
- "message": "Vybrané položky přesunuty do $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Položky přesunuty do $ORGNAME$",
"placeholders": {
@@ -1571,9 +1559,6 @@
"moveToOrgDesc": {
"message": "Vyberte organizaci, do které chcete tuto položku přesunout. Přesun do organizace převede vlastnictví položky této organizaci. Po přesunutí této položky již nebudete přímým vlastníkem této položky."
},
- "moveManyToOrgDesc": {
- "message": "Vyberte organizaci, do které chcete tyto položky přesunout. Přesun do organizace převede vlastnictví položek této organizaci. Po přesunutí již nebudete přímým vlastníkem těchto položek."
- },
"collectionsDesc": {
"message": "Upravte kolekce, ve kterých je tato položka sdílená. Pouze uživatelé organizace, kteří mají přístup k těmto kolekcím, budou moci tuto položku vidět."
},
@@ -1607,23 +1592,6 @@
}
}
},
- "moveSelectedItemsCountDesc": {
- "message": "Vybrali jste $COUNT$ položek. $MOVEABLE_COUNT$ položek lze přesunout do organizace, $NONMOVEABLE_COUNT$ nelze.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "10"
- },
- "moveable_count": {
- "content": "$2",
- "example": "8"
- },
- "nonmoveable_count": {
- "content": "$3",
- "example": "2"
- }
- }
- },
"verificationCodeTotp": {
"message": "Ověřovací kód (TOTP)"
},
diff --git a/apps/web/src/locales/cy/messages.json b/apps/web/src/locales/cy/messages.json
index 8086379e611..2fd61e01526 100644
--- a/apps/web/src/locales/cy/messages.json
+++ b/apps/web/src/locales/cy/messages.json
@@ -879,9 +879,6 @@
"filter": {
"message": "Filter"
},
- "moveSelectedToOrg": {
- "message": "Move selected to organization"
- },
"deleteSelected": {
"message": "Delete selected"
},
@@ -937,15 +934,6 @@
}
}
},
- "movedItemsToOrg": {
- "message": "Selected items moved to $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Items moved to $ORGNAME$",
"placeholders": {
@@ -1571,9 +1559,6 @@
"moveToOrgDesc": {
"message": "Choose an organization that you wish to move this item to. Moving to an organization transfers ownership of the item to that organization. You will no longer be the direct owner of this item once it has been moved."
},
- "moveManyToOrgDesc": {
- "message": "Choose an organization that you wish to move these items to. Moving to an organization transfers ownership of the items to that organization. You will no longer be the direct owner of these items once they have been moved."
- },
"collectionsDesc": {
"message": "Edit the collections that this item is being shared with. Only organization users with access to these collections will be able to see this item."
},
@@ -1607,23 +1592,6 @@
}
}
},
- "moveSelectedItemsCountDesc": {
- "message": "You have selected $COUNT$ item(s). $MOVEABLE_COUNT$ item(s) can be moved to an organization, $NONMOVEABLE_COUNT$ cannot.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "10"
- },
- "moveable_count": {
- "content": "$2",
- "example": "8"
- },
- "nonmoveable_count": {
- "content": "$3",
- "example": "2"
- }
- }
- },
"verificationCodeTotp": {
"message": "Verification code (TOTP)"
},
diff --git a/apps/web/src/locales/da/messages.json b/apps/web/src/locales/da/messages.json
index 34eb15b2f2f..f8a335011e5 100644
--- a/apps/web/src/locales/da/messages.json
+++ b/apps/web/src/locales/da/messages.json
@@ -879,9 +879,6 @@
"filter": {
"message": "Filtre"
},
- "moveSelectedToOrg": {
- "message": "Flyt valgte til organisation"
- },
"deleteSelected": {
"message": "Slet valgte"
},
@@ -937,15 +934,6 @@
}
}
},
- "movedItemsToOrg": {
- "message": "Valgte emner flyttet til $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Emner flyttet til $ORGNAME$",
"placeholders": {
@@ -1571,9 +1559,6 @@
"moveToOrgDesc": {
"message": "Vælg den organisation, til hvilken dette emne ønskes flyttet. Flytning overfører ejerskab af emnet til organisationen, og du vil efter flytningen ikke længere være den direkte ejer af emnet."
},
- "moveManyToOrgDesc": {
- "message": "Vælg organisationen, til hvilken disse emner ønskes flyttet. Flytning til en organisation overfører ejerskabet af emnerne til denne organisation, og du vil efter flytningen ikke længere være den direkte ejer af disse emner."
- },
"collectionsDesc": {
"message": "Redigér de samlinger, med hvilke dette emne deles. Kun organisationsbrugere med adgang til disse samlinger vil kunne se dette emne."
},
@@ -1607,23 +1592,6 @@
}
}
},
- "moveSelectedItemsCountDesc": {
- "message": "$COUNT$ emne(r) valgt, hvoraf $MOVEABLE_COUNT$ kan flyttes til en organisation, mens $NONMOVEABLE_COUNT$ ikke kan.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "10"
- },
- "moveable_count": {
- "content": "$2",
- "example": "8"
- },
- "nonmoveable_count": {
- "content": "$3",
- "example": "2"
- }
- }
- },
"verificationCodeTotp": {
"message": "Bekræftelseskode (TOTP)"
},
diff --git a/apps/web/src/locales/de/messages.json b/apps/web/src/locales/de/messages.json
index f4b0cc7eb42..e0a25e99ae2 100644
--- a/apps/web/src/locales/de/messages.json
+++ b/apps/web/src/locales/de/messages.json
@@ -429,7 +429,7 @@
"message": "Zum Sortieren ziehen"
},
"dragToReorder": {
- "message": "Drag to reorder"
+ "message": "Ziehen zum umsortieren"
},
"cfTypeText": {
"message": "Text"
@@ -474,7 +474,7 @@
"message": "Ordner bearbeiten"
},
"editWithName": {
- "message": "Edit $ITEM$: $NAME$",
+ "message": "$ITEM$: $NAME$ bearbeiten",
"placeholders": {
"item": {
"content": "$1",
@@ -879,9 +879,6 @@
"filter": {
"message": "Filter"
},
- "moveSelectedToOrg": {
- "message": "Auswahl in Organisation verschieben"
- },
"deleteSelected": {
"message": "Auswahl löschen"
},
@@ -937,15 +934,6 @@
}
}
},
- "movedItemsToOrg": {
- "message": "Ausgewählte Einträge in $ORGNAME$ verschoben",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Einträge verschoben nach $ORGNAME$",
"placeholders": {
@@ -1571,9 +1559,6 @@
"moveToOrgDesc": {
"message": "Wähle eine Organisation aus, in die du diesen Eintrag verschieben möchtest. Das Verschieben in eine Organisation überträgt das Eigentum an diese Organisation. Du bist nicht mehr der direkte Eigentümer dieses Eintrags, sobald er verschoben wurde."
},
- "moveManyToOrgDesc": {
- "message": "Wähle eine Organisation aus, in die du diese Einträge verschieben möchtest. Das Verschieben in eine Organisation überträgt das Eigentum der Einträge an diese Organisation. Du bist nicht mehr der direkte Eigentümer dieser Einträge, sobald sie verschoben wurden."
- },
"collectionsDesc": {
"message": "Bearbeite die Sammlungen, mit denen dieser Eintrag geteilt wird. Nur Organisationsmitglieder mit Zugriff auf diese Sammlungen werden diesen Eintrag sehen können."
},
@@ -1607,23 +1592,6 @@
}
}
},
- "moveSelectedItemsCountDesc": {
- "message": "Du hast $COUNT$ Eintrag/Einträge ausgewählt. $MOVEABLE_COUNT$ Eintrag/Einträge kann/können in eine Organisation verschoben werden, $NONMOVEABLE_COUNT$ nicht.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "10"
- },
- "moveable_count": {
- "content": "$2",
- "example": "8"
- },
- "nonmoveable_count": {
- "content": "$3",
- "example": "2"
- }
- }
- },
"verificationCodeTotp": {
"message": "Verifizierungscode (TOTP)"
},
@@ -4541,7 +4509,7 @@
}
},
"reorderFieldUp": {
- "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$",
+ "message": "$LABEL$ nach oben verschoben, Position $INDEX$ von $LENGTH$",
"placeholders": {
"label": {
"content": "$1",
@@ -4558,7 +4526,7 @@
}
},
"reorderFieldDown": {
- "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$",
+ "message": "$LABEL$ nach unten verschoben, Position $INDEX$ von $LENGTH$",
"placeholders": {
"label": {
"content": "$1",
@@ -9371,13 +9339,13 @@
"message": "Konfiguriere die Geräteverwaltung für Bitwarden mithilfe der Implementierungsanleitung für deine Plattform."
},
"deviceIdMissing": {
- "message": "Device ID is missing"
+ "message": "Geräte-ID fehlt"
},
"deviceTypeMissing": {
- "message": "Device type is missing"
+ "message": "Gerätetyp fehlt"
},
"deviceCreationDateMissing": {
- "message": "Device creation date is missing"
+ "message": "Geräteerstellungsdatum fehlt"
},
"desktopRequired": {
"message": "Desktop-Rechner erforderlich"
@@ -10381,34 +10349,34 @@
"message": "Der Name der Organisation darf 50 Zeichen nicht überschreiten."
},
"sshKeyWrongPassword": {
- "message": "The password you entered is incorrect."
+ "message": "Dein eingegebenes Passwort ist falsch."
},
"importSshKey": {
- "message": "Import"
+ "message": "Importieren"
},
"confirmSshKeyPassword": {
- "message": "Confirm password"
+ "message": "Passwort bestätigen"
},
"enterSshKeyPasswordDesc": {
- "message": "Enter the password for the SSH key."
+ "message": "Gib das Passwort für den SSH-Schlüssel ein."
},
"enterSshKeyPassword": {
- "message": "Enter password"
+ "message": "Passwort eingeben"
},
"invalidSshKey": {
- "message": "The SSH key is invalid"
+ "message": "Der SSH-Schlüssel ist ungültig"
},
"sshKeyTypeUnsupported": {
- "message": "The SSH key type is not supported"
+ "message": "Der SSH-Schlüsseltyp wird nicht unterstützt"
},
"importSshKeyFromClipboard": {
- "message": "Import key from clipboard"
+ "message": "Schlüssel aus Zwischenablage importieren"
},
"sshKeyImported": {
- "message": "SSH key imported successfully"
+ "message": "SSH-Schlüssel erfolgreich importiert"
},
"copySSHPrivateKey": {
- "message": "Copy private key"
+ "message": "Privaten Schlüssel kopieren"
},
"openingExtension": {
"message": "Bitwarden-Browser-Erweiterung wird geöffnet"
@@ -10577,6 +10545,6 @@
"message": "Diese Ereignisse sind nur Beispiele und spiegeln keine realen Ereignisse in deiner Bitwarden-Organisation wider."
},
"cannotCreateCollection": {
- "message": "Free organizations may have up to 2 collections. Upgrade to a paid plan to add more collections."
+ "message": "Kostenlose Organisationen können bis zu 2 Sammlungen haben. Upgrade auf ein kostenpflichtiges Abo, um mehr Sammlungen hinzuzufügen."
}
}
diff --git a/apps/web/src/locales/el/messages.json b/apps/web/src/locales/el/messages.json
index 79cee27da51..e20b5cc52a0 100644
--- a/apps/web/src/locales/el/messages.json
+++ b/apps/web/src/locales/el/messages.json
@@ -879,9 +879,6 @@
"filter": {
"message": "Φίλτρο"
},
- "moveSelectedToOrg": {
- "message": "Μετακίνηση Επιλεγμένων στον Οργανισμό"
- },
"deleteSelected": {
"message": "Διαγραφή επιλεγμένων"
},
@@ -937,15 +934,6 @@
}
}
},
- "movedItemsToOrg": {
- "message": "Τα επιλεγμένα αντικείμενα μετακινήθηκαν στο $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Τα αντικείμενα μεταφέρθηκαν στο $ORGNAME$",
"placeholders": {
@@ -1571,9 +1559,6 @@
"moveToOrgDesc": {
"message": "Επιλέξτε έναν οργανισμό στον οποίο θέλετε να μετακινήσετε αυτό το στοιχείο. Η μετακίνηση σε έναν οργανισμό μεταβιβάζει την ιδιοκτησία του στοιχείου σε αυτό τον οργανισμό. Δεν θα είστε πλέον ο άμεσος ιδιοκτήτης αυτού του στοιχείου μόλις το μετακινήσετε."
},
- "moveManyToOrgDesc": {
- "message": "Επιλέξτε έναν οργανισμό στον οποίο θέλετε να μετακινήσετε αυτά τα στοιχεία. Η μετακίνηση σε έναν οργανισμό μεταβιβάζει την ιδιοκτησία του στοιχείου σε αυτό τον οργανισμό. Δεν θα είστε πλέον ο άμεσος ιδιοκτήτης αυτού του στοιχείου μόλις το μετακινήσετε."
- },
"collectionsDesc": {
"message": "Επεξεργαστείτε τις συλλογές με τις οποίες μοιράζεται αυτό το στοιχείο. Μόνο οι χρήστες των οργανισμών που έχουν πρόσβαση σε αυτές τις συλλογές θα μπορούν να βλέπουν αυτό το στοιχείο."
},
@@ -1607,23 +1592,6 @@
}
}
},
- "moveSelectedItemsCountDesc": {
- "message": "Έχετε επιλέξει $COUNT$ αντικείμενα. $MOVEABLE_COUNT$ αντικείμενα μπορούν να μετακινηθούν σε έναν οργανισμό, $NONMOVEABLE_COUNT$ δεν μπορεί.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "10"
- },
- "moveable_count": {
- "content": "$2",
- "example": "8"
- },
- "nonmoveable_count": {
- "content": "$3",
- "example": "2"
- }
- }
- },
"verificationCodeTotp": {
"message": "Κωδικός Επαλήθευσης (TOTP)"
},
diff --git a/apps/web/src/locales/en_GB/messages.json b/apps/web/src/locales/en_GB/messages.json
index 16a013394c1..b211ec070ab 100644
--- a/apps/web/src/locales/en_GB/messages.json
+++ b/apps/web/src/locales/en_GB/messages.json
@@ -879,9 +879,6 @@
"filter": {
"message": "Filter"
},
- "moveSelectedToOrg": {
- "message": "Move selected to organisation"
- },
"deleteSelected": {
"message": "Delete selected"
},
@@ -937,15 +934,6 @@
}
}
},
- "movedItemsToOrg": {
- "message": "Selected items moved to $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Items moved to $ORGNAME$",
"placeholders": {
@@ -1571,9 +1559,6 @@
"moveToOrgDesc": {
"message": "Choose an organisation that you wish to move this item to. Moving to an organisation transfers ownership of the item to that organisation. You will no longer be the direct owner of this item once it has been moved."
},
- "moveManyToOrgDesc": {
- "message": "Choose an organisation that you wish to move these items to. Moving to an organisation transfers ownership of the items to that organisation. You will no longer be the direct owner of these items once they have been moved."
- },
"collectionsDesc": {
"message": "Edit the collections that this item is being shared with. Only organisation users with access to these collections will be able to see this item."
},
@@ -1607,23 +1592,6 @@
}
}
},
- "moveSelectedItemsCountDesc": {
- "message": "You have selected $COUNT$ item(s). $MOVEABLE_COUNT$ item(s) can be moved to an organisation, $NONMOVEABLE_COUNT$ cannot.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "10"
- },
- "moveable_count": {
- "content": "$2",
- "example": "8"
- },
- "nonmoveable_count": {
- "content": "$3",
- "example": "2"
- }
- }
- },
"verificationCodeTotp": {
"message": "Verification code (TOTP)"
},
diff --git a/apps/web/src/locales/en_IN/messages.json b/apps/web/src/locales/en_IN/messages.json
index 4a1fa36f3d9..6b4b6e11ab3 100644
--- a/apps/web/src/locales/en_IN/messages.json
+++ b/apps/web/src/locales/en_IN/messages.json
@@ -879,9 +879,6 @@
"filter": {
"message": "Filter"
},
- "moveSelectedToOrg": {
- "message": "Move Selected to Organisation"
- },
"deleteSelected": {
"message": "Delete selected"
},
@@ -937,15 +934,6 @@
}
}
},
- "movedItemsToOrg": {
- "message": "Selected items moved to $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Items moved to $ORGNAME$",
"placeholders": {
@@ -1571,9 +1559,6 @@
"moveToOrgDesc": {
"message": "Choose an organisation that you wish to move this item to. Moving to an organisation transfers ownership of the item to that organisation. You will no longer be the direct owner of this item once it has been moved."
},
- "moveManyToOrgDesc": {
- "message": "Choose an organisation that you wish to move these items to. Moving to an organisation transfers ownership of the items to that organisation. You will no longer be the direct owner of these items once they have been moved."
- },
"collectionsDesc": {
"message": "Edit the collections that this item is being shared with. Only organisation users with access to these collections will be able to see this item."
},
@@ -1607,23 +1592,6 @@
}
}
},
- "moveSelectedItemsCountDesc": {
- "message": "You have selected $COUNT$ item(s). $MOVEABLE_COUNT$ item(s) can be moved to an organisation, $NONMOVEABLE_COUNT$ cannot.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "10"
- },
- "moveable_count": {
- "content": "$2",
- "example": "8"
- },
- "nonmoveable_count": {
- "content": "$3",
- "example": "2"
- }
- }
- },
"verificationCodeTotp": {
"message": "Verification code (TOTP)"
},
diff --git a/apps/web/src/locales/eo/messages.json b/apps/web/src/locales/eo/messages.json
index 182566d919e..ce40767f4c1 100644
--- a/apps/web/src/locales/eo/messages.json
+++ b/apps/web/src/locales/eo/messages.json
@@ -879,9 +879,6 @@
"filter": {
"message": "Filter"
},
- "moveSelectedToOrg": {
- "message": "Movu Elektitaĵojn al Organizo"
- },
"deleteSelected": {
"message": "Forigi Elektitajn"
},
@@ -937,15 +934,6 @@
}
}
},
- "movedItemsToOrg": {
- "message": "Selected items moved to $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Items moved to $ORGNAME$",
"placeholders": {
@@ -1571,9 +1559,6 @@
"moveToOrgDesc": {
"message": "Choose an organization that you wish to move this item to. Moving to an organization transfers ownership of the item to that organization. You will no longer be the direct owner of this item once it has been moved."
},
- "moveManyToOrgDesc": {
- "message": "Choose an organization that you wish to move these items to. Moving to an organization transfers ownership of the items to that organization. You will no longer be the direct owner of these items once they have been moved."
- },
"collectionsDesc": {
"message": "Redaktu la kolektojn kun kiuj ĉi tiu ero estas dividita. Nur organizaj uzantoj kun aliro al ĉi tiuj kolektoj povos vidi ĉi tiun eron."
},
@@ -1607,23 +1592,6 @@
}
}
},
- "moveSelectedItemsCountDesc": {
- "message": "You have selected $COUNT$ item(s). $MOVEABLE_COUNT$ item(s) can be moved to an organization, $NONMOVEABLE_COUNT$ cannot.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "10"
- },
- "moveable_count": {
- "content": "$2",
- "example": "8"
- },
- "nonmoveable_count": {
- "content": "$3",
- "example": "2"
- }
- }
- },
"verificationCodeTotp": {
"message": "Kontrola Kodo (TOTP)"
},
diff --git a/apps/web/src/locales/es/messages.json b/apps/web/src/locales/es/messages.json
index e5d68dbe458..1c71a29f48b 100644
--- a/apps/web/src/locales/es/messages.json
+++ b/apps/web/src/locales/es/messages.json
@@ -879,9 +879,6 @@
"filter": {
"message": "Filtro"
},
- "moveSelectedToOrg": {
- "message": "Mover los seleccionados a la organización"
- },
"deleteSelected": {
"message": "Eliminar selección"
},
@@ -937,15 +934,6 @@
}
}
},
- "movedItemsToOrg": {
- "message": "Elementos seleccionados movidos a $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Items moved to $ORGNAME$",
"placeholders": {
@@ -1571,9 +1559,6 @@
"moveToOrgDesc": {
"message": "Elige una organización a la que deseas mover este objeto. Moviendo a una organización transfiere la propiedad del objeto a esa organización. Ya no serás el dueño directo de este objeto una vez que haya sido movido."
},
- "moveManyToOrgDesc": {
- "message": "Elija una organización a la que desea mover estos elementos. Moviendo a una organización transfiere la propiedad de los elementos a esa organización. Ya no serás el dueño directo de estos objetos una vez que hayan sido movidos."
- },
"collectionsDesc": {
"message": "Elige las colecciones con la que este elemento va a ser compartido. Solo los miembros de la organización que puedan acceder a esas colecciones podrán ver el elemento."
},
@@ -1607,23 +1592,6 @@
}
}
},
- "moveSelectedItemsCountDesc": {
- "message": "Ha seleccionado $COUNT$ elemento(s). Se pueden mover $MOVEABLE_COUNT$ elemento(s) a una organización, no se pueden mover $NONMOVEABLE_COUNT$.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "10"
- },
- "moveable_count": {
- "content": "$2",
- "example": "8"
- },
- "nonmoveable_count": {
- "content": "$3",
- "example": "2"
- }
- }
- },
"verificationCodeTotp": {
"message": "Código de verificación (TOTP)"
},
diff --git a/apps/web/src/locales/et/messages.json b/apps/web/src/locales/et/messages.json
index 3bdf7f0dd23..f9a5b810ded 100644
--- a/apps/web/src/locales/et/messages.json
+++ b/apps/web/src/locales/et/messages.json
@@ -879,9 +879,6 @@
"filter": {
"message": "Filter"
},
- "moveSelectedToOrg": {
- "message": "Teisalda valitud organisatsiooni"
- },
"deleteSelected": {
"message": "Kustuta valitud"
},
@@ -937,15 +934,6 @@
}
}
},
- "movedItemsToOrg": {
- "message": "Valitud kirjed teisaldati $ORGNAME$-le",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Kirjed liigutatud $ORGNAME$",
"placeholders": {
@@ -1571,9 +1559,6 @@
"moveToOrgDesc": {
"message": "Vali organisatsioon, kuhu soovid seda kirjet teisaldada. Teisaldamisega saab kirje omanikuks organisatsioon. Pärast kirje teisaldamist ei ole sa enam selle otsene omanik."
},
- "moveManyToOrgDesc": {
- "message": "Vali organisatsioon, kuhu soovid seda kirjet teisaldada. Teisaldamisega saab kirje omanikuks organisatsioon. Pärast kirje teisaldamist ei ole sa enam selle otsene omanik."
- },
"collectionsDesc": {
"message": "Muuda kollektsioone, millega seda kirjet jagatakse. Seda kirjet näevad üksnes organisatsiooni kasutajad, kes omavad nendele kollektsioonidele ligipääsu."
},
@@ -1607,23 +1592,6 @@
}
}
},
- "moveSelectedItemsCountDesc": {
- "message": "Valisid $COUNT$ kirje(t). $MOVEABLE_COUNT$ kirje(t) saab teisaldada organisatsiooni, $NONMOVEABLE_COUNT$ ei saa.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "10"
- },
- "moveable_count": {
- "content": "$2",
- "example": "8"
- },
- "nonmoveable_count": {
- "content": "$3",
- "example": "2"
- }
- }
- },
"verificationCodeTotp": {
"message": "Kinnituskood (TOTP)"
},
diff --git a/apps/web/src/locales/eu/messages.json b/apps/web/src/locales/eu/messages.json
index e02ce0e6b6e..d0e99c73e66 100644
--- a/apps/web/src/locales/eu/messages.json
+++ b/apps/web/src/locales/eu/messages.json
@@ -879,9 +879,6 @@
"filter": {
"message": "Filter"
},
- "moveSelectedToOrg": {
- "message": "Mugitu hautatutako antolakundera"
- },
"deleteSelected": {
"message": "Ezabatu hautatutakoa"
},
@@ -937,15 +934,6 @@
}
}
},
- "movedItemsToOrg": {
- "message": "Hautatutako elementuak $ORGNAME$-ra mugituak",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Items moved to $ORGNAME$",
"placeholders": {
@@ -1571,9 +1559,6 @@
"moveToOrgDesc": {
"message": "Aukeratu elementu hau zein erakundetara eraman nahi duzun. Erakunde batera pasatzeak elementuaren jabetza erakunde horretara transferitzen du. Zu ez zara elementu honen jabe zuzena izango mugitzen duzunean."
},
- "moveManyToOrgDesc": {
- "message": "Aukeratu elementu hauek zein erakundetara eraman nahi dituzun. Erakunde batera pasatzeak elementuen jabetzak erakunde horretara transferitzen ditu. Zu ez zara elementu horien jabe zuzena izango mugitzen dituzunean."
- },
"collectionsDesc": {
"message": "Aukeratu elementu hau zein bildumarekin partekatzen den. Bilduma horietarako sarbidea duten erakundeko erabiltzaileek bakarrik ikus dezakete elementu hau."
},
@@ -1607,23 +1592,6 @@
}
}
},
- "moveSelectedItemsCountDesc": {
- "message": "$COUNT$ artikulu hautatu d(it)uzu. $MOVEABLE_COUNT$ artikulu erakunde batera alda daite(z)ke, $NONMOVEABLE_COUNT$ ezin d(ir)a.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "10"
- },
- "moveable_count": {
- "content": "$2",
- "example": "8"
- },
- "nonmoveable_count": {
- "content": "$3",
- "example": "2"
- }
- }
- },
"verificationCodeTotp": {
"message": "Egiaztatze-kodea (TOTP)"
},
diff --git a/apps/web/src/locales/fa/messages.json b/apps/web/src/locales/fa/messages.json
index a3f38606637..04cddc65f2f 100644
--- a/apps/web/src/locales/fa/messages.json
+++ b/apps/web/src/locales/fa/messages.json
@@ -879,9 +879,6 @@
"filter": {
"message": "فیلتر"
},
- "moveSelectedToOrg": {
- "message": "انتقال مورد انتخاب شده به سازمان"
- },
"deleteSelected": {
"message": "حذف موارد انتخاب شده"
},
@@ -937,15 +934,6 @@
}
}
},
- "movedItemsToOrg": {
- "message": "موارد انتخاب شده به $ORGNAME$ منتقل شدند",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Items moved to $ORGNAME$",
"placeholders": {
@@ -1571,9 +1559,6 @@
"moveToOrgDesc": {
"message": "سازمانی را انتخاب کنید که میخواهید این مورد را به آن منتقل کنید. انتقال به یک سازمان، مالکیت مورد را به آن سازمان منتقل میکند. پس از انتقال این مورد، دیگر مالک مستقیم آن نخواهید بود."
},
- "moveManyToOrgDesc": {
- "message": "سازمانی را انتخاب کنید که میخواهید این موارد را به آن منتقل کنید. انتقال به یک سازمان، مالکیت موارد را به آن سازمان منتقل میکند. پس از انتقال این موارد، دیگر مالک مستقیم آنها نخواهید بود."
- },
"collectionsDesc": {
"message": "مجموعههایی را ویرایش کنید که این مورد با آنها به اشتراک گذاشته میشود. فقط کاربران سازمانی که به این مجموعهها دسترسی دارند میتوانند این مورد را ببینند."
},
@@ -1607,23 +1592,6 @@
}
}
},
- "moveSelectedItemsCountDesc": {
- "message": "شما $COUNT$ مورد را انتخاب کرده اید. $MOVEABLE_COUNT$ مورد را میتوان به یک سازمان منتقل کرد، $NONMOVEABLE_COUNT$ تا را نمیتواند.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "10"
- },
- "moveable_count": {
- "content": "$2",
- "example": "8"
- },
- "nonmoveable_count": {
- "content": "$3",
- "example": "2"
- }
- }
- },
"verificationCodeTotp": {
"message": "کد تأیید (TOTP)"
},
diff --git a/apps/web/src/locales/fi/messages.json b/apps/web/src/locales/fi/messages.json
index 1f79cda1bd1..9427a4e8e22 100644
--- a/apps/web/src/locales/fi/messages.json
+++ b/apps/web/src/locales/fi/messages.json
@@ -879,9 +879,6 @@
"filter": {
"message": "Suodattimet"
},
- "moveSelectedToOrg": {
- "message": "Siirrä valitut organisaatiolle"
- },
"deleteSelected": {
"message": "Poista valitut"
},
@@ -937,15 +934,6 @@
}
}
},
- "movedItemsToOrg": {
- "message": "Valitut kohteet siirrettiin organisaatiolle $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Kohteet siirrettiin organisaatiolle $ORGNAME$",
"placeholders": {
@@ -1571,9 +1559,6 @@
"moveToOrgDesc": {
"message": "Valitse organisaatio, jolle haluat siirtää kohteen. Tämä siirtää kohteen organisaation omistukseen, etkä tämän jälkeen ole enää sen suora omistaja."
},
- "moveManyToOrgDesc": {
- "message": "Valitse organisaatio, jolle haluat siirtää kohteet. Tämä siirtää kohteet organisaation omistukseen, etkä tämän jälkeen ole enää niiden suora omistaja."
- },
"collectionsDesc": {
"message": "Muokkaa kokoelmia, joihin tämä kohde on jaettu. Kohteen näkevät vain ne organisaation käyttäjät, joilla on käyttöoikeudet näihin kokoelmiin."
},
@@ -1607,23 +1592,6 @@
}
}
},
- "moveSelectedItemsCountDesc": {
- "message": "Olet valinnut $COUNT$ kohdetta. $MOVEABLE_COUNT$ kohdetta voidaan siirtää organisaatioon, $NONMOVEABLE_COUNT$ ei.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "10"
- },
- "moveable_count": {
- "content": "$2",
- "example": "8"
- },
- "nonmoveable_count": {
- "content": "$3",
- "example": "2"
- }
- }
- },
"verificationCodeTotp": {
"message": "Todennuskoodi (TOTP)"
},
diff --git a/apps/web/src/locales/fil/messages.json b/apps/web/src/locales/fil/messages.json
index 9bee2309a3c..414bbda91d8 100644
--- a/apps/web/src/locales/fil/messages.json
+++ b/apps/web/src/locales/fil/messages.json
@@ -879,9 +879,6 @@
"filter": {
"message": "Mga pansala"
},
- "moveSelectedToOrg": {
- "message": "Ilipat sa napiling organisasyon"
- },
"deleteSelected": {
"message": "Burahin ang napili"
},
@@ -937,15 +934,6 @@
}
}
},
- "movedItemsToOrg": {
- "message": "Inilipat ang mga napiling item sa $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Items moved to $ORGNAME$",
"placeholders": {
@@ -1571,9 +1559,6 @@
"moveToOrgDesc": {
"message": "Piliin kung saang organisasyon mo ililipat ang item na ito. Magiging pag-aari ng organisasyon ang anumang item na ililipat mo rito. Hindi ka na ang direktang may-ari ng item na ito pagkalipat."
},
- "moveManyToOrgDesc": {
- "message": "Piliin kung saang organisasyon mo ililipat ang mga item na ito. Magiging pag-aari ng organisasyon ang mga item na ililipat mo rito. Hindi ka na ang direktang may-ari ng mga item na ito pagkalipat."
- },
"collectionsDesc": {
"message": "Baguhin kung saang mga koleksyon ibinabahagi ang item na ito. Ang mga user sa organisasyon na may access sa koleksyon na ito lang ang makakakita sa item."
},
@@ -1607,23 +1592,6 @@
}
}
},
- "moveSelectedItemsCountDesc": {
- "message": "$COUNT$ item ang napili mo. Pwedeng mailipat sa isang organisasyon ang $MOVEABLE_COUNT$ item, habang $NONMOVEABLE_COUNT$ item ang hindi pwedeng mailipat.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "10"
- },
- "moveable_count": {
- "content": "$2",
- "example": "8"
- },
- "nonmoveable_count": {
- "content": "$3",
- "example": "2"
- }
- }
- },
"verificationCodeTotp": {
"message": "Code pamberipika (TOTP)"
},
diff --git a/apps/web/src/locales/fr/messages.json b/apps/web/src/locales/fr/messages.json
index 06cef8497cd..95aa573f14b 100644
--- a/apps/web/src/locales/fr/messages.json
+++ b/apps/web/src/locales/fr/messages.json
@@ -879,9 +879,6 @@
"filter": {
"message": "Filtre"
},
- "moveSelectedToOrg": {
- "message": "Déplacer la sélection vers l'organisation"
- },
"deleteSelected": {
"message": "Supprimer la sélection"
},
@@ -937,15 +934,6 @@
}
}
},
- "movedItemsToOrg": {
- "message": "Les éléments sélectionnés ont été déplacés vers $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Éléments déplacés vers $ORGNAME$",
"placeholders": {
@@ -1571,9 +1559,6 @@
"moveToOrgDesc": {
"message": "Choisissez une organisation vers laquelle vous souhaitez déplacer cet élément. Déplacer un élément vers une organisation transfère la propriété de l'élément à cette organisation. Vous ne serez plus le propriétaire direct de cet élément une fois qu'il aura été déplacé."
},
- "moveManyToOrgDesc": {
- "message": "Choisissez une organisation vers laquelle vous souhaitez déplacer ces éléments. Déplacer des éléments vers une organisation transfère la propriété des éléments à cette organisation. Vous ne serez plus le propriétaire direct de ces éléments une fois qu'ils auront été déplacés."
- },
"collectionsDesc": {
"message": "Modifier les collections avec lesquelles cet élément est partagé. Seuls les utilisateurs de l'organisation avec un accès à ces collections pourront voir cet élément."
},
@@ -1607,23 +1592,6 @@
}
}
},
- "moveSelectedItemsCountDesc": {
- "message": "Vous avez sélectionné $COUNT$ élément(s). $MOVEABLE_COUNT$ élément(s) peuvent être déplacés vers une organisation, $NONMOVEABLE_COUNT$ ne le peuvent pas.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "10"
- },
- "moveable_count": {
- "content": "$2",
- "example": "8"
- },
- "nonmoveable_count": {
- "content": "$3",
- "example": "2"
- }
- }
- },
"verificationCodeTotp": {
"message": "Code de vérification (TOTP)"
},
diff --git a/apps/web/src/locales/gl/messages.json b/apps/web/src/locales/gl/messages.json
index 986832dde9a..38a94f9307f 100644
--- a/apps/web/src/locales/gl/messages.json
+++ b/apps/web/src/locales/gl/messages.json
@@ -879,9 +879,6 @@
"filter": {
"message": "Filter"
},
- "moveSelectedToOrg": {
- "message": "Move selected to organization"
- },
"deleteSelected": {
"message": "Delete selected"
},
@@ -937,15 +934,6 @@
}
}
},
- "movedItemsToOrg": {
- "message": "Selected items moved to $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Items moved to $ORGNAME$",
"placeholders": {
@@ -1571,9 +1559,6 @@
"moveToOrgDesc": {
"message": "Choose an organization that you wish to move this item to. Moving to an organization transfers ownership of the item to that organization. You will no longer be the direct owner of this item once it has been moved."
},
- "moveManyToOrgDesc": {
- "message": "Choose an organization that you wish to move these items to. Moving to an organization transfers ownership of the items to that organization. You will no longer be the direct owner of these items once they have been moved."
- },
"collectionsDesc": {
"message": "Edit the collections that this item is being shared with. Only organization users with access to these collections will be able to see this item."
},
@@ -1607,23 +1592,6 @@
}
}
},
- "moveSelectedItemsCountDesc": {
- "message": "You have selected $COUNT$ item(s). $MOVEABLE_COUNT$ item(s) can be moved to an organization, $NONMOVEABLE_COUNT$ cannot.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "10"
- },
- "moveable_count": {
- "content": "$2",
- "example": "8"
- },
- "nonmoveable_count": {
- "content": "$3",
- "example": "2"
- }
- }
- },
"verificationCodeTotp": {
"message": "Verification code (TOTP)"
},
diff --git a/apps/web/src/locales/he/messages.json b/apps/web/src/locales/he/messages.json
index e8c6849a7d8..4df39c8496f 100644
--- a/apps/web/src/locales/he/messages.json
+++ b/apps/web/src/locales/he/messages.json
@@ -309,25 +309,25 @@
"message": "תוקף"
},
"securityCode": {
- "message": "קוד האבטחה (CVV)"
+ "message": "קוד אבטחה (CVV)"
},
"securityCodeSlashCVV": {
"message": "קוד אבטחה / CVV"
},
"identityName": {
- "message": "שם הזהות"
+ "message": "שם זהות"
},
"company": {
"message": "חברה"
},
"ssn": {
- "message": "מספר ביטוח לאומי"
+ "message": "מספר תעודת זהות"
},
"passportNumber": {
"message": "מספר דרכון"
},
"licenseNumber": {
- "message": "מספר רשיון"
+ "message": "מספר רישיון"
},
"email": {
"message": "אימייל"
@@ -396,10 +396,10 @@
"message": "אם חידשת אותו, עדכן את פרטי הכרטיס"
},
"expirationMonth": {
- "message": "תוקף אשראי - חודש"
+ "message": "חודש תפוגה"
},
"expirationYear": {
- "message": "תוקף אשראי - שנה"
+ "message": "שנת תפוגה"
},
"authenticatorKeyTotp": {
"message": "מפתח מאמת (TOTP)"
@@ -460,7 +460,7 @@
"message": "לא מוקצה"
},
"noneFolder": {
- "message": "ללא תיקיה",
+ "message": "ללא תיקייה",
"description": "This is the folder for uncategorized items"
},
"selfOwnershipLabel": {
@@ -468,7 +468,7 @@
"description": "Used as a label to indicate that the user is the owner of an item."
},
"addFolder": {
- "message": "הוסף תיקיה"
+ "message": "הוסף תיקייה"
},
"editFolder": {
"message": "ערוך תיקייה"
@@ -532,10 +532,10 @@
"message": "לעולם לא"
},
"toggleVisibility": {
- "message": "הצג או הסתר"
+ "message": "שנה מצב נראות"
},
"toggleCollapse": {
- "message": "הצג או קפל",
+ "message": "שנה מצב כיווץ",
"description": "Toggling an expand/collapse state."
},
"generatePassword": {
@@ -642,13 +642,13 @@
"message": "זהות"
},
"typeSecureNote": {
- "message": "פתק מאובטח"
+ "message": "הערה מאובטחת"
},
"typeSshKey": {
"message": "מפתח SSH"
},
"typeLoginPlural": {
- "message": "התחברויות"
+ "message": "כניסות"
},
"typeCardPlural": {
"message": "כרטיסים"
@@ -657,7 +657,7 @@
"message": "זהויות"
},
"typeSecureNotePlural": {
- "message": "פתקים מאובטחים"
+ "message": "הערות מאובטחות"
},
"folders": {
"message": "תיקיות"
@@ -773,7 +773,7 @@
"message": "שתף"
},
"moveToOrganization": {
- "message": "העברה לארגון"
+ "message": "העבר לארגון"
},
"valueCopied": {
"message": "השדה $VALUE$ הועתק לזיכרון",
@@ -879,9 +879,6 @@
"filter": {
"message": "מסנן"
},
- "moveSelectedToOrg": {
- "message": "העבר בחירה לארגון"
- },
"deleteSelected": {
"message": "מחק בחירה"
},
@@ -916,16 +913,16 @@
"message": "בחר קובץ."
},
"maxFileSize": {
- "message": "גודל הקובץ המירבי הוא 500 מגה."
+ "message": "גודל הקובץ המרבי הוא 500MB."
},
"addedItem": {
- "message": "פריט שהתווסף"
+ "message": "הפריט נוסף"
},
"editedItem": {
- "message": "פריט שנערך"
+ "message": "הפריט נשמר"
},
"movedItemToOrg": {
- "message": "$ITEMNAME$ הועבר ל־$ORGNAME$",
+ "message": "$ITEMNAME$ הועבר אל $ORGNAME$",
"placeholders": {
"itemname": {
"content": "$1",
@@ -937,15 +934,6 @@
}
}
},
- "movedItemsToOrg": {
- "message": "פריטים נבחרים הועברו ל־$ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "פריטים הועברו אל $ORGNAME$",
"placeholders": {
@@ -983,22 +971,22 @@
"message": "הפריטים נשלחו לסל המחזור"
},
"movedItems": {
- "message": "פריטים שהועברו"
+ "message": "פריטים הועברו"
},
"overwritePasswordConfirmation": {
"message": "האם אתה בטוח שברצונך לדרוס את הסיסמה הנוכחית?"
},
"editedFolder": {
- "message": "תיקיה שנערכה"
+ "message": "תיקייה נשמרה"
},
"addedFolder": {
- "message": "תיקיה שנוספה"
+ "message": "תיקייה נוספה"
},
"deleteFolderConfirmation": {
"message": "האם אתה בטוח שברצונך למחוק את התיקייה?"
},
"deletedFolder": {
- "message": "תיקיה שנמחקה"
+ "message": "תיקייה נמחקה"
},
"editInfo": {
"message": "ערוך מידע"
@@ -1037,7 +1025,7 @@
"message": "האם אתה בטוח שברצונך להתנתק?"
},
"logOut": {
- "message": "התנתק"
+ "message": "צא"
},
"ok": {
"message": "אישור"
@@ -1181,7 +1169,7 @@
"message": "התחל ניסיון"
},
"logIn": {
- "message": "התחבר"
+ "message": "היכנס"
},
"logInToBitwarden": {
"message": "היכנס אל Bitwarden"
@@ -1250,13 +1238,13 @@
"message": "הקלד שוב סיסמה ראשית"
},
"masterPassHint": {
- "message": "רמז לסיסמה ראשית (אופציונאלי)"
+ "message": "רמז לסיסמה הראשית (אופציונלי)"
},
"newMasterPassHint": {
"message": "רמז לסיסמה הראשית חדש (אופציונלי)"
},
"masterPassHintLabel": {
- "message": "רמז לסיסמה ראשית"
+ "message": "רמז לסיסמה הראשית"
},
"masterPassHintText": {
"message": "אם תשכח את הסיסמה שלך, הרמז לסיסמה יכול להישלח לדוא\"ל שלך. $CURRENT$/$MAXIMUM$ תווים לכל היותר.",
@@ -1302,7 +1290,7 @@
"message": "נדרשת הזנה מחדש של הסיסמה הראשית."
},
"masterPasswordMinlength": {
- "message": "הסיסמת הראשית חייבת להכיל $VALUE$ תווים לפחות.",
+ "message": "סיסמה ראשית חייבת להיות לפחות באורך $VALUE$ תווים.",
"description": "The Master Password must be at least a specific number of characters long.",
"placeholders": {
"value": {
@@ -1336,7 +1324,7 @@
"message": "נא לבחור תאריך תפוגה שהוא בעתיד."
},
"emailAddress": {
- "message": "כתובת אימייל"
+ "message": "כתובת דוא\"ל"
},
"yourVaultIsLockedV2": {
"message": "הכספת שלך נעולה"
@@ -1494,28 +1482,28 @@
"message": "הכנס את מפתח האבטחה שלך אל כניסת ה-USB במחשבך. אם יש לו כפתור, לחץ עליו."
},
"loginUnavailable": {
- "message": "פרטי כניסה לא זמינים"
+ "message": "כניסה לא זמינה"
},
"noTwoStepProviders": {
- "message": "כניסה דו-שלבית פעילה בחשבון זה, אך אף אחד מספקי הכניסה הדו-שלבית לא נתמכים בדפדפן זה."
+ "message": "לחשבון זה מוגדרת כניסה דו־שלבית, עם זאת, אף אחד מהספקים הדו־שלביים שהוגדרו אינו נתמך על ידי דפדפן זה."
},
"noTwoStepProviders2": {
"message": "אנא השתמש בדפדפן נתמך (כמו לדוגמא Chrome) ו\\או הוסף ספק כניסה דו-שלבית הנתמך בדפדפן זה (כמו לדוגמא אפליקצית אימות)."
},
"twoStepOptions": {
- "message": "אפשרויות כניסה דו שלבית"
+ "message": "אפשרויות כניסה דו־שלבית"
},
"selectTwoStepLoginMethod": {
"message": "בחר שיטת כניסה דו־שלבית"
},
"recoveryCodeDesc": {
- "message": "איבדת גישה לכל ספקי האימות הדו-שלבי שלך? השתמש בקוד האימות כדי לבטל את הספקים הקיימים מתוך החשבון שלך."
+ "message": "איבדת גישה לכל ספקי הכניסות הדו־שלביות שלך? השתמש בקוד השחזור שלך כדי לכבות את כל ספקי הכניסות הדו־שלביות מהחשבון שלך."
},
"recoveryCodeTitle": {
"message": "קוד שחזור"
},
"authenticatorAppTitle": {
- "message": "אפליקציית אימות"
+ "message": "יישום מאמת"
},
"authenticatorAppDescV2": {
"message": "הזן קוד שנוצר על ידי יישום מאמת כמו מאמת Bitwarden.",
@@ -1525,7 +1513,7 @@
"message": "מפתח אבטחה OTP של Yubico"
},
"yubiKeyDesc": {
- "message": "השתמש בYubiKey עבור גישה לחשבון שלך. עובד עם YubiKey מסדרה 4, סדרה 5, ומכשירי NEO."
+ "message": "השתמש ב־YubiKey מסדרה 4, 5, או מכשיר NEO."
},
"duoDescV2": {
"message": "הזן קוד שנוצר על ידי Duo Security.",
@@ -1536,7 +1524,7 @@
"description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated."
},
"u2fDesc": {
- "message": "השתמש בכל מפתח אבטחה התומך בFIDO U2F עבור גישה לחשבונך."
+ "message": "השתמש בכל מפתח אבטחה תואם FIDO U2F כדי לגשת לחשבונך."
},
"u2fTitle": {
"message": "מפתח אבטחה FIDO U2F"
@@ -1551,7 +1539,7 @@
"message": "(הועבר מ־FIDO)"
},
"openInNewTab": {
- "message": "פתח בכרטיסייה חדשה"
+ "message": "פתח בכרטיסיה חדשה"
},
"emailTitle": {
"message": "אימייל"
@@ -1571,14 +1559,11 @@
"moveToOrgDesc": {
"message": "בחר ארגון שאליו ברצונך להעביר פריט זה. העברה אל ארגון מעבירה בעלות של הפריט אל אותו ארגון. לא תוכל להיות הבעלים הישיר של פריט זה ברגע שהוא הועבר."
},
- "moveManyToOrgDesc": {
- "message": "בחר ארגון שאליו ברצונך להעביר פריטים אלה. העברה אל ארגון מעבירה בעלות של הפריטים אל אותו ארגון. לא תוכל להיות הבעלים הישיר של פריטים אלה ברגע שהם הועברו."
- },
"collectionsDesc": {
"message": "ערוך את האוסף המשותף של פריט זה. רק משתמשים מורשים מתוך הארגון יוכלו לראות פריט זה."
},
"deleteSelectedItemsDesc": {
- "message": "בחרת $COUNT$ פריט(ים) למחיקה. האם אתה בטוח שברצונך למחוק את כולם?",
+ "message": "$COUNT$ פריט(ים) ישלח(ו) לאשפה.",
"placeholders": {
"count": {
"content": "$1",
@@ -1607,23 +1592,6 @@
}
}
},
- "moveSelectedItemsCountDesc": {
- "message": "בחרת $COUNT$ פריט(ים). $MOVEABLE_COUNT$ פריט(ים) ניתן להעביר אל ארגון, $NONMOVEABLE_COUNT$ לא ניתן.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "10"
- },
- "moveable_count": {
- "content": "$2",
- "example": "8"
- },
- "nonmoveable_count": {
- "content": "$3",
- "example": "2"
- }
- }
- },
"verificationCodeTotp": {
"message": "קוד אימות (TOTP)"
},
@@ -1655,7 +1623,7 @@
"message": "ייצוא זה מכיל את נתוני הכספת שלך בפורמט לא מוצפן. אתה לא אמור לאחסן או לשלוח את הקובץ המיוצא דרך ערוצים לא מאובטחים (כמו דוא\"ל). מחק אותו מיד לאחר שסיימת להשתמש בו."
},
"encExportKeyWarningDesc": {
- "message": "ייצוא זה מצפין את הנתונים שלך באמצעות מפתח ההצפנה של חשבונך. אם אי פעם תבצע סיבוב (רוטציה) למפתח ההצפנה של חשבונך, תצטרך לייצא שוב משום שלא תוכל לפענח קובץ ייצוא זה."
+ "message": "ייצוא זה מצפין את הנתונים שלך באמצעות מפתח ההצפנה של חשבונך. אם אי פעם תבצע סיבוב למפתח ההצפנה של חשבונך, תצטרך לייצא שוב משום שלא תוכל לפענח קובץ ייצוא זה."
},
"encExportAccountWarningDesc": {
"message": "מפתחות הצפנת חשבון הם ייחודים לכל חשבון משתמש של Bitwarden, לכן אינך יכול לייבא ייצוא מוצפן אל תוך חשבון אחר."
@@ -1667,7 +1635,7 @@
"message": "ייצא מ־"
},
"exportVault": {
- "message": "יצוא כספת"
+ "message": "ייצא כספת"
},
"exportSecrets": {
"message": "ייצא סודות"
@@ -1718,23 +1686,23 @@
"message": "קובץ זה מוגן סיסמה. נא להזין את סיסמת הקובץ כדי לייבא נתונים."
},
"exportSuccess": {
- "message": "הוצאת המידע מהכספת שלך הסתיימה."
+ "message": "נתוני הכספת יוצאו"
},
"passwordGenerator": {
- "message": "יוצר הסיסמאות"
+ "message": "מחולל הסיסמאות"
},
"minComplexityScore": {
- "message": "ניקוד מורכבות מינימלי"
+ "message": "ציון מורכבות מינימלי"
},
"minNumbers": {
- "message": "מינימום ספרות"
+ "message": "מינימום מספרים"
},
"minSpecial": {
- "message": "מינימום תווים מיוחדים",
+ "message": "מינימום מיוחדים",
"description": "Minimum special characters"
},
"ambiguous": {
- "message": "המנע מאותיות ותווים דומים",
+ "message": "הימנע מתווים דו־משמעיים",
"description": "deprecated. Use avoidAmbiguous instead."
},
"avoidAmbiguous": {
@@ -1763,7 +1731,7 @@
"message": "תווים מיוחדים (*&^%$#@!)"
},
"numWords": {
- "message": "מספר מילים"
+ "message": "מספר המילים"
},
"wordSeparator": {
"message": "מפריד מילים"
@@ -1773,7 +1741,7 @@
"description": "Make the first letter of a word uppercase."
},
"includeNumber": {
- "message": "כלול מספרים"
+ "message": "כלול מספר"
},
"generatorPolicyInEffect": {
"message": "דרישות מדיניות ארגונית הוחלו על אפשרויות המחולל שלך.",
@@ -1808,10 +1776,10 @@
"description": "To clear something out. Example: To clear browser history."
},
"accountUpdated": {
- "message": "החשבון עודכן"
+ "message": "החשבון נשמר"
},
"changeEmail": {
- "message": "החלף אימייל"
+ "message": "שנה דוא\"ל"
},
"changeEmailTwoFactorWarning": {
"message": "המשך התהליך ישנה את כתובת הדוא\"ל של החשבון שלך. זה לא ישנה את כתובת הדוא\"ל המשמשת עבור אימות כניסה דו־שלבית. אתה יכול לשנות את כתובת דוא\"ל זו בהגדרות הכניסה הדו־שלבית."
@@ -1835,7 +1803,7 @@
"message": "בכדי להמשיך הסשן הנוכחי ינותק, ותדרש להזין את פרטי הכניסה החדשים. כל הסשנים הפעילים במכשירים אחרים ישארו פעילים עד שעה ממועד הכניסה החדשה."
},
"emailChanged": {
- "message": "כתובת האימייל שונתה"
+ "message": "דוא\"ל נשמר"
},
"logBackIn": {
"message": "אנא התחבר שוב."
@@ -1853,7 +1821,7 @@
"message": "החלף סיסמה ראשית"
},
"masterPasswordChanged": {
- "message": "הסיסמה הראשית הוחלפה"
+ "message": "הסיסמה הראשית נשמרה"
},
"currentMasterPass": {
"message": "סיסמה ראשית נוכחית"
@@ -1871,7 +1839,7 @@
"message": "אלגוריתם KDF"
},
"kdfIterations": {
- "message": "איטרציות KDF"
+ "message": "חזרות KDF"
},
"kdfIterationsDesc": {
"message": "קביעת ערך גבוה עבור מספר האיטרציות של KDF עוזרת בהגנה על הסיסמה הראשית שלך מפני תקיפת Brute force (תְּקִיפָה כּוֹחָנִית). אנו ממליצים להשתמש בערך $VALUE$ או ערך גבוה יותר.",
@@ -1883,7 +1851,7 @@
}
},
"kdfIterationsWarning": {
- "message": "קביעת ערך גבוה מדי עבור מספר האיטרציות KDF עלול לגרום לבעיות ביצועים בזמן הכניסה (ובזמן ביטול הנעילה) לחשבון Bitwarden במכשירים בעלי מעבד חלש. אנו ממליצים שתעלה את הערך בקפיצות של $INCREMENT$ ובדוק את ההשפעה של הביצועים בכל המכשירים שלך.",
+ "message": "הגדרת חזרות KDF שלך לערך גבוה מדי עלולה לגרום לביצועים ירודים בעת כניסה אל (וביטול נעילת) Bitwarden במכשירים איטיים או ישנים יותר. אנו ממליצים להגדיל את הערך במרווחים של $INCREMENT$ ואז לבדוק את כל המכשירים שלך.",
"placeholders": {
"increment": {
"content": "$1",
@@ -1908,19 +1876,19 @@
"message": "שנה KDF"
},
"encKeySettingsChanged": {
- "message": "הגדרות מפתח ההצפנה השתנו"
+ "message": "הגדרות מפתח ההצפנה נשמרו"
},
"dangerZone": {
- "message": "אזור מסוכן"
+ "message": "אזור סכנה"
},
"deauthorizeSessions": {
- "message": "בטל הרשאות סשנים"
+ "message": "בטל אישור הפעלות"
},
"deauthorizeSessionsDesc": {
"message": "מודאג אם השארת את החשבון שלך מחובר במכשיר אחר? המשך כאן להסרת ההרשאות של סשנים מכל המחשבים או המכשירים שהשתמשת בעבר. צעד אבטחה זה מומלץ אם השתמשת בעבר במחשב ציבורי או ששמרת את הסיסמה בטעות במכשיר שאינו שלך. כמו כן, צעד זה ינקה גם את כל הסיסמאות השמורות עבור סשנים שהשתמשו באימות דו-שלבי."
},
"deauthorizeSessionsWarning": {
- "message": "בכדי להמשיך הסשן הנוכחי ינותק, ותדרש להזין את פרטי הכניסה החדשים וגם את פרטי האימות הדו-שלבי, אם הוא מאופשר. כל הסשנים הפעילים במכשירים אחרים ישארו פעילים עד שעה ממועד הכניסה החדשה."
+ "message": "המשך התהליך יוציא אותך גם מההפעלה הנוכחית שלך, מה שידרוש ממך להיכנס חזרה. אתה גם תתבקש לבצע כניסה דו־שלבית שוב, אם מוגדרת. הפעלות פעילות במכשירים אחרים עלולות להישאר פעילות למשך עד שעה אחת."
},
"newDeviceLoginProtection": {
"message": "כניסת מכשיר חדש"
@@ -1944,7 +1912,7 @@
"message": "שינויי הגנת כניסת מכשיר חדש נשמרו"
},
"sessionsDeauthorized": {
- "message": "הוסרה ההרשאה מכל הסשנים"
+ "message": "כל אישורי ההפעלות בוטלו"
},
"accountIsOwnedMessage": {
"message": "חשבון זה הוא בבעלות $ORGANIZATIONNAME$",
@@ -1956,7 +1924,7 @@
}
},
"purgeVault": {
- "message": "מחק תוכן כספת"
+ "message": "טיהור כספת"
},
"purgedOrganizationVault": {
"message": "מחק תוכן כספת ארגונית."
@@ -1974,13 +1942,13 @@
"message": "מחיקת תוכן הכספת היא סופית. פעולה זו היא בלתי הפיכה."
},
"vaultPurged": {
- "message": "המידע בכספת נמחק."
+ "message": "הכספת טוהרה."
},
"deleteAccount": {
"message": "מחק חשבון"
},
"deleteAccountDesc": {
- "message": "המשך כאן בכדי למחוק את החשבון שלך וכל המידע המשויך אליו."
+ "message": "המשך למטה כדי למחוק את החשבון שלך ואת כל נתוני הכספת."
},
"deleteAccountWarning": {
"message": "מחיקת החשבון היא פעולה בלתי הפיכה."
@@ -2030,7 +1998,7 @@
"message": "הייתה בעיה עם הנתונים שניסית לייבא. נא לפתור את השגיאות למטה בקובץ המקור שלך ולנסות שוב."
},
"importSuccess": {
- "message": "נתונים יובאו בהצלחה אל תוך הכספת שלך."
+ "message": "הנתונים יובאו בהצלחה"
},
"importSuccessNumberOfItems": {
"message": "בסך הכל יובאו $AMOUNT$ פריטים.",
@@ -2146,10 +2114,10 @@
"message": "אם אתה משתמש באותם פרטי כניסה עבור אתרים שונים באותו דומיין, באפשרות לסמן את האתר כ\"שווה\". הערכים הרגילים שנוצרים על ידי Bitwarden מסומנים כדומיין \"גלובלי\"."
},
"globalEqDomains": {
- "message": "דומיינים גלובליים שווים"
+ "message": "דומיינים שקולים גלובליים"
},
"customEqDomains": {
- "message": "דומיינים שווים מותאמים אישית"
+ "message": "דומיינים שקולים מותאמים אישית"
},
"exclude": {
"message": "אל תכלול"
@@ -2176,10 +2144,10 @@
}
},
"domainsUpdated": {
- "message": "הדומיינים עודכנו"
+ "message": "הדומיינים נשמרו"
},
"twoStepLogin": {
- "message": "התחברות בשני-שלבים"
+ "message": "כניסה דו־שלבית"
},
"twoStepLoginEnforcement": {
"message": "אכיפת כניסה דו־שלבית"
@@ -2204,13 +2172,13 @@
"message": "אם הגדרת SSO או מתכוון לעשות כן, ייתכן שכניסה דו־שלבית כבר נאכפת דרך ספק הזהות שלך."
},
"twoStepLoginRecoveryWarning": {
- "message": "שים לב: שימוש לא נכון בכניסה דו-שלבית עשוי לגרום לך להנעל ללא גישה לחשבון Bitwarden שלך. מומלץ לשמור קוד שחזור לגישה לחשבון שלך למקרה שלא תוכל להשתמש בספק הכניסה הדו-שלבית (לדוגמא: איבדת את הפלאפון או את מפתח החומרה שלך). גם צוות התמיכה של Bitwarden לא יוכל לעזור לך במקרה שתאבד גישה לחשבון שלך. אנו ממליצים שתכתוב או תדפיס את קודי השחזור ותשמור אותם במקום בטוח."
+ "message": "הגדרת כניסה דו־שלבית יכולה לנעול אותך לצמיתות מחוץ לחשבון Bitwarden שלך. קוד שחזור מאפשר לך לגשת לחשבון שלך במקרה שאתה לא יכול להשתמש בספק הכניסה הד־שלבית הרגיל שלך (דוגמה: איבדת את המכשיר שלך). התמיכה של Bitwarden לא תוכל לסייע לך אם תאבד גישה לחשבון שלך. אנו ממליצים שתכתוב או תדפיס את קוד השחזור ותשמור אותו במקום בטוח."
},
"yourSingleUseRecoveryCode": {
"message": "ניתן להשתמש בקוד השחזור החד־פעמי שלך כדי לכבות כניסה דו־שלבית במקרה שאתה מאבד גישה לספק הכניסה הדו־שלבית שלך. Bitwarden ממליץ לך לרשום את קוד השחזור ולשמור אותו במקום בטוח."
},
"viewRecoveryCode": {
- "message": "צפה בקוד שחזור"
+ "message": "הצג קוד שחזור"
},
"providers": {
"message": "ספקים",
@@ -2230,25 +2198,25 @@
"description": "Premium membership"
},
"premiumMembership": {
- "message": "חשבון פרימיום"
+ "message": "חברות פרימיום"
},
"premiumRequired": {
- "message": "נדרש חשבון פרימיום"
+ "message": "נדרש פרימיום"
},
"premiumRequiredDesc": {
- "message": "בכדי להשתמש ביכולת זו יש צורך בחשבון פרמיום."
+ "message": "נדרשת חברות פרימיום כדי להשתמש בתכונה זו."
},
"youHavePremiumAccess": {
"message": "יש לך גישת פרימיום"
},
"alreadyPremiumFromOrg": {
- "message": "לארגון שאתה חבר בו, כבר יש גישת פרימיום, ולכן יש לך גישה ליכולות פרמיום."
+ "message": "כבר יש לך גישה ליכולות פרימיום בזכות ארגון שאתה חבר בו."
},
"manage": {
"message": "נהל"
},
"manageCollection": {
- "message": "נהל אוסף"
+ "message": "ניהול אוסף"
},
"viewItems": {
"message": "הצג פריטים"
@@ -2263,7 +2231,7 @@
"message": "ערוך פריטים, סיסמאות מוסתרות"
},
"disable": {
- "message": "בטל"
+ "message": "כבה"
},
"revokeAccess": {
"message": "בטל גישה"
@@ -2272,7 +2240,7 @@
"message": "בטל"
},
"twoStepLoginProviderEnabled": {
- "message": "ספק כניסה דו-שלבית זה נתמך בחשבון שלך."
+ "message": "ספק כניסה דו־שלבית זה פעיל בחשבון שלך."
},
"twoStepLoginAuthDesc": {
"message": "הזן את הסיסמה הראשית שלך בכדי לשנות הגדרות הנוגעות לכניסה דו-שלבית."
@@ -2323,10 +2291,10 @@
"message": "במקרה שאתה צריך את אפשרות הכניסה זמינה גם במכשיר אחר, כאן ניתן למצוא את קוד הQR (או המפתח) הנחוץ לאפליקציית האימות במכשיר הנוסף."
},
"twoStepDisableDesc": {
- "message": "האם אתה בטוח שברצונך לבטל את הספק הזה עבור הכניסה הדו-שלבית?"
+ "message": "האם אתה בטוח שברצונך לכבות ספק כניסה דו־שלבית זה?"
},
"twoStepDisabled": {
- "message": "ספק עבור כניסה דו-שלבית מבוטל."
+ "message": "ספק כניסה דו־שלבית כבוי."
},
"twoFactorYubikeyAdd": {
"message": "הוסף מפתח YubiKey לחשבונך"
@@ -2344,7 +2312,7 @@
"message": "שמור את הטופס."
},
"twoFactorYubikeyWarning": {
- "message": "עקב מגבלות פלטפורמה, לא ניתן להשתמש בYubiKey בכל האפליקציות של Bitwarden. עליך לאפשר ספק כניסה דו-שלבית נוסף למקרה שבו הYubiKey שלך לא זמין. פלטפורמות נתמכות:"
+ "message": "עקב מגבלות פלטפורמה, לא ניתן להשתמש במפתחות YubiKey בכל היישומים של Bitwarden. עליך להגדיר ספק כניסה דו־שלבית אחר כך שתוכל לגשת לחשבון שלך כאשר לא ניתן להשתמש במפתחות YubiKey. פלטפורמות נתמכות:"
},
"twoFactorYubikeySupportUsb": {
"message": "כספת רשת, אפליקציית שולחן עבודה, שורת הפקודה, וכל התוספים לדפדפן על מכשיר עם חיבור USB עבור הYubiKey שלך."
@@ -2392,7 +2360,7 @@
"message": "מפתחות YubiKey עודכנו"
},
"disableAllKeys": {
- "message": "בטל את כל המפתחות"
+ "message": "השבת את כל המפתחות"
},
"twoFactorDuoDesc": {
"message": "הזן את פרטי אפליקציית Bitwarden מתוך עמוד הניהול של Duo."
@@ -2404,7 +2372,7 @@
"message": "סוד לקוח"
},
"twoFactorDuoApiHostname": {
- "message": "שם שרת הAPI"
+ "message": "שם מארח API"
},
"twoFactorEmailDesc": {
"message": "עקוב אחר הצעדים הבאים להגדרת כניסה דו-שלבית עם אימייל:"
@@ -2446,16 +2414,16 @@
"message": "שמור את הטופס."
},
"twoFactorU2fWarning": {
- "message": "עקב מגבלות פלטפורמה, לא ניתן להשתמש בFIDO U2F בכל האפליקציות של Bitwarden. עליך לאפשר ספק כניסה דו-שלבית נוסף למקרה שבו הFIDO U2F שלך לא זמין. פלטפורמות נתמכות:"
+ "message": "עקב מגבלות פלטפורמה, לא ניתן להשתמש ב־FIDO U2F בכל היישומים של Bitwarden. עליך להגדיר ספק כניסה דו־שלבית אחר כך שתוכל לגשת לחשבון שלך כאשר לא ניתן להשתמש ב־FIDO U2F. פלטפורמות נתמכות:"
},
"twoFactorU2fSupportWeb": {
- "message": "כספת ברשת ותוספי אבטחה למחשב נייח\\נייד עם דפדפן תומך בU2F (כרום, אופרה, Vivaldi, או פיירפוקס עם תמיכה בFIDO U2F)."
+ "message": "כספת רשת והרחבות דפדפן במחשב נייח/נייד עם דפדפן התומך ב־U2F (Vivaldi, Opera, Chrome או Firefox עם FIDO U2F מופעל)."
},
"twoFactorU2fWaiting": {
"message": "ממתין ללחיצה על כפתור במפתח האבטחה שלך"
},
"twoFactorU2fClickSave": {
- "message": "לחץ על כפתור \"שמירה\" בכדי לאפשר כניסה דו-שלבית בעזרת מפתח אבטחה זה."
+ "message": "לחץ על הלחצן \"שמור\" למטה כדי להפעיל את מפתח האבטחה הזה עבור כניסה דו־שלבית."
},
"twoFactorU2fProblemReadingTryAgain": {
"message": "היתה בעיה בקריאת מפתח האבטחה. נסה בשנית."
@@ -2467,7 +2435,7 @@
"message": "קוד השחזור שלך עבור כניסה דו שלבית לBitwarden"
},
"twoFactorRecoveryNoCode": {
- "message": "עדיין לא הוספת אף ספק לכניסה דו-שלבית. לאחר שתאפשר כניסה באמצעות ספק עם כניסה דו שלבית תוכל לבדוק כאן שוב ולראות את קוד השחזור שלך."
+ "message": "עדיין לא הגדרת אף ספק כניסה דו־שלבית. לאחר שתגדיר ספק כניסה דו־שלבית, תוכל לבדוק שוב כאן עבור קוד השחזור שלך."
},
"printCode": {
"message": "הדפס קוד",
@@ -2485,10 +2453,10 @@
"description": "Vault health reports can be used to evaluate the security of your Bitwarden individual or organization vault."
},
"unsecuredWebsitesReport": {
- "message": "דוח אתרים לא מאובטחים"
+ "message": "אתרים לא מאובטחים"
},
"unsecuredWebsitesReportDesc": {
- "message": "שימוש באתרים לא מאובטחים שמתחילים בקידומת http:// יכול להיות מסוכן. אם האתר מאפשר זאת, תמיד נסה להשתמש בקידומת https:// כך שהחיבור יהיה מוצפן."
+ "message": "כתובות URL שמתחילות עם //:http אינן משתמשות בהצפנה הטובה ביותר שזמינה. שנה את הכתובות URI של הכניסות עבור החשבונות האלה כך שיתחילו עם //:https בשביל גלישה בטוחה יותר."
},
"unsecuredWebsitesFound": {
"message": "נמצאו אתרים לא מאובטחים"
@@ -2510,13 +2478,13 @@
"message": "לא נמצאו פריטים בכספת המכילים כתובות לא מאובטחות."
},
"inactive2faReport": {
- "message": "דוח 2FA לא פעילים"
+ "message": "כניסה דו־שלבית לא פעילה"
},
"inactive2faReportDesc": {
- "message": "אימות דו-שלבי (2FA) היא הגדרת אבטחה חשובה שעוזרת לאבטח את החשבון שלך. אם האתר מאפשר זאת, מומלץ לאפשר את האימות הדו-שלבי."
+ "message": "כניסה דו-שלבית מוסיפה שכבת הגנה לחשבונות שלך. הגדר כניסה דו־שלבית באמצעות המאמת של Bitwarden עבור החשבונות האלה או השתמש בשיטה חלופית."
},
"inactive2faFound": {
- "message": "נמצאו פרטי כניסות שלא פעילה בהן אופציית 2FA"
+ "message": "נמצאו כניסות ללא כניסה דו־שלבית"
},
"inactive2faFoundReportDesc": {
"message": "מצאנו $COUNT$ אתרים בכספת שלך שייתכן שלא הוגדרו עם כניסה דו־שלבית (על פי 2fa.directory). כדי להגן עוד יותר על החשבונות הללו, עליך להגדיר כניסה דו־שלבית.",
@@ -2532,22 +2500,22 @@
}
},
"noInactive2fa": {
- "message": "לא נמצאו אתרים ללא אימות דו-שלבי בכספת שלך."
+ "message": "לא נמצאו אתרים בכספת שלך עם תצורת כניסה דו־שלבית חסרה."
},
"instructions": {
"message": "הוראות"
},
"exposedPasswordsReport": {
- "message": "דו\"ח סיסמאות שנחשפו"
+ "message": "סיסמאות חשופות"
},
"exposedPasswordsReportDesc": {
"message": "סיסמאות חשופות בפרצת נתונים הן מטרות קלות עבור תוקפים. שנה סיסמאות אלה כדי למנוע פריצות פוטנציאליות."
},
"exposedPasswordsFound": {
- "message": "נמצאו סיסמאות שנחשפו"
+ "message": "נמצאו סיסמאות חשופות"
},
"exposedPasswordsFoundReportDesc": {
- "message": "מצאנו $COUNT$ פריטים בכספת שלך שיש להם סיסמאות שנחפשו בפרצות נתונים ידועות. עליך לשנות אותם כך שישתמשו בסיסמה חדשה.",
+ "message": "מצאנו $COUNT$ פריטים בכספת שלך שיש להם סיסמאות שנחשפו בפרצות נתונים ידועות. עליך לשנות אותם כך שישתמשו בסיסמה חדשה.",
"placeholders": {
"count": {
"content": "$1",
@@ -2563,7 +2531,7 @@
"message": "לא נמצאו פריטים בכספת שלך שנחשפו בפריצות ידועות."
},
"checkExposedPasswords": {
- "message": "בדוק אם קיימות סיסמאות שנפרצו"
+ "message": "בדוק סיסמאות חשופות"
},
"timesExposed": {
"message": "פעמים נחשפו"
@@ -2578,10 +2546,10 @@
}
},
"weakPasswordsReport": {
- "message": "דו\"ח סיסמאות חלשות"
+ "message": "סיסמאות חלשות"
},
"weakPasswordsReportDesc": {
- "message": "סיסמאות חלשות קלות לניחוש על ידי האקרים וכלים אוטומטיים לפריצת סיסמאות. מחולל הסיסמאות של Bitwarden יכול לעזור לך ליצור סיסמאות חזקות."
+ "message": "תוקפים יכולים לנחש סיסמאות חלשות בקלות. שנה את הסיסמאות הללו לסיסמאות חזקות באמצעות מחולל הסיסמאות."
},
"weakPasswordsFound": {
"message": "נמצאו סיסמאות חלשות"
@@ -2606,13 +2574,13 @@
"message": "חולשה"
},
"reusedPasswordsReport": {
- "message": "דו\"ח סיסמאות משומשות"
+ "message": "סיסמאות בשימוש חוזר"
},
"reusedPasswordsReportDesc": {
- "message": "אם שירות שהשתמשת בו נפרץ, שימוש באותה הסיסמה במקום אחר מאפשר להאקרים לקבל גישה לחשבונות נוספים שלך בקלות רבה. מומלץ מאוד להשתמש בסיסמה יחודית עבור כל חשבון או שירות."
+ "message": "שימוש חוזר של סיסמאות מקל על תוקפים לפרוץ לחשבונות מרובים. שנה את הסיסמאות הללו כך שכל אחת תהיה ייחודית."
},
"reusedPasswordsFound": {
- "message": "נמצאו סיסמאות משומשות"
+ "message": "נמצאו סיסמאות בשימוש חוזר"
},
"reusedPasswordsFoundReportDesc": {
"message": "מצאנו $COUNT$ סיסמאות שנמצאות בשימוש חוזר בכספת שלך. עליך לשנות אותם לערך ייחודי.",
@@ -2643,16 +2611,16 @@
}
},
"dataBreachReport": {
- "message": "דו\"ח פריצת אבטחה"
+ "message": "פרצת נתונים"
},
"breachDesc": {
- "message": "אירוע \"דליפה\" הוא תקרית שבה המידע של האתר היה נגיש בצורה לא חוקית להאקרים והם הפיצו אותו באופן פומבי. עבור על המידע שנחשף (כתובות אימייל, סיסמאות, כרטיסי אשראי וכו') ובצע את הפעולות הנחוצות, לדוגמא - לשנות את הסיסמאות שפורסמו."
+ "message": "חשבונות שנפרצו יכולים לחשוף את המידע האישי שלך. אבטח חשבונות שנפרצו על ידי הפעלת אימות דו־גורמי (2FA) או יצירת סיסמה חזקה יותר."
},
"breachCheckUsernameEmail": {
"message": "בדוק את כל שמות המשתמשים או כתובות המייל שאתה משתמש בהם."
},
"checkBreaches": {
- "message": "בדוק פריצות אבטחה"
+ "message": "בדוק פרצות"
},
"breachUsernameNotFound": {
"message": "שם המשתמש $USERNAME$ לא נמצא בפריצות אבטחה ידועות.",
@@ -2681,7 +2649,7 @@
}
},
"breachFound": {
- "message": "נמצאו חשבונות שדלפו"
+ "message": "נמצאו חשבונות שנפרצו"
},
"compromisedData": {
"message": "מידע שנחשף"
@@ -2693,10 +2661,10 @@
"message": "משתמשים שהושפעו"
},
"breachOccurred": {
- "message": "פריצת אבטחה אירעה"
+ "message": "אירעה פרצה"
},
"breachReported": {
- "message": "פריצת אבטחה דווחה"
+ "message": "דווחה פרצה"
},
"reportError": {
"message": "אירעה שגיאה בטעינת הדו\"ח. נסה שוב"
@@ -2711,15 +2679,15 @@
"message": "סוג תשלום"
},
"accountCredit": {
- "message": "מאזן החשבון",
+ "message": "אשראי חשבון",
"description": "Financial term. In the case of Bitwarden, a positive balance means that you owe money, while a negative balance means that you have a credit (Bitwarden owes you money)."
},
"accountBalance": {
- "message": "יתרת חשבון",
+ "message": "מאזן חשבון",
"description": "Financial term. In the case of Bitwarden, a positive balance means that you owe money, while a negative balance means that you have a credit (Bitwarden owes you money)."
},
"addCredit": {
- "message": "הוסף קרדיט",
+ "message": "הוסף אשראי",
"description": "Add more credit to your account's balance."
},
"amount": {
@@ -2743,7 +2711,7 @@
"message": "שדרגת לפרימיום."
},
"premiumUpgradeUnlockFeatures": {
- "message": "שדרג את חשבונך לפרמיום כדי להשתמש ביכולות נהדרות נוספות."
+ "message": "שדרג את חשבונך לחברות פרמיום ופתח כמה תכונות נוספות נהדרות."
},
"premiumSignUpStorage": {
"message": "1 ג'יגה של מקום אחסון מוצפן עבור קבצים מצורפים."
@@ -2764,7 +2732,7 @@
"message": "קדימות בתמיכה הטכנית."
},
"premiumSignUpFuture": {
- "message": "כל יכולות הפרימיום העתידיות שנפתח. עוד יכולות מגיעות בקרוב!"
+ "message": "כל תכונות הפרימיום העתידיות. עוד מגיעות בקרוב!"
},
"premiumPrice": {
"message": "הכל רק ב-$PRICE$ לשנה!",
@@ -2798,7 +2766,7 @@
"message": "גישת פרימיום"
},
"premiumAccessDesc": {
- "message": "ניתן להוסיף גישת פרימיום לכל חברי הארגון שלך ב-$PRICE$ ל$INTERVAL$.",
+ "message": "אתה יכול להוסיף גישת פרימיום לכל חברי הארגון שלך עבור $PRICE$ /$INTERVAL$.",
"placeholders": {
"price": {
"content": "$1",
@@ -2811,7 +2779,7 @@
}
},
"additionalStorageGb": {
- "message": "מקום אחסון נוסף (בג'יגה)"
+ "message": "אחסון נוסף (GB)"
},
"additionalStorageGbDesc": {
"message": "# של ג'יגה בייט נוספים"
@@ -2868,7 +2836,7 @@
"message": "שיטת התשלום שלך תחויב עבור כל מנוי שלא שולם."
},
"paymentChargedWithTrial": {
- "message": "התוכנית שבחרת מגיעה עם 7 ימי נסיון חינמי. שיטת התשלום שבחרת לא תחויב עד לתום תקופת הנסיון. ביצוע החשבון יתבצע על בסיס מתחדש בכל $INTERVAL$. באפשרותך לבטל בכל עת."
+ "message": "התוכנית שלך מגיעה עם 7 ימי ניסיון בחינם. שיטת התשלום שלך לא תחויב עד שהניסיון יסתיים. אתה רשאי לבטל בכל עת."
},
"paymentInformation": {
"message": "פרטי תשלום"
@@ -2883,7 +2851,7 @@
"message": "כרטיס אשראי"
},
"paypalClickSubmit": {
- "message": "לחץ על כפתור PayPal בכדי להכנס לחשבון PayPal שלך, ואז לחץ על כפתור התשלום כדי להמשיך."
+ "message": "בחר את הלחצן PayPal כדי להיכנס לחשבון PayPal שלך, ואז לחץ על הלחצן 'שלח' למטה כדי להמשיך."
},
"cancelSubscription": {
"message": "בטל מנוי"
@@ -2895,13 +2863,13 @@
"message": "המנוי בוטל."
},
"pendingCancellation": {
- "message": "בקשת ביטול ממתינה"
+ "message": "ממתין לביטול"
},
"subscriptionPendingCanceled": {
"message": "המנוי סומן כמיועד לביטול בסיום תקופת החיוב הנוכחית."
},
"reinstateSubscription": {
- "message": "הפעל מחדש את המנוי"
+ "message": "החזר מנוי"
},
"reinstateConfirmation": {
"message": "האם אתה בטוח שברצונך להסיר את בקשת הביטול הממתינה ולהפעיל מחדש את חשבונך?"
@@ -2913,10 +2881,10 @@
"message": "האם אתה בטוח שברצונך לבטל? ביטול המנוי יגרום לאיבוד כל האפשרויות השמורות למנויים בסיום מחזור החיוב הנוכחי."
},
"canceledSubscription": {
- "message": "המנוי בוטל."
+ "message": "המנוי בוטל"
},
"neverExpires": {
- "message": "ללא תאריך תפוגה"
+ "message": "לא פג תוקף לעולם"
},
"status": {
"message": "סטטוס"
@@ -2937,7 +2905,7 @@
"message": "עדכן רישיון"
},
"manageSubscription": {
- "message": "ניהול מנוי"
+ "message": "נהל מנוי"
},
"launchCloudSubscription": {
"message": "הפעל מנוי ענן"
@@ -3039,7 +3007,7 @@
}
},
"contactSupport": {
- "message": "צור קשר עם התמיכה"
+ "message": "צור קשר עם תמיכת הלקוחות"
},
"contactSupportShort": {
"message": "פנה לתמיכה"
@@ -3063,7 +3031,7 @@
}
},
"uploadLicenseFilePremium": {
- "message": "כדי לשדרג את החשבון שלך לפרמיום עליך להעלות קובץ רשיון תקין."
+ "message": "כדי לשדרג את החשבון שלך לחברות פרימיום, אתה צריך להעלות קובץ רישיון חוקי."
},
"uploadLicenseFileOrg": {
"message": "ליצירת שרת on-premises בארגון לך עליך להעלות קובץ רשיון תקין."
@@ -3084,7 +3052,7 @@
"message": "החשבון הזה נמצא בבעלות עסק."
},
"billingEmail": {
- "message": "מייל לחשבוניות"
+ "message": "דוא\"ל לחיוב"
},
"businessName": {
"message": "שם העסק"
@@ -3096,10 +3064,10 @@
"message": "משתמשים"
},
"userSeats": {
- "message": "כסאות משתמשים"
+ "message": "מקומות למשתמשים"
},
"additionalUserSeats": {
- "message": "כסאות משתמשים נוספים"
+ "message": "מקומות למשתמשים נוספים"
},
"userSeatsDesc": {
"message": "כמות כסאות משתמשים"
@@ -3209,7 +3177,7 @@
"message": "הוסף ושתף עם כמות בלתי מוגבלת של משתמשים"
},
"createUnlimitedCollections": {
- "message": "צור מספר בלתי מוגבל של אוספים"
+ "message": "צור אוספים ללא הגבלה"
},
"gbEncryptedFileStorage": {
"message": "גודל קובץ מוצפן: $SIZE$",
@@ -3221,16 +3189,16 @@
}
},
"onPremHostingOptional": {
- "message": "אחסון שרת מקומי (אופציונאלי)"
+ "message": "אירוח מקומי (אופציונלי)"
},
"usersGetPremium": {
- "message": "המשתמשים יקבלו גישה ליכולות פרימיום"
+ "message": "המשתמשים יקבלו גישה לתכונות פרימיום"
},
"controlAccessWithGroups": {
"message": "שלוט על גישת משתמשים בעזרת קבוצות"
},
"syncUsersFromDirectory": {
- "message": "סנכרן את המשתמשים והקבוצות עם Active Directory"
+ "message": "סנכרן את המשתמשים והקבוצות שלך מתוך ספריה"
},
"trackAuditLogs": {
"message": "עקוב אחר פעולות המשתמשים בעזרת יומן ביקורת"
@@ -3290,7 +3258,7 @@
"message": "שנתי"
},
"basePrice": {
- "message": "מחיר בסיסי"
+ "message": "מחיר בסיס"
},
"organizationCreated": {
"message": "הארגון נוצר"
@@ -3299,7 +3267,7 @@
"message": "הארגון החדש שלך מוכן!"
},
"organizationUpgraded": {
- "message": "הארגון שלך שודרג."
+ "message": "הארגון שודרג"
},
"leave": {
"message": "יציאה"
@@ -3308,7 +3276,7 @@
"message": "האם אתה בטוח שברצונך לצאת מהארגון?"
},
"leftOrganization": {
- "message": "יצאת מהארגון."
+ "message": "עזבת את הארגון"
},
"defaultCollection": {
"message": "אוסף ברירת מחדל"
@@ -3317,13 +3285,13 @@
"message": "קבל עזרה"
},
"getApps": {
- "message": "הורד את האפליקציות"
+ "message": "הורד את היישומים"
},
"loggedInAs": {
"message": "מחובר בשם"
},
"eventLogs": {
- "message": "יומן אירועים"
+ "message": "יומני אירועים"
},
"people": {
"message": "אנשים"
@@ -3368,7 +3336,7 @@
"message": "כאשר חבר מוסר, אין לו יותר גישה לנתוני הארגון ופעולה זו היא בלתי הפיכה. כדי להוסיף את החבר בחזרה לארגון, יש להזמין ולקלוט אותו שוב."
},
"revokeUserConfirmation": {
- "message": "כאשר חבר מבוטל, אין לו יותר גישה לנתוני הארגון. כדי לשחזר במהירות גישת חבר, עבור לכרטיסייה 'מבוטל'."
+ "message": "כאשר חבר מבוטל, אין לו יותר גישה לנתוני הארגון. כדי לשחזר במהירות גישת חבר, עבור לכרטיסיה 'מבוטל'."
},
"removeUserConfirmationKeyConnector": {
"message": "אזהרה! משתמש זה דורש Key Connector כדי לנהל את ההצפנה שלו. הסרת משתמש זה מהארגון שלך תשבית לצמיתות את החשבון שלו. פעולה זו אינה ניתנת לביטול. האם ברצונך להמשיך?"
@@ -3377,7 +3345,7 @@
"message": "מזהה חיצוני"
},
"externalIdDesc": {
- "message": "ניתן להשתמש במזהה החיצוני כקישור בין משאב זה למערכת חיצונית כמו לדוגמא תיקיית משתמש."
+ "message": "מזהה חיצוני הוא הפניה לא מוצפנת בשימוש על ידי מחבר הספריות וה־API של Bitwarden."
},
"nestCollectionUnder": {
"message": "לקנן אוסף תחת"
@@ -3419,7 +3387,7 @@
"message": "הזמן משתמש חדש לארגון שלך על ידי הזנת כתובת האימייל שלהם שמשמשת אותם בחשבון Bitwarden. אם אין להם חשבון Bitwarden, הם יתבקשו ליצור חשבון."
},
"inviteMultipleEmailDesc": {
- "message": "באפשרותך להזמין עד $COUNT$ משתמשים בכל פעם על ידי הפרדת הכתובות בעזרת פסיק.",
+ "message": "הזן עד $COUNT$ כתובות דוא\"ל על ידי הפרדה עם פסיק.",
"placeholders": {
"count": {
"content": "$1",
@@ -3452,7 +3420,7 @@
"message": "בעלים"
},
"ownerDesc": {
- "message": "החשבון בעל ההרשאות הגבוהות ביותר שיכול לנהל את כל ההיבטים של הארגון."
+ "message": "נהל את כל ההיבטים של הארגון שלך, כולל חיובים ומנויים"
},
"clientOwnerDesc": {
"message": "על משתמש זה להיות עצמאי מהספק. אם הספק מנותק מהארגון, משתמש זה ישמור על הבעלות של הארגון."
@@ -3461,13 +3429,13 @@
"message": "מנהל"
},
"adminDesc": {
- "message": "מנהלים יכולים לגשת ולנהל את כל הפריטים, האוספים והמשתמשים שבארגונך."
+ "message": "נהל גישת ארגון, כל האוספים, חברים, דיווח, והגדרות אבטחה"
},
"user": {
"message": "משתמש"
},
"userDesc": {
- "message": "משתמש רגיל עם גישה לאוספים נבחרים בארגון שלך."
+ "message": "גישה והוספת פריטים לאוספים מוקצים"
},
"all": {
"message": "הכל"
@@ -3518,19 +3486,19 @@
"message": "מנהל הסודות של Bitwarden"
},
"loggedIn": {
- "message": "מחובר."
+ "message": "מחובר"
},
"changedPassword": {
- "message": "סיסמת החשבון שונתה."
+ "message": "סיסמת החשבון שונתה"
},
"enabledUpdated2fa": {
- "message": "כניסה דו שלבית הופעלה\\עודכנה."
+ "message": "כניסה דו־שלבית נשמרה"
},
"disabled2fa": {
- "message": "בטל כניסה דו שלבית."
+ "message": "כניסה דו־שלבית כבויה"
},
"recovered2fa": {
- "message": "חשבון שוחזר מכניסה דו שלבית."
+ "message": "חשבון שוחזר מכניסה דו־שלבית."
},
"failedLogin": {
"message": "נסיון כניסה נכשל עם סיסמה שגויה."
@@ -3552,10 +3520,10 @@
"description": "PIN code. Ex. The short code (often numeric) that you use to unlock a device."
},
"exportedVault": {
- "message": "יצוא כספת."
+ "message": "הכספת יוצאה"
},
"exportedOrganizationVault": {
- "message": "יצוא של תוכן הכספת הארגונית."
+ "message": "כספת הארגון יוצאה."
},
"editedOrgSettings": {
"message": "הגדרות הארגון נערכו."
@@ -4042,22 +4010,22 @@
"message": "סוג משתמש"
},
"groupAccess": {
- "message": "גישה לקבוצה"
+ "message": "גישה קבוצתית"
},
"groupAccessUserDesc": {
- "message": "ערוך את הקבוצות שמשתמש זה משויך אליהן."
+ "message": "הענק לחבר גישה לאוספים על ידי הוספתו לקבוצה אחת או יותר."
},
"invitedUsers": {
- "message": "משתמשים שהוזמנו."
+ "message": "משתמש(ים) הוזמנ(ו)"
},
"resendInvitation": {
"message": "שלח הזמנה מחדש"
},
"resendEmail": {
- "message": "שלח מייל בשנית"
+ "message": "שלח דוא\"ל מחדש"
},
"hasBeenReinvited": {
- "message": "$USER$ הוזמן מחדש.",
+ "message": "$USER$ הוזמן מחדש",
"placeholders": {
"user": {
"content": "$1",
@@ -4081,10 +4049,10 @@
}
},
"confirmUsers": {
- "message": "אשר משתמשים"
+ "message": "אשר חברים"
},
"usersNeedConfirmed": {
- "message": "ישנם משתמשים שקיבלו את הזמנתך, אך עדיין צריך לאשר אותם. למשתמשים אלו לא תהיה גישה לארגון עד שיאשרו אותם."
+ "message": "יש לך חברים שקיבלו את ההזמנה שלהם, אבל עדיין צריך לאשר אותם. לחברים לא תהיה גישה לארגון עד שיאושרו."
},
"startDate": {
"message": "תאריך התחלה"
@@ -4093,7 +4061,7 @@
"message": "תאריך סיום"
},
"verifyEmail": {
- "message": "אמת כתובת אימייל"
+ "message": "אמת דוא\"ל"
},
"verifyEmailDesc": {
"message": "אמת את האימייל שלך בכדי לאפשר גישה לכל היכולות."
@@ -4105,7 +4073,7 @@
"message": "בדוק אם קיבלת את קוד האימות באימייל."
},
"emailVerified": {
- "message": "כתובת האימייל שלך אומתה."
+ "message": "דוא\"ל החשבון אומת"
},
"emailVerifiedV2": {
"message": "דוא\"ל אומת"
@@ -4114,10 +4082,10 @@
"message": "לא ניתן לאמת את האימייל שלך. נסה לשלוח מייל אימות חדש."
},
"emailVerificationRequired": {
- "message": "יש לאמת את כתובת האימייל"
+ "message": "נדרש אימות דוא\"ל"
},
"emailVerificationRequiredDesc": {
- "message": "נדרש אישור אימות בדוא\"ל כדי לאפשר שימוש בתכונה זו."
+ "message": "אתה מוכרח לאמת את הדוא\"ל שלך כדי להשתמש בתכונה זו."
},
"updateBrowser": {
"message": "עדכן דפדפן"
@@ -4226,16 +4194,16 @@
"message": "זכור אימייל"
},
"recoverAccountTwoStepDesc": {
- "message": "אם אין באפשרות לגשת לחשבונך דרך השיטות הדו-שלביות הרגילות, תוכל להשתמש בקוד לשחזור האימות הדו שלבי בכדי לבטל את כל ספקי האימות הדו שלבי בחשבונך."
+ "message": "אם אינך יכול לגשת לחשבון שלך דרך שיטות הכניסה הדו־שלבית הרגילות שלך, אתה יכול להשתמש בקוד השחזור של הכניסה הדו־שלבית שלך כדי לכבות את כל הספקים הדו־שלביים בחשבונך."
},
"logInBelowUsingYourSingleUseRecoveryCode": {
"message": "היכנס למטה באמצעות קוד השחזור החד־פעמי שלך. זה יכבה את כל הספקים הדו־שלביים בחשבון שלך."
},
"recoverAccountTwoStep": {
- "message": "שחזר כניסה דו שלבית לחשבון"
+ "message": "שחזר כניסה דו־שלבית לחשבון"
},
"twoStepRecoverDisabled": {
- "message": "כניסה דו שלבית בוטלה בחשבונך."
+ "message": "כניסה דו־שלבית כבויה בחשבונך."
},
"learnMore": {
"message": "למידע נוסף"
@@ -4247,7 +4215,7 @@
"message": "אם החשבון שלך אכן קיים, שלחנו אליך מייל עם הוראות נוספות."
},
"deleteRecoverConfirmDesc": {
- "message": "ביקשת למחוק את חשבון ה-Bitwarden שלך. לחץ על הכפתור למטה בכדי לאשר זאת."
+ "message": "ביקשת למחוק את החשבון Bitwarden שלך. לחץ על הכפתור למטה כדי לאשר."
},
"deleteRecoverOrgConfirmDesc": {
"message": "ביקשת למחוק את ארגון ה־Bitwarden שלך."
@@ -4292,7 +4260,7 @@
"message": "הארגון עודכן"
},
"taxInformation": {
- "message": "מידע מיסים"
+ "message": "פרטי מס"
},
"taxInformationDesc": {
"message": "עבור לקוחות בתוך ארצות הברית, יש לכתוב מיקוד לצורך דיווח מיסוי. עבור לקוחות ממדינות אחרות ניתן למלא מספר זיהוי מס (VAT/GST) ו\\או כתובת שתופיע על הקבלות שלך."
@@ -4302,7 +4270,7 @@
"description": "A billing plan/package. For example: Families, Teams, Enterprise, etc."
},
"changeBillingPlan": {
- "message": "שנה תוכנית",
+ "message": "שדרג תוכנית",
"description": "A billing plan/package. For example: Families, Teams, Enterprise, etc."
},
"changeBillingPlanUpgrade": {
@@ -4320,7 +4288,7 @@
}
},
"viewInvoice": {
- "message": "צפה בחשבונית"
+ "message": "הצג חשבונית"
},
"downloadInvoice": {
"message": "הורד חשבונית"
@@ -4335,10 +4303,10 @@
"message": "אופציית תשלום באמצעות חשבון בנק זמינה אך ורק ללקוחות תושבי ארצות הברית. תצטרך לאמת את פרטי החשבון. אנו נבצע 2 מיקרו-הפקדות בתוך 1-2 ימי עסקים. הזן את הסכומים בעמוד פרטי הארגון המשלם בכדי לאמת את חשבון הבנק."
},
"verifyBankAccountFailureWarning": {
- "message": "בעיות באימות פרטי החשבון עלולות להסתיים בתשלומים ש'התפספסו' ויכולות לגרום למנוי שלך, להתבטל."
+ "message": "כשל באימות חשבון הבנק יגרום לפספוס תשלום ולהשעיית המנוי שלך."
},
"verifiedBankAccount": {
- "message": "חשבון בנק אומת."
+ "message": "חשבון הבנק אומת"
},
"bankAccount": {
"message": "חשבון בנק"
@@ -4354,7 +4322,7 @@
}
},
"routingNumber": {
- "message": "מספר הניתוב",
+ "message": "מספר ניתוב",
"description": "Bank account routing number"
},
"accountNumber": {
@@ -4389,18 +4357,18 @@
"message": "עלות מקום פוטנציאלית מרבית"
},
"addSeats": {
- "message": "הוסף כסאות",
+ "message": "הוסף מקומות",
"description": "Seat = User Seat"
},
"removeSeats": {
- "message": "הסר כסאות",
+ "message": "הסר מקומות",
"description": "Seat = User Seat"
},
"subscriptionDesc": {
- "message": "התאמות למנוי שלך יגרמו שינויים יחסיים לסך כל החיובים שלך. אם משתמשים חדשים שהוזמנו חורגים ממקומות המנוי שלך, תקבל באופן מיידי חיוב יחסי עבור המשתמשים הנוספים."
+ "message": "התאמות למנוי שלך יגרמו לשינויים יחסיים לסך כל החיובים שלך. אם משתמשים חדשים שהוזמנו חורגים ממקומות המנוי שלך, תקבל באופן מיידי חיוב יחסי עבור המשתמשים הנוספים."
},
"subscriptionUserSeats": {
- "message": "המנוי שלך מתיר עד $COUNT$ משתמשים.",
+ "message": "המנוי שלך מאפשר בסך הכל $COUNT$ חברים.",
"placeholders": {
"count": {
"content": "$1",
@@ -4427,7 +4395,7 @@
"message": "לעזרה נוספת בניהול המנוי שלך, נא לפנות לתמיכת הלקוחות."
},
"subscriptionUserSeatsUnlimitedAutoscale": {
- "message": "התאמות למנוי שלך יגרמו שינויים יחסיים לסך כל החיובים שלך. אם חברים חדשים שהוזמנו חורגים ממקומות המנוי שלך, תקבל באופן מיידי חיוב יחסי עבור החברים הנוספים."
+ "message": "התאמות למנוי שלך יגרמו לשינויים יחסיים לסך כל החיובים שלך. אם חברים חדשים שהוזמנו חורגים ממקומות המנוי שלך, תקבל באופן מיידי חיוב יחסי עבור החברים הנוספים."
},
"smStandaloneTrialSeatCountUpdateMessageFragment1": {
"message": "אם אתה רוצה להוסיף מקומות נוספים של"
@@ -4436,7 +4404,7 @@
"message": "ללא ההצעה המצורפת, נא לפנות אל"
},
"subscriptionUserSeatsLimitedAutoscale": {
- "message": "התאמות למנוי שלך יגרמו שינויים יחסיים לסך כל החיובים שלך. אם חברים חדשים שהוזמנו חורגים ממקומות המנוי שלך, תקבל באופן מיידי חיוב יחסי עבור החברים הנוספים עד שתושג מגבלת $MAX$ המקומות שלך.",
+ "message": "התאמות למנוי שלך יגרמו לשינויים יחסיים לסך כל החיובים שלך. אם חברים חדשים שהוזמנו חורגים ממקומות המנוי שלך, תקבל באופן מיידי חיוב יחסי עבור החברים הנוספים עד שתושג מגבלת $MAX$ המקומות שלך.",
"placeholders": {
"max": {
"content": "$1",
@@ -4481,7 +4449,7 @@
}
},
"subscriptionMaxReached": {
- "message": "התאמות למנוי שלך יגרמו שינויים יחסיים לסך כל החיובים שלך. אתה לא יכול להזמין יותר מ־$COUNT$ חברים מבלי להגדיל את מקומות המנוי שלך.",
+ "message": "התאמות למנוי שלך יגרמו לשינויים יחסיים לסך כל החיובים שלך. אתה לא יכול להזמין יותר מ־$COUNT$ חברים מבלי להגדיל את מקומות המנוי שלך.",
"placeholders": {
"count": {
"content": "$1",
@@ -4499,10 +4467,10 @@
}
},
"seatsToAdd": {
- "message": "כסאות להוספה"
+ "message": "מקומות להוספה"
},
"seatsToRemove": {
- "message": "כסאות להסרה"
+ "message": "מקומות להסרה"
},
"seatsAddNote": {
"message": "הוספת כסאות משתמשים משנה את העלויות. פעולה זו מחוייבת באופן מיידי לפי שיטת החיוב שלך. בנוסף, החיוב הבא יכלול את ההפרש היחסי ממחזור החיוב הנוכחי."
@@ -4608,7 +4576,7 @@
"message": "תכונה זו לא זמינה בתוכנית החינמיית עבור ארגונים. עבור לתוכנית בתשלום בכדי להשתמש בתכונות נוספות."
},
"createOrganizationStep1": {
- "message": "יצירת ארגון: צעד 1"
+ "message": "צור ארגון: שלב 1"
},
"createOrganizationCreatePersonalAccount": {
"message": "לפני יצירת הארגון, עליך ליצור חשבון אישי חינמי."
@@ -4650,16 +4618,16 @@
"message": "מסננים"
},
"vaultTimeout": {
- "message": "משך זמן מירבי עבור חיבור לכספת"
+ "message": "פסק זמן לכספת"
},
"vaultTimeout1": {
"message": "פסק זמן"
},
"vaultTimeoutDesc": {
- "message": "בחר כמה זמן יעבור כדי שהכספת תסגר לאחר חוסר פעילות ותבצע את הפעולה שנבחרה."
+ "message": "בחר מתי הכספת שלך תנקוט בפעולת פסק הזמן לכספת."
},
"vaultTimeoutLogoutDesc": {
- "message": "בחר מתי הכספת שלך תסגר."
+ "message": "בחר מתי הכספת שלך תינעל."
},
"oneMinute": {
"message": "דקה אחת"
@@ -4695,7 +4663,7 @@
"description": "ex. Date this password was updated"
},
"organizationIsDisabled": {
- "message": "הארגון הושבת."
+ "message": "הארגון הושעה"
},
"secretsAccessSuspended": {
"message": "לא ניתן לגשת אל ארגונים מושעים. נא לפנות לבעל הארגון שלך עבור סיוע."
@@ -4750,13 +4718,13 @@
"message": "סיסמה ראשית חלשה"
},
"weakMasterPasswordDesc": {
- "message": "הסיסמה הראשית שבחרת חלשה מאוד. עליך לבחור סיסמה חזקה יותר (או להשתמש במשפט במקום מילה אחת) בכדי לאבטח את החשבון שלך. האם אתה בטוח שברצונך להשתמש בסיסמה ראשית זו?"
+ "message": "סיסמה חלשה זוהתה. השתמש בסיסמה חזקה כדי להגן על חשבונך. האם אתה בטוח שאתה רוצה להשתמש בסיסמה חלשה?"
},
"rotateAccountEncKey": {
"message": "כמו כן החלף את מפתח ההצפנה של החשבון שלי"
},
"rotateEncKeyTitle": {
- "message": "החלף מפתח הצפנה"
+ "message": "סובב מפתח הצפנה"
},
"rotateEncKeyConfirmation": {
"message": "האם אתה בטוח שברצונך להחליף (לבצע רוטציה) של מפתח ההצפנה בחשבונך?"
@@ -4789,7 +4757,7 @@
"message": "ביטוי טביעת אצבע"
},
"dontAskFingerprintAgain": {
- "message": "אל תבקש ממני לאמת את משפט טביעת האצבע יותר",
+ "message": "לעולם אל תנחה משתמשים מוזמנים לאמת ביטוי טביעת אצבע (לא מומלץ)",
"description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing."
},
"youWillBeNotifiedOnceTheRequestIsApproved": {
@@ -4822,10 +4790,10 @@
"description": "'OAuth 2.0' is a programming protocol. It should probably not be translated."
},
"viewApiKey": {
- "message": "צפה במפתח API"
+ "message": "הצג מפתח API"
},
"rotateApiKey": {
- "message": "קבל מפתח API חדש"
+ "message": "סובב מפתח API"
},
"selectOneCollection": {
"message": "עליך לבחור לפחות אוסף אחד."
@@ -4843,7 +4811,7 @@
"message": "דרישות סיסמה ראשית"
},
"masterPassPolicyDesc": {
- "message": "קבע דרישות מינימום עבור חוזק הסיסמה הראשית."
+ "message": "קבע דרישות עבור חוזק הסיסמה הראשית."
},
"passwordStrengthScore": {
"message": "ציון חוזק סיסמה $SCORE$",
@@ -4858,16 +4826,16 @@
"message": "דרוש כניסה דו-שלבית"
},
"twoStepLoginPolicyDesc": {
- "message": "דרוש מהמשתמשים להגדיר כניסה דו-שלבית בחשבונות האישיים שלהם."
+ "message": "דרוש מחברים להגדיר כניסה דו־שלבית."
},
"twoStepLoginPolicyWarning": {
- "message": "חברי ארגון ללא הגדרת כניסה דו-שלבית יוסרו מהארגון ויקבלו אימייל המסביר את השינוי."
+ "message": "חברי ארגון שאינם בעלים או מנהלים ואין להם כניסה דו־שלבית מוגדרת עבור חשבונם יוסרו מהארגון ויקבלו דוא\"ל המודיע להם על השינוי."
},
"twoStepLoginPolicyUserWarning": {
- "message": "הינך חבר בארגון המחייב כניסה דו-שלבית מוגדרת בחשבונך. אם תבטל את כל הספקים המאפשרים כניסה דו-שלבית, תוסר אוטומטית מהארגון."
+ "message": "הנך חבר בארגון אשר דורש שכניסה דו־שלבית תהיה מוגדרת בחשבון המשתמש שלך. אם תכבה את כל ספקי הכניסה הדו־שלבית, אתה תוסר באופן אוטומטי מהארגונים האלה."
},
"passwordGeneratorPolicyDesc": {
- "message": "הגדר דרישות מינימום במחולל הסיסמאות."
+ "message": "הגדר דרישות עבור מחולל הסיסמאות."
},
"masterPasswordPolicyInEffect": {
"message": "אחד או יותר מכללי מדיניות הארגון דורשים שסיסמתך הראשית תעמוד בדרישות הבאות:"
@@ -4912,23 +4880,23 @@
"message": "הסיסמה הראשית החדשה שלך לא עומדת בדרישות המדיניות."
},
"minimumNumberOfWords": {
- "message": "מספר מינימאלי של מילים"
+ "message": "מספר מינימלי של מילים"
},
"overridePasswordTypePolicy": {
"message": "סוג סיסמה",
"description": "Name of the password generator policy that overrides the user's password/passphrase selection."
},
"userPreference": {
- "message": "העדפות משתמש"
+ "message": "העדפת משתמש"
},
"vaultTimeoutAction": {
- "message": "פעולה לביצוע בכספת בתום זמן החיבור"
+ "message": "פעולת פסק זמן לכספת"
},
"vaultTimeoutActionLockDesc": {
- "message": "בכדי לקבל גישה לכספת נעולה, יש להזין את הסיסמה הראשית שוב."
+ "message": "נדרשת סיסמה ראשית או שיטת ביטול נעילה אחרת כדי לגשת לכספת שלך שוב."
},
"vaultTimeoutActionLogOutDesc": {
- "message": "בכדי לקבל גישה לכספת שיצאו ממנה, יש לבצע אימות מחדש."
+ "message": "נדרש אימות מחדש כדי לגשת לכספת שלך שוב."
},
"lock": {
"message": "נעילה",
@@ -4939,25 +4907,25 @@
"description": "Noun: A special folder for holding deleted items that have not yet been permanently deleted"
},
"searchTrash": {
- "message": "חפש בסל המחזור"
+ "message": "חפש באשפה"
},
"permanentlyDelete": {
"message": "מחק לצמיתות"
},
"permanentlyDeleteSelected": {
- "message": "מחק לצמיתות פריטים שנבחרו"
+ "message": "מחק לצמיתות את מה שנבחר"
},
"permanentlyDeleteItem": {
- "message": "מחק לצמיתות פריט שנבחר"
+ "message": "מחק לצמיתות פריט"
},
"permanentlyDeleteItemConfirmation": {
"message": "האם אתה בטוח שברצונך למחוק את הפריט הזה?"
},
"permanentlyDeletedItem": {
- "message": "פריט שנמחק לצמיתות"
+ "message": "הפריט נמחק לצמיתות"
},
"permanentlyDeletedItems": {
- "message": "פריטים שנמחקו לצמיתות"
+ "message": "הפריטים נמחקו לצמיתות"
},
"permanentlyDeleteSelectedItemsDesc": {
"message": "בחרת $COUNT$ פריט(ים) למחיקה לצמיתות. האם אתה בטוח שברצונך למחוק את כולם?",
@@ -4969,7 +4937,7 @@
}
},
"permanentlyDeletedItemId": {
- "message": "פריט שנמחק לצמיתות $ID$.",
+ "message": "הפריט $ID$ נמחק לצמיתות",
"placeholders": {
"id": {
"content": "$1",
@@ -4981,16 +4949,16 @@
"message": "שחזר"
},
"restoreSelected": {
- "message": "שחזר בחירה"
+ "message": "שחזר את מה שנבחר"
},
"restoredItem": {
- "message": "פריט ששוחזר"
+ "message": "הפריט שוחזר"
},
"restoredItems": {
- "message": "פריטים ששוחזרו"
+ "message": "הפריטים שוחזרו"
},
"restoredItemId": {
- "message": "פריט ששוחזר $ID$.",
+ "message": "הפריט $ID$ שוחזר",
"placeholders": {
"id": {
"content": "$1",
@@ -5002,7 +4970,7 @@
"message": "יציאה תגרום להסרת כל גישה שיש לך לכספת ודורשת אימות אונליין לאחר משך זמן מסויים. האם אתה בטוח שברצונך להשתמש באפשרות זו?"
},
"vaultTimeoutLogOutConfirmationTitle": {
- "message": "אישור פעולת אימות לאחר חוסר פעילות"
+ "message": "אישור פעולת פסק זמן"
},
"hidePasswords": {
"message": "הסתר סיסמאות"
@@ -5020,7 +4988,7 @@
"message": "מידע מיסוי עודכן."
},
"setMasterPassword": {
- "message": "קבע סיסמה ראשית"
+ "message": "הגדר סיסמה ראשית"
},
"identifier": {
"message": "מזהה"
@@ -5029,7 +4997,7 @@
"message": "מזהה ארגוני"
},
"ssoLogInWithOrgIdentifier": {
- "message": "הכנס באמצעות פורטל ההזדהות האחודה (SSO) הארגוני שלך. אנא הזן את המזהה הארגוני שלך כדי להתחיל."
+ "message": "כנס באמצעות הפורטל לכניסה יחידה של הארגון שלך. אנא הזן את מזהה ה־SSO של הארגון שלך כדי להתחיל."
},
"singleSignOnEnterOrgIdentifier": {
"message": "הזן את מזהה ה־SSO של הארגון שלך כדי להתחיל"
@@ -5038,7 +5006,7 @@
"message": "כדי להיכנס ספק ה־SSO שלך, הזן את מזהה ה־SSO של הארגון שלך כדי להתחיל. ייתכן שתצטרך להזין את מזהה SSO זה כאשר אתה נכנס ממכשיר חדש."
},
"enterpriseSingleSignOn": {
- "message": "כניסה ארגונית אחודה"
+ "message": "כניסה יחידה ארגונית"
},
"ssoHandOff": {
"message": "ניתן לסגור את הטאב הנוכחי ולהמשיך את השימוש בתוסף."
@@ -5074,13 +5042,13 @@
"message": "אימות SSO דרך SAML2.0 וOpenID Connect"
},
"includeEnterprisePolicies": {
- "message": "מדיניות ארגונית"
+ "message": "פוליסות ארגוניות"
},
"ssoValidationFailed": {
"message": "אימות SSO נכשל"
},
"ssoIdentifierRequired": {
- "message": "מזהה הארגון נחוץ."
+ "message": "נדרש מזהה SSO של הארגון."
},
"ssoIdentifier": {
"message": "מזהה SSO"
@@ -5102,7 +5070,7 @@
"message": "ארגון יחיד"
},
"singleOrgDesc": {
- "message": "מונע מהמשתמשים אפשרות צירוף לארגונים אחרים."
+ "message": "מנע מחברים מלהצטרף לארגונים אחרים."
},
"singleOrgPolicyDesc": {
"message": "הגבל משתמשים מלהצטרף לארגונים אחרים. מדיניות זו נדרשת עבור ארגונים שאפשרו אימות דומיין."
@@ -5111,16 +5079,16 @@
"message": "לפי מדיניות הארגון שלך, אין באפשרותך להצטרף ליותר מארגון אחד. אנא צור קשר עם מנהלי הארגון שלך, או לחלופין - צור חשבון Bitwarden נפרד."
},
"singleOrgPolicyWarning": {
- "message": "חברי ארגון שאינם הבעלים או המנהלים וכבר עכשיו הם חלק מארגון אחר - יוסרו מהארגון שלך."
+ "message": "חברי ארגון שאינם בעלים או מנהלים ושכבר חברים בארגון אחר יוסרו מהארגון שלך."
},
"singleOrgPolicyMemberWarning": {
"message": "חברים שאינם עומדים בדרישות במצב מבוטל עד שיעזבו את כל שאר הארגונים. מנהלים הם פטורים ויכולים לשחזר חברים ברגע שיעמדו בדרישות."
},
"requireSso": {
- "message": "אימות בעזרת כניסה אחודה"
+ "message": "דרוש אימות כניסה יחידה"
},
"requireSsoPolicyDesc": {
- "message": "מחייב את המשתמשים להשתמש בכניסה אחודה של הארגון."
+ "message": "דרוש מחברים להיכנס עם שיטת הכניסה היחידה הארגונית."
},
"prerequisite": {
"message": "תנאים מקדימים"
@@ -5132,7 +5100,7 @@
"message": "מדיניות ארגון יחיד לא הוגדרה."
},
"requireSsoExemption": {
- "message": "מנהלי ובעלי הארגון מוחרגים מאכיפת מדיניות זו."
+ "message": "מנהלי ובעלי הארגון פטורים מהאכיפה של מדיניות זו."
},
"limitSendViews": {
"message": "הגבל צפיות"
@@ -5169,7 +5137,7 @@
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"createSend": {
- "message": "צור Send חדש",
+ "message": "סֵנְד חדש",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"editSend": {
@@ -5177,15 +5145,15 @@
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"createdSend": {
- "message": "הSend נוצר בהצלחה",
+ "message": "סֵנְד נשמר",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"editedSend": {
- "message": "הSend נערך",
+ "message": "סֵנְד נשמר",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"deletedSend": {
- "message": "הSend נמחק",
+ "message": "סֵנְד נמחק",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"deleteSend": {
@@ -5211,7 +5179,7 @@
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"maxAccessCount": {
- "message": "כמות גישות מקסימלית"
+ "message": "מספר גישות מרבי"
},
"disabled": {
"message": "מבוטל"
@@ -5220,14 +5188,14 @@
"message": "מבוטל"
},
"sendLink": {
- "message": "לינק לSend",
+ "message": "קישור סֵנְד",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"copyLink": {
"message": "העתק קישור"
},
"copySendLink": {
- "message": "העתק לינק לSend",
+ "message": "העתק קישור סֵנְד",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"removePassword": {
@@ -5264,11 +5232,11 @@
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"sendProtectedPasswordDontKnow": {
- "message": "לא יודע מה הסיסמה? בקש מהשולח את הסיסמה עבור הSend.",
+ "message": "לא יודע את הסיסמה? בקש מהשולח את הסיסמה הדרושה עבור סֵנְד זה.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"sendHiddenByDefault": {
- "message": "הSend הזה מוסתר כברירת מחדל. באפשרותך לשנות את מצב ההסתרה בעזרת הכפתור להלן.",
+ "message": "סֵנְד זה מוסתר כברירת מחדל. אתה יכול לשנות את מצב הנראות שלו באמצעות הלחצן למטה.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"downloadAttachments": {
@@ -5739,7 +5707,7 @@
"message": "WebAuthn אינו נתמך בדפדפן זה."
},
"webAuthnSuccess": {
- "message": "WebAuthn אומת בהצלחה! אתה רשאי לסגור כרטיסייה זו."
+ "message": "WebAuthn אומת בהצלחה! אתה רשאי לסגור כרטיסיה זו."
},
"hintEqualsPassword": {
"message": "רמז הסיסמה שלך לא יכול להיות אותו הדבר כמו הסיסמה שלך."
@@ -5886,7 +5854,7 @@
"message": "כאשר החברים מוסרים, אין להם יותר גישה אל נתוני הארגון ופעולה זו היא בלתי הפיכה. כדי להוסיף את החברים בחזרה לארגון, יש להזמין ולקלוט אותם שוב. התהליך עלול לקחת מספר שניות להשלמה ולא ניתן לקטוע או לבטל אותו."
},
"revokeUsersWarning": {
- "message": "כאשר החברים מבוטלים, אין להם יותר גישה אל נתוני הארגון. כדי לשחזר במהירות גישת חבר, עבור לכרטיסייה 'מבוטל'. התהליך עלול לקחת מספר שניות להשלמה ולא ניתן לקטוע או לבטל אותו."
+ "message": "כאשר החברים מבוטלים, אין להם יותר גישה אל נתוני הארגון. כדי לשחזר במהירות גישת חבר, עבור לכרטיסיה 'מבוטל'. התהליך עלול לקחת מספר שניות להשלמה ולא ניתן לקטוע או לבטל אותו."
},
"theme": {
"message": "ערכת נושא"
@@ -6094,7 +6062,7 @@
"message": "הוסף"
},
"updatedMasterPassword": {
- "message": "סיסמה ראשית נשמרה"
+ "message": "הסיסמה הראשית נשמרה"
},
"updateMasterPassword": {
"message": "עדכן סיסמה ראשית"
@@ -6795,7 +6763,7 @@
"message": "מפתחות"
},
"billingHistory": {
- "message": "היסטורית חיובים"
+ "message": "היסטוריית חיובים"
},
"backToReports": {
"message": "חזרה לדוחות"
@@ -7468,7 +7436,7 @@
"description": "Notification for the successful editing of a secret."
},
"secretCreated": {
- "message": "סוד נוצר",
+ "message": "הסוד נוצר",
"description": "Notification for the successful creation of a secret."
},
"newSecret": {
@@ -7491,7 +7459,7 @@
"message": "אין סודות באשפה."
},
"serviceAccountsNoItemsMessage": {
- "message": "צור חשבון שירות חדש כדי להתחיל לאטמט סודות גישה.",
+ "message": "צור חשבון שירות חדש כדי להתחיל לאטמט גישת סודות.",
"description": "Message to encourage the user to start creating service accounts."
},
"serviceAccountsNoItemsTitle": {
@@ -7536,98 +7504,98 @@
}
},
"deleteServiceAccountToast": {
- "message": "Service account deleted"
+ "message": "חשבון השירות נמחק"
},
"deleteServiceAccountsToast": {
- "message": "Service accounts deleted"
+ "message": "חשבונות השירות נמחקו"
},
"searchServiceAccounts": {
- "message": "Search service accounts",
+ "message": "חפש חשבונות שירות",
"description": "Placeholder text for searching service accounts."
},
"editServiceAccount": {
- "message": "Edit service account",
+ "message": "ערוך חשבון שירות",
"description": "Title for editing a service account."
},
"addProject": {
- "message": "Add project",
+ "message": "הוסף פרויקט",
"description": "Title for creating a new project."
},
"projectEdited": {
- "message": "Project edited",
+ "message": "הפרויקט נערך",
"description": "Notification for the successful editing of a project."
},
"projectSaved": {
- "message": "Project saved",
+ "message": "הפרויקט נשמר",
"description": "Notification for the successful saving of a project."
},
"projectCreated": {
- "message": "Project created",
+ "message": "הפרויקט נוצר",
"description": "Notification for the successful creation of a project."
},
"projectName": {
- "message": "Project name",
+ "message": "שם הפרויקט",
"description": "Label for entering the name of a project."
},
"newProject": {
- "message": "New project",
+ "message": "פרויקט חדש",
"description": "Title for creating a new project."
},
"softDeleteSecretWarning": {
- "message": "Deleting secrets can affect existing integrations.",
+ "message": "מחיקת סודות עשויה להשפיע על שילובים קיימים.",
"description": "Warns that deleting secrets can have consequences on integrations"
},
"softDeletesSuccessToast": {
- "message": "Secrets sent to trash",
+ "message": "סודות נשלחו לאשפה",
"description": "Notifies that the selected secrets have been moved to the trash"
},
"hardDeleteSecretConfirmation": {
- "message": "Are you sure you want to permanently delete this secret?"
+ "message": "האם אתה בטוח שברצונך למחוק לצמיתות את הסוד הזה?"
},
"hardDeleteSecretsConfirmation": {
- "message": "Are you sure you want to permanently delete these secrets?"
+ "message": "האם אתה בטוח שברצונך למחוק לצמיתות את הסודות האלה?"
},
"hardDeletesSuccessToast": {
- "message": "Secrets permanently deleted"
+ "message": "הסודות נמחקו לצמיתות"
},
"smAccess": {
- "message": "Access",
+ "message": "גישה",
"description": "Title indicating what permissions a service account has"
},
"projectCommaSecret": {
- "message": "Project, Secret",
+ "message": "פרויקט, סוד",
"description": ""
},
"serviceAccountName": {
- "message": "Service account name",
+ "message": "שם חשבון שירות",
"description": "Label for the name of a service account"
},
"serviceAccountCreated": {
- "message": "Service account created",
+ "message": "חשבון השירות נוצר",
"description": "Notifies that a new service account has been created"
},
"serviceAccountUpdated": {
- "message": "Service account updated",
+ "message": "חשבון השירות עודכן",
"description": "Notifies that a service account has been updated"
},
"newSaSelectAccess": {
- "message": "Type or select projects or secrets",
+ "message": "הקלד או בחר פרויקטים או סודות",
"description": "Instructions for selecting projects or secrets for a new service account"
},
"newSaTypeToFilter": {
- "message": "Type to filter",
+ "message": "הקלד כדי לסנן",
"description": "Instructions for filtering a list of projects or secrets"
},
"deleteProjectsToast": {
- "message": "Projects deleted",
+ "message": "הפרויקטים נמחקו",
"description": "Notifies that the selected projects have been deleted"
},
"deleteProjectToast": {
- "message": "Project deleted",
+ "message": "הפרויקט נמחק",
"description": "Notifies that a project has been deleted"
},
"deleteProjectDialogMessage": {
- "message": "Deleting project $PROJECT$ is permanent and irreversible.",
+ "message": "מחיקת הפרויקט $PROJECT$ היא לצמיתות ובלתי הפיכה.",
"description": "Informs users that projects are hard deleted and not sent to trash",
"placeholders": {
"project": {
@@ -7637,7 +7605,7 @@
}
},
"deleteProjectInputLabel": {
- "message": "Type \"$CONFIRM$\" to continue",
+ "message": "הקלד \"$CONFIRM$\" כדי להמשיך",
"description": "Users are prompted to type 'confirm' to delete a project",
"placeholders": {
"confirm": {
@@ -7647,7 +7615,7 @@
}
},
"deleteProjectConfirmMessage": {
- "message": "Delete $PROJECT$",
+ "message": "מחק $PROJECT$",
"description": "Confirmation prompt to delete a specific project, where '$PROJECT$' is a placeholder for the name of the project.",
"placeholders": {
"project": {
@@ -7657,7 +7625,7 @@
}
},
"deleteProjectsConfirmMessage": {
- "message": "Delete $COUNT$ Projects",
+ "message": "מחק $COUNT$ פרויקטים",
"description": "Confirmation prompt to delete multiple projects, where '$COUNT$' is a placeholder for the number of projects to be deleted.",
"placeholders": {
"count": {
@@ -7667,119 +7635,119 @@
}
},
"deleteProjectsDialogMessage": {
- "message": "Deleting projects is permanent and irreversible.",
+ "message": "מחיקת פרויקטים היא לצמיתות ובלתי הפיכה.",
"description": "This message is displayed in a dialog box as a warning before proceeding with project deletion."
},
"projectsNoItemsTitle": {
- "message": "No projects to display",
+ "message": "אין פרויקטים להצגה",
"description": "Empty state to be displayed when there are no projects to display in the list."
},
"projectsNoItemsMessage": {
- "message": "Add a new project to get started organizing secrets.",
+ "message": "הוסף פרויקט חדש כדי להתחיל לארגן סודות.",
"description": "Message to be displayed when there are no projects to display in the list."
},
"smConfirmationRequired": {
- "message": "Confirmation required",
+ "message": "נדרש אישור",
"description": "Indicates that user confirmation is required for an action to proceed."
},
"bulkDeleteProjectsErrorMessage": {
- "message": "The following projects could not be deleted:",
+ "message": "לא היה ניתן למחוק את הפרויקטים הבאים:",
"description": "Message to be displayed when there is an error during bulk project deletion."
},
"softDeleteSuccessToast": {
- "message": "Secret sent to trash",
+ "message": "הסוד נשלח לאשפה",
"description": "Notification to be displayed when a secret is successfully sent to the trash."
},
"hardDeleteSuccessToast": {
- "message": "Secret permanently deleted"
+ "message": "הסוד נמחק לצמיתות"
},
"accessTokens": {
- "message": "Access tokens",
+ "message": "אסימוני גישה",
"description": "Title for the section displaying access tokens."
},
"newAccessToken": {
- "message": "New access token",
+ "message": "אסימון גישה חדש",
"description": "Button label for creating a new access token."
},
"expires": {
- "message": "Expires",
+ "message": "יפוג",
"description": "Label for the expiration date of an access token."
},
"canRead": {
- "message": "Can read",
+ "message": "יכול/ה לקרוא",
"description": "Label for the access level of an access token (Read only)."
},
"accessTokensNoItemsTitle": {
- "message": "No access tokens to show",
+ "message": "אין אסימוני גישה להציג",
"description": "Title to be displayed when there are no access tokens to display in the list."
},
"accessTokensNoItemsDesc": {
- "message": "To get started, create an access token",
+ "message": "כדי להתחיל, צור אסימון גישה",
"description": "Message to be displayed when there are no access tokens to display in the list."
},
"downloadAccessToken": {
- "message": "Download or copy before closing.",
+ "message": "הורד או העתק לפני שתסגור.",
"description": "Message to be displayed before closing an access token, reminding the user to download or copy it."
},
"expiresOnAccessToken": {
- "message": "Expires on:",
+ "message": "יפוג ב:",
"description": "Label for the expiration date of an access token."
},
"accessTokenCallOutTitle": {
- "message": "Access tokens are not stored and cannot be retrieved",
+ "message": "אסימוני גישה אינם מאוחסנים ולא ניתן לאחזר אותם",
"description": "Notification to inform the user that access tokens are only displayed once and cannot be retrieved again."
},
"copyToken": {
- "message": "Copy token",
+ "message": "העתק אסימון",
"description": "Copies the generated access token to the user's clipboard."
},
"accessToken": {
- "message": "Access token",
+ "message": "אסימון גישה",
"description": "A unique string that gives a client application (eg. CLI) access to a secret or set of secrets."
},
"accessTokenExpirationRequired": {
- "message": "Expiration date required",
+ "message": "נדרש תאריך תפוגה",
"description": "Error message indicating that an expiration date for the access token must be set."
},
"accessTokenCreatedAndCopied": {
- "message": "Access token created and copied to clipboard",
+ "message": "אסימון גישה נוצר והועתק ללוח",
"description": "Notification to inform the user that the access token has been created and copied to the clipboard."
},
"revokeAccessToken": {
- "message": "Revoke access token",
+ "message": "בטל אסימון גישה",
"description": "Invalidates / cancels an access token and as such removes access to secrets for the client application."
},
"revokeAccessTokens": {
- "message": "Revoke access tokens"
+ "message": "בטל אסימוני גישה"
},
"revokeAccessTokenDesc": {
- "message": "Revoking access tokens is permanent and irreversible."
+ "message": "ביטול אסימוני גישה הוא לצמיתות ובלתי הפיך."
},
"accessTokenRevoked": {
- "message": "Access tokens revoked",
+ "message": "אסימוני הגישה בוטלו",
"description": "Toast message after deleting one or multiple access tokens."
},
"noAccessTokenSelected": {
- "message": "No access token selected to revoke",
+ "message": "לא נבחר אסימון גישה לביטול",
"description": "Toast error message after trying to delete access tokens but not selecting any access tokens."
},
"submenu": {
- "message": "Submenu"
+ "message": "תפריט משנה"
},
"from": {
- "message": "From"
+ "message": "מאת"
},
"to": {
- "message": "To"
+ "message": "אל"
},
"member": {
- "message": "Member"
+ "message": "חבר"
},
"update": {
- "message": "Update"
+ "message": "עדכן"
},
"plusNMore": {
- "message": "+ $QUANTITY$ more",
+ "message": "+ עוד $QUANTITY$",
"placeholders": {
"quantity": {
"content": "$1",
@@ -7788,106 +7756,106 @@
}
},
"groupInfo": {
- "message": "Group info"
+ "message": "פרטי קבוצה"
},
"editGroupMembersDesc": {
- "message": "Grant members access to the group's assigned collections."
+ "message": "הענק לחברים גישה לאוספים המוקצים של הקבוצה."
},
"editGroupCollectionsDesc": {
- "message": "Grant access to collections by adding them to this group."
+ "message": "העתק גישה לאוספים בכך שתוסיף אותם לקבוצה זו."
},
"restrictedCollectionAssignmentDesc": {
- "message": "You can only assign collections you manage."
+ "message": "אתה יכול רק להקצות אוספים שאתה מנהל."
},
"selectMembers": {
- "message": "Select members"
+ "message": "בחר חברים"
},
"selectCollections": {
- "message": "Select collections"
+ "message": "בחר אוספים"
},
"role": {
- "message": "Role"
+ "message": "תפקיד"
},
"removeMember": {
- "message": "Remove member"
+ "message": "הסר חבר"
},
"collection": {
- "message": "Collection"
+ "message": "אוסף"
},
"noCollection": {
- "message": "No collection"
+ "message": "אין אוסף"
},
"noCollectionsAdded": {
- "message": "No collections added"
+ "message": "לא נוספו אוספים"
},
"noMembersAdded": {
- "message": "No members added"
+ "message": "לא נוספו חברים"
},
"noGroupsAdded": {
- "message": "No groups added"
+ "message": "לא נוספו קבוצות"
},
"group": {
- "message": "Group"
+ "message": "קבוצה"
},
"domainVerification": {
- "message": "Domain verification"
+ "message": "אימות דומיין"
},
"newDomain": {
- "message": "New domain"
+ "message": "דומיין חדש"
},
"noDomains": {
- "message": "No domains"
+ "message": "אין דומיינים"
},
"noDomainsSubText": {
- "message": "Connecting a domain allows members to skip the SSO identifier field during Login with SSO."
+ "message": "חיבור דומיין מאפשר לחברים לדלג על שדה מזהה SSO במהלך כניסה עם SSO."
},
"verifyDomain": {
- "message": "Verify domain"
+ "message": "אמת דומיין"
},
"reverifyDomain": {
- "message": "Reverify domain"
+ "message": "אמת מחדש דומיין"
},
"copyDnsTxtRecord": {
- "message": "Copy DNS TXT record"
+ "message": "העתק רשומת DNS TXT"
},
"dnsTxtRecord": {
- "message": "DNS TXT record"
+ "message": "רשומת DNS TXT"
},
"dnsTxtRecordInputHint": {
- "message": "Copy and paste the TXT record into your DNS Provider."
+ "message": "העתק והדבק את רשומת ה־TXT אל ספק ה־DNS שלך."
},
"domainNameInputHint": {
- "message": "Example: mydomain.com. Subdomains require separate entries to be verified."
+ "message": "דוגמה: mydomain.com. תת-דומיינים דורשים שרשומות נפרדות יאומתו."
},
"automaticDomainVerification": {
- "message": "Automatic Domain Verification"
+ "message": "אימות דומיין אוטומטי"
},
"automaticDomainVerificationProcess": {
- "message": "Bitwarden will attempt to verify the domain 3 times during the first 72 hours. If the domain can’t be verified, check the DNS record in your host and manually verify. The domain will be removed from your organization in 7 days if it is not verified"
+ "message": "Bitwarden ינסה לאמת את הדומיין 3 פעמים במהלך 72 השעות הראשונות. אם הדומיין אינו ניתן לאימות, בדוק את רשומת ה־DNS במארח שלך ואמת באופן ידני. הדומיין יוסר מהארגון שלך תוך 7 ימים אם הוא לא מאומת"
},
"invalidDomainNameMessage": {
- "message": "Input is not a valid format. Format: mydomain.com. Subdomains require separate entries to be verified."
+ "message": "הקלט אינו בפורמט תקין. פורמט: mydomain.com. תת-דומיינים דורשים שרשומות נפרדות יאומתו."
},
"removeDomain": {
- "message": "Remove domain"
+ "message": "הסר דומיין"
},
"removeDomainWarning": {
- "message": "Removing a domain cannot be undone. Are you sure you want to continue?"
+ "message": "הסרת דומיין אינה ניתנת לביטול. האם אתה בטוח שברצונך להמשיך?"
},
"domainRemoved": {
- "message": "Domain removed"
+ "message": "דומיין הוסר"
},
"domainSaved": {
- "message": "Domain saved"
+ "message": "דומיין נשמר"
},
"domainVerified": {
- "message": "Domain verified"
+ "message": "דומיין אומת"
},
"duplicateDomainError": {
- "message": "You can't claim the same domain twice."
+ "message": "אתה לא יכול לדרוש את אותו הדומיין פעמיים."
},
"domainNotAvailable": {
- "message": "Someone else is using $DOMAIN$. Use a different domain to continue.",
+ "message": "מישהו אחר משתמש ב־$DOMAIN$. השתמש בדומיין אחר כדי להמשיך.",
"placeholders": {
"DOMAIN": {
"content": "$1",
@@ -7896,7 +7864,7 @@
}
},
"domainNotVerified": {
- "message": "$DOMAIN$ not verified. Check your DNS record.",
+ "message": "$DOMAIN$ אינו מאומת. בדוק את רשומת ה־DNS שלך.",
"placeholders": {
"DOMAIN": {
"content": "$1",
@@ -7905,28 +7873,28 @@
}
},
"domainStatusVerified": {
- "message": "Verified"
+ "message": "מאומת"
},
"domainStatusUnverified": {
- "message": "Unverified"
+ "message": "לא מאומת"
},
"domainNameTh": {
- "message": "Name"
+ "message": "שם"
},
"domainStatusTh": {
- "message": "Status"
+ "message": "מצב"
},
"lastChecked": {
- "message": "Last checked"
+ "message": "נבדק לאחרונה"
},
"editDomain": {
- "message": "Edit domain"
+ "message": "ערוך דומיין"
},
"domainFormInvalid": {
- "message": "There are form errors that need your attention"
+ "message": "ישנן שגיאות טופס שדורשות את תשומת לבך"
},
"addedDomain": {
- "message": "Added domain $DOMAIN$",
+ "message": "דומיין $DOMAIN$ נוסף",
"placeholders": {
"DOMAIN": {
"content": "$1",
@@ -7935,7 +7903,7 @@
}
},
"removedDomain": {
- "message": "Removed domain $DOMAIN$",
+ "message": "דומיין $DOMAIN$ הוסר",
"placeholders": {
"DOMAIN": {
"content": "$1",
@@ -7944,7 +7912,7 @@
}
},
"domainVerifiedEvent": {
- "message": "$DOMAIN$ verified",
+ "message": "$DOMAIN$ מאומת",
"placeholders": {
"DOMAIN": {
"content": "$1",
@@ -7953,7 +7921,7 @@
}
},
"domainNotVerifiedEvent": {
- "message": "$DOMAIN$ not verified",
+ "message": "$DOMAIN$ אינו מאומת",
"placeholders": {
"DOMAIN": {
"content": "$1",
@@ -7962,79 +7930,79 @@
}
},
"verificationRequiredForActionSetPinToContinue": {
- "message": "Verification required for this action. Set a PIN to continue."
+ "message": "נדרש אימות עבור פעולה זו. הגדר PIN כדי להמשיך."
},
"setPin": {
- "message": "Set 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": "שלח קוד מחדש"
},
"memberColumnHeader": {
- "message": "Member"
+ "message": "חבר"
},
"groupSlashMemberColumnHeader": {
- "message": "Group/Member"
+ "message": "קבוצה/חבר"
},
"selectGroupsAndMembers": {
- "message": "Select groups and members"
+ "message": "בחר קבוצות וחברים"
},
"selectGroups": {
- "message": "Select groups"
+ "message": "בחר קבוצות"
},
"userPermissionOverrideHelperDesc": {
- "message": "Permissions set for a member will replace permissions set by that member's group."
+ "message": "הרשאות שהוגדרו עבור חבר יחליפו הרשאות שהוגדרו על ידי הקבוצה של אותו חבר."
},
"noMembersOrGroupsAdded": {
- "message": "No members or groups added"
+ "message": "לא נוספו חברים או קבוצות"
},
"deleted": {
- "message": "Deleted"
+ "message": "נמחקו"
},
"memberStatusFilter": {
- "message": "Member status filter"
+ "message": "מסנן מצב חבר"
},
"inviteMember": {
- "message": "Invite member"
+ "message": "הזמן חבר"
},
"needsConfirmation": {
- "message": "Needs confirmation"
+ "message": "צריך אישור"
},
"memberRole": {
- "message": "Member role"
+ "message": "תפקיד חבר"
},
"moreFromBitwarden": {
- "message": "More from Bitwarden"
+ "message": "עוד מאת Bitwarden"
},
"switchProducts": {
- "message": "Switch products"
+ "message": "החלף מוצרים"
},
"freeOrgInvLimitReachedManageBilling": {
- "message": "Free organizations may have up to $SEATCOUNT$ members. Upgrade to a paid plan to invite more members.",
+ "message": "לארגונים חינמיים יכולים להיות עד $SEATCOUNT$ חברים. שדרג לתוכנית בתשלום כדי להזמין חברים נוספים.",
"placeholders": {
"seatcount": {
"content": "$1",
@@ -8043,7 +8011,7 @@
}
},
"freeOrgInvLimitReachedNoManageBilling": {
- "message": "Free organizations may have up to $SEATCOUNT$ members. Contact your organization owner to upgrade.",
+ "message": "לארגונים חינמיים יכולים להיות עד $SEATCOUNT$ חברים. צור קשר עם בעלי הארגון שלך כדי לשדרג.",
"placeholders": {
"seatcount": {
"content": "$1",
@@ -8052,7 +8020,7 @@
}
},
"teamsStarterPlanInvLimitReachedManageBilling": {
- "message": "Teams Starter plans may have up to $SEATCOUNT$ members. Upgrade to your plan to invite more members.",
+ "message": "לתוכניות צוותים מתחילים יכולים להיות עד $SEATCOUNT$ חברים. שדרג את התוכנית שלך כדי להזמין עוד חברים.",
"placeholders": {
"seatcount": {
"content": "$1",
@@ -8061,7 +8029,7 @@
}
},
"teamsStarterPlanInvLimitReachedNoManageBilling": {
- "message": "Teams Starter plans may have up to $SEATCOUNT$ members. Contact your organization owner to upgrade your plan and invite more members.",
+ "message": "לתוכניות צוותים מתחילים יכולים להיות עד $SEATCOUNT$ חברים. צור קשר עם בעלי הארגון שלך כדי לשדרג את התוכנית שלך ולהזמין עוד חברים.",
"placeholders": {
"seatcount": {
"content": "$1",
@@ -8070,7 +8038,7 @@
}
},
"freeOrgMaxCollectionReachedManageBilling": {
- "message": "Free organizations may have up to $COLLECTIONCOUNT$ collections. Upgrade to a paid plan to add more collections.",
+ "message": "לארגונים חינמיים יכולים להיות עד $COLLECTIONCOUNT$ אוספים. שדרג לתוכנית בתשלום כדי להוסיף עוד אוספים.",
"placeholders": {
"COLLECTIONCOUNT": {
"content": "$1",
@@ -8079,7 +8047,7 @@
}
},
"freeOrgMaxCollectionReachedNoManageBilling": {
- "message": "Free organizations may have up to $COLLECTIONCOUNT$ collections. Contact your organization owner to upgrade.",
+ "message": "לארגונים חינמיים יכולים להיות עד $COLLECTIONCOUNT$ אוספים. צור קשר עם בעלי הארגון שלך כדי לשדרג.",
"placeholders": {
"COLLECTIONCOUNT": {
"content": "$1",
@@ -8088,16 +8056,16 @@
}
},
"server": {
- "message": "Server"
+ "message": "שרת"
},
"exportData": {
- "message": "Export data"
+ "message": "ייצא נתונים"
},
"exportingOrganizationSecretDataTitle": {
- "message": "Exporting Organization Secret Data"
+ "message": "ייצוא נתונים ארגון סודיים"
},
"exportingOrganizationSecretDataDescription": {
- "message": "Only the Secrets Manager data associated with $ORGANIZATION$ will be exported. Items in other products or from other organizations will not be included.",
+ "message": "רק נתוני מנהל הסודות המשויכים עם $ORGANIZATION$ ייוצאו. פריטים במוצרים אחרים או מארגונים אחרים לא יכללו.",
"placeholders": {
"ORGANIZATION": {
"content": "$1",
@@ -8106,61 +8074,61 @@
}
},
"fileUpload": {
- "message": "File upload"
+ "message": "העלאת קובץ"
},
"upload": {
- "message": "Upload"
+ "message": "העלה"
},
"acceptedFormats": {
- "message": "Accepted Formats:"
+ "message": "פורמטים מקובלים:"
},
"copyPasteImportContents": {
- "message": "Copy & paste import contents:"
+ "message": "העתק & הדבק תוכן ייבוא:"
},
"or": {
- "message": "or"
+ "message": "או"
},
"unlockWithBiometrics": {
- "message": "Unlock with biometrics"
+ "message": "בטל נעילה עם זיהוי ביומטרי"
},
"unlockWithPin": {
- "message": "Unlock with PIN"
+ "message": "בטל נעילה עם PIN"
},
"unlockWithMasterPassword": {
- "message": "Unlock with master password"
+ "message": "בטל נעילה עם סיסמה ראשית"
},
"licenseAndBillingManagement": {
- "message": "License and billing management"
+ "message": "ניהול רישיון וחיובים"
},
"automaticSync": {
- "message": "Automatic sync"
+ "message": "סנכרון אוטומטי"
},
"manualUpload": {
- "message": "Manual upload"
+ "message": "העלאה ידנית"
},
"manualBillingTokenUploadDesc": {
- "message": "If you do not want to opt into billing sync, manually upload your license here. This will not automatically unlock Families sponsorships."
+ "message": "אם אתה לא רוצה להסכים לסנכרון חיובים, העלה ידנית את הרישיון שלך כאן. זה לא יפתח באופן אוטומטי חסויות למשפחות."
},
"syncLicense": {
- "message": "Sync License"
+ "message": "סנכרן רישיון"
},
"licenseSyncSuccess": {
- "message": "Successfully synced license"
+ "message": "הרישיון סונכרן בהצלחה"
},
"licenseUploadSuccess": {
- "message": "Successfully uploaded license"
+ "message": "הרישיון הועלה בהצלחה"
},
"lastLicenseSync": {
- "message": "Last license sync"
+ "message": "סנכרון רישיון אחרון"
},
"billingSyncHelp": {
- "message": "Billing Sync help"
+ "message": "עזרה עם סנכרון חיובים"
},
"licensePaidFeaturesHelp": {
- "message": "License paid features help"
+ "message": "עזרה עם תכונות בתשלום של רישיון"
},
"selfHostGracePeriodHelp": {
- "message": "After your subscription expires, you have 60 days to apply an updated license file to your organization. Grace period ends $GRACE_PERIOD_END_DATE$.",
+ "message": "לאחר שהמנוי שלך יפוג, יש לך 60 ימים להחיל קובץ רישיון מעודכן לארגון שלך. תקופת חסד מסתיימת $GRACE_PERIOD_END_DATE$.",
"placeholders": {
"GRACE_PERIOD_END_DATE": {
"content": "$1",
@@ -8169,67 +8137,67 @@
}
},
"uploadLicense": {
- "message": "Upload license"
+ "message": "העלה רישיון"
},
"projectPeopleDescription": {
- "message": "Grant groups or people access to this project."
+ "message": "הענק לקבוצות או אנשים גישה לפרויקט זה."
},
"projectPeopleSelectHint": {
- "message": "Type or select people or groups"
+ "message": "הקלד או בחר אנשים או קבוצות"
},
"projectServiceAccountsDescription": {
- "message": "Grant service accounts access to this project."
+ "message": "הענק לחשבונות שירות גישה לפרויקט הזה."
},
"projectServiceAccountsSelectHint": {
- "message": "Type or select service accounts"
+ "message": "הקלד או בחר חשבונות שירות"
},
"projectEmptyPeopleAccessPolicies": {
- "message": "Add people or groups to start collaborating"
+ "message": "הוסף אנשים או קבוצות כדי להתחיל לשתף פעולה"
},
"projectEmptyServiceAccountAccessPolicies": {
- "message": "Add service accounts to grant access"
+ "message": "הוסף חשבונות שירות כדי להעניק גישה"
},
"serviceAccountPeopleDescription": {
- "message": "Grant groups or people access to this service account."
+ "message": "הענק לקבוצות או אנשים גישה לחשבון שירות זה."
},
"serviceAccountProjectsDescription": {
- "message": "Assign projects to this service account. "
+ "message": "הקצה פרויקטים לחשבון השירות הזה: "
},
"serviceAccountEmptyProjectAccesspolicies": {
- "message": "Add projects to grant access"
+ "message": "הוסף פרויקטים כדי להעניק גישה"
},
"canReadWrite": {
- "message": "Can read, write"
+ "message": "יכול/ה לקרוא, לכתוב"
},
"groupSlashUser": {
- "message": "Group/User"
+ "message": "קבוצה/משתמש"
},
"lowKdfIterations": {
- "message": "Low KDF Iterations"
+ "message": "חזרות KDF נמוכות"
},
"updateLowKdfIterationsDesc": {
- "message": "Update your encryption settings to meet new security recommendations and improve account protection."
+ "message": "שדרג את הגדרות ההצפנה שלך כדי לעמוד בהמלצות אבטחה חדשות ולשפר את הגנת החשבון."
},
"kdfSettingsChangeLogoutWarning": {
- "message": "Proceeding will log you out of all active sessions. You will need to log back in and complete two-step login, if any. We recommend exporting your vault before changing your encryption settings to prevent data loss."
+ "message": "המשך התהליך יוציא אותך מכל ההפעלות הפעילות שלך. תידרש להיכנס חזרה כדי להמשיך כניסה דו-שלבית, אם ישנה. אנו ממליצים על ייצוא הכספת שלך לפני שינוי הגדרות ההצפנה שלך כדי למנוע איבוד נתונים."
},
"secretsManager": {
- "message": "Secrets Manager"
+ "message": "מנהל הסודות"
},
"secretsManagerAccessDescription": {
- "message": "Activate user access to Secrets Manager."
+ "message": "הפעל גישת משתמש אל מנהל הסודות."
},
"userAccessSecretsManagerGA": {
- "message": "This user can access Secrets Manager"
+ "message": "משתמש זה יכול לגשת למנהל הסודות"
},
"important": {
- "message": "Important:"
+ "message": "חשוב:"
},
"viewAll": {
- "message": "View all"
+ "message": "הצג הכל"
},
"showingPortionOfTotal": {
- "message": "Showing $PORTION$ of $TOTAL$",
+ "message": "מראה $PORTION$ מתוך $TOTAL$",
"placeholders": {
"portion": {
"content": "$1",
@@ -8242,16 +8210,16 @@
}
},
"resolveTheErrorsBelowAndTryAgain": {
- "message": "Resolve the errors below and try again."
+ "message": "פתור את השגיאות למטה ונסה שוב."
},
"description": {
- "message": "Description"
+ "message": "תיאור"
},
"errorReadingImportFile": {
- "message": "An error occurred when trying to read the import file"
+ "message": "אירעה שגיאה בעת ניסיון לקרוא את קובץ הייבוא"
},
"accessedSecret": {
- "message": "Accessed secret $SECRET_ID$.",
+ "message": "ניגש אל סוד $SECRET_ID$.",
"placeholders": {
"secret_id": {
"content": "$1",
@@ -8264,32 +8232,32 @@
"description": "Software Development Kit"
},
"createAnAccount": {
- "message": "Create an account"
+ "message": "צור חשבון"
},
"createSecret": {
- "message": "Create a secret"
+ "message": "צור סוד"
},
"createProject": {
- "message": "Create a project"
+ "message": "צור פרויקט"
},
"createServiceAccount": {
- "message": "Create a service account"
+ "message": "צור חשבון שירות"
},
"downloadThe": {
- "message": "Download the",
+ "message": "הורד את",
"description": "Link to a downloadable resource. This will be used as part of a larger phrase. Example: Download the Secrets Manager CLI"
},
"smCLI": {
- "message": "Secrets Manager CLI"
+ "message": "מנהל הסודות CLI"
},
"importSecrets": {
- "message": "Import secrets"
+ "message": "ייבא סודות"
},
"getStarted": {
- "message": "Get started"
+ "message": "התחל"
},
"complete": {
- "message": "$COMPLETED$/$TOTAL$ Complete",
+ "message": "$COMPLETED$ מתוך $TOTAL$ הושלמו",
"placeholders": {
"COMPLETED": {
"content": "$1",
@@ -8302,64 +8270,64 @@
}
},
"restoreSecret": {
- "message": "Restore secret"
+ "message": "שחזר סוד"
},
"restoreSecrets": {
- "message": "Restore secrets"
+ "message": "שחזר סודות"
},
"restoreSecretPrompt": {
- "message": "Are you sure you want to restore this secret?"
+ "message": "האם אתה בטוח שברצונך לשחזר את הסוד הזה?"
},
"restoreSecretsPrompt": {
- "message": "Are you sure you want to restore these secrets?"
+ "message": "האם אתה בטוח שברצונך לשחזר את הסודות האלה?"
},
"secretRestoredSuccessToast": {
- "message": "Secret restored"
+ "message": "הסוד שוחזר"
},
"secretsRestoredSuccessToast": {
- "message": "Secrets restored"
+ "message": "הסודות שוחזרו"
},
"selectionIsRequired": {
- "message": "Selection is required."
+ "message": "נדרשת בחירה."
},
"saPeopleWarningTitle": {
- "message": "Access tokens still available"
+ "message": "אסימוני גישה עדיין זמינים"
},
"saPeopleWarningMessage": {
- "message": "Removing people from a service account does not remove the access tokens they created. For security best practice, it is recommended to revoke access tokens created by people removed from a service account."
+ "message": "הסרת אנשים מחשבון שירות אינה מסירה את אסימוני הגישה שהם יצרו. עבור גישת האבטחה הטובה ביותר, מומלץ לבטל אסימוני גישה שנוצרו על ידי אנשים שהוסרו מחשבון שירות."
},
"smAccessRemovalWarningProjectTitle": {
- "message": "Remove access to this project"
+ "message": "הסר גישה לפרויקט זה"
},
"smAccessRemovalWarningProjectMessage": {
- "message": "This action will remove your access to the project."
+ "message": "פעולה זו תסיר את הגישה שלך לפרויקט זה."
},
"smAccessRemovalWarningSaTitle": {
- "message": "Remove access to this service account"
+ "message": "הסר גישה לחשבון שירות זה"
},
"smAccessRemovalWarningSaMessage": {
- "message": "This action will remove your access to the service account."
+ "message": "פעולה זו תסיר את הגישה שלך לחשבון השירות."
},
"removeAccess": {
- "message": "Remove access"
+ "message": "הסר גישה"
},
"checkForBreaches": {
- "message": "Check known data breaches for this password"
+ "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": "סיסמה חלשה זוהתה ונמצאה בפרצת נתונים. השתמש בסיסמה חזקה וייחודית כדי להגן על חשבונך. האם אתה בטוח שאתה רוצה להשתמש בסיסמה זו?"
},
"characterMinimum": {
- "message": "$LENGTH$ character minimum",
+ "message": "$LENGTH$ תווים לכל הפחות",
"placeholders": {
"length": {
"content": "$1",
@@ -8368,7 +8336,7 @@
}
},
"masterPasswordMinimumlength": {
- "message": "Master password must be at least $LENGTH$ characters long.",
+ "message": "סיסמה ראשית חייבת להיות לפחות באורך $LENGTH$ תווים.",
"placeholders": {
"length": {
"content": "$1",
@@ -8377,65 +8345,65 @@
}
},
"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."
},
"dismiss": {
- "message": "Dismiss"
+ "message": "התעלם"
},
"notAvailableForFreeOrganization": {
- "message": "This feature is not available for free organizations. Contact your organization owner to upgrade."
+ "message": "תכונה זו אינה זמינה עבור ארגונים חינמיים. צור קשר עם בעלי הארגון שלך כדי לשדרג."
},
"smProjectSecretsNoItemsNoAccess": {
- "message": "Contact your organization's admin to manage secrets for this project.",
+ "message": "צור קשר עם מנהל הארגון שלך כדי לנהל סודות עבור פרויקט זה.",
"description": "The message shown to the user under a project's secrets tab when the user only has read access to the project."
},
"enforceOnLoginDesc": {
- "message": "Require existing members to change their passwords"
+ "message": "דרוש מחברים קיימים לשנות את הסיסמאות שלהם"
},
"smProjectDeleteAccessRestricted": {
- "message": "You don't have permissions to delete this project",
+ "message": "אין לך הרשאות למחוק את הפרויקט הזה",
"description": "The individual description shown to the user when the user doesn't have access to delete a project."
},
"smProjectsDeleteBulkConfirmation": {
- "message": "The following projects can not be deleted. Would you like to continue?",
+ "message": "הפרויקטים הבאים אינם ניתנים למחיקה. האם ברצונך להמשיך?",
"description": "The message shown to the user when bulk deleting projects and the user doesn't have access to some projects."
},
"updateKdfSettings": {
- "message": "Update KDF settings"
+ "message": "עדכן הגדרות KDF"
},
"loginInitiated": {
- "message": "Login initiated"
+ "message": "הכניסה החלה"
},
"rememberThisDeviceToMakeFutureLoginsSeamless": {
- "message": "Remember this device to make future logins seamless"
+ "message": "זכור מכשיר זה כדי להפוך כניסות עתידיות לחלקות"
},
"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": "בקש אישור מנהל"
},
"trustedDeviceEncryption": {
- "message": "Trusted device encryption"
+ "message": "הצפנת מכשיר מהימן"
},
"trustedDevices": {
- "message": "Trusted devices"
+ "message": "מכשירים מהימנים"
},
"memberDecryptionOptionTdeDescPart1": {
"message": "חברים לא יצטרכו סיסמה ראשית בעת כניסה עם SSO. סיסמה ראשית מוחלפת עם מפתח הצפנה המאוחסן במכשיר, מה שהופך את המכשיר הזה למהימן. המכשיר הראשון בו חבר יוצר את החשבון שלו ונכנס אליו יהיה מהימן. מכשירים חדשים יצטרכו להיות מאושרים על ידי מכשיר מהימן קיים או על ידי מנהל.",
@@ -8466,15 +8434,15 @@
"description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'"
},
"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",
@@ -8483,7 +8451,7 @@
}
},
"notFound": {
- "message": "$RESOURCE$ not found",
+ "message": "$RESOURCE$ לא נמצא",
"placeholders": {
"resource": {
"content": "$1",
@@ -8492,86 +8460,86 @@
}
},
"verificationRequired": {
- "message": "Verification required",
+ "message": "נדרש אימות",
"description": "Default title for the user verification dialog."
},
"recoverAccount": {
- "message": "Recover account"
+ "message": "שחזר חשבון"
},
"updatedTempPassword": {
- "message": "User updated a password issued through account recovery."
+ "message": "משתמש עדכן סיסמה שהונפקה באמצעות שחזור חשבון."
},
"activatedAccessToSecretsManager": {
- "message": "Activated access to Secrets Manager",
+ "message": "הופעלה גישה למנהל הסודות",
"description": "Confirmation message that one or more users gained access to Secrets Manager"
},
"activateAccess": {
- "message": "Activate access"
+ "message": "הפעל גישה"
},
"bulkEnableSecretsManagerDescription": {
- "message": "Grant the following members access to Secrets Manager. The role granted in the Password Manager will apply to Secrets Manager.",
+ "message": "הענק לחברים הבאים גישה למנהל הסודות. התפקיד שהוענק במנהל הסיסמאות יחול על מנהל הסודות.",
"description": "This description is shown to an admin when they are attempting to add more users to Secrets Manager."
},
"activateSecretsManager": {
- "message": "Activate Secrets Manager"
+ "message": "הפעל את מנהל הסודות"
},
"yourOrganizationsFingerprint": {
- "message": "Your organization's fingerprint phrase",
+ "message": "ביטוי טביעת האצבע של ארגונך",
"description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their organization's public key with another user, for the purposes of sharing."
},
"deviceApprovals": {
- "message": "Device approvals"
+ "message": "אישורי מכשירים"
},
"deviceApprovalsDesc": {
- "message": "Approve login requests below to allow the requesting member to finish logging in. Unapproved requests expire after 1 week. Verify the member’s information before approving."
+ "message": "אשר בקשות כניסה למטה כדי לאפשר לחבר המבקש לסיים להיכנס. בקשות לא מאושרות יפוגו לאחר שבוע אחד. אמת את המידע של החבר לפני שתאשר."
},
"deviceInfo": {
- "message": "Device info"
+ "message": "מידע על המכשיר"
},
"time": {
- "message": "Time"
+ "message": "זמן"
},
"denyAllRequests": {
- "message": "Deny all requests"
+ "message": "דחה את כל הבקשות"
},
"denyRequest": {
- "message": "Deny request"
+ "message": "דחה בקשה"
},
"approveRequest": {
- "message": "Approve request"
+ "message": "אשר בקשה"
},
"deviceApproved": {
- "message": "Device approved"
+ "message": "המכשיר אושר"
},
"deviceRemoved": {
- "message": "Device removed"
+ "message": "המכשיר הוסר"
},
"removeDevice": {
- "message": "Remove device"
+ "message": "הסר מכשיר"
},
"removeDeviceConfirmation": {
- "message": "Are you sure you want to remove this device?"
+ "message": "האם אתה בטוח שברצונך להסיר מכשיר זה?"
},
"noDeviceRequests": {
- "message": "No device requests"
+ "message": "אין בקשות של מכשירים"
},
"noDeviceRequestsDesc": {
- "message": "Member device approval requests will appear here"
+ "message": "בקשות לאישור מכשיר של חבר יופיעו כאן"
},
"loginRequestDenied": {
- "message": "Login request denied"
+ "message": "בקשת כניסה נדחתה"
},
"allLoginRequestsDenied": {
- "message": "All login requests denied"
+ "message": "כל בקשות הכניסה נדחו"
},
"loginRequestApproved": {
- "message": "Login request approved"
+ "message": "בקשת כניסה אושרה"
},
"removeOrgUserNoMasterPasswordTitle": {
- "message": "Account does not have master password"
+ "message": "לחשבון אין סיסמה ראשית"
},
"removeOrgUserNoMasterPasswordDesc": {
- "message": "Removing $USER$ without setting a master password for them may restrict access to their full account. Are you sure you want to continue?",
+ "message": "הסרת $USER$ מבלי להגדיר סיסמה ראשית עבורו עשויה להגביל גישה לחשבון המלא שלו. האם אתה בטוח שברצונך להמשיך?",
"placeholders": {
"user": {
"content": "$1",
@@ -8580,13 +8548,13 @@
}
},
"noMasterPassword": {
- "message": "No master password"
+ "message": "אין סיסמה ראשית"
},
"removeMembersWithoutMasterPasswordWarning": {
- "message": "Removing members who do not have master passwords without setting one for them may restrict access to their full account."
+ "message": "הסרת חברים שאין להם סיסמאות ראשיות מבלי להגדיר אחת עבורם עשויה להגביל גישה לחשבון המלא שלהם."
},
"approvedAuthRequest": {
- "message": "Approved device for $ID$.",
+ "message": "מכשיר אושר עבור $ID$.",
"placeholders": {
"id": {
"content": "$1",
@@ -8595,7 +8563,7 @@
}
},
"rejectedAuthRequest": {
- "message": "Denied device for $ID$.",
+ "message": "מכשיר נדחה עבור $ID$.",
"placeholders": {
"id": {
"content": "$1",
@@ -8604,13 +8572,13 @@
}
},
"requestedDeviceApproval": {
- "message": "Requested device approval."
+ "message": "התבקש אישור מכשיר."
},
"tdeOffboardingPasswordSet": {
- "message": "User set a master password during TDE offboarding."
+ "message": "המשתמש הגדיר סיסמה ראשית במהלך תהליך יציאת TDE."
},
"startYour7DayFreeTrialOfBitwardenFor": {
- "message": "Start your 7-Day free trial of Bitwarden for $ORG$",
+ "message": "התחל את תקופת הניסיון בחינם למשך 7 יום של Bitwarden עבור $ORG$",
"placeholders": {
"org": {
"content": "$1",
@@ -8619,7 +8587,7 @@
}
},
"startYour7DayFreeTrialOfBitwardenSecretsManagerFor": {
- "message": "Start your 7-Day free trial of Bitwarden Secrets Manager for $ORG$",
+ "message": "התחל את תקופת הניסיון בחינם למשך 7 יום של מנהל הסודות של Bitwarden עבור $ORG$",
"placeholders": {
"org": {
"content": "$1",
@@ -8628,51 +8596,51 @@
}
},
"next": {
- "message": "Next"
+ "message": "הבא"
},
"ssoLoginIsRequired": {
- "message": "SSO login is required"
+ "message": "נדרשת כניסת SSO"
},
"selectedRegionFlag": {
- "message": "Selected region flag"
+ "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": "מכשיר מהימן"
},
"sendsNoItemsTitle": {
- "message": "No active Sends",
+ "message": "אין סֵנְדים פעילים",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"sendsNoItemsMessage": {
- "message": "Use Send to securely share encrypted information with anyone.",
+ "message": "השתמש בסֵנְד כדי לשתף באופן מאובטח מידע מוצפן עם כל אחד.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"inviteUsers": {
- "message": "Invite Users"
+ "message": "הזמן משתמשים"
},
"secretsManagerForPlan": {
- "message": "Secrets Manager for $PLAN$",
+ "message": "מנהל הסודות עבור $PLAN$",
"placeholders": {
"plan": {
"content": "$1",
@@ -8681,19 +8649,19 @@
}
},
"secretsManagerForPlanDesc": {
- "message": "For engineering and DevOps teams to manage secrets throughout the software development lifecycle."
+ "message": "עבור צוותי הנדסה ו-DevOps כדי שיוכלו לנהל סודות לאורך כל מחזור החיים של פיתוח התוכנה."
},
"free2PersonOrganization": {
- "message": "Free 2-person Organizations"
+ "message": "ארגונים של 2 אנשים בחינם"
},
"unlimitedSecrets": {
- "message": "Unlimited secrets"
+ "message": "סודות ללא הגבלה"
},
"unlimitedProjects": {
- "message": "Unlimited projects"
+ "message": "פרויקטים ללא הגבלה"
},
"projectsIncluded": {
- "message": "$COUNT$ projects included",
+ "message": "$COUNT$ פרויקטים כלולים",
"placeholders": {
"count": {
"content": "$1",
@@ -8702,7 +8670,7 @@
}
},
"serviceAccountsIncluded": {
- "message": "$COUNT$ service accounts included",
+ "message": "$COUNT$ חשבונות שירות כלולים",
"placeholders": {
"count": {
"content": "$1",
@@ -8711,7 +8679,7 @@
}
},
"additionalServiceAccountCost": {
- "message": "$COST$ per month for additional service accounts",
+ "message": "$COST$ לחודש עבור חשבונות שירות נוספים",
"placeholders": {
"cost": {
"content": "$1",
@@ -8720,16 +8688,16 @@
}
},
"subscribeToSecretsManager": {
- "message": "Subscribe to Secrets Manager"
+ "message": "הירשם כמנוי למנהל הסודות"
},
"addSecretsManagerUpgradeDesc": {
- "message": "Add Secrets Manager to your upgraded plan to maintain access to any secrets created with your previous plan."
+ "message": "הוסף את מנהל הסודות לתוכנית המשודרגת שלך כדי לשמור על גישה לכל הסודות שנוצרו עם התוכנית הקודמת שלך."
},
"additionalServiceAccounts": {
- "message": "Additional service accounts"
+ "message": "חשבונות שירות נוספים"
},
"includedServiceAccounts": {
- "message": "Your plan comes with $COUNT$ service accounts.",
+ "message": "התוכנית שלך מגיעה עם $COUNT$ חשבונות שירות.",
"placeholders": {
"count": {
"content": "$1",
@@ -8738,7 +8706,7 @@
}
},
"addAdditionalServiceAccounts": {
- "message": "You can add additional service accounts for $COST$ per month.",
+ "message": "אתה יכול להוסיף חשבונות שירות נוספים עבור $COST$ לחודש.",
"placeholders": {
"cost": {
"content": "$1",
@@ -8747,92 +8715,92 @@
}
},
"collectionManagement": {
- "message": "Collection management"
+ "message": "ניהול אוספים"
},
"collectionManagementDesc": {
- "message": "Manage the collection behavior for the organization"
+ "message": "נהל את התנהגות האוספים עבור הארגון"
},
"limitCollectionCreationDesc": {
- "message": "Limit collection creation to owners and admins"
+ "message": "הגבל יצירת אוספים לבעלים ומנהלים"
},
"limitCollectionDeletionDesc": {
- "message": "Limit collection deletion to owners and admins"
+ "message": "הגבל מחיקת אוספים לבעלים ומנהלים"
},
"limitItemDeletionDesc": {
- "message": "Limit item deletion to members with the Can manage permission"
+ "message": "הגבל מחיקת פריטים לחברים עם הרשאת יכולת ניהול"
},
"allowAdminAccessToAllCollectionItemsDesc": {
- "message": "Owners and admins can manage all collections and items"
+ "message": "בעלים ומנהלים יכולים לנהל את כל האוספים והפריטים"
},
"updatedCollectionManagement": {
- "message": "Updated collection management setting"
+ "message": "הגדרת ניהול אוספים עודכנה"
},
"passwordManagerPlanPrice": {
- "message": "Password Manager plan price"
+ "message": "מחיר תוכנית מנהל הסיסמאות"
},
"secretsManagerPlanPrice": {
- "message": "Secrets Manager plan price"
+ "message": "מחיר תוכנית מנהל הסודות"
},
"passwordManager": {
- "message": "Password Manager"
+ "message": "מנהל הסיסמאות"
},
"freeOrganization": {
- "message": "Free Organization"
+ "message": "ארגון חינמי"
},
"limitServiceAccounts": {
- "message": "Limit service accounts (optional)"
+ "message": "הגבל חשבונות שירות (אופציונלי)"
},
"limitServiceAccountsDesc": {
- "message": "Set a limit for your service accounts. Once this limit is reached, you will not be able to create new service accounts."
+ "message": "הגדר מגבלה עבור חשבונות השירות שלך. ברגע שתגיע למגבלה זו, לא תוכל ליצור חשבונות שירות חדשים."
},
"serviceAccountLimit": {
- "message": "Service account limit (optional)"
+ "message": "מגבלת חשבונות שירות (אופציונלי)"
},
"maxServiceAccountCost": {
- "message": "Max potential service account cost"
+ "message": "עלות מרבית פוטנציאלית של חשבון שירות"
},
"loggedInExclamation": {
- "message": "Logged in!"
+ "message": "נכנסת!"
},
"beta": {
- "message": "Beta"
+ "message": "בטא"
},
"assignCollectionAccess": {
- "message": "Assign collection access"
+ "message": "הקצה גישה לאוסף"
},
"editedCollections": {
- "message": "Edited collections"
+ "message": "אוספים שנערכו"
},
"baseUrl": {
- "message": "Server URL"
+ "message": "URL של שרת"
},
"selfHostBaseUrl": {
- "message": "Self-host server URL",
+ "message": "URL של שרת אירוח עצמי",
"description": "Label for field requesting a self-hosted integration service URL"
},
"alreadyHaveAccount": {
- "message": "Already have an account?"
+ "message": "כבר יש לך חשבון?"
},
"toggleSideNavigation": {
- "message": "Toggle side navigation"
+ "message": "החלף מצב ניווט צדדי"
},
"skipToContent": {
- "message": "Skip to content"
+ "message": "דלג לתוכן"
},
"managePermissionRequired": {
- "message": "At least one member or group must have can manage permission."
+ "message": "לפחות חבר אחד או קבוצה אחת חייבים להיות בעלי הרשאת יכולת ניהול."
},
"typePasskey": {
- "message": "Passkey"
+ "message": "מפתח גישה"
},
"passkeyNotCopied": {
- "message": "Passkey will not be copied"
+ "message": "מפתח גישה לא יועתק"
},
"passkeyNotCopiedAlert": {
- "message": "The passkey will not be copied to the cloned item. Do you want to continue cloning this item?"
+ "message": "מפתח הגישה לא יועתק לפריט המשוכפל. האם אתה רוצה להמשיך לשכפל פריט זה?"
},
"modifiedCollectionManagement": {
- "message": "Modified collection management setting $ID$.",
+ "message": "שונתה הגדרת ניהול אוספים $ID$.",
"placeholders": {
"id": {
"content": "$1",
@@ -8841,60 +8809,60 @@
}
},
"seeDetailedInstructions": {
- "message": "See detailed instructions on our help site at",
+ "message": "ראה הוראות מפורטות באתר העזרה שלנו ב-",
"description": "This is followed a by a hyperlink to the help website."
},
"installBrowserExtension": {
- "message": "Install browser extension"
+ "message": "התקן הרחבת דפדפן"
},
"installBrowserExtensionDetails": {
- "message": "Use the extension to quickly save logins and auto-fill forms without opening the web app."
+ "message": "השתמש בהרחבה כדי לשמור כניסות ולמלא אוטומטית טפסים במהירות מבלי לפתוח את יישום הרשת."
},
"projectAccessUpdated": {
- "message": "Project access updated"
+ "message": "גישת פרויקט עודכנה"
},
"unexpectedErrorSend": {
- "message": "An unexpected error has occurred while loading this Send. Try again later."
+ "message": "אירעה שגיאה בלתי צפויה בעת טעינת סֵנְד זה. נסה שוב מאוחר יותר."
},
"seatLimitReached": {
- "message": "Seat limit has been reached"
+ "message": "מגבלת מקום הושגה"
},
"contactYourProvider": {
- "message": "Contact your provider to purchase additional seats."
+ "message": "צור קשר עם הספק שלך כדי לרכוש מקומות נוספים."
},
"seatLimitReachedContactYourProvider": {
- "message": "Seat limit has been reached. Contact your provider to purchase additional seats."
+ "message": "מגבלת מקום הושגה. צור קשר עם הספק שלך כדי לרכוש מקומות נוספים."
},
"collectionAccessRestricted": {
- "message": "Collection access is restricted"
+ "message": "הגישה לאוסף מוגבלת"
},
"readOnlyCollectionAccess": {
- "message": "You do not have access to manage this collection."
+ "message": "אין לך גישה לנהל את האוסף הזה."
},
"grantManageCollectionWarningTitle": {
- "message": "Missing Manage Collection Permissions"
+ "message": "חסרות הרשאות ניהול אוסף"
},
"grantManageCollectionWarning": {
- "message": "Grant Manage collection permissions to allow full collection management including deletion of collection."
+ "message": "הענק הרשאות ניהול אוסף כדי לאפשר ניהול אוסף מלא כולל מחיקה של אוסף."
},
"grantCollectionAccess": {
- "message": "Grant groups or members access to this collection."
+ "message": "הענק לקבוצות או חברים גישה לאוסף זה."
},
"grantCollectionAccessMembersOnly": {
- "message": "Grant members access to this collection."
+ "message": "הענק לחברים גישה לאוסף זה."
},
"adminCollectionAccess": {
- "message": "Administrators can access and manage collections."
+ "message": "מנהלים יכולים לגשת ולנהל אוספים."
},
"serviceAccountAccessUpdated": {
- "message": "Service account access updated"
+ "message": "גישת חשבון השירות עודכנה"
},
"commonImportFormats": {
- "message": "Common formats",
+ "message": "פורמטים נפוצים",
"description": "Label indicating the most common import formats"
},
"maintainYourSubscription": {
- "message": "To maintain your subscription for $ORG$, ",
+ "message": "כדי לשמור על המנוי שלך עבור$ORG$, ",
"description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method.'",
"placeholders": {
"org": {
@@ -8904,103 +8872,103 @@
}
},
"addAPaymentMethod": {
- "message": "add a payment method",
+ "message": "הוסף אמצעי תשלום",
"description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method'"
},
"organizationInformation": {
- "message": "Organization information"
+ "message": "מידע על הארגון"
},
"confirmationDetails": {
- "message": "Confirmation details"
+ "message": "פרטי אישור"
},
"smFreeTrialThankYou": {
- "message": "Thank you for signing up for Bitwarden Secrets Manager!"
+ "message": "תודה לך שנרשמת למנהל הסודות של Bitwarden!"
},
"smFreeTrialConfirmationEmail": {
- "message": "We've sent a confirmation email to your email at "
+ "message": "שלחנו דוא\"ל אימות לדוא\"ל שלך ב־"
},
"sorryToSeeYouGo": {
- "message": "Sorry to see you go! Help improve Bitwarden by sharing why you're canceling.",
+ "message": "מצטערים לראות אותך הולך! עזור לשפר את Bitwarden על ידי שיתוף הסיבה לביטול.",
"description": "A message shown to users as part of an offboarding survey asking them to provide more information on their subscription cancelation."
},
"selectCancellationReason": {
- "message": "Select a reason for canceling",
+ "message": "בחר סיבה לביטול",
"description": "Used as a form field label for a select input on the offboarding survey."
},
"anyOtherFeedback": {
- "message": "Is there any other feedback you'd like to share?",
+ "message": "האם יש משוב אחר כלשהו שתרצה לשתף?",
"description": "Used as a form field label for a textarea input on the offboarding survey."
},
"missingFeatures": {
- "message": "Missing features",
+ "message": "תכונות חסרות",
"description": "An option for the offboarding survey shown when a user cancels their subscription."
},
"movingToAnotherTool": {
- "message": "Moving to another tool",
+ "message": "עובר לכלי אחר",
"description": "An option for the offboarding survey shown when a user cancels their subscription."
},
"tooDifficultToUse": {
- "message": "Too difficult to use",
+ "message": "קשה מדי לשימוש",
"description": "An option for the offboarding survey shown when a user cancels their subscription."
},
"notUsingEnough": {
- "message": "Not using enough",
+ "message": "לא משתמש מספיק",
"description": "An option for the offboarding survey shown when a user cancels their subscription."
},
"tooExpensive": {
- "message": "Too expensive",
+ "message": "יקר מדי",
"description": "An option for the offboarding survey shown when a user cancels their subscription."
},
"freeForOneYear": {
- "message": "Free for 1 year"
+ "message": "חינם לשנה אחת"
},
"newWebApp": {
- "message": "Welcome to the new and improved web app. Learn more about what’s changed."
+ "message": "ברוך הבא ליישום הרשת החדש והמשופר. למד עוד על מה שהשתנה."
},
"releaseBlog": {
- "message": "Read release blog"
+ "message": "קרא בלוג שחרור"
},
"adminConsole": {
- "message": "Admin Console"
+ "message": "מסוף מנהל"
},
"providerPortal": {
- "message": "Provider Portal"
+ "message": "פורטל ספקים"
},
"success": {
- "message": "Success"
+ "message": "הצלחה"
},
"restrictedGroupAccess": {
- "message": "You cannot add yourself to groups."
+ "message": "אתה לא יכול להוסיף את עצמך לקבוצות."
},
"cannotAddYourselfToCollections": {
- "message": "You cannot add yourself to collections."
+ "message": "אתה לא יכול להוסיף את עצמך לאוספים."
},
"assign": {
- "message": "Assign"
+ "message": "הקצה"
},
"assignToCollections": {
- "message": "Assign to collections"
+ "message": "הקצה לאוספים"
},
"assignToTheseCollections": {
- "message": "Assign to these collections"
+ "message": "הקצה לאוספים אלה"
},
"bulkCollectionAssignmentDialogDescriptionSingular": {
- "message": "Only organization members with access to these collections will be able to see the item."
+ "message": "רק חברי ארגון עם גישה לאוספים אלה יוכלו לראות את הפריט."
},
"bulkCollectionAssignmentDialogDescriptionPlural": {
- "message": "Only organization members with access to these collections will be able to see the items."
+ "message": "רק חברי ארגון עם גישה לאוספים אלה יוכלו לראות את הפריטים."
},
"selectCollectionsToAssign": {
- "message": "Select collections to assign"
+ "message": "בחר אוספים להקצות"
},
"noCollectionsAssigned": {
- "message": "No collections have been assigned"
+ "message": "לא הוקצו אוספים"
},
"successfullyAssignedCollections": {
- "message": "Successfully assigned collections"
+ "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",
@@ -9013,61 +8981,61 @@
}
},
"addField": {
- "message": "Add field"
+ "message": "הוסף שדה"
},
"editField": {
- "message": "Edit field"
+ "message": "ערוך שדה"
},
"items": {
- "message": "Items"
+ "message": " פריטים "
},
"assignedSeats": {
- "message": "Assigned seats"
+ "message": "מקומות שהוקצו"
},
"assigned": {
- "message": "Assigned"
+ "message": "הוקצו"
},
"used": {
- "message": "Used"
+ "message": "בשימוש"
},
"remaining": {
- "message": "Remaining"
+ "message": "נותרו"
},
"unlinkOrganization": {
- "message": "Unlink organization"
+ "message": "בטל קישור לארגון"
},
"manageSeats": {
- "message": "MANAGE SEATS"
+ "message": "נהל מקומות"
},
"manageSeatsDescription": {
- "message": "Adjustments to seats will be reflected in the next billing cycle."
+ "message": "התאמות למקומות ישתקפו במחזור החיוב הבא."
},
"unassignedSeatsDescription": {
- "message": "Unassigned seats"
+ "message": "מקומות לא מוקצים"
},
"purchaseSeatDescription": {
- "message": "Additional seats purchased"
+ "message": "מקומות נוספים נרכשו"
},
"assignedSeatCannotUpdate": {
- "message": "Assigned Seats can not be updated. Please contact your organization owner for assistance."
+ "message": "לא ניתן לעדכן מקומות מוקצים. נא ליצור קשר עם בעלי הארגון שלך עבור סיוע."
},
"subscriptionUpdateFailed": {
- "message": "Subscription update failed"
+ "message": "עדכון מנוי נכשל"
},
"trial": {
- "message": "Trial",
+ "message": "ניסיון",
"description": "A subscription status label."
},
"pastDue": {
- "message": "Past due",
+ "message": "עבר את המועד",
"description": "A subscription status label"
},
"subscriptionExpired": {
- "message": "Subscription expired",
+ "message": "פג תוקף המנוי",
"description": "The date header used when a subscription is past due."
},
"pastDueWarningForChargeAutomatically": {
- "message": "You have a grace period of $DAYS$ days from your subscription expiration date to maintain your subscription. Please resolve the past due invoices by $SUSPENSION_DATE$.",
+ "message": "יש לך תקופת חסד של $DAYS$ ימים מתאריך תפוגת המנוי שלך כדי לשמור על המנוי שלך. נא ליישב את החשבוניות שמועד פירעונן עבר עד $SUSPENSION_DATE$.",
"placeholders": {
"days": {
"content": "$1",
@@ -9081,7 +9049,7 @@
"description": "A warning shown to the user when their subscription is past due and they are charged automatically."
},
"pastDueWarningForSendInvoice": {
- "message": "You have a grace period of $DAYS$ days from the date your first unpaid invoice is due to maintain your subscription. Please resolve the past due invoices by $SUSPENSION_DATE$.",
+ "message": "יש לך תקופת חסד של $DAYS$ ימים מתאריך הפירעון של החשבונית הראשונה שלך שלא שולמה כדי לשמור על המנוי שלך. נא ליישב את החשבוניות שמועד פירעונן עבר עד $SUSPENSION_DATE$.",
"placeholders": {
"days": {
"content": "$1",
@@ -9095,54 +9063,54 @@
"description": "A warning shown to the user when their subscription is past due and they pay via invoice."
},
"unpaidInvoice": {
- "message": "Unpaid invoice",
+ "message": "חשבונית שלא שולמה",
"description": "The header of a warning box shown to a user whose subscription is unpaid."
},
"toReactivateYourSubscription": {
- "message": "To reactivate your subscription, please resolve the past due invoices.",
+ "message": "כדי להפעיל מחדש את המנוי שלך, נא ליישב את החשבוניות שמועד פירעונן עבר.",
"description": "The body of a warning box shown to a user whose subscription is unpaid."
},
"cancellationDate": {
- "message": "Cancellation date",
+ "message": "תאריך ביטול",
"description": "The date header used when a subscription is cancelled."
},
"machineAccountsCannotCreate": {
- "message": "Machine accounts cannot be created in suspended organizations. Please contact your organization owner for assistance."
+ "message": "לא ניתן ליצור חשבונות מכונה בארגונים מושעים. נא ליצור קשר עם בעלי הארגון שלך עבור סיוע."
},
"machineAccount": {
- "message": "Machine account",
+ "message": "חשבון מכונה",
"description": "A machine user which can be used to automate processes and access secrets in the system."
},
"machineAccounts": {
- "message": "Machine accounts",
+ "message": "חשבונות מכונה",
"description": "The title for the section that deals with machine accounts."
},
"newMachineAccount": {
- "message": "New machine account",
+ "message": "חשבון מכונה חדש",
"description": "Title for creating a new machine account."
},
"machineAccountsNoItemsMessage": {
- "message": "Create a new machine account to get started automating secret access.",
+ "message": "צור חשבון מכונה חדש כדי להתחיל לאטמט גישת סודות.",
"description": "Message to encourage the user to start creating machine accounts."
},
"machineAccountsNoItemsTitle": {
- "message": "Nothing to show yet",
+ "message": "אין מה להראות עדיין",
"description": "Title to indicate that there are no machine accounts to display."
},
"deleteMachineAccounts": {
- "message": "Delete machine accounts",
+ "message": "מחק חשבונות מכונה",
"description": "Title for the action to delete one or multiple machine accounts."
},
"deleteMachineAccount": {
- "message": "Delete machine account",
+ "message": "מחק חשבון מכונה",
"description": "Title for the action to delete a single machine account."
},
"viewMachineAccount": {
- "message": "View machine account",
+ "message": "הצג חשבון מכונה",
"description": "Action to view the details of a machine account."
},
"deleteMachineAccountDialogMessage": {
- "message": "Deleting machine account $MACHINE_ACCOUNT$ is permanent and irreversible.",
+ "message": "מחיקת חשבון מכונה $MACHINE_ACCOUNT$ היא לצמיתות ובלתי הפיכה.",
"placeholders": {
"machine_account": {
"content": "$1",
@@ -9151,10 +9119,10 @@
}
},
"deleteMachineAccountsDialogMessage": {
- "message": "Deleting machine accounts is permanent and irreversible."
+ "message": "מחיקת חשבונות מכונה היא לצמיתות ובלתי הפיכה."
},
"deleteMachineAccountsConfirmMessage": {
- "message": "Delete $COUNT$ machine accounts",
+ "message": "מחק $COUNT$ חשבונות מכונה",
"placeholders": {
"count": {
"content": "$1",
@@ -9163,60 +9131,60 @@
}
},
"deleteMachineAccountToast": {
- "message": "Machine account deleted"
+ "message": "חשבון מכונה נמחק"
},
"deleteMachineAccountsToast": {
- "message": "Machine accounts deleted"
+ "message": "חשבונות מכונה נמחקו"
},
"searchMachineAccounts": {
- "message": "Search machine accounts",
+ "message": "חפש חשבונות מכונה",
"description": "Placeholder text for searching machine accounts."
},
"editMachineAccount": {
- "message": "Edit machine account",
+ "message": "ערוך חשבון מכונה",
"description": "Title for editing a machine account."
},
"machineAccountName": {
- "message": "Machine account name",
+ "message": "שם חשבון מכונה",
"description": "Label for the name of a machine account"
},
"machineAccountCreated": {
- "message": "Machine account created",
+ "message": "חשבון מכונה נוצר",
"description": "Notifies that a new machine account has been created"
},
"machineAccountUpdated": {
- "message": "Machine account updated",
+ "message": "חשבון מכונה עודכן",
"description": "Notifies that a machine account has been updated"
},
"projectMachineAccountsDescription": {
- "message": "Grant machine accounts access to this project."
+ "message": "הענק לחשבונות מכונה גישה לפרויקט הזה."
},
"projectMachineAccountsSelectHint": {
- "message": "Type or select machine accounts"
+ "message": "הקלד או בחר חשבונות מכונה"
},
"projectEmptyMachineAccountAccessPolicies": {
- "message": "Add machine accounts to grant access"
+ "message": "הוסף חשבונות מכונה כדי להעניק גישה"
},
"machineAccountPeopleDescription": {
- "message": "Grant groups or people access to this machine account."
+ "message": "הענק לקבוצות או אנשים גישה לחשבון מכונה זה."
},
"machineAccountProjectsDescription": {
- "message": "Assign projects to this machine account. "
+ "message": "הקצה פרויקטים לחשבון המכונה הזה: "
},
"createMachineAccount": {
- "message": "Create a machine account"
+ "message": "צור חשבון מכונה"
},
"maPeopleWarningMessage": {
- "message": "Removing people from a machine account does not remove the access tokens they created. For security best practice, it is recommended to revoke access tokens created by people removed from a machine account."
+ "message": "הסרת אנשים מחשבון מכונה אינה מסירה את אסימוני הגישה שהם יצרו. עבור גישת האבטחה הטובה ביותר, מומלץ לבטל אסימוני גישה שנוצרו על ידי אנשים שהוסרו מחשבון מכונה."
},
"smAccessRemovalWarningMaTitle": {
- "message": "Remove access to this machine account"
+ "message": "הסר גישה לחשבון מכונה זה"
},
"smAccessRemovalWarningMaMessage": {
- "message": "This action will remove your access to the machine account."
+ "message": "פעולה זו תסיר את הגישה שלך לחשבון המכונה."
},
"machineAccountsIncluded": {
- "message": "$COUNT$ machine accounts included",
+ "message": "$COUNT$ חשבונות מכונה כלולים",
"placeholders": {
"count": {
"content": "$1",
@@ -9225,7 +9193,7 @@
}
},
"additionalMachineAccountCost": {
- "message": "$COST$ per month for additional machine accounts",
+ "message": "$COST$ לחודש עבור חשבונות מכונה נוספים",
"placeholders": {
"cost": {
"content": "$1",
@@ -9234,10 +9202,10 @@
}
},
"additionalMachineAccounts": {
- "message": "Additional machine accounts"
+ "message": "חשבונות מכונה נוספים"
},
"includedMachineAccounts": {
- "message": "Your plan comes with $COUNT$ machine accounts.",
+ "message": "התוכנית שלך מגיעה עם $COUNT$ חשבונות מכונה.",
"placeholders": {
"count": {
"content": "$1",
@@ -9246,7 +9214,7 @@
}
},
"addAdditionalMachineAccounts": {
- "message": "You can add additional machine accounts for $COST$ per month.",
+ "message": "אתה יכול להוסיף חשבונות מכונה נוספים עבור $COST$ לחודש.",
"placeholders": {
"cost": {
"content": "$1",
@@ -9255,31 +9223,31 @@
}
},
"limitMachineAccounts": {
- "message": "Limit machine accounts (optional)"
+ "message": "הגבל חשבונות מכונה (אופציונלי)"
},
"limitMachineAccountsDesc": {
- "message": "Set a limit for your machine accounts. Once this limit is reached, you will not be able to create new machine accounts."
+ "message": "הגדר מגבלה עבור חשבונות המכונה שלך. ברגע שתגיע למגבלה זו, לא תוכל ליצור חשבונות מכונה חדשים."
},
"machineAccountLimit": {
- "message": "Machine account limit (optional)"
+ "message": "מגבלת חשבונות מכונה (אופציונלי)"
},
"maxMachineAccountCost": {
- "message": "Max potential machine account cost"
+ "message": "עלות מרבית פוטנציאלית של חשבון מכונה"
},
"machineAccountAccessUpdated": {
- "message": "Machine account access updated"
+ "message": "גישת חשבון המכונה עודכנה"
},
"restrictedGroupAccessDesc": {
- "message": "You cannot add yourself to a group."
+ "message": "אתה לא יכול להוסיף את עצמך לקבוצה."
},
"deleteProvider": {
- "message": "Delete provider"
+ "message": "מחק ספק"
},
"deleteProviderConfirmation": {
- "message": "Deleting a provider is permanent and irreversible. Enter your master password to confirm the deletion of the provider and all associated data."
+ "message": "מחיקת ספק היא לצמיתות ובלתי הפיכה. הזן את הסיסמה הראשית שלך כדי לאשר את המחיקה של הספק וכל הנתונים המשויכים."
},
"deleteProviderName": {
- "message": "Cannot delete $ID$",
+ "message": "לא ניתן למחוק את $ID$",
"placeholders": {
"id": {
"content": "$1",
@@ -9288,7 +9256,7 @@
}
},
"deleteProviderWarningDescription": {
- "message": "You must unlink all clients before you can delete $ID$.",
+ "message": "אתה מוכרח לבטל קישור של כל הלקוחות לפני שתוכל למחוק את $ID$.",
"placeholders": {
"id": {
"content": "$1",
@@ -9297,96 +9265,96 @@
}
},
"providerDeleted": {
- "message": "Provider deleted"
+ "message": "הספק נמחק"
},
"providerDeletedDesc": {
- "message": "The Provider and all associated data has been deleted."
+ "message": "הספק וכל המידע המשויך נמחק."
},
"deleteProviderRecoverConfirmDesc": {
- "message": "You have requested to delete this Provider. Use the button below to confirm."
+ "message": "ביקשת למחוק את הספק הזה. השתמש בלחצן למטה כדי לאשר."
},
"deleteProviderWarning": {
- "message": "Deleting your provider is permanent. It cannot be undone."
+ "message": "מחיקת הספק שלך היא לצמיתות. לא ניתן לבטלה."
},
"errorAssigningTargetCollection": {
- "message": "Error assigning target collection."
+ "message": "שגיאה בהקצאת אוסף יעד."
},
"errorAssigningTargetFolder": {
- "message": "Error assigning target folder."
+ "message": "שגיאה בהקצאת תיקיית יעד."
},
"integrationsAndSdks": {
- "message": "Integrations & SDKs",
+ "message": "שילובים & ערכות SDK",
"description": "The title for the section that deals with integrations and SDKs."
},
"integrations": {
- "message": "Integrations"
+ "message": "שילובים"
},
"integrationsDesc": {
- "message": "Automatically sync secrets from Bitwarden Secrets Manager to a third-party service."
+ "message": "סנכרן באופן אוטומטי סודות ממנהל סודות של Bitwarden לשירות צד שלישי."
},
"sdks": {
- "message": "SDKs"
+ "message": "ערכות SDK"
},
"sdksDesc": {
- "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications."
+ "message": "השתמש ב־SDK של מנהל הסודות של Bitwarden בשפות התכנות הבאות כדי לבנות את היישומים שלך."
},
"ssoDescStart": {
- "message": "Configure",
+ "message": "קבע תצורת",
"description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider."
},
"ssoDescEnd": {
- "message": "for Bitwarden using the implementation guide for your Identity Provider.",
+ "message": "עבור Bitwarden באמצעות מדריך היישום עבור ספק הזהות שלך.",
"description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider."
},
"userProvisioning": {
- "message": "User provisioning"
+ "message": "הקצאת משתמשים"
},
"scimIntegration": {
"message": "SCIM"
},
"scimIntegrationDescStart": {
- "message": "Configure ",
+ "message": "קבע תצורת ",
"description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider"
},
"scimIntegrationDescEnd": {
- "message": "(System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider.",
+ "message": "(מערכת לניהול זהות חוצה תחומים) כדי להקצות באופן אוטומטי משתמשים וקבוצות אל Bitwarden באמצעות מדריך היישום עבור ספק הזהות שלך.",
"description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider"
},
"bwdc": {
- "message": "Bitwarden Directory Connector"
+ "message": "מחבר הספריות של Bitwarden"
},
"bwdcDesc": {
- "message": "Configure Bitwarden Directory Connector to automatically provision users and groups using the implementation guide for your Identity Provider."
+ "message": "קבע תצורת מחבר הספריות של Bitwarden כדי להקצות באופן אוטומטי משתמשים וקבוצות אל Bitwarden באמצעות מדריך היישום עבור ספק הזהות שלך."
},
"eventManagement": {
- "message": "Event management"
+ "message": "ניהול אירועים"
},
"eventManagementDesc": {
- "message": "Integrate Bitwarden event logs with your SIEM (system information and event management) system by using the implementation guide for your platform."
+ "message": "שלב את יומני האירועים של Bitwarden עם מערכת ה־SIEM (מידע מערכת וניהול אירועים) שלך על ידי שימוש במדריך היישום עבור הפלטפורמה שלך."
},
"deviceManagement": {
- "message": "Device management"
+ "message": "ניהול מכשירים"
},
"deviceManagementDesc": {
- "message": "Configure device management for Bitwarden using the implementation guide for your platform."
+ "message": "קבע תצורת ניהול מכשירים עבור Bitwarden באמצעות מדריך היישום עבור הפלטפורמה שלך."
},
"deviceIdMissing": {
- "message": "Device ID is missing"
+ "message": "מזהה המכשיר חסר"
},
"deviceTypeMissing": {
- "message": "Device type is missing"
+ "message": "סוג המכשיר חסר"
},
"deviceCreationDateMissing": {
- "message": "Device creation date is missing"
+ "message": "תאריך יצירת המכשיר חסר"
},
"desktopRequired": {
- "message": "Desktop required"
+ "message": "נדרש שולחן עבודה"
},
"reopenLinkOnDesktop": {
- "message": "Reopen this link from your email on a desktop."
+ "message": "פתח מחדש קישור זה מהדוא\"ל שלך בשולחן עבודה."
},
"integrationCardTooltip": {
- "message": "Launch $INTEGRATION$ implementation guide.",
+ "message": "פתח מדריך יישום $INTEGRATION$.",
"placeholders": {
"integration": {
"content": "$1",
@@ -9395,7 +9363,7 @@
}
},
"smIntegrationTooltip": {
- "message": "Set up $INTEGRATION$.",
+ "message": "הגדר את $INTEGRATION$.",
"placeholders": {
"integration": {
"content": "$1",
@@ -9404,7 +9372,7 @@
}
},
"smSdkTooltip": {
- "message": "View $SDK$ repository",
+ "message": "הצג מאגר $SDK$",
"placeholders": {
"sdk": {
"content": "$1",
@@ -9413,7 +9381,7 @@
}
},
"integrationCardAriaLabel": {
- "message": "open $INTEGRATION$ implementation guide in a new tab.",
+ "message": "פתח מדריך מימוש $INTEGRATION$ בכרטיסיה חדשה.",
"placeholders": {
"integration": {
"content": "$1",
@@ -9422,7 +9390,7 @@
}
},
"smSdkAriaLabel": {
- "message": "view $SDK$ repository in a new tab.",
+ "message": "הצג מאגר $SDK$ בכרטיסיה חדשה.",
"placeholders": {
"sdk": {
"content": "$1",
@@ -9431,7 +9399,7 @@
}
},
"smIntegrationCardAriaLabel": {
- "message": "set up $INTEGRATION$ implementation guide in a new tab.",
+ "message": "הגדר מדריך יישום $INTEGRATION$ בכרטיסיה חדשה.",
"placeholders": {
"integration": {
"content": "$1",
@@ -9440,67 +9408,67 @@
}
},
"createNewClientToManageAsProvider": {
- "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle."
+ "message": "צור ארגון לקוחות חדש כדי לנהל כספק. מקומות נוספים ישתקפו במחזור החיוב הבא."
},
"selectAPlan": {
- "message": "Select a plan"
+ "message": "בחר תוכנית"
},
"thirtyFivePercentDiscount": {
- "message": "35% Discount"
+ "message": "35% הנחה"
},
"monthPerMember": {
- "message": "month per member"
+ "message": "חודש לכל חבר"
},
"monthPerMemberBilledAnnually": {
- "message": "month per member billed annually"
+ "message": "חודש לכל חבר מחויב מדי שנה"
},
"seats": {
- "message": "Seats"
+ "message": "מקומות"
},
"addOrganization": {
- "message": "Add organization"
+ "message": "הוסף ארגון"
},
"createdNewClient": {
- "message": "Successfully created new client"
+ "message": "יצר בהצלחה לקוח חדש"
},
"noAccess": {
- "message": "No access"
+ "message": "אין גישה"
},
"collectionAdminConsoleManaged": {
- "message": "This collection is only accessible from the admin console"
+ "message": "אוסף זה זמין רק ממסוף המנהל"
},
"organizationOptionsMenu": {
- "message": "Toggle Organization Menu"
+ "message": "שנה מצב תפריט ארגון"
},
"vaultItemSelect": {
- "message": "Select vault item"
+ "message": "בחר פריט כספת"
},
"collectionItemSelect": {
- "message": "Select collection item"
+ "message": "בחר פריט אוסף"
},
"manageBillingFromProviderPortalMessage": {
- "message": "Manage billing from the Provider Portal"
+ "message": "נהל חיובים מפורטל הספקים"
},
"continueSettingUpFreeTrial": {
- "message": "Continue setting up your free trial of Bitwarden"
+ "message": "המשך בהגדרת הניסיון החינמי של Bitwarden שלך"
},
"continueSettingUpFreeTrialPasswordManager": {
- "message": "Continue setting up your free trial of Bitwarden Password Manager"
+ "message": "המשך בהגדרת הניסיון החינמי של מנהל הסיסמאות Bitwarden שלך"
},
"continueSettingUpFreeTrialSecretsManager": {
- "message": "Continue setting up your free trial of Bitwarden Secrets Manager"
+ "message": "המשך בהגדרת הניסיון החינמי של מנהל הסודות Bitwarden שלך"
},
"enterTeamsOrgInfo": {
- "message": "Enter your Teams organization information"
+ "message": "הזן את פרטי ארגון הצוותים שלך"
},
"enterFamiliesOrgInfo": {
- "message": "Enter your Families organization information"
+ "message": "הזן את פרטי ארגון המשפחות שלך"
},
"enterEnterpriseOrgInfo": {
- "message": "Enter your Enterprise organization information"
+ "message": "הזן את פרטי הארגון הארגוני שלך"
},
"viewItemsIn": {
- "message": "View items in $NAME$",
+ "message": "הצג פריטים ב־$NAME$",
"description": "Button to view the contents of a folder or collection",
"placeholders": {
"name": {
@@ -9510,7 +9478,7 @@
}
},
"backTo": {
- "message": "Back to $NAME$",
+ "message": "חזרה אל $NAME$",
"description": "Navigate back to a previous folder or collection",
"placeholders": {
"name": {
@@ -9520,11 +9488,11 @@
}
},
"back": {
- "message": "Back",
+ "message": "הקודם",
"description": "Button text to navigate back"
},
"removeItem": {
- "message": "Remove $NAME$",
+ "message": "הסר $NAME$",
"description": "Remove a selected option, such as a folder or collection",
"placeholders": {
"name": {
@@ -9534,34 +9502,34 @@
}
},
"viewInfo": {
- "message": "View info"
+ "message": "הצג מידע"
},
"viewAccess": {
- "message": "View access"
+ "message": "הצג גישה"
},
"noCollectionsSelected": {
- "message": "You have not selected any collections."
+ "message": "לא בחרת אוספים כלשהם."
},
"updateName": {
- "message": "Update name"
+ "message": "עדכן שם"
},
"updatedOrganizationName": {
- "message": "Updated organization name"
+ "message": "שם ארגון מעודכן"
},
"providerPlan": {
- "message": "Managed Service Provider"
+ "message": "ספק שירות מנוהל"
},
"managedServiceProvider": {
- "message": "Managed service provider"
+ "message": "ספק שירות מנוהל"
},
"multiOrganizationEnterprise": {
- "message": "Multi-organization enterprise"
+ "message": "ארגון רב-ארגוני"
},
"orgSeats": {
- "message": "Organization Seats"
+ "message": "מקומות ארגון"
},
"providerDiscount": {
- "message": "$AMOUNT$% Discount",
+ "message": "$AMOUNT$% הנחה",
"placeholders": {
"amount": {
"content": "$1",
@@ -9570,28 +9538,28 @@
}
},
"lowKDFIterationsBanner": {
- "message": "Low KDF iterations. Increase your iterations to improve the security of your account."
+ "message": "חזרות KDF נמוכות. הגדל את מספר החזרות שלך כדי לשפר את האבטחה של חשבונך."
},
"changeKDFSettings": {
- "message": "Change KDF settings"
+ "message": "שנה הגדרות KDF"
},
"secureYourInfrastructure": {
- "message": "Secure your infrastructure"
+ "message": "אבטח את התשתית שלך"
},
"protectYourFamilyOrBusiness": {
- "message": "Protect your family or business"
+ "message": "הגן על המשפחה או העסק שלך"
},
"upgradeOrganizationCloseSecurityGaps": {
- "message": "Close security gaps with monitoring reports"
+ "message": "צמצם פערי אבטחה עם דוחות ניטור"
},
"upgradeOrganizationCloseSecurityGapsDesc": {
- "message": "Stay ahead of security vulnerabilities by upgrading to a paid plan for enhanced monitoring."
+ "message": "היה תמיד לפני פרצות אבטחה על ידי שדרוג לתוכנית בתשלום עבור ניטור משופר."
},
"approveAllRequests": {
- "message": "Approve all requests"
+ "message": "אשר את כל הבקשות"
},
"allLoginRequestsApproved": {
- "message": "All login requests approved"
+ "message": "כל בקשות הכניסה אושרו"
},
"payPal": {
"message": "PayPal"
@@ -9600,92 +9568,92 @@
"message": "Bitcoin"
},
"updatedTaxInformation": {
- "message": "Updated tax information"
+ "message": "פרטי מס מעודכנים"
},
"billingInvalidTaxIdError": {
- "message": "Invalid tax ID, if you believe this is an error please contact support."
+ "message": "מזהה מס לא תקין, אם אתה מאמין שזאת שגיאה, אנא פנה לתמיכה."
},
"billingTaxIdTypeInferenceError": {
- "message": "We were unable to validate your tax ID, if you believe this is an error please contact support."
+ "message": "לא הצלחנו לאמת את מזהה המס שלך, אם אתה מאמין שזאת שגיאה, אנא פנה לתמיכה."
},
"billingPreviewInvalidTaxIdError": {
- "message": "Invalid tax ID, if you believe this is an error please contact support."
+ "message": "מזהה מס לא תקין, אם אתה מאמין שזאת שגיאה, אנא פנה לתמיכה."
},
"billingPreviewInvoiceError": {
- "message": "An error occurred while previewing the invoice. Please try again later."
+ "message": "אירעה שגיאה בזמן הצגת התצוגה המקדימה של החשבונית. נא לנסות שוב מאוחר יותר."
},
"unverified": {
- "message": "Unverified"
+ "message": "לא מאומת"
},
"verified": {
- "message": "Verified"
+ "message": "מאומת"
},
"viewSecret": {
- "message": "View secret"
+ "message": "הצג סוד"
},
"noClients": {
- "message": "There are no clients to list"
+ "message": "אין לקוחות להצגה ברשימה"
},
"providerBillingEmailHint": {
- "message": "This email address will receive all invoices pertaining to this provider",
+ "message": "כתובת דוא\"ל זו תקבל את כל החשבוניות הנוגעות לספק זה",
"description": "A hint that shows up on the Provider setup page to inform the admin the billing email will receive the provider's invoices."
},
"upgradeOrganizationEnterprise": {
- "message": "Identify security risks by auditing member access"
+ "message": "זהה סיכוני אבטחה על ידי ביקורת של גישת חברים"
},
"onlyAvailableForEnterpriseOrganization": {
- "message": "Quickly view member access across the organization by upgrading to an Enterprise plan."
+ "message": "הצג במהירות גישת חברים ברחבי הארגון על ידי שדרוג לתוכנית ארגונית."
},
"date": {
- "message": "Date"
+ "message": "תאריך"
},
"exportClientReport": {
- "message": "Export client report"
+ "message": "ייצא דוח לקוח"
},
"memberAccessReport": {
- "message": "Member access"
+ "message": "גישת חברים"
},
"memberAccessReportDesc": {
- "message": "Ensure members have access to the right credentials and their accounts are secure. Use this report to obtain a CSV of member access and account configurations."
+ "message": "וודא שלחברים יש גישה לתעודות הנכונות ושהחשבונות שלהם מאובטחים. השתמש בדוח הזה כדי לקבל CSV של גישת חברים ותצורות חשבון."
},
"memberAccessReportPageDesc": {
- "message": "Audit organization member access across groups, collections, and collection items. The CSV export provides a detailed breakdown per member, including information on collection permissions and account configurations."
+ "message": "ערוך ביקורת של גישת חברי ארגון ברחבי קבוצות, אוספים, ופריטי אוספים. ייצוא ה־CSV מספק פירוט מפורט לכל חבר, כולל מידע על הרשאות של אוספים ותצורות חשבון."
},
"memberAccessReportNoCollection": {
- "message": "(No Collection)"
+ "message": "(אין אוסף)"
},
"memberAccessReportNoCollectionPermission": {
- "message": "(No Collection Permission)"
+ "message": "(אין הרשאת אוסף)"
},
"memberAccessReportNoGroup": {
- "message": "(No Group)"
+ "message": "(אין קבוצה)"
},
"memberAccessReportTwoFactorEnabledTrue": {
- "message": "On"
+ "message": "מופעל"
},
"memberAccessReportTwoFactorEnabledFalse": {
- "message": "Off"
+ "message": "כבוי"
},
"memberAccessReportAuthenticationEnabledTrue": {
- "message": "On"
+ "message": "מופעל"
},
"memberAccessReportAuthenticationEnabledFalse": {
- "message": "Off"
+ "message": "כבוי"
},
"higherKDFIterations": {
- "message": "Higher KDF iterations can help protect your master password from being brute forced by an attacker."
+ "message": "ערכי חזרות KDF גבוהים יותר יכולים לעזור להגן על הסיסמה הראשית מפני תקיפה כוחנית על ידי תוקף."
},
"incrementsOf100,000": {
- "message": "increments of 100,000"
+ "message": "במרווחים של 100,000"
},
"smallIncrements": {
- "message": "small increments"
+ "message": "במרווחים קטנים"
},
"kdfIterationRecommends": {
- "message": "We recommend 600,000 or more"
+ "message": "אנו ממליצים על 600,000 או יותר"
},
"kdfToHighWarningIncreaseInIncrements": {
- "message": "For older devices, setting your KDF too high may lead to performance issues. Increase the value in $VALUE$ and test your devices.",
+ "message": "עבור מכשירים ישנים יותר, הגדרת ה־KDF שלך לערך גבוה מדי עשויה להוביל לבעיות ביצועים. הגדל את הערך ב־$VALUE$ ובדוק את המכשירים שלך.",
"placeholders": {
"value": {
"content": "$1",
@@ -9694,31 +9662,31 @@
}
},
"providerReinstate": {
- "message": " Contact Customer Support to reinstate your subscription."
+ "message": " צור קשר עם שירות הלקוחות כדי להחזיר את המנוי שלך."
},
"secretPeopleDescription": {
- "message": "Grant groups or people access to this secret. Permissions set for people will override permissions set by groups."
+ "message": "הענק לקבוצות או אנשים גישה לסוד הזה. הרשאות שהוגדרו עבור אנשים ידרסו הרשאות שהוגדרו על ידי קבוצות."
},
"secretPeopleEmptyMessage": {
- "message": "Add people or groups to share access to this secret"
+ "message": "הוסף אנשים או קבוצות כדי לשתף גישה לסוד הזה"
},
"secretMachineAccountsDescription": {
- "message": "Grant machine accounts access to this secret."
+ "message": "הענק לחשבונות מכונה גישה לסוד הזה."
},
"secretMachineAccountsEmptyMessage": {
- "message": "Add machine accounts to grant access to this secret"
+ "message": "הוסף חשבונות מכונה כדי להעניק גישה לסוד הזה"
},
"smAccessRemovalWarningSecretTitle": {
- "message": "Remove access to this secret"
+ "message": "הסר גישה לסוד הזה"
},
"smAccessRemovalSecretMessage": {
- "message": "This action will remove your access to this secret."
+ "message": "פעולה זו תסיר את הגישה שלך לסוד הזה."
},
"invoice": {
- "message": "Invoice"
+ "message": "חשבונית"
},
"unassignedSeatsAvailable": {
- "message": "You have $SEATS$ unassigned seats available.",
+ "message": "יש לך $SEATS$ מקומות לא מוקצים זמינים.",
"placeholders": {
"seats": {
"content": "$1",
@@ -9728,61 +9696,61 @@
"description": "A message showing how many unassigned seats are available for a provider."
},
"contactYourProviderForAdditionalSeats": {
- "message": "Contact your provider admin to purchase additional seats."
+ "message": "צור קשר עם מנהל הספק שלך כדי לרכוש מקומות נוספים."
},
"open": {
- "message": "Open",
+ "message": "פתח",
"description": "The status of an invoice."
},
"uncollectible": {
- "message": "Uncollectible",
+ "message": "אינה ניתנת לאיסוף",
"description": "The status of an invoice."
},
"clientDetails": {
- "message": "Client details"
+ "message": "פרטי לקוח"
},
"downloadCSV": {
- "message": "Download CSV"
+ "message": "הורד CSV"
},
"monthlySubscriptionUserSeatsMessage": {
- "message": "Adjustments to your subscription will result in prorated charges to your billing totals on your next billing period. "
+ "message": "התאמות למנוי שלך יגרמו לשינויים יחסיים לסך כל החיובים שלך במחזור החיוב הבא שלך. "
},
"annualSubscriptionUserSeatsMessage": {
- "message": "Adjustments to your subscription will result in prorated charges on a monthly billing cycle. "
+ "message": "התאמות למנוי שלך יגרמו לשינויים יחסיים במחזור חיוב חודשי. "
},
"billingHistoryDescription": {
- "message": "Download a CSV to obtain client details for each billing date. Prorated charges are not included in the CSV and may vary from the linked invoice. For the most accurate billing details, refer to your monthly invoices.",
+ "message": "הורד CSV כדי לקבל פרטי לקוח עבור כל תאריך חיוב. חיובים יחסיים אינם כלולים ב־CSV ועשויים להיות שונים מהחשבונית המקושרת. עבור פרטי החיוב המדויקים ביותר, עיין בחשבוניות החודשיות שלך.",
"description": "A paragraph on the Billing History page of the Provider Portal letting users know they can download a CSV report for their invoices that does not include prorations."
},
"noInvoicesToList": {
- "message": "There are no invoices to list",
+ "message": "אין חשבוניות להצגה ברשימה",
"description": "A paragraph on the Billing History page of the Provider Portal letting users know they can download a CSV report for their invoices that does not include prorations."
},
"providerClientVaultPrivacyNotification": {
- "message": "Notice: Later this month, client vault privacy will be improved and provider members will no longer have direct access to client vault items. For questions,",
+ "message": "הודעה: מאוחר יותר החודש, הפרטיות של כספת לקוח תשופר וחברי ספק לא יוכלו לגשת ישירות לפריטי כספת לקוח. לשאלות,",
"description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'Notice: Later this month, client vault privacy will be improved and provider members will no longer have direct access to client vault items. For questions, please contact Bitwarden support'."
},
"contactBitwardenSupport": {
- "message": "contact Bitwarden support.",
+ "message": "פנה לתמיכת Bitwarden.",
"description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'Notice: Later this month, client vault privacy will be improved and provider members will no longer have direct access to client vault items. For questions, please contact Bitwarden support'. 'Bitwarden' should not be translated"
},
"sponsored": {
- "message": "Sponsored"
+ "message": "ממומן"
},
"licenseAndBillingManagementDesc": {
- "message": "After making updates in the Bitwarden cloud server, upload your license file to apply the most recent changes."
+ "message": "לאחר ביצוע עדכונים בשרת הענן של Bitwarden, העלה את קובץ הרישיון שלך כדי להחיל את השינויים הכי אחרונים."
},
"addToFolder": {
- "message": "Add to folder"
+ "message": "הוסף לתיקייה"
},
"selectFolder": {
- "message": "Select folder"
+ "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",
@@ -9791,7 +9759,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",
@@ -9800,7 +9768,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",
@@ -9813,85 +9781,85 @@
}
},
"data": {
- "message": "Data"
+ "message": "נתונים"
},
"purchasedSeatsRemoved": {
- "message": "purchased seats removed"
+ "message": "מקומות שנרכשו הוסרו"
},
"environmentVariables": {
- "message": "Environment variables"
+ "message": "משתני סביבה"
},
"organizationId": {
- "message": "Organization ID"
+ "message": "מזהה ארגון"
},
"projectIds": {
- "message": "Project IDs"
+ "message": "מזהי פרויקט"
},
"projectId": {
- "message": "Project ID"
+ "message": "מזהה פרויקט"
},
"projectsAccessedByMachineAccount": {
- "message": "The following projects can be accessed by this machine account."
+ "message": "ניתן לגשת לפרויקטים הבאים על ידי חשבון מכונה זה."
},
"config": {
- "message": "Config"
+ "message": "תצורה"
},
"learnMoreAboutEmergencyAccess": {
- "message": "Learn more about emergency access"
+ "message": "למד עוד על גישת חירום"
},
"learnMoreAboutMatchDetection": {
- "message": "Learn more about match detection"
+ "message": "למד עוד על זיהוי התאמה"
},
"learnMoreAboutMasterPasswordReprompt": {
- "message": "Learn more about master password re-prompt"
+ "message": "למד עוד על בקשה חוזרת של סיסמה ראשית"
},
"learnMoreAboutSearchingYourVault": {
- "message": "Learn more about searching your vault"
+ "message": "למד עוד על חיפוש הכספת שלך"
},
"learnMoreAboutYourAccountFingerprintPhrase": {
- "message": "Learn about your account fingerprint phrase"
+ "message": "למד עוד על ביטוי טביעת האצבע של החשבון שלך"
},
"impactOfRotatingYourEncryptionKey": {
- "message": "Impact of rotating your encryption key"
+ "message": "השפעת סיבוב מפתח ההצפנה שלך"
},
"learnMoreAboutEncryptionAlgorithms": {
- "message": "Learn more about encryption algorithms"
+ "message": "למד עוד על אלגוריתמי הצפנה"
},
"learnMoreAboutKDFIterations": {
- "message": "Learn more about KDF iterations"
+ "message": "למד עוד על חזרות KDF"
},
"learnMoreAboutLocalization": {
- "message": "Learn more about localization"
+ "message": "למד עוד על לוקליזציה"
},
"learnMoreAboutWebsiteIcons": {
- "message": "Learn more about using website icons"
+ "message": "למד עוד על שימוש בסמלי אתרים"
},
"learnMoreAboutUserAccess": {
- "message": "Learn more about user access"
+ "message": "למד עוד על גישת משתמש"
},
"learnMoreAboutMemberRoles": {
- "message": "Learn more about member roles and permissions"
+ "message": "למד עוד על תפקידים והרשאות של חברים"
},
"whatIsACvvNumber": {
- "message": "What is a CVV number?"
+ "message": "מה זה מספר CVV?"
},
"learnMoreAboutApi": {
- "message": "Learn more about Bitwarden's API"
+ "message": "למד עוד על ה־API של Bitwarden"
},
"fileSend": {
- "message": "File Send"
+ "message": "סֵנְד של קובץ"
},
"fileSends": {
- "message": "File Sends"
+ "message": "סֵנְדים של קובץ"
},
"textSend": {
- "message": "Text Send"
+ "message": "סֵנְד של טקסט"
},
"textSends": {
- "message": "Text Sends"
+ "message": "סֵנְדים של טקסט"
},
"includesXMembers": {
- "message": "for $COUNT$ member",
+ "message": "עבור $COUNT$ חברים",
"placeholders": {
"count": {
"content": "$1",
@@ -9909,10 +9877,10 @@
}
},
"optionalOnPremHosting": {
- "message": "Optional on-premises hosting"
+ "message": "אירוח מקומי אופציונלי"
},
"upgradeFreeOrganization": {
- "message": "Upgrade your $NAME$ organization ",
+ "message": "שדרג את ארגון ה$NAME$ שלך ",
"placeholders": {
"name": {
"content": "$1",
@@ -9921,10 +9889,10 @@
}
},
"includeSsoAuthenticationMessage": {
- "message": "SSO Authentication"
+ "message": "אימות SSO"
},
"familiesPlanInvLimitReachedManageBilling": {
- "message": "Families organizations may have up to $SEATCOUNT$ members. Upgrade to a paid plan to invite more members.",
+ "message": "ארגוני משפחות יכולים לכלול עד $SEATCOUNT$ חברים. שדרג לתוכנית בתשלום כדי להזמין עוד חברים.",
"placeholders": {
"seatcount": {
"content": "$1",
@@ -9933,7 +9901,7 @@
}
},
"familiesPlanInvLimitReachedNoManageBilling": {
- "message": "Families organizations may have up to $SEATCOUNT$ members. Contact your organization owner to upgrade.",
+ "message": "ארגוני משפחות יכולים לכלול עד $SEATCOUNT$ חברים. פנה לבעלי הארגון שלך כדי לשדרג.",
"placeholders": {
"seatcount": {
"content": "$1",
@@ -9942,10 +9910,10 @@
}
},
"upgradePlans": {
- "message": "Upgrade your plan to invite members and experience powerful security features."
+ "message": "שדרג את התוכנית שלך כדי להזמין חברים ולחוות תכונות אבטחה עוצמתיות."
},
"upgradeDiscount": {
- "message": "Save $AMOUNT$%",
+ "message": "חסוך $AMOUNT$%",
"placeholders": {
"amount": {
"content": "$1",
@@ -9954,146 +9922,146 @@
}
},
"enterprisePlanUpgradeMessage": {
- "message": "Advanced capabilities for larger organizations"
+ "message": "יכולות מתקדמות עבור ארגונים גדולים יותר"
},
"teamsPlanUpgradeMessage": {
- "message": "Resilient protection for growing teams"
+ "message": "הגנה עמידה עבור צוותים בצמיחה"
},
"teamsInviteMessage": {
- "message": "Invite unlimited members"
+ "message": "הזמן מספר בלתי מוגבל של חברים"
},
"accessToCreateGroups": {
- "message": "Access to create groups"
+ "message": "גישה ליצור קבוצות"
},
"syncGroupsAndUsersFromDirectory": {
- "message": "Sync groups and users from a directory"
+ "message": "סנכרן קבוצות ומשתמשים מתוך ספריה"
},
"familyPlanUpgradeMessage": {
- "message": "Secure your family logins"
+ "message": "אבטח את הכניסות של המשפחה שלך"
},
"accessToPremiumFeatures": {
- "message": "Access to Premium features"
+ "message": "גישה לתכונות פרימיום"
},
"additionalStorageGbMessage": {
- "message": "GB additional storage"
+ "message": "GB אחסון נוסף"
},
"sshKeyAlgorithm": {
- "message": "Key algorithm"
+ "message": "אלגוריתם מפתח"
},
"sshPrivateKey": {
- "message": "Private key"
+ "message": "מפתח פרטי"
},
"sshPublicKey": {
- "message": "Public key"
+ "message": "מפתח ציבורי"
},
"sshFingerprint": {
- "message": "Fingerprint"
+ "message": "טביעת אצבע"
},
"sshKeyFingerprint": {
- "message": "Fingerprint"
+ "message": "טביעת אצבע"
},
"sshKeyPrivateKey": {
- "message": "Private key"
+ "message": "מפתח פרטי"
},
"sshKeyPublicKey": {
- "message": "Public key"
+ "message": "מפתח ציבורי"
},
"sshKeyAlgorithmED25519": {
"message": "ED25519"
},
"sshKeyAlgorithmRSA2048": {
- "message": "RSA 2048-Bit"
+ "message": "RSA 2048 סיביות"
},
"sshKeyAlgorithmRSA3072": {
- "message": "RSA 3072-Bit"
+ "message": "RSA 3072 סיביות"
},
"sshKeyAlgorithmRSA4096": {
- "message": "RSA 4096-Bit"
+ "message": "RSA 4096 סיביות"
},
"premiumAccounts": {
- "message": "6 premium accounts"
+ "message": "6 חשבונות פרימיום"
},
"unlimitedSharing": {
- "message": "Unlimited sharing"
+ "message": "שיתוף ללא הגבלה"
},
"unlimitedCollections": {
- "message": "Unlimited collections"
+ "message": "אוספים ללא הגבלה"
},
"secureDataSharing": {
- "message": "Secure data sharing"
+ "message": "שיתוף נתונים מאובטח"
},
"eventLogMonitoring": {
- "message": "Event log monitoring"
+ "message": "ניטור יומן אירועים"
},
"directoryIntegration": {
- "message": "Directory integration"
+ "message": "שילוב ספריה"
},
"passwordLessSso": {
- "message": "Passwordless SSO"
+ "message": "SSO ללא סיסמה"
},
"accountRecovery": {
- "message": "Account recovery"
+ "message": "שחזור חשבון"
},
"customRoles": {
- "message": "Custom roles"
+ "message": "תפקידים מותאמים אישית"
},
"unlimitedSecretsStorage": {
- "message": "Unlimited secrets storage"
+ "message": "אחסון סודות ללא הגבלה"
},
"unlimitedUsers": {
- "message": "Unlimited users"
+ "message": "משתמשים ללא הגבלה"
},
"UpTo50MachineAccounts": {
- "message": "Up to 50 machine accounts"
+ "message": "עד 50 חשבונות מכונה"
},
"UpTo20MachineAccounts": {
- "message": "Up to 20 machine accounts"
+ "message": "עד 20 חשבונות מכונה"
},
"current": {
- "message": "Current"
+ "message": "נוכחי"
},
"secretsManagerSubscriptionInfo": {
- "message": "Your Secrets Manager subscription will upgrade based on the plan selected"
+ "message": "מנוי מנהל הסודות שלך ישודרג בהתאם לתוכנית שנבחרה"
},
"bitwardenPasswordManager": {
- "message": "Bitwarden Password Manager"
+ "message": "מנהל הסיסמאות Bitwarden"
},
"secretsManagerComplimentaryPasswordManager": {
- "message": "Your complimentary one year Password Manager subscription will upgrade to the selected plan. You will not be charged until the complimentary period is over."
+ "message": "מנוי מנהל הסיסמאות חינמי לשנה אחת שלך ישודרג לתוכנית שנבחרה. לא תחויב עד שהתקופה החינמית תסתיים."
},
"fileSavedToDevice": {
- "message": "File saved to device. Manage from your device downloads."
+ "message": "הקובץ נשמר למכשיר. נהל מהורדות המכשיר שלך."
},
"publicApi": {
- "message": "Public API",
+ "message": "API ציבורי",
"description": "The text, 'API', is an acronym and should not be translated."
},
"showCharacterCount": {
- "message": "Show character count"
+ "message": "הצג מונה תווים"
},
"hideCharacterCount": {
- "message": "Hide character count"
+ "message": "הסתר מונה תווים"
},
"editAccess": {
- "message": "Edit access"
+ "message": "ערוך גישה"
},
"textHelpText": {
- "message": "Use text fields for data like security questions"
+ "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, שם, תווית aria או מציין מיקום."
},
"uppercaseDescription": {
- "message": "Include uppercase characters",
+ "message": "כלול תווי אות גדולה",
"description": "Tooltip for the password generator uppercase character checkbox"
},
"uppercaseLabel": {
@@ -10101,7 +10069,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": {
@@ -10109,7 +10077,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": {
@@ -10117,27 +10085,27 @@
"description": "Label for the password generator numbers checkbox"
},
"specialCharactersDescription": {
- "message": "Include special characters",
+ "message": "כלול תווים מיוחדים",
"description": "Full description for the password generator special characters checkbox"
},
"addAttachment": {
"message": "הוסף צרופה"
},
"maxFileSizeSansPunctuation": {
- "message": "Maximum file size is 500 MB"
+ "message": "גודל הקובץ המרבי הוא 500MB"
},
"permanentlyDeleteAttachmentConfirmation": {
"message": "האם אתה בטוח שברצונך למחוק לצמיתות צרופה זו?"
},
"manageSubscriptionFromThe": {
- "message": "Manage subscription from the",
+ "message": "נהל מנוי מתוך",
"description": "This represents the beginning of a sentence. The full sentence will be 'Manage subscription from the Provider Portal', but 'Provider Portal' will be a link and thus cannot be included in the translation file."
},
"toHostBitwardenOnYourOwnServer": {
- "message": "To host Bitwarden on your own server, you will need to upload your license file. To support Free Families plans and advanced billing capabilities for your self-hosted organization, you will need to set up automatic sync in your self-hosted organization."
+ "message": "כדי לארח את Bitwarden בשרת שלך, תצטרך להעלות את קובץ הרישיון שלך. כדי לתמוך בתוכניות למשפחות בחינם וביכולות חיוב מתקדמות עבור הארגון באירוח עצמי שלך, תצטרך להגדיר סנכרון אוטומטי בארגון האירוח העצמי שלך."
},
"selfHostingTitleProper": {
- "message": "Self-Hosting"
+ "message": "אירוח עצמי"
},
"claim-domain-single-org-warning": {
"message": "דרישת דומיין תפעיל את מדיניות הארגון היחיד."
@@ -10146,7 +10114,7 @@
"message": "חברים שאינם עומדים בדרישות יבוטלו. מנהלים יכולים לשחזר חברים ברגע שהם עזבו את כל הארגונים האחרים."
},
"deleteOrganizationUser": {
- "message": "Delete $NAME$",
+ "message": "מחק $NAME$",
"placeholders": {
"name": {
"content": "$1",
@@ -10156,7 +10124,7 @@
}
},
"deleteOrganizationUserWarningDesc": {
- "message": "This will permanently delete all items owned by $NAME$. Collection items are not impacted.",
+ "message": "זה ימחק לצמיתות את כל הפריטים המנוהלים על ידי $NAME$. פריטי אוסף אינם מושפעים.",
"description": "Warning description for the delete organization user dialog",
"placeholders": {
"name": {
@@ -10166,11 +10134,11 @@
}
},
"deleteManyOrganizationUsersWarningDesc": {
- "message": "This will permanently delete all items owned by the following members. Collection items are not impacted.",
+ "message": "זה ימחק לצמיתות את כל הפריטים המנוהלים על ידי החברים הבאים. פריטי אוסף אינם מושפעים.",
"description": "Warning description for the bulk delete organization users dialog"
},
"organizationUserDeleted": {
- "message": "Deleted $NAME$",
+ "message": "$NAME$ נמחק",
"placeholders": {
"name": {
"content": "$1",
@@ -10179,10 +10147,10 @@
}
},
"organizationUserDeletedDesc": {
- "message": "The user was removed from the organization and all associated user data has been deleted."
+ "message": "המשתמש הוסר מהארגון וכל נתוני המשתמש המשויכים נמחקו."
},
"deletedUserId": {
- "message": "Deleted user $ID$ - an owner / admin deleted the user account",
+ "message": "משתמש $ID$ נמחק - בעלים / מנהל מחק את חשבון המשתמש",
"placeholders": {
"id": {
"content": "$1",
@@ -10191,7 +10159,7 @@
}
},
"userLeftOrganization": {
- "message": "User $ID$ left organization",
+ "message": "משתמש $ID$ עזב את הארגון",
"placeholders": {
"id": {
"content": "$1",
@@ -10200,7 +10168,7 @@
}
},
"suspendedOrganizationTitle": {
- "message": "The $ORGANIZATION$ is suspended",
+ "message": "ה־$ORGANIZATION$ מושעה",
"placeholders": {
"organization": {
"content": "$1",
@@ -10209,37 +10177,37 @@
}
},
"suspendedUserOrgMessage": {
- "message": "Contact your organization owner for assistance."
+ "message": "צור קשר עם בעלי הארגון שלך עבור סיוע."
},
"suspendedOwnerOrgMessage": {
- "message": "To regain access to your organization, add a payment method."
+ "message": "כדי לקבל גישה מחדש לארגון שלך, הוסף אמצעי תשלום."
},
"deleteMembers": {
- "message": "Delete members"
+ "message": "מחק חברים"
},
"noSelectedMembersApplicable": {
- "message": "This action is not applicable to any of the selected members."
+ "message": "פעולה זו אינה ישימה לאף אחד מהחברים שנבחרו."
},
"deletedSuccessfully": {
- "message": "Deleted successfully"
+ "message": "נמחק בהצלחה"
},
"freeFamiliesSponsorship": {
- "message": "Remove Free Bitwarden Families sponsorship"
+ "message": "הסר חסות Bitwarden למשפחות בחינם"
},
"freeFamiliesSponsorshipPolicyDesc": {
- "message": "Do not allow members to redeem a Families plan through this organization."
+ "message": "אל תאפשר לחברים לממש תוכנית משפחות דרך ארגון זה."
},
"verifyBankAccountWithStatementDescriptorWarning": {
- "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the organization's billing page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended."
+ "message": "תשלום באמצעות חשבון בנק זמינה רק ללקוחות בארצות הברית. אתה תידרש לאמת את חשבון הבנק שלך. אנחנו נבצע מיקרו־הפקדה בתוך 1-2 ימי עסקים. הזן את קוד תיאור ההצהרה מהפקדה זו בדף החיוב של הארגון כדי לאמת את חשבון הבנק. כשל באימות חשבון הבנק יגרום לפספוס תשלום ולהשעיית המנוי שלך."
},
"verifyBankAccountWithStatementDescriptorInstructions": {
- "message": "We have made a micro-deposit to your bank account (this may take 1-2 business days). Enter the six-digit code starting with 'SM' found on the deposit description. Failure to verify the bank account will result in a missed payment and your subscription being suspended."
+ "message": "ביצענו מיקרו־הפקדה לחשבון הבנק שלך (זה עשוי לקחת 1-2 ימי עסקים). הזן את הקוד בן שש הספרות המתחיל ב־'SM' הנמצא בתיאור ההפקדה. כשל באימות חשבון הבנק יגרום לפספוס תשלום ולהשעיית המנוי שלך."
},
"descriptorCode": {
- "message": "Descriptor code"
+ "message": "קוד מתאר"
},
"cannotRemoveViewOnlyCollections": {
- "message": "You cannot remove collections with View only permissions: $COLLECTIONS$",
+ "message": "אינך יכול להסיר אוספים עם הרשאות הצגה בלבד: $COLLECTIONS$",
"placeholders": {
"collections": {
"content": "$1",
@@ -10248,22 +10216,22 @@
}
},
"importantNotice": {
- "message": "Important notice"
+ "message": "הודעה חשובה"
},
"setupTwoStepLogin": {
- "message": "Set up two-step login"
+ "message": "הגדר כניסה דו־שלבית"
},
"newDeviceVerificationNoticeContentPage1": {
- "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025."
+ "message": "Bitwarden ישלח קוד לדוא\"ל החשבון שלך כדי לאמת כניסות ממכשירים חדשים החל מפברואר 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."
+ "message": "אתה יכול להגדיר כניסה דו־שלבית כדרך חלופית להגן על החשבון שלך או לשנות את הדוא\"ל שלך לאחד שאתה יכול לגשת אליו."
},
"remindMeLater": {
- "message": "Remind me later"
+ "message": "הזכר לי מאוחר יותר"
},
"newDeviceVerificationNoticePageOneFormContent": {
- "message": "Do you have reliable access to your email, $EMAIL$?",
+ "message": "יש לך גישה מהימנה לדוא\"ל שלך, $EMAIL$?",
"placeholders": {
"email": {
"content": "$1",
@@ -10272,49 +10240,49 @@
}
},
"newDeviceVerificationNoticePageOneEmailAccessNo": {
- "message": "No, I do not"
+ "message": "לא, אין לי"
},
"newDeviceVerificationNoticePageOneEmailAccessYes": {
- "message": "Yes, I can reliably access my email"
+ "message": "כן, אני יכול לגשת לדוא\"ל שלי באופן מהימן"
},
"turnOnTwoStepLogin": {
- "message": "Turn on two-step login"
+ "message": "הפעל כניסה דו־שלבית"
},
"changeAcctEmail": {
- "message": "Change account email"
+ "message": "שנה דוא\"ל חשבון"
},
"removeMembers": {
- "message": "Remove members"
+ "message": "הסר חברים"
},
"devices": {
- "message": "Devices"
+ "message": "מכשירים"
},
"deviceListDescription": {
- "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now."
+ "message": "החשבון שלך היה מחובר לכל אחד מהמכשירים למטה. אם אתה לא מזהה מכשיר, הסר אותו עכשיו."
},
"deviceListDescriptionTemp": {
- "message": "Your account was logged in to each of the devices below."
+ "message": "החשבון שלך היה מחובר לכל אחד מהמכשירים למטה."
},
"claimedDomains": {
- "message": "Claimed domains"
+ "message": "דומיינים שנדרשו"
},
"claimDomain": {
- "message": "Claim domain"
+ "message": "דרוש דומיין"
},
"reclaimDomain": {
- "message": "Reclaim domain"
+ "message": "דרוש דומיין מחדש"
},
"claimDomainNameInputHint": {
- "message": "Example: mydomain.com. Subdomains require separate entries to be claimed."
+ "message": "דוגמה: mydomain.com. תת-דומיינים דורשים שרשומות נפרדות ידרשו."
},
"automaticClaimedDomains": {
- "message": "Automatic Claimed Domains"
+ "message": "דומיינים שנדרשו אוטומטית"
},
"automaticDomainClaimProcess": {
- "message": "Bitwarden will attempt to claim the domain 3 times during the first 72 hours. If the domain can’t be claimed, check the DNS record in your host and manually claim. The domain will be removed from your organization in 7 days if it is not claimed."
+ "message": "Bitwarden ינסה לדרוש את הדומיין 3 פעמים במהלך 72 השעות הראשונות. אם לא ניתן לדרוש את הדומיין, בדוק את רשומת ה־DNS במארח שלך ודרוש באופן ידני. הדומיין יוסר מהארגון שלך תוך 7 ימים אם הוא לא נדרש."
},
"domainNotClaimed": {
- "message": "$DOMAIN$ not claimed. Check your DNS records.",
+ "message": "$DOMAIN$ אינו נדרש. בדוק את רשומות ה־DNS שלך.",
"placeholders": {
"DOMAIN": {
"content": "$1",
@@ -10323,19 +10291,19 @@
}
},
"domainStatusClaimed": {
- "message": "Claimed"
+ "message": "נדרש"
},
"domainStatusUnderVerification": {
- "message": "Under verification"
+ "message": "תחת אימות"
},
"claimedDomainsDesc": {
- "message": "Claim a domain to own all member accounts whose email address matches the domain. Members will be able to skip the SSO identifier when logging in. Administrators will also be able to delete member accounts."
+ "message": "דרוש דומיין כדי להחזיק בכל חשבונות החברים אשר כתובת הדוא\"ל שלהם תואמת את הדומיין. חברים יוכלו לדלג על מזהה ה־SSO בעת כניסה. מנהלים גם יוכלו למחוק חשבונות חברים."
},
"invalidDomainNameClaimMessage": {
- "message": "Input is not a valid format. Format: mydomain.com. Subdomains require separate entries to be claimed."
+ "message": "הקלט אינו בפורמט תקין. פורמט: mydomain.com. תת-דומיינים דורשים שרשומות נפרדות ידרשו."
},
"domainClaimedEvent": {
- "message": "$DOMAIN$ claimed",
+ "message": "$DOMAIN$ נדרש",
"placeholders": {
"DOMAIN": {
"content": "$1",
@@ -10344,7 +10312,7 @@
}
},
"domainNotClaimedEvent": {
- "message": "$DOMAIN$ not claimed",
+ "message": "$DOMAIN$ לא נדרש",
"placeholders": {
"DOMAIN": {
"content": "$1",
@@ -10353,7 +10321,7 @@
}
},
"updatedRevokeSponsorshipConfirmationForSentSponsorship": {
- "message": "If you remove $EMAIL$, the sponsorship for this Family plan cannot be redeemed. Are you sure you want to continue?",
+ "message": "אם תסיר את $EMAIL$, החסות עבור תוכנית משפחות זו לא תהיה ניתנת למימוש. האם אתה בטוח שברצונך להמשיך?",
"placeholders": {
"email": {
"content": "$1",
@@ -10362,7 +10330,7 @@
}
},
"updatedRevokeSponsorshipConfirmationForAcceptedSponsorship": {
- "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end and the saved payment method will be charged $40 + applicable tax on $DATE$. You will not be able to redeem a new sponsorship until $DATE$. Are you sure you want to continue?",
+ "message": "אם תסיר את $EMAIL$, החסות עבור תוכנית משפחות זו תסתיים ואמצעי התשלום השמור יחויב $40 + מס רלוונטי בתאריך $DATE$. לא תוכל לממש חסות חדשה עד $DATE$. האם אתה בטוח שברצונך להמשיך?",
"placeholders": {
"email": {
"content": "$1",
@@ -10375,40 +10343,40 @@
}
},
"domainClaimed": {
- "message": "Domain claimed"
+ "message": "דומיין נדרש"
},
"organizationNameMaxLength": {
"message": "שם ארגון לא יכול לחרוג מ־50 תווים."
},
"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 יובא בהצלחה"
},
"copySSHPrivateKey": {
- "message": "Copy private key"
+ "message": "העתק מפתח פרטי"
},
"openingExtension": {
"message": "פותח את הרחבת היישום של Bitwarden"
@@ -10417,7 +10385,7 @@
"message": "משהו השתבש..."
},
"openingExtensionError": {
- "message": "We had trouble opening the Bitwarden browser extension. Click the button to open it now."
+ "message": "התקשינו לפתוח את הרחבת הדפדפן של Bitwarden. לחץ על הלחצן כדי לפתוח אותה עכשיו."
},
"openExtension": {
"message": "פתח הרחבה"
@@ -10435,7 +10403,7 @@
"message": "פתח בהצלחה את הרחבת הדפדפן של Bitwarden. אתה יכול לסקור עכשיו את הסיסמאות בסיכון שלך."
},
"openExtensionManuallyPart1": {
- "message": "We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon",
+ "message": "התקשינו לפתוח את הרחבת הדפדפן של Bitwarden. פתח את הסמל של Bitwarden",
"description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'"
},
"openExtensionManuallyPart2": {
@@ -10443,7 +10411,7 @@
"description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'"
},
"resellerRenewalWarningMsg": {
- "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.",
+ "message": "המנוי שלך יתחדש בקרוב. כדי להבטיח שירות רציף, צור קשר עם $RESELLER$ כדי לאשר את החידוש שלך לפני $RENEWAL_DATE$.",
"placeholders": {
"reseller": {
"content": "$1",
@@ -10456,7 +10424,7 @@
}
},
"resellerOpenInvoiceWarningMgs": {
- "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.",
+ "message": "חשבונית עבור המנוי שלך הונפקה בתאריך $ISSUED_DATE$. כדי להבטיח שירות רציף, צור קשר עם $RESELLER$ כדי לאשר את החידוש שלך לפני $DUE_DATE$.",
"placeholders": {
"reseller": {
"content": "$1",
@@ -10473,7 +10441,7 @@
}
},
"resellerPastDueWarningMsg": {
- "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.",
+ "message": "החשבונית עבור המנוי שלך לא שולמה. כדי להבטיח שירות רציף, צור קשר עם $RESELLER$ כדי לאשר את החידוש שלך לפני $GRACE_PERIOD_END$.",
"placeholders": {
"reseller": {
"content": "$1",
@@ -10501,16 +10469,16 @@
}
},
"accountDeprovisioningNotification": {
- "message": "Administrators now have the ability to delete member accounts that belong to a claimed domain."
+ "message": "למנהלים עכשיו יש את היכולת למחוק חשבונות של חברים ששייכים לדומיין שנדרש."
},
"deleteManagedUserWarningDesc": {
- "message": "This action will delete the member account including all items in their vault. This replaces the previous Remove action."
+ "message": "פעולה זו תמחק את חשבון החבר כולל כל הפריטים בכספת שלו. זו מחליפה את פעולת ההסרה הקודמת."
},
"deleteManagedUserWarning": {
"message": "מחיקה היא פעולה חדשה!"
},
"seatsRemaining": {
- "message": "You have $REMAINING$ seats remaining out of $TOTAL$ seats assigned to this organization. Contact your provider to manage your subscription.",
+ "message": "יש לך $REMAINING$ מקומות נותרים מתוך $TOTAL$ מקומות שהוקצו לארגון זה. פנה לספק שלך כדי לנהל את המנוי שלך.",
"placeholders": {
"remaining": {
"content": "$1",
@@ -10526,13 +10494,13 @@
"message": "ארגון קיים"
},
"selectOrganizationProviderPortal": {
- "message": "בחר ארגון להוספה אל פורטל הספק שלך."
+ "message": "בחר ארגון להוספה אל פורטל הספקים שלך."
},
"noOrganizations": {
"message": "אין ארגונים להצגה ברשימה"
},
"yourProviderSubscriptionCredit": {
- "message": "Your provider subscription will receive a credit for any remaining time in the organization's subscription."
+ "message": "מנוי הספק שלך יקבל זיכוי עבור כל הזמן שנותר במנוי של הארגון."
},
"doYouWantToAddThisOrg": {
"message": "האם ברצונך להוסיף ארגון זה אל $PROVIDER$?",
@@ -10550,7 +10518,7 @@
"message": "מקומות מוקצים עולים על מקומות פנויים."
},
"changeAtRiskPassword": {
- "message": "Change at-risk password"
+ "message": "שנה סיסמה בסיכון"
},
"removeUnlockWithPinPolicyTitle": {
"message": "הסר ביטול נעילה עם PIN"
@@ -10559,7 +10527,7 @@
"message": "אל תאפשר לחברים לבטל את נעילת החשבון שלהם עם PIN."
},
"limitedEventLogs": {
- "message": "$PRODUCT_TYPE$ plans do not have access to real event logs",
+ "message": "לתוכניות מסוג $PRODUCT_TYPE$ אין גישה ליומני אירועים אמיתיים",
"placeholders": {
"product_type": {
"content": "$1",
@@ -10577,6 +10545,6 @@
"message": "האירועים האלה הם דוגמאות בלבד ולא משקפים אירועים אמיתיים בתוך ארגון ה־Bitwarden שלך."
},
"cannotCreateCollection": {
- "message": "Free organizations may have up to 2 collections. Upgrade to a paid plan to add more collections."
+ "message": "לארגונים חינמיים יכולים להיות עד 2 אוספים. שדרג לתוכנית בתשלום כדי להוסיף עוד אוספים."
}
}
diff --git a/apps/web/src/locales/hi/messages.json b/apps/web/src/locales/hi/messages.json
index 9d1f8d10b3d..1cab0dc843b 100644
--- a/apps/web/src/locales/hi/messages.json
+++ b/apps/web/src/locales/hi/messages.json
@@ -879,9 +879,6 @@
"filter": {
"message": "Filter"
},
- "moveSelectedToOrg": {
- "message": "Move selected to organization"
- },
"deleteSelected": {
"message": "चयनित मिटाएं"
},
@@ -937,15 +934,6 @@
}
}
},
- "movedItemsToOrg": {
- "message": "Selected items moved to $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Items moved to $ORGNAME$",
"placeholders": {
@@ -1571,9 +1559,6 @@
"moveToOrgDesc": {
"message": "Choose an organization that you wish to move this item to. Moving to an organization transfers ownership of the item to that organization. You will no longer be the direct owner of this item once it has been moved."
},
- "moveManyToOrgDesc": {
- "message": "Choose an organization that you wish to move these items to. Moving to an organization transfers ownership of the items to that organization. You will no longer be the direct owner of these items once they have been moved."
- },
"collectionsDesc": {
"message": "Edit the collections that this item is being shared with. Only organization users with access to these collections will be able to see this item."
},
@@ -1607,23 +1592,6 @@
}
}
},
- "moveSelectedItemsCountDesc": {
- "message": "You have selected $COUNT$ item(s). $MOVEABLE_COUNT$ item(s) can be moved to an organization, $NONMOVEABLE_COUNT$ cannot.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "10"
- },
- "moveable_count": {
- "content": "$2",
- "example": "8"
- },
- "nonmoveable_count": {
- "content": "$3",
- "example": "2"
- }
- }
- },
"verificationCodeTotp": {
"message": "Verification code (TOTP)"
},
diff --git a/apps/web/src/locales/hr/messages.json b/apps/web/src/locales/hr/messages.json
index 9b31b496e51..eaecafa5c0d 100644
--- a/apps/web/src/locales/hr/messages.json
+++ b/apps/web/src/locales/hr/messages.json
@@ -879,9 +879,6 @@
"filter": {
"message": "Filtriraj"
},
- "moveSelectedToOrg": {
- "message": "Premjesti odabrano u Organizaciju"
- },
"deleteSelected": {
"message": "Obriši odabrano"
},
@@ -937,15 +934,6 @@
}
}
},
- "movedItemsToOrg": {
- "message": "Odabrane stavke premještene u $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Stavke premještene u $ORGNAME$",
"placeholders": {
@@ -1571,9 +1559,6 @@
"moveToOrgDesc": {
"message": "Odaberi organizaciju u koju želiš premjestiti ovu stavku. Premještanje prenosi vlasništvo stavke na organizaciju. Nakon premještanja više nećeš biti izravni vlasnik ove stavke."
},
- "moveManyToOrgDesc": {
- "message": "Odaberi organizaciju u koju želiš premjestiti ovu stavku. Premještanje prenosi vlasništvo stavke na organizaciju. Nakon premještanja više nećeš biti izravni vlasnik ove stavke."
- },
"collectionsDesc": {
"message": "Uredi zbirke s kojima se ova stavka koristi. Samo korisnici organizacije s pristupom ovim zbirkama će ih moći vidjeti."
},
@@ -1607,23 +1592,6 @@
}
}
},
- "moveSelectedItemsCountDesc": {
- "message": "$MOVEABLE_COUNT$ od $COUNT$ odabranih stavki može biti premješteno u Organiziaciju; $NONMOVEABLE_COUNT$ nije moguće premjestiti.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "10"
- },
- "moveable_count": {
- "content": "$2",
- "example": "8"
- },
- "nonmoveable_count": {
- "content": "$3",
- "example": "2"
- }
- }
- },
"verificationCodeTotp": {
"message": "Kôd za provjeru (TOTP)"
},
diff --git a/apps/web/src/locales/hu/messages.json b/apps/web/src/locales/hu/messages.json
index f72e1e81ce4..3d6a2e258a4 100644
--- a/apps/web/src/locales/hu/messages.json
+++ b/apps/web/src/locales/hu/messages.json
@@ -879,9 +879,6 @@
"filter": {
"message": "Szűrő"
},
- "moveSelectedToOrg": {
- "message": "A kiválasztott áthelyezése szervezetbe"
- },
"deleteSelected": {
"message": "Kiválasztott törlése"
},
@@ -937,15 +934,6 @@
}
}
},
- "movedItemsToOrg": {
- "message": "A kiválasztott elemek átkerültek $ORGNAME$ szervezethez",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Az elemek áthelyezésre kerültek: $ORGNAME$",
"placeholders": {
@@ -1571,9 +1559,6 @@
"moveToOrgDesc": {
"message": "Válasszunk egy szervezetet, ahová áthelyezni szeretnénk ezt az elemet. A szervezetbe áthelyezés átruházza az elem tulajdonjogát az adott szervezetre. Az áthelyezés után többé nem leszünk az elem közvetlen tulajdonosa."
},
- "moveManyToOrgDesc": {
- "message": "Válasszunk egy szervezetet, ahová áthelyezni szeretnénk ezeket az elemeket. A szervezetbe áthelyezés átruházza az elemek tulajdonjogát az adott szervezetre. Az áthelyezés után többé nem leszünk az elemek közvetlen tulajdonosa."
- },
"collectionsDesc": {
"message": "A megosztásra kerülő elem gyűjteményének szerkesztése. Csak az ezeket a gyűjteményeket elérő szervezeti felhasználók látják ezt az elemet."
},
@@ -1607,23 +1592,6 @@
}
}
},
- "moveSelectedItemsCountDesc": {
- "message": "$COUNT$ elem került kiválasztásra. $MOVEABLE_COUNT$ elem áthelyezhető szervezethezi, $NONMOVEABLE_COUNT$ nem.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "10"
- },
- "moveable_count": {
- "content": "$2",
- "example": "8"
- },
- "nonmoveable_count": {
- "content": "$3",
- "example": "2"
- }
- }
- },
"verificationCodeTotp": {
"message": "Verification Code (TOTP)"
},
diff --git a/apps/web/src/locales/id/messages.json b/apps/web/src/locales/id/messages.json
index 5a8546fca6f..22b4e3a6132 100644
--- a/apps/web/src/locales/id/messages.json
+++ b/apps/web/src/locales/id/messages.json
@@ -1,6 +1,6 @@
{
"allApplications": {
- "message": "All applications"
+ "message": "Semua aplikasi"
},
"criticalApplications": {
"message": "Critical applications"
@@ -15,7 +15,7 @@
"message": "Risk Insights"
},
"passwordRisk": {
- "message": "Password Risk"
+ "message": "Petunjuk Sandi"
},
"reviewAtRiskPasswords": {
"message": "Review at-risk passwords (weak, exposed, or reused) across applications. Select your most critical applications to prioritize security actions for your users to address at-risk passwords."
@@ -33,16 +33,16 @@
"message": "Notified members"
},
"revokeMembers": {
- "message": "Revoke members"
+ "message": "Cabut Pengguna"
},
"restoreMembers": {
- "message": "Restore members"
+ "message": "Pulihkan pengguna"
},
"cannotRestoreAccessError": {
"message": "Cannot restore organization access"
},
"allApplicationsWithCount": {
- "message": "All applications ($COUNT$)",
+ "message": "Semua aplikasi ($COUNT$)",
"placeholders": {
"count": {
"content": "$1",
@@ -54,7 +54,7 @@
"message": "Create new login item"
},
"criticalApplicationsWithCount": {
- "message": "Critical applications ($COUNT$)",
+ "message": "Semua aplikasi ($COUNT$)",
"placeholders": {
"count": {
"content": "$1",
@@ -72,7 +72,7 @@
}
},
"noAppsInOrgTitle": {
- "message": "No applications found in $ORG NAME$",
+ "message": "Tidak menemukan aplikasi di $ORG NAME$",
"placeholders": {
"org name": {
"content": "$1",
@@ -99,19 +99,19 @@
"message": "Apps marked as critical"
},
"application": {
- "message": "Application"
+ "message": "Aplikasi"
},
"atRiskPasswords": {
"message": "At-risk passwords"
},
"requestPasswordChange": {
- "message": "Request password change"
+ "message": "Minta petunjuk kata sandi"
},
"totalPasswords": {
- "message": "Total passwords"
+ "message": "Jumlah Kata Sandi"
},
"searchApps": {
- "message": "Search applications"
+ "message": "Cari aplikasi"
},
"atRiskMembers": {
"message": "At-risk members"
@@ -126,7 +126,7 @@
}
},
"atRiskApplicationsWithCount": {
- "message": "At-risk applications ($COUNT$)",
+ "message": "Semua aplikasi ($COUNT$)",
"placeholders": {
"count": {
"content": "$1",
@@ -150,13 +150,13 @@
}
},
"totalMembers": {
- "message": "Total members"
+ "message": "Jumlah Anggota"
},
"atRiskApplications": {
"message": "At-risk applications"
},
"totalApplications": {
- "message": "Total applications"
+ "message": "Semua aplikasi"
},
"unmarkAsCriticalApp": {
"message": "Unmark as critical app"
@@ -202,10 +202,10 @@
"message": "Catatan"
},
"privateNote": {
- "message": "Private note"
+ "message": "Catatan pribadi"
},
"note": {
- "message": "Note"
+ "message": "Catatan"
},
"customFields": {
"message": "Kolom Ubahsuai"
@@ -217,19 +217,19 @@
"message": "Login credentials"
},
"personalDetails": {
- "message": "Personal details"
+ "message": "Rincian pribadi"
},
"identification": {
- "message": "Identification"
+ "message": "Identifikasi"
},
"contactInfo": {
- "message": "Contact info"
+ "message": "Info kontak"
},
"cardDetails": {
- "message": "Card details"
+ "message": "Rincian kartu"
},
"cardBrandDetails": {
- "message": "$BRAND$ details",
+ "message": "Rincian $BRAND$",
"placeholders": {
"brand": {
"content": "$1",
@@ -238,19 +238,19 @@
}
},
"itemHistory": {
- "message": "Item history"
+ "message": "Riwayat benda"
},
"authenticatorKey": {
- "message": "Authenticator key"
+ "message": "Kunci Autentikator"
},
"autofillOptions": {
- "message": "Autofill options"
+ "message": "Pilihan isi otomatis"
},
"websiteUri": {
- "message": "Website (URI)"
+ "message": "Situs web (URI)"
},
"websiteUriCount": {
- "message": "Website (URI) $COUNT$",
+ "message": "$COUNT$ Situs web (URI)",
"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": {
@@ -496,7 +496,7 @@
"message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums"
},
"deleteFolderPermanently": {
- "message": "Are you sure you want to permanently delete this folder?"
+ "message": "Apakah Anda yakin akan menghapus keranjang ini selamanya?"
},
"baseDomain": {
"message": "Domain dasar",
@@ -542,7 +542,7 @@
"message": "Buat Kata Sandi"
},
"generatePassphrase": {
- "message": "Generate passphrase"
+ "message": "Buat frasa sandi"
},
"checkPassword": {
"message": "Periksa apakah kata sandi telah terekspos."
@@ -597,11 +597,11 @@
"description": "Search Login type"
},
"searchCard": {
- "message": "Search cards",
+ "message": "Cari kode",
"description": "Search Card type"
},
"searchIdentity": {
- "message": "Search identities",
+ "message": "Cari identitas",
"description": "Search Identity type"
},
"searchSecureNote": {
@@ -612,10 +612,10 @@
"message": "Cari Brankas"
},
"searchMyVault": {
- "message": "Search my vault"
+ "message": "Cari brankas"
},
"searchOrganization": {
- "message": "Search organization"
+ "message": "Cari organisasi"
},
"searchMembers": {
"message": "Search members"
@@ -832,31 +832,31 @@
"message": "Copy website"
},
"copyNotes": {
- "message": "Copy notes"
+ "message": "Salin catatan"
},
"copyAddress": {
"message": "Copy address"
},
"copyPhone": {
- "message": "Copy phone"
+ "message": "Salin nomor telepon"
},
"copyEmail": {
- "message": "Copy email"
+ "message": "Salin alamat surat elektronik"
},
"copyCompany": {
- "message": "Copy company"
+ "message": "Salin perusahaan"
},
"copySSN": {
- "message": "Copy Social Security number"
+ "message": "Salin nomor Keamanan Sosial"
},
"copyPassportNumber": {
- "message": "Copy passport number"
+ "message": "Salin nomor paspor"
},
"copyLicenseNumber": {
- "message": "Copy license number"
+ "message": "Salin nomor lisensi"
},
"copyName": {
- "message": "Copy name"
+ "message": "Salin nama"
},
"me": {
"message": "Saya"
@@ -879,9 +879,6 @@
"filter": {
"message": "Filter"
},
- "moveSelectedToOrg": {
- "message": "Pindahkan terpilih ke Organisasi"
- },
"deleteSelected": {
"message": "Hapus yang Dipilih"
},
@@ -937,15 +934,6 @@
}
}
},
- "movedItemsToOrg": {
- "message": "Item terpilih dipindah ke $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Items moved to $ORGNAME$",
"placeholders": {
@@ -1004,34 +992,34 @@
"message": "Edit info"
},
"access": {
- "message": "Access"
+ "message": "Akses"
},
"accessLevel": {
- "message": "Access level"
+ "message": "Tingkat akses"
},
"accessing": {
- "message": "Accessing"
+ "message": "Sedang mengakses"
},
"loggedOut": {
"message": "Keluar"
},
"loggedOutDesc": {
- "message": "You have been logged out of your account."
+ "message": "Anda telah keluar dari akun Anda."
},
"loginExpired": {
"message": "Sesi masuk Anda telah berakhir."
},
"restartRegistration": {
- "message": "Restart registration"
+ "message": "Mulai ulang pendaftaran"
},
"expiredLink": {
- "message": "Expired link"
+ "message": "Tautan telah kadaluwarsa"
},
"pleaseRestartRegistrationOrTryLoggingIn": {
- "message": "Please restart registration or try logging in."
+ "message": "Mohon mulai ulang pendaftaran atau coba masuk."
},
"youMayAlreadyHaveAnAccount": {
- "message": "You may already have an account"
+ "message": "Anda mungkin telah memiliki sebuah akun"
},
"logOutConfirmation": {
"message": "Anda yakin ingin keluar?"
@@ -1049,7 +1037,7 @@
"message": "Tidak"
},
"location": {
- "message": "Location"
+ "message": "Lokasi"
},
"loginOrCreateNewAccount": {
"message": "Masuk atau buat akun baru untuk mengakses brankas Anda."
@@ -1058,10 +1046,10 @@
"message": "Masuk dengan perangkat"
},
"loginWithDeviceEnabledNote": {
- "message": "Log in with device must be set up in the settings of the Bitwarden app. Need another option?"
+ "message": "Masuk dengan perangkat harus diatur di pengaturan aplikasi ini. Perlu pilihan lainnya?"
},
"needAnotherOptionV1": {
- "message": "Need another option?"
+ "message": "Perlu pilihan lainnya?"
},
"loginWithMasterPassword": {
"message": "Masuk dengan kata sandi utama"
@@ -1076,13 +1064,13 @@
"message": "Use a different log in method"
},
"logInWithPasskey": {
- "message": "Log in with passkey"
+ "message": "Masuk dengan kunci sandi"
},
"useSingleSignOn": {
- "message": "Use single sign-on"
+ "message": "Gunakan masuk tunggal"
},
"welcomeBack": {
- "message": "Welcome back"
+ "message": "Selamat datang kembali"
},
"invalidPasskeyPleaseTryAgain": {
"message": "Invalid Passkey. Please try again."
@@ -1109,10 +1097,10 @@
"message": "Error creating passkey"
},
"errorCreatingPasskeyInfo": {
- "message": "There was a problem creating your passkey."
+ "message": "Terdapat masalah mengimpor kuncimu."
},
"passkeySuccessfullyCreated": {
- "message": "Passkey successfully created!"
+ "message": "Akun berhasil dibuat!"
},
"customPasskeyNameInfo": {
"message": "Name your passkey to help you identify it."
@@ -1571,9 +1559,6 @@
"moveToOrgDesc": {
"message": "Pilihlah sebuah organisasi yang Anda ingin memindahkan item ini. Memindahkan berarti memberikan kepemilikan kepada organisasi tersebut. Anda tidak akan lagi menjadi pemilik item ini."
},
- "moveManyToOrgDesc": {
- "message": "Pilihlah sebuah organisasi yang Anda ingin memindahkan item ini. Memindahkan berarti memberikan kepemilikan kepada organisasi tersebut. Anda tidak akan lagi menjadi pemilik item ini."
- },
"collectionsDesc": {
"message": "Edit koleksi tempat item ini dibagikan. Hanya pengguna organisasi dengan akses ke koleksi ini yang dapat melihat item ini."
},
@@ -1607,23 +1592,6 @@
}
}
},
- "moveSelectedItemsCountDesc": {
- "message": "Anda telah memilih $COUNT$ item. $MOVEABLE_COUNT$ item bisa dipindahkan ke sebuah organisasi, $NONMOVEABLE_COUNT$ tidak bisa.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "10"
- },
- "moveable_count": {
- "content": "$2",
- "example": "8"
- },
- "nonmoveable_count": {
- "content": "$3",
- "example": "2"
- }
- }
- },
"verificationCodeTotp": {
"message": "Kode Verifikasi (TOTP)"
},
diff --git a/apps/web/src/locales/it/messages.json b/apps/web/src/locales/it/messages.json
index 9b90e7c1253..0cbc425d588 100644
--- a/apps/web/src/locales/it/messages.json
+++ b/apps/web/src/locales/it/messages.json
@@ -879,9 +879,6 @@
"filter": {
"message": "Filtra"
},
- "moveSelectedToOrg": {
- "message": "Sposta selezionati in organizzazione"
- },
"deleteSelected": {
"message": "Elimina selezionati"
},
@@ -937,15 +934,6 @@
}
}
},
- "movedItemsToOrg": {
- "message": "Elementi selezionati spostati in $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Elementi spostati su $ORGNAME$",
"placeholders": {
@@ -1061,7 +1049,7 @@
"message": "L'accesso con dispositivo deve essere abilitato nelle impostazioni dell'app Bitwarden. Ti serve un'altra opzione?"
},
"needAnotherOptionV1": {
- "message": "Need another option?"
+ "message": "Ti serve un'alternativa?"
},
"loginWithMasterPassword": {
"message": "Accedi con password principale"
@@ -1076,13 +1064,13 @@
"message": "Usa un altro metodo di accesso"
},
"logInWithPasskey": {
- "message": "Log in with passkey"
+ "message": "Accedi con passkey"
},
"useSingleSignOn": {
- "message": "Use single sign-on"
+ "message": "Usa login unificato (SSO)"
},
"welcomeBack": {
- "message": "Welcome back"
+ "message": "Bentornat*"
},
"invalidPasskeyPleaseTryAgain": {
"message": "Passkey non valida. Riprova."
@@ -1166,7 +1154,7 @@
"message": "Crea account"
},
"newToBitwarden": {
- "message": "New to Bitwarden?"
+ "message": "Hai appena iniziato ad usare Bitwarden?"
},
"setAStrongPassword": {
"message": "Imposta una password robusta"
@@ -1184,31 +1172,31 @@
"message": "Accedi"
},
"logInToBitwarden": {
- "message": "Log in to Bitwarden"
+ "message": "Accedi a Bitwarden"
},
"enterTheCodeSentToYourEmail": {
- "message": "Enter the code sent to your email"
+ "message": "Inserisci il codice che hai ricevuto via email"
},
"enterTheCodeFromYourAuthenticatorApp": {
- "message": "Enter the code from your authenticator app"
+ "message": "Inserisci il codice generato dalla tua app di autenticazione"
},
"pressYourYubiKeyToAuthenticate": {
- "message": "Press your YubiKey to authenticate"
+ "message": "Conferma con la tua chiave YubiKey"
},
"authenticationTimeout": {
- "message": "Authentication timeout"
+ "message": "Tempo per l'autenticazione scaduto"
},
"authenticationSessionTimedOut": {
- "message": "The authentication session timed out. Please restart the login process."
+ "message": "La sessione di autenticazione è scaduta. Accedi di nuovo."
},
"verifyYourIdentity": {
"message": "Verifica la tua identità"
},
"weDontRecognizeThisDevice": {
- "message": "We don't recognize this device. Enter the code sent to your email to verify your identity."
+ "message": "Inserisci il codice che hai ricevuto via email per confermare la tua identità."
},
"continueLoggingIn": {
- "message": "Continue logging in"
+ "message": "Continua l'accesso"
},
"whatIsADevice": {
"message": "Cos'è un dispositivo?"
@@ -1278,13 +1266,13 @@
"message": "Account email"
},
"requestHint": {
- "message": "Request hint"
+ "message": "Richiedi suggerimento"
},
"requestPasswordHint": {
- "message": "Request password hint"
+ "message": "Richiedi suggerimento per la password"
},
"enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": {
- "message": "Enter your account email address and your password hint will be sent to you"
+ "message": "Inserisci l'indirizzo email del tuo account Bitwarden per ricevere il suggerimento"
},
"getMasterPasswordHint": {
"message": "Ottieni suggerimento per la password principale"
@@ -1342,7 +1330,7 @@
"message": "La cassaforte è bloccata"
},
"yourAccountIsLocked": {
- "message": "Your account is locked"
+ "message": "Il tuo account è bloccato"
},
"uuid": {
"message": "UUID"
@@ -1379,7 +1367,7 @@
"message": "Non hai i permessi necessari per visualizzare tutti gli elementi in questa raccolta."
},
"youDoNotHavePermissions": {
- "message": "You do not have permissions to this collection"
+ "message": "Non hai l'autorizzazione per l'accesso a questa raccolta"
},
"noCollectionsInList": {
"message": "Nessuna raccolta da mostrare."
@@ -1436,7 +1424,7 @@
"message": "Sblocca Bitwarden sul tuo dispositivo. Assicurati che la frase di impronta digitale corrisponda a quella sottostante prima di approvare."
},
"aNotificationWasSentToYourDevice": {
- "message": "A notification was sent to your device"
+ "message": "Una notifica è stata inviata al tuo dispositivo"
},
"versionNumber": {
"message": "Versione $VERSION_NUMBER$",
@@ -1472,7 +1460,7 @@
"message": "Ricordami"
},
"dontAskAgainOnThisDeviceFor30Days": {
- "message": "Don't ask again on this device for 30 days"
+ "message": "Non chiedere più per 30 giorni su questo dispositivo"
},
"sendVerificationCodeEmailAgain": {
"message": "Invia di nuovo l'email con codice di verifica"
@@ -1481,11 +1469,11 @@
"message": "Usa un altro metodo di verifica in due passaggi"
},
"selectAnotherMethod": {
- "message": "Select another method",
+ "message": "Scegli un altro metodo",
"description": "Select another two-step login method"
},
"useYourRecoveryCode": {
- "message": "Use your recovery code"
+ "message": "Usa un codice di recupero"
},
"insertYubiKey": {
"message": "Inserisci la tua YubiKey nella porta USB del computer e premi il suo pulsante."
@@ -1506,7 +1494,7 @@
"message": "Opzioni verifica in due passaggi"
},
"selectTwoStepLoginMethod": {
- "message": "Select two-step login method"
+ "message": "Scegli il metodo di accesso in due passaggi"
},
"recoveryCodeDesc": {
"message": "Hai perso l'accesso a tutti i tuoi metodi di verifica in due passaggi? Usa il tuo codice di recupero per disattivarli tutti dal tuo account."
@@ -1551,7 +1539,7 @@
"message": "(Trasferito da FIDO)"
},
"openInNewTab": {
- "message": "Open in new tab"
+ "message": "Apri in una nuova scheda del browser"
},
"emailTitle": {
"message": "Email"
@@ -1571,9 +1559,6 @@
"moveToOrgDesc": {
"message": "Scegli un'organizzazione in cui vuoi spostare questo elemento. Spostarlo in un'organizzazione trasferisce la proprietà dell'elemento all'organizzazione. Una volta spostato, non sarai più il proprietario diretto di questo elemento."
},
- "moveManyToOrgDesc": {
- "message": "Scegli un'organizzazione in cui vuoi spostare questi elementi. Spostarli in un'organizzazione trasferisce la proprietà degli elementi all'organizzazione. Una volta spostati, non sarai più il proprietario diretto di questi elementi."
- },
"collectionsDesc": {
"message": "Modifica le raccolte con cui questo elemento è condiviso. Solo gli utenti di organizzazioni che hanno accesso a queste raccolte potranno visualizzare questo elemento."
},
@@ -1607,23 +1592,6 @@
}
}
},
- "moveSelectedItemsCountDesc": {
- "message": "Hai selezionato $COUNT$ elementi. $MOVEABLE_COUNT$ elementi possono essere spostati in un'organizzazione, $NONMOVEABLE_COUNT$ no.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "10"
- },
- "moveable_count": {
- "content": "$2",
- "example": "8"
- },
- "nonmoveable_count": {
- "content": "$3",
- "example": "2"
- }
- }
- },
"verificationCodeTotp": {
"message": "Codice di verifica (TOTP)"
},
@@ -1738,7 +1706,7 @@
"description": "deprecated. Use avoidAmbiguous instead."
},
"avoidAmbiguous": {
- "message": "Avoid ambiguous characters",
+ "message": "Evita caratteri ambigui",
"description": "Label for the avoid ambiguous characters checkbox."
},
"length": {
@@ -1776,32 +1744,32 @@
"message": "Includi numero"
},
"generatorPolicyInEffect": {
- "message": "Enterprise policy requirements have been applied to your generator options.",
+ "message": "I requisiti della politica aziendale sono stati applicati alle opzioni del generatore.",
"description": "Indicates that a policy limits the credential generator screen."
},
"passwordHistory": {
"message": "Cronologia delle password"
},
"generatorHistory": {
- "message": "Generator history"
+ "message": "Cronologia generazione"
},
"clearGeneratorHistoryTitle": {
- "message": "Clear generator history"
+ "message": "Cancella cronologia"
},
"cleargGeneratorHistoryDescription": {
- "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?"
+ "message": "Vuoi davvero eliminare definitivamente tutti gli elementi della cronologia?"
},
"noPasswordsInList": {
"message": "Non ci sono password da mostrare."
},
"clearHistory": {
- "message": "Clear history"
+ "message": "Cancella cronologia"
},
"nothingToShow": {
- "message": "Nothing to show"
+ "message": "Nessun elemento"
},
"nothingGeneratedRecently": {
- "message": "You haven't generated anything recently"
+ "message": "Nessuna generazione recente"
},
"clear": {
"message": "Cancella",
@@ -1947,7 +1915,7 @@
"message": "Tutte le sessioni revocate"
},
"accountIsOwnedMessage": {
- "message": "This account is owned by $ORGANIZATIONNAME$",
+ "message": "Questo account è gestito da $ORGANIZATIONNAME$",
"placeholders": {
"organizationName": {
"content": "$1",
@@ -2207,7 +2175,7 @@
"message": "Impostare la verifica in due passaggi potrebbe bloccarti permanentemente fuori dal tuo account Bitwarden. Un codice di recupero ti permette di accedere al tuo account il caso non potessi più usare il tuo solito metodo di verifica in due passaggi (per esempio se perdi il telefono). L'assistenza clienti di Bitwarden non sarà in grado di aiutarti se perdi l'accesso al tuo account. Scrivi o stampa il tuo codice di recupero e conservalo in un luogo sicuro."
},
"yourSingleUseRecoveryCode": {
- "message": "Your single-use recovery code can be used to turn off two-step login in the event that you lose access to your two-step login provider. Bitwarden recommends you write down the recovery code and keep it in a safe place."
+ "message": "Puoi usare il codice di recupero monouso se non hai accesso a nessuno dei metodi impostati per l'accesso in due passaggi. Se accedi con un codice, l'accesso in due passaggi sarà disattivato. Conserva il codice in un luogo sicuro e accessibile solo a te."
},
"viewRecoveryCode": {
"message": "Visualizza codice di recupero"
@@ -2566,7 +2534,7 @@
"message": "Controlla password esposte"
},
"timesExposed": {
- "message": "Times exposed"
+ "message": "Quante volte vittima o a rischio di data breach"
},
"exposedXTimes": {
"message": "Esposta $COUNT$ volte",
@@ -2603,7 +2571,7 @@
"message": "Nessun elemento nella tua cassaforte ha password deboli."
},
"weakness": {
- "message": "Weakness"
+ "message": "Debolezza"
},
"reusedPasswordsReport": {
"message": "Password riutilizzate"
@@ -2631,7 +2599,7 @@
"message": "Nessun login nella tua cassaforte ha password riutilizzate."
},
"timesReused": {
- "message": "Times reused"
+ "message": "Quante volte riutilizzata"
},
"reusedXTimes": {
"message": "Riutilizzata $COUNT$ volte",
@@ -2931,7 +2899,7 @@
"message": "Scarica licenza"
},
"viewBillingToken": {
- "message": "View Billing Token"
+ "message": "Visualizza token di fatturazione"
},
"updateLicense": {
"message": "Aggiorna licenza"
@@ -2980,10 +2948,10 @@
"message": "Fatture"
},
"noUnpaidInvoices": {
- "message": "No unpaid invoices."
+ "message": "Nessuna fattura non pagata."
},
"noPaidInvoices": {
- "message": "No paid invoices."
+ "message": "Nessuna fattura pagata."
},
"paid": {
"message": "Pagata",
@@ -3597,7 +3565,7 @@
}
},
"viewAllLogInOptions": {
- "message": "View all log in options"
+ "message": "Visualizza tutte le opzioni di accesso"
},
"viewAllLoginOptions": {
"message": "Visualizza tutte le opzioni di accesso"
@@ -4123,7 +4091,7 @@
"message": "Aggiorna browser"
},
"generatingRiskInsights": {
- "message": "Generating your risk insights..."
+ "message": "Generazione delle tue informazioni sui rischi..."
},
"updateBrowserDesc": {
"message": "Stai utilizzando un browser non supportato. La cassaforte web potrebbe non funzionare correttamente."
@@ -4135,7 +4103,7 @@
"message": "Rivedi richiesta di accesso"
},
"freeTrialEndPromptCount": {
- "message": "Your free trial ends in $COUNT$ days.",
+ "message": "Il tuo periodo di prova scade tra $COUNT$ giorni.",
"placeholders": {
"count": {
"content": "$1",
@@ -4144,7 +4112,7 @@
}
},
"freeTrialEndPromptMultipleDays": {
- "message": "$ORGANIZATION$, your free trial ends in $COUNT$ days.",
+ "message": "$ORGANIZATION$, la tua prova gratuita termina tra $COUNT$ giorni.",
"placeholders": {
"count": {
"content": "$2",
@@ -4157,7 +4125,7 @@
}
},
"freeTrialEndPromptTomorrow": {
- "message": "$ORGANIZATION$, your free trial ends tomorrow.",
+ "message": "$ORGANIZATION$, la tua prova gratuita termina domani.",
"placeholders": {
"organization": {
"content": "$1",
@@ -4166,10 +4134,10 @@
}
},
"freeTrialEndPromptTomorrowNoOrgName": {
- "message": "Your free trial ends tomorrow."
+ "message": "La tua prova gratuita termina domani."
},
"freeTrialEndPromptToday": {
- "message": "$ORGANIZATION$, your free trial ends today.",
+ "message": "$ORGANIZATION$, la tua prova gratuita termina domani.",
"placeholders": {
"organization": {
"content": "$1",
@@ -4178,7 +4146,7 @@
}
},
"freeTrialEndingTodayWithoutOrgName": {
- "message": "Your free trial ends today."
+ "message": "La tua prova gratuita termina oggi."
},
"clickHereToAddPaymentMethod": {
"message": "Clicca qui per aggiungere un metodo di pagamento."
@@ -9016,7 +8984,7 @@
"message": "Aggiungi campo"
},
"editField": {
- "message": "Edit field"
+ "message": "Campo 'Modifica'"
},
"items": {
"message": "Elementi"
@@ -9331,62 +9299,62 @@
"message": "Usa l'SDK di Bitwarden Secrets Manager nei seguenti linguaggi di programmazione per creare le tue applicazioni."
},
"ssoDescStart": {
- "message": "Configure",
+ "message": "Configura",
"description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider."
},
"ssoDescEnd": {
- "message": "for Bitwarden using the implementation guide for your Identity Provider.",
+ "message": "per Bitwarden consultando la guida del tuo gestore di identità.",
"description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider."
},
"userProvisioning": {
- "message": "User provisioning"
+ "message": "Provisioning utenti"
},
"scimIntegration": {
"message": "SCIM"
},
"scimIntegrationDescStart": {
- "message": "Configure ",
+ "message": "Configura ",
"description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider"
},
"scimIntegrationDescEnd": {
- "message": "(System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider.",
+ "message": "(System for Cross-domain Identity Management) per configurare automaticamente utenti e gruppi in Bitwarden consultando la guida del tuo gestore di identità.",
"description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider"
},
"bwdc": {
"message": "Bitwarden Directory Connector"
},
"bwdcDesc": {
- "message": "Configure Bitwarden Directory Connector to automatically provision users and groups using the implementation guide for your Identity Provider."
+ "message": "Configura Bitwarden Directory Connector per configurare automaticamente utenti e gruppi in Bitwarden consultando la guida del tuo gestore di identità."
},
"eventManagement": {
- "message": "Event management"
+ "message": "Gestione eventi"
},
"eventManagementDesc": {
- "message": "Integrate Bitwarden event logs with your SIEM (system information and event management) system by using the implementation guide for your platform."
+ "message": "Integra i log degli eventi Bitwarden con il tuo sistema SIEM (informazioni di sistema e gestione degli eventi) consultando la guida della tua piattaforma."
},
"deviceManagement": {
- "message": "Device management"
+ "message": "Gestione dispositivi"
},
"deviceManagementDesc": {
- "message": "Configure device management for Bitwarden using the implementation guide for your platform."
+ "message": "Configura la gestione dispositivi consultando la guida per la tua piattaforma."
},
"deviceIdMissing": {
- "message": "Device ID is missing"
+ "message": "ID dispositivo mancante"
},
"deviceTypeMissing": {
- "message": "Device type is missing"
+ "message": "Tipo di dispositivo mancante"
},
"deviceCreationDateMissing": {
- "message": "Device creation date is missing"
+ "message": "Data di creazione del dispositivo mancante"
},
"desktopRequired": {
- "message": "Desktop required"
+ "message": "Desktop richiesto"
},
"reopenLinkOnDesktop": {
- "message": "Reopen this link from your email on a desktop."
+ "message": "Riapri questo link dalla tua email su un computer portatile o fisso."
},
"integrationCardTooltip": {
- "message": "Launch $INTEGRATION$ implementation guide.",
+ "message": "Avvia la guida di $INTEGRATION$.",
"placeholders": {
"integration": {
"content": "$1",
@@ -9395,7 +9363,7 @@
}
},
"smIntegrationTooltip": {
- "message": "Set up $INTEGRATION$.",
+ "message": "Configura $INTEGRATION$.",
"placeholders": {
"integration": {
"content": "$1",
@@ -9404,7 +9372,7 @@
}
},
"smSdkTooltip": {
- "message": "View $SDK$ repository",
+ "message": "Visualizza repository $SDK$",
"placeholders": {
"sdk": {
"content": "$1",
@@ -9413,7 +9381,7 @@
}
},
"integrationCardAriaLabel": {
- "message": "open $INTEGRATION$ implementation guide in a new tab.",
+ "message": "apri la guida di $INTEGRATION$ in una nuova scheda.",
"placeholders": {
"integration": {
"content": "$1",
@@ -9422,7 +9390,7 @@
}
},
"smSdkAriaLabel": {
- "message": "view $SDK$ repository in a new tab.",
+ "message": "visualizza il repository $SDK$ in una nuova scheda.",
"placeholders": {
"sdk": {
"content": "$1",
@@ -9431,7 +9399,7 @@
}
},
"smIntegrationCardAriaLabel": {
- "message": "set up $INTEGRATION$ implementation guide in a new tab.",
+ "message": "apri la guida di $INTEGRATION$ in una nuova scheda.",
"placeholders": {
"integration": {
"content": "$1",
@@ -9482,13 +9450,13 @@
"message": "Gestisci la fatturazione dal Portale del Fornitore"
},
"continueSettingUpFreeTrial": {
- "message": "Continue setting up your free trial of Bitwarden"
+ "message": "Continua a configurare la tua prova gratuita di Bitwarden"
},
"continueSettingUpFreeTrialPasswordManager": {
- "message": "Continue setting up your free trial of Bitwarden Password Manager"
+ "message": "Continua a configurare la tua prova gratuita di Bitwarden Password Manager"
},
"continueSettingUpFreeTrialSecretsManager": {
- "message": "Continue setting up your free trial of Bitwarden Secrets Manager"
+ "message": "Continua a configurare la tua prova gratuita di Bitwarden Secrets Manager"
},
"enterTeamsOrgInfo": {
"message": "Inserisci le informazioni dell'organizzazione del tuo team"
@@ -9552,10 +9520,10 @@
"message": "Fornitore di servizi gestiti"
},
"managedServiceProvider": {
- "message": "Managed service provider"
+ "message": "Fornitore di servizi gestiti"
},
"multiOrganizationEnterprise": {
- "message": "Multi-organization enterprise"
+ "message": "Azienda multi-organizzazione"
},
"orgSeats": {
"message": "Slot dell'organizzazione"
@@ -9603,16 +9571,16 @@
"message": "Informazioni fiscali aggiornate"
},
"billingInvalidTaxIdError": {
- "message": "Invalid tax ID, if you believe this is an error please contact support."
+ "message": "Codice fiscale non valido. Se credi che si tratti di un errore, contatta il supporto."
},
"billingTaxIdTypeInferenceError": {
- "message": "We were unable to validate your tax ID, if you believe this is an error please contact support."
+ "message": "Non è stato possibile convalidare il tuo codice fiscale. Se credi che si tratti di un errore, contatta il supporto."
},
"billingPreviewInvalidTaxIdError": {
- "message": "Invalid tax ID, if you believe this is an error please contact support."
+ "message": "Codice fiscale non valido. Se credi che si tratti di un errore, contatta il supporto."
},
"billingPreviewInvoiceError": {
- "message": "An error occurred while previewing the invoice. Please try again later."
+ "message": "Errore nella generazione dell'anteprima della ricevuta. Prova di nuovo."
},
"unverified": {
"message": "Non verificato"
@@ -9819,55 +9787,55 @@
"message": "slot acquistati rimossi"
},
"environmentVariables": {
- "message": "Environment variables"
+ "message": "Variabili d'ambiente"
},
"organizationId": {
- "message": "Organization ID"
+ "message": "ID organizzazione"
},
"projectIds": {
- "message": "Project IDs"
+ "message": "ID progetto"
},
"projectId": {
- "message": "Project ID"
+ "message": "ID Progetto"
},
"projectsAccessedByMachineAccount": {
- "message": "The following projects can be accessed by this machine account."
+ "message": "I seguenti progetti possono essere accessibili da questo account macchina."
},
"config": {
- "message": "Config"
+ "message": "Configurazione"
},
"learnMoreAboutEmergencyAccess": {
- "message": "Learn more about emergency access"
+ "message": "Ulteriori informazioni sull'accesso d'emergenza"
},
"learnMoreAboutMatchDetection": {
- "message": "Learn more about match detection"
+ "message": "Maggiori informazioni sulla rilevazione di corrispondenza"
},
"learnMoreAboutMasterPasswordReprompt": {
- "message": "Learn more about master password re-prompt"
+ "message": "Informazioni sulla richiesta aggiuntiva di inserimento della password principale"
},
"learnMoreAboutSearchingYourVault": {
- "message": "Learn more about searching your vault"
+ "message": "Informazioni sulla ricerca nella cassaforte"
},
"learnMoreAboutYourAccountFingerprintPhrase": {
- "message": "Learn about your account fingerprint phrase"
+ "message": "Impara la frase di autenticazione del tuo account"
},
"impactOfRotatingYourEncryptionKey": {
- "message": "Impact of rotating your encryption key"
+ "message": "Impatto della modifica periodica della chiave crittografica"
},
"learnMoreAboutEncryptionAlgorithms": {
- "message": "Learn more about encryption algorithms"
+ "message": "Informazioni sugli algoritmi di crittografia"
},
"learnMoreAboutKDFIterations": {
- "message": "Learn more about KDF iterations"
+ "message": "Informazioni sulle iterazioni KDF"
},
"learnMoreAboutLocalization": {
- "message": "Learn more about localization"
+ "message": "Informazioni sulla localizzazione"
},
"learnMoreAboutWebsiteIcons": {
- "message": "Learn more about using website icons"
+ "message": "Ulteriori informazioni sull'utilizzo delle favicon dei siti Web"
},
"learnMoreAboutUserAccess": {
- "message": "Learn more about user access"
+ "message": "Informazioni sull'accesso utente"
},
"learnMoreAboutMemberRoles": {
"message": "Ulteriori informazioni sui ruoli e i permessi dei membri"
@@ -10272,19 +10240,19 @@
}
},
"newDeviceVerificationNoticePageOneEmailAccessNo": {
- "message": "No, I do not"
+ "message": "No"
},
"newDeviceVerificationNoticePageOneEmailAccessYes": {
- "message": "Yes, I can reliably access my email"
+ "message": "Sì, ho accesso all'email"
},
"turnOnTwoStepLogin": {
- "message": "Turn on two-step login"
+ "message": "Attiva l'accesso in due passaggi"
},
"changeAcctEmail": {
- "message": "Change account email"
+ "message": "Cambia l'email dell'account"
},
"removeMembers": {
- "message": "Remove members"
+ "message": "Rimuovi membri"
},
"devices": {
"message": "Dispositivi"
@@ -10296,25 +10264,25 @@
"message": "Il tuo account è stato connesso a ciascuno dei dispositivi qui sotto."
},
"claimedDomains": {
- "message": "Claimed domains"
+ "message": "Domini verificati"
},
"claimDomain": {
- "message": "Claim domain"
+ "message": "Verifica dominio"
},
"reclaimDomain": {
- "message": "Reclaim domain"
+ "message": "Rivendica dominio"
},
"claimDomainNameInputHint": {
- "message": "Example: mydomain.com. Subdomains require separate entries to be claimed."
+ "message": "Esempio: ilmiodominio.com. I sotto-domini richiedono voci separate."
},
"automaticClaimedDomains": {
- "message": "Automatic Claimed Domains"
+ "message": "Domini verificati automatici"
},
"automaticDomainClaimProcess": {
- "message": "Bitwarden will attempt to claim the domain 3 times during the first 72 hours. If the domain can’t be claimed, check the DNS record in your host and manually claim. The domain will be removed from your organization in 7 days if it is not claimed."
+ "message": "Bitwarden tenterà di verificare il dominio 3 volte durante le prossime 72 ore. Se il dominio non può essere acquisito, controlla il record DNS del tuo servizio di hosting e procedi manualmente. Il dominio sarà rimosso dall'organizzazione dopo 7 giorni se la procedura non andrà a buon fine."
},
"domainNotClaimed": {
- "message": "$DOMAIN$ not claimed. Check your DNS records.",
+ "message": "$DOMAIN$ non verificato. Controlla il record DNS.",
"placeholders": {
"DOMAIN": {
"content": "$1",
@@ -10323,19 +10291,19 @@
}
},
"domainStatusClaimed": {
- "message": "Claimed"
+ "message": "Verificato"
},
"domainStatusUnderVerification": {
- "message": "Under verification"
+ "message": "In attesa di verifica"
},
"claimedDomainsDesc": {
- "message": "Claim a domain to own all member accounts whose email address matches the domain. Members will be able to skip the SSO identifier when logging in. Administrators will also be able to delete member accounts."
+ "message": "Richiedi un dominio per acquisire la proprietà di tutti gli account membri il cui indirizzo email corrisponde al dominio. I membri saranno in grado di saltare l'identificatore SSO durante l'accesso. Gli amministratori potranno anche eliminare gli account membri."
},
"invalidDomainNameClaimMessage": {
- "message": "Input is not a valid format. Format: mydomain.com. Subdomains require separate entries to be claimed."
+ "message": "L'input non è in un formato valido. Formato: ilmiodominio.com. I sotto-domini richiedono voci separate."
},
"domainClaimedEvent": {
- "message": "$DOMAIN$ claimed",
+ "message": "$DOMAIN$ verificato",
"placeholders": {
"DOMAIN": {
"content": "$1",
@@ -10344,7 +10312,7 @@
}
},
"domainNotClaimedEvent": {
- "message": "$DOMAIN$ not claimed",
+ "message": "$DOMAIN$ non verificato",
"placeholders": {
"DOMAIN": {
"content": "$1",
@@ -10353,7 +10321,7 @@
}
},
"updatedRevokeSponsorshipConfirmationForSentSponsorship": {
- "message": "If you remove $EMAIL$, the sponsorship for this Family plan cannot be redeemed. Are you sure you want to continue?",
+ "message": "Se rimuovi $EMAIL$, la sponsorizzazione per questo piano Famiglia non potrà essere riscattata. Vuoi davvero procedere?",
"placeholders": {
"email": {
"content": "$1",
@@ -10362,7 +10330,7 @@
}
},
"updatedRevokeSponsorshipConfirmationForAcceptedSponsorship": {
- "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end and the saved payment method will be charged $40 + applicable tax on $DATE$. You will not be able to redeem a new sponsorship until $DATE$. Are you sure you want to continue?",
+ "message": "Se rimuovi $EMAIL$, la sponsorizzazione per questo piano Famiglia finirà e sul metodo di pagamento impostato saranno addebitati 40 $ + imposte applicabile su $DATE$. Non sarai in grado di riscattare una nuova sponsorizzazione fino a $DATE$. Vuoi comunque procedere?",
"placeholders": {
"email": {
"content": "$1",
@@ -10375,10 +10343,10 @@
}
},
"domainClaimed": {
- "message": "Domain claimed"
+ "message": "Dominio verificato"
},
"organizationNameMaxLength": {
- "message": "Organization name cannot exceed 50 characters."
+ "message": "Il nome dell'organizzazione non può superare i 50 caratteri."
},
"sshKeyWrongPassword": {
"message": "La parola d'accesso inserita non è corretta."
@@ -10411,39 +10379,39 @@
"message": "Copia chiave privata"
},
"openingExtension": {
- "message": "Opening the Bitwarden browser extension"
+ "message": "Apertura dell'estensione del browser Bitwarden"
},
"somethingWentWrong": {
- "message": "Something went wrong..."
+ "message": "Si è verificato un problema..."
},
"openingExtensionError": {
- "message": "We had trouble opening the Bitwarden browser extension. Click the button to open it now."
+ "message": "Non è stato possibile aprire l'estensione di Bitwarden. Riprova cliccando sul pulsante."
},
"openExtension": {
- "message": "Open extension"
+ "message": "Apri estensione"
},
"doNotHaveExtension": {
- "message": "Don't have the Bitwarden browser extension?"
+ "message": "Non hai l'estensione di Bitwarden installata nel tuo browser?"
},
"installExtension": {
- "message": "Install extension"
+ "message": "Installa estensione"
},
"openedExtension": {
- "message": "Opened the browser extension"
+ "message": "Estensione avviata"
},
"openedExtensionViewAtRiskPasswords": {
- "message": "Successfully opened the Bitwarden browser extension. You can now review your at-risk passwords."
+ "message": "L'estensione di Bitwarden è installata e funzionante. Ora è possibile avere una panoramica delle password a rischio."
},
"openExtensionManuallyPart1": {
- "message": "We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon",
+ "message": "Non è stato possibile aprire l'estensione di Bitwarden. Clicca sull'icona di Bitwarden",
"description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'"
},
"openExtensionManuallyPart2": {
- "message": "from the toolbar.",
+ "message": "dalla barra degli strumenti.",
"description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'"
},
"resellerRenewalWarningMsg": {
- "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.",
+ "message": "Il tuo abbonamento sarà rinnovato a breve. Per assicurarti un servizio continuativo, contatta $RESELLER$ e conferma il tuo rinnovo prima del $RENEWAL_DATE$.",
"placeholders": {
"reseller": {
"content": "$1",
@@ -10456,7 +10424,7 @@
}
},
"resellerOpenInvoiceWarningMgs": {
- "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.",
+ "message": "La ricevuta per l'abbonamento è stata emessa il $ISSUED_DATE$. Per assicurarti un servizio continuativo, contatta $RESELLER$ e conferma il tuo rinnovo prima del $DUE_DATE$.",
"placeholders": {
"reseller": {
"content": "$1",
@@ -10473,7 +10441,7 @@
}
},
"resellerPastDueWarningMsg": {
- "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.",
+ "message": "Non abbiamo ricevuto il pagamento per il tuo abbonamento. Per assicurarti un servizio continuativo, contatta $RESELLER$ e conferma il tuo rinnovo prima del $GRACE_PERIOD_END$.",
"placeholders": {
"reseller": {
"content": "$1",
diff --git a/apps/web/src/locales/ja/messages.json b/apps/web/src/locales/ja/messages.json
index 4559a2a07bd..6860b30b360 100644
--- a/apps/web/src/locales/ja/messages.json
+++ b/apps/web/src/locales/ja/messages.json
@@ -879,9 +879,6 @@
"filter": {
"message": "フィルター"
},
- "moveSelectedToOrg": {
- "message": "選択したものを組織に移動"
- },
"deleteSelected": {
"message": "選択したものを削除"
},
@@ -937,15 +934,6 @@
}
}
},
- "movedItemsToOrg": {
- "message": "選択したアイテムを $ORGNAME$ に移動しました",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "アイテムを $ORGNAME$ に移動しました",
"placeholders": {
@@ -1571,9 +1559,6 @@
"moveToOrgDesc": {
"message": "このアイテムを移動する組織を選択してください。組織に移動すると、アイテムの所有権はその組織に移ります。移動した後、あなたはこのアイテムの直接の所有者ではなくなります。"
},
- "moveManyToOrgDesc": {
- "message": "このアイテムを移動する組織を選択してください。組織に移動すると、アイテムの所有権がその組織に移行します。 このアイテムが移動された後、あなたはこのアイテムの直接の所有者にはなりません。"
- },
"collectionsDesc": {
"message": "このアイテムを共有するコレクションを編集します。共有したアイテムは、当該コレクションにアクセスできる組織ユーザーにのみ表示されます。"
},
@@ -1607,23 +1592,6 @@
}
}
},
- "moveSelectedItemsCountDesc": {
- "message": "$COUNT$ アイテムを選択しました。 $MOVEABLE_COUNT$ アイテムは組織に移動できます。 $NONMOVEABLE_COUNT$ アイテムはできません。",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "10"
- },
- "moveable_count": {
- "content": "$2",
- "example": "8"
- },
- "nonmoveable_count": {
- "content": "$3",
- "example": "2"
- }
- }
- },
"verificationCodeTotp": {
"message": "認証コード (TOTP)"
},
diff --git a/apps/web/src/locales/ka/messages.json b/apps/web/src/locales/ka/messages.json
index 49d8d32e02d..f5f4bfb3ae1 100644
--- a/apps/web/src/locales/ka/messages.json
+++ b/apps/web/src/locales/ka/messages.json
@@ -879,9 +879,6 @@
"filter": {
"message": "ფილტრი"
},
- "moveSelectedToOrg": {
- "message": "გადაყვანა შერჩეულის ორგანიზაციაში"
- },
"deleteSelected": {
"message": "წაშლა შერჩეულის"
},
@@ -937,15 +934,6 @@
}
}
},
- "movedItemsToOrg": {
- "message": "მონიშნული საგანები გადაყვანილია $ORGNAME$-ში",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Items moved to $ORGNAME$",
"placeholders": {
@@ -1571,9 +1559,6 @@
"moveToOrgDesc": {
"message": "Choose an organization that you wish to move this item to. Moving to an organization transfers ownership of the item to that organization. You will no longer be the direct owner of this item once it has been moved."
},
- "moveManyToOrgDesc": {
- "message": "Choose an organization that you wish to move these items to. Moving to an organization transfers ownership of the items to that organization. You will no longer be the direct owner of these items once they have been moved."
- },
"collectionsDesc": {
"message": "Edit the collections that this item is being shared with. Only organization users with access to these collections will be able to see this item."
},
@@ -1607,23 +1592,6 @@
}
}
},
- "moveSelectedItemsCountDesc": {
- "message": "თქვენ მონიშნეთ $COUNT$ საგანი(ები). $MOVEABLE_COUNT$ საგან(ები)-ს გადატანა შესაძლებელია ორგანიზაციაში, $NONMOVEABLE_COUNT$ ვერა",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "10"
- },
- "moveable_count": {
- "content": "$2",
- "example": "8"
- },
- "nonmoveable_count": {
- "content": "$3",
- "example": "2"
- }
- }
- },
"verificationCodeTotp": {
"message": "ერთჯერადი კოდი (TOTP)"
},
diff --git a/apps/web/src/locales/km/messages.json b/apps/web/src/locales/km/messages.json
index 9105ec373c3..f334131b69b 100644
--- a/apps/web/src/locales/km/messages.json
+++ b/apps/web/src/locales/km/messages.json
@@ -879,9 +879,6 @@
"filter": {
"message": "Filter"
},
- "moveSelectedToOrg": {
- "message": "Move selected to organization"
- },
"deleteSelected": {
"message": "Delete selected"
},
@@ -937,15 +934,6 @@
}
}
},
- "movedItemsToOrg": {
- "message": "Selected items moved to $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Items moved to $ORGNAME$",
"placeholders": {
@@ -1571,9 +1559,6 @@
"moveToOrgDesc": {
"message": "Choose an organization that you wish to move this item to. Moving to an organization transfers ownership of the item to that organization. You will no longer be the direct owner of this item once it has been moved."
},
- "moveManyToOrgDesc": {
- "message": "Choose an organization that you wish to move these items to. Moving to an organization transfers ownership of the items to that organization. You will no longer be the direct owner of these items once they have been moved."
- },
"collectionsDesc": {
"message": "Edit the collections that this item is being shared with. Only organization users with access to these collections will be able to see this item."
},
@@ -1607,23 +1592,6 @@
}
}
},
- "moveSelectedItemsCountDesc": {
- "message": "You have selected $COUNT$ item(s). $MOVEABLE_COUNT$ item(s) can be moved to an organization, $NONMOVEABLE_COUNT$ cannot.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "10"
- },
- "moveable_count": {
- "content": "$2",
- "example": "8"
- },
- "nonmoveable_count": {
- "content": "$3",
- "example": "2"
- }
- }
- },
"verificationCodeTotp": {
"message": "Verification code (TOTP)"
},
diff --git a/apps/web/src/locales/kn/messages.json b/apps/web/src/locales/kn/messages.json
index 14b055c1c7c..dbf2cab3bb5 100644
--- a/apps/web/src/locales/kn/messages.json
+++ b/apps/web/src/locales/kn/messages.json
@@ -879,9 +879,6 @@
"filter": {
"message": "Filter"
},
- "moveSelectedToOrg": {
- "message": "ಆಯ್ದ ಸಂಸ್ಥೆಗೆ ಸರಿಸಿ"
- },
"deleteSelected": {
"message": "ಆಯ್ಕೆಮಾಡಿದ ಅಳಿಸಿ"
},
@@ -937,15 +934,6 @@
}
}
},
- "movedItemsToOrg": {
- "message": "ಆಯ್ದ ವಸ್ತುಗಳನ್ನು $ORGNAME$ ಗೆ ಸರಿಸಲಾಗಿದೆ",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Items moved to $ORGNAME$",
"placeholders": {
@@ -1571,9 +1559,6 @@
"moveToOrgDesc": {
"message": "ಈ ಐಟಂ ಅನ್ನು ಸರಿಸಲು ನೀವು ಬಯಸುವ ಸಂಸ್ಥೆಯನ್ನು ಆರಿಸಿ. ಸಂಸ್ಥೆಗೆ ಹೋಗುವುದರಿಂದ ವಸ್ತುವಿನ ಮಾಲೀಕತ್ವವನ್ನು ಆ ಸಂಸ್ಥೆಗೆ ವರ್ಗಾಯಿಸುತ್ತದೆ. ಈ ಐಟಂ ಅನ್ನು ಸರಿಸಿದ ನಂತರ ನೀವು ಇನ್ನು ಮುಂದೆ ಅದರ ನೇರ ಮಾಲೀಕರಾಗಿರುವುದಿಲ್ಲ."
},
- "moveManyToOrgDesc": {
- "message": "ಈ ವಸ್ತುಗಳನ್ನು ಸರಿಸಲು ನೀವು ಬಯಸುವ ಸಂಸ್ಥೆಯನ್ನು ಆರಿಸಿ. ಸಂಸ್ಥೆಗೆ ಹೋಗುವುದರಿಂದ ವಸ್ತುಗಳ ಮಾಲೀಕತ್ವವನ್ನು ಆ ಸಂಸ್ಥೆಗೆ ವರ್ಗಾಯಿಸುತ್ತದೆ. ಈ ವಸ್ತುಗಳನ್ನು ಸರಿಸಿದ ನಂತರ ನೀವು ಇನ್ನು ಮುಂದೆ ಅವರ ನೇರ ಮಾಲೀಕರಾಗಿರುವುದಿಲ್ಲ."
- },
"collectionsDesc": {
"message": "ಈ ಐಟಂ ಹಂಚಿಕೊಳ್ಳುತ್ತಿರುವ ಸಂಗ್ರಹಗಳನ್ನು ಸಂಪಾದಿಸಿ. ಈ ಸಂಗ್ರಹಣೆಗಳಿಗೆ ಪ್ರವೇಶ ಹೊಂದಿರುವ ಸಂಸ್ಥೆಯ ಬಳಕೆದಾರರಿಗೆ ಮಾತ್ರ ಈ ಐಟಂ ಅನ್ನು ನೋಡಲು ಸಾಧ್ಯವಾಗುತ್ತದೆ."
},
@@ -1607,23 +1592,6 @@
}
}
},
- "moveSelectedItemsCountDesc": {
- "message": "ನೀವು $COUNT$ ಐಟಂ (ಗಳನ್ನು) ಆಯ್ಕೆ ಮಾಡಿದ್ದೀರಿ. $MOVEABLE_COUNT$ ಐಟಂ (ಗಳನ್ನು) ಸಂಸ್ಥೆಗೆ ಸರಿಸಬಹುದು, $NONMOVEABLE_COUNT$ ಸಾಧ್ಯವಿಲ್ಲ.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "10"
- },
- "moveable_count": {
- "content": "$2",
- "example": "8"
- },
- "nonmoveable_count": {
- "content": "$3",
- "example": "2"
- }
- }
- },
"verificationCodeTotp": {
"message": "ಪರಿಶೀಲನಾ ಕೋಡ್ಗಳು (TOTP)"
},
diff --git a/apps/web/src/locales/ko/messages.json b/apps/web/src/locales/ko/messages.json
index 0a24d9f7db8..1f3bbda3736 100644
--- a/apps/web/src/locales/ko/messages.json
+++ b/apps/web/src/locales/ko/messages.json
@@ -879,9 +879,6 @@
"filter": {
"message": "필터"
},
- "moveSelectedToOrg": {
- "message": "선택한 항목을 조직으로 이동함"
- },
"deleteSelected": {
"message": "선택 항목 삭제"
},
@@ -937,15 +934,6 @@
}
}
},
- "movedItemsToOrg": {
- "message": "선택한 항목이 $ORGNAME$(으)로 이동됨",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Items moved to $ORGNAME$",
"placeholders": {
@@ -1571,9 +1559,6 @@
"moveToOrgDesc": {
"message": "이 항목을 이동할 조직을 선택하십시오. 항목이 조직으로 이동되면 소유권이 조직으로 이전됩니다. 일단 이동되면, 더는 이동된 항목의 직접적인 소유자가 아니게 됩니다."
},
- "moveManyToOrgDesc": {
- "message": "이 항목을 이동할 조직을 선택하십시오. 항목이 조직으로 이동되면 소유권이 조직으로 이전됩니다. 일단 이동되면, 더는 이동된 항목의 직접적인 소유자가 아니게 됩니다."
- },
"collectionsDesc": {
"message": "이 항목이 공유될 콜렉션을 수정하십시오. 이 콜렉션에 접근할 수 있는 조직 사용자만 이 항목을 볼 수 있습니다."
},
@@ -1607,23 +1592,6 @@
}
}
},
- "moveSelectedItemsCountDesc": {
- "message": "$COUNT$개의 항목을 선택하셨습니다. $MOVEABLE_COUNT$개의 항목은 조직으로 이동시킬 수 있지만 나머지 $NONMOVEABLE_COUNT$개의 항목은 이동시킬 수 없습니다.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "10"
- },
- "moveable_count": {
- "content": "$2",
- "example": "8"
- },
- "nonmoveable_count": {
- "content": "$3",
- "example": "2"
- }
- }
- },
"verificationCodeTotp": {
"message": "인증 코드 (TOTP)"
},
diff --git a/apps/web/src/locales/lv/messages.json b/apps/web/src/locales/lv/messages.json
index 31c8a616915..7c8c3b2831e 100644
--- a/apps/web/src/locales/lv/messages.json
+++ b/apps/web/src/locales/lv/messages.json
@@ -879,9 +879,6 @@
"filter": {
"message": "Atlase"
},
- "moveSelectedToOrg": {
- "message": "Pārvietot atzīmēto uz apvienību"
- },
"deleteSelected": {
"message": "Izdzēst atlasītos"
},
@@ -937,15 +934,6 @@
}
}
},
- "movedItemsToOrg": {
- "message": "Atzīmētie vienumi pārvietoti uz $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Vienumi pārvietoti uz $ORGNAME$",
"placeholders": {
@@ -1571,9 +1559,6 @@
"moveToOrgDesc": {
"message": "Jāizvēlas apvienība, uz kuru pārvietot šo vienumu. Pārvietošana nodod šī vienuma piederību apvienībai. Pēc šī vienuma pārvietošanas Tu vairs nebūsi tā tiešais īpašnieks."
},
- "moveManyToOrgDesc": {
- "message": "Jāizvēlas apvienība, uz kuru pārvietot šos vienumus. Pārvietošana nodod šo vienumu piederību apvienībai. Pēc šo vienumu pārvietošanas Tu vairs nebūsi to tiešais īpašnieks."
- },
"collectionsDesc": {
"message": "Labot krājumus, ar kuriem šis vienums ir kopīgots. Tikai apvienības lietotāji, kam ir piekļuve šiem krājumiem, redzēs šo vienumu."
},
@@ -1607,23 +1592,6 @@
}
}
},
- "moveSelectedItemsCountDesc": {
- "message": "Ir atzīmēts(i) $COUNT$ vienums(i). $MOVEABLE_COUNT$ vienums(i) var tikt pārvietoti uz apvienību, bet $NONMOVEABLE_COUNT$ nē.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "10"
- },
- "moveable_count": {
- "content": "$2",
- "example": "8"
- },
- "nonmoveable_count": {
- "content": "$3",
- "example": "2"
- }
- }
- },
"verificationCodeTotp": {
"message": "Apstiprinājuma kods (TOTP)"
},
diff --git a/apps/web/src/locales/ml/messages.json b/apps/web/src/locales/ml/messages.json
index c1689d9108c..46ce3b62533 100644
--- a/apps/web/src/locales/ml/messages.json
+++ b/apps/web/src/locales/ml/messages.json
@@ -879,9 +879,6 @@
"filter": {
"message": "Filter"
},
- "moveSelectedToOrg": {
- "message": "Move selected to organization"
- },
"deleteSelected": {
"message": "തിരഞ്ഞെടുത്തത് ഇല്ലാതാക്കുക"
},
@@ -937,15 +934,6 @@
}
}
},
- "movedItemsToOrg": {
- "message": "Selected items moved to $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Items moved to $ORGNAME$",
"placeholders": {
@@ -1571,9 +1559,6 @@
"moveToOrgDesc": {
"message": "Choose an organization that you wish to move this item to. Moving to an organization transfers ownership of the item to that organization. You will no longer be the direct owner of this item once it has been moved."
},
- "moveManyToOrgDesc": {
- "message": "Choose an organization that you wish to move these items to. Moving to an organization transfers ownership of the items to that organization. You will no longer be the direct owner of these items once they have been moved."
- },
"collectionsDesc": {
"message": "Edit the collections that this item is being shared with. Only organization users with access to these collections will be able to see this item."
},
@@ -1607,23 +1592,6 @@
}
}
},
- "moveSelectedItemsCountDesc": {
- "message": "You have selected $COUNT$ item(s). $MOVEABLE_COUNT$ item(s) can be moved to an organization, $NONMOVEABLE_COUNT$ cannot.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "10"
- },
- "moveable_count": {
- "content": "$2",
- "example": "8"
- },
- "nonmoveable_count": {
- "content": "$3",
- "example": "2"
- }
- }
- },
"verificationCodeTotp": {
"message": "സ്ഥിരീകരണ കോഡ് (TOTP)"
},
diff --git a/apps/web/src/locales/mr/messages.json b/apps/web/src/locales/mr/messages.json
index 9105ec373c3..f334131b69b 100644
--- a/apps/web/src/locales/mr/messages.json
+++ b/apps/web/src/locales/mr/messages.json
@@ -879,9 +879,6 @@
"filter": {
"message": "Filter"
},
- "moveSelectedToOrg": {
- "message": "Move selected to organization"
- },
"deleteSelected": {
"message": "Delete selected"
},
@@ -937,15 +934,6 @@
}
}
},
- "movedItemsToOrg": {
- "message": "Selected items moved to $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Items moved to $ORGNAME$",
"placeholders": {
@@ -1571,9 +1559,6 @@
"moveToOrgDesc": {
"message": "Choose an organization that you wish to move this item to. Moving to an organization transfers ownership of the item to that organization. You will no longer be the direct owner of this item once it has been moved."
},
- "moveManyToOrgDesc": {
- "message": "Choose an organization that you wish to move these items to. Moving to an organization transfers ownership of the items to that organization. You will no longer be the direct owner of these items once they have been moved."
- },
"collectionsDesc": {
"message": "Edit the collections that this item is being shared with. Only organization users with access to these collections will be able to see this item."
},
@@ -1607,23 +1592,6 @@
}
}
},
- "moveSelectedItemsCountDesc": {
- "message": "You have selected $COUNT$ item(s). $MOVEABLE_COUNT$ item(s) can be moved to an organization, $NONMOVEABLE_COUNT$ cannot.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "10"
- },
- "moveable_count": {
- "content": "$2",
- "example": "8"
- },
- "nonmoveable_count": {
- "content": "$3",
- "example": "2"
- }
- }
- },
"verificationCodeTotp": {
"message": "Verification code (TOTP)"
},
diff --git a/apps/web/src/locales/my/messages.json b/apps/web/src/locales/my/messages.json
index 9105ec373c3..f334131b69b 100644
--- a/apps/web/src/locales/my/messages.json
+++ b/apps/web/src/locales/my/messages.json
@@ -879,9 +879,6 @@
"filter": {
"message": "Filter"
},
- "moveSelectedToOrg": {
- "message": "Move selected to organization"
- },
"deleteSelected": {
"message": "Delete selected"
},
@@ -937,15 +934,6 @@
}
}
},
- "movedItemsToOrg": {
- "message": "Selected items moved to $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Items moved to $ORGNAME$",
"placeholders": {
@@ -1571,9 +1559,6 @@
"moveToOrgDesc": {
"message": "Choose an organization that you wish to move this item to. Moving to an organization transfers ownership of the item to that organization. You will no longer be the direct owner of this item once it has been moved."
},
- "moveManyToOrgDesc": {
- "message": "Choose an organization that you wish to move these items to. Moving to an organization transfers ownership of the items to that organization. You will no longer be the direct owner of these items once they have been moved."
- },
"collectionsDesc": {
"message": "Edit the collections that this item is being shared with. Only organization users with access to these collections will be able to see this item."
},
@@ -1607,23 +1592,6 @@
}
}
},
- "moveSelectedItemsCountDesc": {
- "message": "You have selected $COUNT$ item(s). $MOVEABLE_COUNT$ item(s) can be moved to an organization, $NONMOVEABLE_COUNT$ cannot.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "10"
- },
- "moveable_count": {
- "content": "$2",
- "example": "8"
- },
- "nonmoveable_count": {
- "content": "$3",
- "example": "2"
- }
- }
- },
"verificationCodeTotp": {
"message": "Verification code (TOTP)"
},
diff --git a/apps/web/src/locales/nb/messages.json b/apps/web/src/locales/nb/messages.json
index 62a56ba2a80..24285b8cb9f 100644
--- a/apps/web/src/locales/nb/messages.json
+++ b/apps/web/src/locales/nb/messages.json
@@ -879,9 +879,6 @@
"filter": {
"message": "Filter"
},
- "moveSelectedToOrg": {
- "message": "Flytt valgte til organisasjon"
- },
"deleteSelected": {
"message": "Slett de valgte"
},
@@ -937,15 +934,6 @@
}
}
},
- "movedItemsToOrg": {
- "message": "Valgte elementer flyttet til $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Gjenstandene ble flyttet til $ORGNAME$",
"placeholders": {
@@ -1571,9 +1559,6 @@
"moveToOrgDesc": {
"message": "Velg en organisasjon som du ønsker å flytte dette elementet til. Flytting til en organisasjon overfører eierskap til den aktuelle organisasjonen. Du vil ikke lenger være den direkte eieren av dette elementet når det er flyttet."
},
- "moveManyToOrgDesc": {
- "message": "Velg en organisasjon som du ønsker å flytte dette elementet til. Flytting til en organisasjon overfører eierskap til den aktuelle organisasjonen. Du vil ikke lenger være den direkte eieren av dette elementet når det er flyttet."
- },
"collectionsDesc": {
"message": "Rediger samlingene som dette elementet blir delt med. Kun organisasjonsbrukere med tilgang til disse samlingene vil kunne se dette elementet."
},
@@ -1607,23 +1592,6 @@
}
}
},
- "moveSelectedItemsCountDesc": {
- "message": "Du har valgt $COUNT$ element(er). $MOVEABLE_COUNT$ element(er) kan flyttes til en organisasjon, $NONMOVEABLE_COUNT$ kan ikke.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "10"
- },
- "moveable_count": {
- "content": "$2",
- "example": "8"
- },
- "nonmoveable_count": {
- "content": "$3",
- "example": "2"
- }
- }
- },
"verificationCodeTotp": {
"message": "Verifiseringskode (TOTP)"
},
diff --git a/apps/web/src/locales/ne/messages.json b/apps/web/src/locales/ne/messages.json
index 22257d33c03..8183efd5f9b 100644
--- a/apps/web/src/locales/ne/messages.json
+++ b/apps/web/src/locales/ne/messages.json
@@ -879,9 +879,6 @@
"filter": {
"message": "Filter"
},
- "moveSelectedToOrg": {
- "message": "Move selected to organization"
- },
"deleteSelected": {
"message": "Delete selected"
},
@@ -937,15 +934,6 @@
}
}
},
- "movedItemsToOrg": {
- "message": "Selected items moved to $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Items moved to $ORGNAME$",
"placeholders": {
@@ -1571,9 +1559,6 @@
"moveToOrgDesc": {
"message": "Choose an organization that you wish to move this item to. Moving to an organization transfers ownership of the item to that organization. You will no longer be the direct owner of this item once it has been moved."
},
- "moveManyToOrgDesc": {
- "message": "Choose an organization that you wish to move these items to. Moving to an organization transfers ownership of the items to that organization. You will no longer be the direct owner of these items once they have been moved."
- },
"collectionsDesc": {
"message": "Edit the collections that this item is being shared with. Only organization users with access to these collections will be able to see this item."
},
@@ -1607,23 +1592,6 @@
}
}
},
- "moveSelectedItemsCountDesc": {
- "message": "You have selected $COUNT$ item(s). $MOVEABLE_COUNT$ item(s) can be moved to an organization, $NONMOVEABLE_COUNT$ cannot.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "10"
- },
- "moveable_count": {
- "content": "$2",
- "example": "8"
- },
- "nonmoveable_count": {
- "content": "$3",
- "example": "2"
- }
- }
- },
"verificationCodeTotp": {
"message": "Verification code (TOTP)"
},
diff --git a/apps/web/src/locales/nl/messages.json b/apps/web/src/locales/nl/messages.json
index c265395b237..d193b2496f3 100644
--- a/apps/web/src/locales/nl/messages.json
+++ b/apps/web/src/locales/nl/messages.json
@@ -879,9 +879,6 @@
"filter": {
"message": "Filter"
},
- "moveSelectedToOrg": {
- "message": "Selectie naar organisatie verplaatsen"
- },
"deleteSelected": {
"message": "Selectie verwijderen"
},
@@ -937,15 +934,6 @@
}
}
},
- "movedItemsToOrg": {
- "message": "Geselecteerde items verplaatst naar $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Items verplaatst naar $ORGNAME$",
"placeholders": {
@@ -1571,9 +1559,6 @@
"moveToOrgDesc": {
"message": "Kies een organisatie waarnaar je dit item wilt verplaatsen. Door het verplaatsen krijgt de organisatie de eigendomsrechten van het item. Je bent niet langer de directe eigenaar meer van het item als het is verplaatst."
},
- "moveManyToOrgDesc": {
- "message": "Kies een organisatie waarnaar je deze items wilt verplaatsen. Door het verplaatsen krijgt de organisatie de eigendomsrechten van deze items. Je bent niet langer de directe eigenaar meer van deze items als ze zijn verplaatst."
- },
"collectionsDesc": {
"message": "Wijzig de verzamelingen waarmee dit item gedeeld is. Alleen organisatiegebruikers met toegang tot deze verzamelingen kunnen dit item inzien."
},
@@ -1607,23 +1592,6 @@
}
}
},
- "moveSelectedItemsCountDesc": {
- "message": "Je hebt $COUNT$ item(s) geselecteerd. Je kunt $MOVEABLE_COUNT$ verplaatsen naar een organisatie, $NONMOVEABLE_COUNT$ niet.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "10"
- },
- "moveable_count": {
- "content": "$2",
- "example": "8"
- },
- "nonmoveable_count": {
- "content": "$3",
- "example": "2"
- }
- }
- },
"verificationCodeTotp": {
"message": "Verificatiecode (TOTP)"
},
diff --git a/apps/web/src/locales/nn/messages.json b/apps/web/src/locales/nn/messages.json
index 29dde4a30db..23d4d3b3089 100644
--- a/apps/web/src/locales/nn/messages.json
+++ b/apps/web/src/locales/nn/messages.json
@@ -879,9 +879,6 @@
"filter": {
"message": "Filter"
},
- "moveSelectedToOrg": {
- "message": "Flytt markerte til organisasjon"
- },
"deleteSelected": {
"message": "Slett markerte"
},
@@ -937,15 +934,6 @@
}
}
},
- "movedItemsToOrg": {
- "message": "Dei valde oppføringane vart flytta til $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Items moved to $ORGNAME$",
"placeholders": {
@@ -1571,9 +1559,6 @@
"moveToOrgDesc": {
"message": "Choose an organization that you wish to move this item to. Moving to an organization transfers ownership of the item to that organization. You will no longer be the direct owner of this item once it has been moved."
},
- "moveManyToOrgDesc": {
- "message": "Choose an organization that you wish to move these items to. Moving to an organization transfers ownership of the items to that organization. You will no longer be the direct owner of these items once they have been moved."
- },
"collectionsDesc": {
"message": "Edit the collections that this item is being shared with. Only organization users with access to these collections will be able to see this item."
},
@@ -1607,23 +1592,6 @@
}
}
},
- "moveSelectedItemsCountDesc": {
- "message": "You have selected $COUNT$ item(s). $MOVEABLE_COUNT$ item(s) can be moved to an organization, $NONMOVEABLE_COUNT$ cannot.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "10"
- },
- "moveable_count": {
- "content": "$2",
- "example": "8"
- },
- "nonmoveable_count": {
- "content": "$3",
- "example": "2"
- }
- }
- },
"verificationCodeTotp": {
"message": "Verification code (TOTP)"
},
diff --git a/apps/web/src/locales/or/messages.json b/apps/web/src/locales/or/messages.json
index 9105ec373c3..f334131b69b 100644
--- a/apps/web/src/locales/or/messages.json
+++ b/apps/web/src/locales/or/messages.json
@@ -879,9 +879,6 @@
"filter": {
"message": "Filter"
},
- "moveSelectedToOrg": {
- "message": "Move selected to organization"
- },
"deleteSelected": {
"message": "Delete selected"
},
@@ -937,15 +934,6 @@
}
}
},
- "movedItemsToOrg": {
- "message": "Selected items moved to $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Items moved to $ORGNAME$",
"placeholders": {
@@ -1571,9 +1559,6 @@
"moveToOrgDesc": {
"message": "Choose an organization that you wish to move this item to. Moving to an organization transfers ownership of the item to that organization. You will no longer be the direct owner of this item once it has been moved."
},
- "moveManyToOrgDesc": {
- "message": "Choose an organization that you wish to move these items to. Moving to an organization transfers ownership of the items to that organization. You will no longer be the direct owner of these items once they have been moved."
- },
"collectionsDesc": {
"message": "Edit the collections that this item is being shared with. Only organization users with access to these collections will be able to see this item."
},
@@ -1607,23 +1592,6 @@
}
}
},
- "moveSelectedItemsCountDesc": {
- "message": "You have selected $COUNT$ item(s). $MOVEABLE_COUNT$ item(s) can be moved to an organization, $NONMOVEABLE_COUNT$ cannot.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "10"
- },
- "moveable_count": {
- "content": "$2",
- "example": "8"
- },
- "nonmoveable_count": {
- "content": "$3",
- "example": "2"
- }
- }
- },
"verificationCodeTotp": {
"message": "Verification code (TOTP)"
},
diff --git a/apps/web/src/locales/pl/messages.json b/apps/web/src/locales/pl/messages.json
index fc7cc8ff9fa..9ef5c95bf86 100644
--- a/apps/web/src/locales/pl/messages.json
+++ b/apps/web/src/locales/pl/messages.json
@@ -879,9 +879,6 @@
"filter": {
"message": "Filtr"
},
- "moveSelectedToOrg": {
- "message": "Przenieś zaznaczone do organizacji"
- },
"deleteSelected": {
"message": "Usuń zaznaczone"
},
@@ -937,15 +934,6 @@
}
}
},
- "movedItemsToOrg": {
- "message": "Zaznaczone elementy zostały przeniesione do organizacji $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Elementy przeniesione do $ORGNAME$",
"placeholders": {
@@ -1571,9 +1559,6 @@
"moveToOrgDesc": {
"message": "Wybierz organizację, do której chcesz przenieść ten element. Ta czynność spowoduje utratę własności elementu i przenosi te uprawnienia do organizacji."
},
- "moveManyToOrgDesc": {
- "message": "Wybierz organizację, do której chcesz przenieść te elementy. Ta czynność spowoduje utratę własności elementów i przenosi te uprawnienia do organizacji."
- },
"collectionsDesc": {
"message": "Edytuj kolekcje zawierające ten element. Tylko użytkownicy organizacji posiadający dostęp do tych kolekcji będą mogli zobaczyć ten element."
},
@@ -1607,23 +1592,6 @@
}
}
},
- "moveSelectedItemsCountDesc": {
- "message": "Zaznaczone elementy: $COUNT$\nElementy możliwe do przeniesienia: $MOVEABLE_COUNT$\nElementy niemożliwe do przeniesienia: $NONMOVEABLE_COUNT$",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "10"
- },
- "moveable_count": {
- "content": "$2",
- "example": "8"
- },
- "nonmoveable_count": {
- "content": "$3",
- "example": "2"
- }
- }
- },
"verificationCodeTotp": {
"message": "Kod weryfikacyjny (TOTP)"
},
diff --git a/apps/web/src/locales/pt_BR/messages.json b/apps/web/src/locales/pt_BR/messages.json
index c08cb330666..ea43fd4eae6 100644
--- a/apps/web/src/locales/pt_BR/messages.json
+++ b/apps/web/src/locales/pt_BR/messages.json
@@ -879,9 +879,6 @@
"filter": {
"message": "Filtro"
},
- "moveSelectedToOrg": {
- "message": "Mover Selecionados para a Organização"
- },
"deleteSelected": {
"message": "Excluir Selecionados"
},
@@ -937,15 +934,6 @@
}
}
},
- "movedItemsToOrg": {
- "message": "Itens selecionados movidos para $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Itens movidos para $ORGNAME$",
"placeholders": {
@@ -1571,9 +1559,6 @@
"moveToOrgDesc": {
"message": "Escolha uma organização para a qual deseja mover este item. Mudar para uma organização transfere a propriedade do item para essa organização. Você não será mais o proprietário direto deste item depois que ele for movido."
},
- "moveManyToOrgDesc": {
- "message": "Escolha uma organização para a qual deseja mover esses itens. Mudar para uma organização transfere a propriedade dos itens para essa organização. Você não será mais o proprietário direto desses itens depois que eles forem movidos."
- },
"collectionsDesc": {
"message": "Edite as coleções com as quais este item está sendo compartilhado. Somente usuários da organização com acesso a estas coleções poderão ver esse item."
},
@@ -1607,23 +1592,6 @@
}
}
},
- "moveSelectedItemsCountDesc": {
- "message": "Você selecionou $COUNT$ item(ns). $MOVEABLE_COUNT$ item(ns) podem ser movidos para uma organização, mas, $NONMOVEABLE_COUNT$ não pode.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "10"
- },
- "moveable_count": {
- "content": "$2",
- "example": "8"
- },
- "nonmoveable_count": {
- "content": "$3",
- "example": "2"
- }
- }
- },
"verificationCodeTotp": {
"message": "Código de verificação (TOTP)"
},
diff --git a/apps/web/src/locales/pt_PT/messages.json b/apps/web/src/locales/pt_PT/messages.json
index 883dcb6d6af..9bbda05c93f 100644
--- a/apps/web/src/locales/pt_PT/messages.json
+++ b/apps/web/src/locales/pt_PT/messages.json
@@ -879,9 +879,6 @@
"filter": {
"message": "Filtrar"
},
- "moveSelectedToOrg": {
- "message": "Mover selecionados para a organização"
- },
"deleteSelected": {
"message": "Eliminar selecionados"
},
@@ -937,15 +934,6 @@
}
}
},
- "movedItemsToOrg": {
- "message": "Itens selecionados movidos para $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Itens movidos para $ORGNAME$",
"placeholders": {
@@ -1571,9 +1559,6 @@
"moveToOrgDesc": {
"message": "Escolha uma organização para a qual pretende mover este item. Mover para uma organização transfere a propriedade do item para essa organização. Deixará de ser o proprietário direto deste item depois de este ter sido movido."
},
- "moveManyToOrgDesc": {
- "message": "Escolha uma organização para a qual pretende mover estes itens. Mover para uma organização transfere a propriedade dos itens para essa organização. Deixará de ser o proprietário direto destes itens depois de terem sido movidos."
- },
"collectionsDesc": {
"message": "Edite as coleções com as quais este item está a ser partilhado. Apenas os utilizadores da organização com acesso a estas coleções poderão ver este item."
},
@@ -1607,23 +1592,6 @@
}
}
},
- "moveSelectedItemsCountDesc": {
- "message": "Selecionou $COUNT$ item(ns). $MOVEABLE_COUNT$ item(ns) podem ser movidos para uma organização, $NONMOVEABLE_COUNT$ não podem.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "10"
- },
- "moveable_count": {
- "content": "$2",
- "example": "8"
- },
- "nonmoveable_count": {
- "content": "$3",
- "example": "2"
- }
- }
- },
"verificationCodeTotp": {
"message": "Código de verificação (TOTP)"
},
diff --git a/apps/web/src/locales/ro/messages.json b/apps/web/src/locales/ro/messages.json
index 00c43cf94f8..c57ad26d251 100644
--- a/apps/web/src/locales/ro/messages.json
+++ b/apps/web/src/locales/ro/messages.json
@@ -879,9 +879,6 @@
"filter": {
"message": "Filter"
},
- "moveSelectedToOrg": {
- "message": "Mutați cele selectate în organizație"
- },
"deleteSelected": {
"message": "Ștergere selecție"
},
@@ -937,15 +934,6 @@
}
}
},
- "movedItemsToOrg": {
- "message": "Articolele selectate au fost mutate în $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Items moved to $ORGNAME$",
"placeholders": {
@@ -1571,9 +1559,6 @@
"moveToOrgDesc": {
"message": "Alegeți o organizație la care doriți să mutați acest articol. Mutarea într-o organizație, transferă proprietatea asupra articolului către organizația respectivă. Nu veți mai fi proprietarul direct al acestui articol odată ce a fost mutat."
},
- "moveManyToOrgDesc": {
- "message": "Alegeți o organizație la care doriți să mutați aceste articole. Mutarea într-o organizație, transferă proprietatea asupra articolelor către organizația respectivă. Nu veți mai fi proprietarul direct al acestor articole odată ce au fost mutate."
- },
"collectionsDesc": {
"message": "Editați colecțiile cu care este partajat acest articol. Numai utilizatorii organizației cu acces la aceste colecții vor putea vedea acest articol."
},
@@ -1607,23 +1592,6 @@
}
}
},
- "moveSelectedItemsCountDesc": {
- "message": "Ați selectat $COUNT$ articol(e). $MOVEABLE_COUNT$ articol(e) poate/pot fi mutat(e) într-o organizație, $NONMOVEABLE_COUNT$ nu.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "10"
- },
- "moveable_count": {
- "content": "$2",
- "example": "8"
- },
- "nonmoveable_count": {
- "content": "$3",
- "example": "2"
- }
- }
- },
"verificationCodeTotp": {
"message": "Cod de verificare (TOTP)"
},
diff --git a/apps/web/src/locales/ru/messages.json b/apps/web/src/locales/ru/messages.json
index 703d3591873..a31d13d227e 100644
--- a/apps/web/src/locales/ru/messages.json
+++ b/apps/web/src/locales/ru/messages.json
@@ -159,10 +159,10 @@
"message": "Всего приложений"
},
"unmarkAsCriticalApp": {
- "message": "Снять пометку критического приложения"
+ "message": "Снять отметку критического приложения"
},
"criticalApplicationSuccessfullyUnmarked": {
- "message": "Критическое приложение успешно снято"
+ "message": "Отметка критического приложения успешно снята"
},
"whatTypeOfItem": {
"message": "Выберите тип элемента"
@@ -879,9 +879,6 @@
"filter": {
"message": "Фильтр"
},
- "moveSelectedToOrg": {
- "message": "Переместить выбранное в организацию"
- },
"deleteSelected": {
"message": "Удалить выбранное"
},
@@ -937,15 +934,6 @@
}
}
},
- "movedItemsToOrg": {
- "message": "Выбранные элементы перемещены в $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Элементы перемещены в $ORGNAME$",
"placeholders": {
@@ -1571,9 +1559,6 @@
"moveToOrgDesc": {
"message": "Выберите организацию, в которую вы хотите переместить этот элемент. При перемещении в организацию право собственности на элемент переходит к этой организации. Вы больше не будете прямым владельцем этого элемента после его перемещения."
},
- "moveManyToOrgDesc": {
- "message": "Выберите организацию, в которую вы хотите переместить эти элементы. При перемещении в организацию право собственности на элементы переходит к этой организации. Вы больше не будете прямым владельцем этих элементов после их перемещения."
- },
"collectionsDesc": {
"message": "Отредактируйте коллекции, с которыми совместно используется этот элемент. Данный элемент смогут видеть только пользователи организации, имеющие доступ к этим коллекциям."
},
@@ -1607,23 +1592,6 @@
}
}
},
- "moveSelectedItemsCountDesc": {
- "message": "Вы выбрали $COUNT$ элемента(-ов). $MOVEABLE_COUNT$ элемента(-ов) могут быть перемещены в организацию, $NONMOVEABLE_COUNT$ не могут.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "10"
- },
- "moveable_count": {
- "content": "$2",
- "example": "8"
- },
- "nonmoveable_count": {
- "content": "$3",
- "example": "2"
- }
- }
- },
"verificationCodeTotp": {
"message": "Код подтверждения (TOTP)"
},
@@ -3936,7 +3904,7 @@
"message": "Устройство"
},
"loginStatus": {
- "message": "Статус входа"
+ "message": "Статус авторизации"
},
"firstLogin": {
"message": "Первый вход"
@@ -7282,7 +7250,7 @@
"message": "Для вашего аккаунта требуется двухэтапная аутентификация Duo."
},
"duoTwoFactorRequiredPageSubtitle": {
- "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in."
+ "message": "Для вашего аккаунта требуется двухэтапная аутентификация Duo. Выполните следующие действия, чтобы завершить авторизацию."
},
"followTheStepsBelowToFinishLoggingIn": {
"message": "Следуйте указаниям ниже, чтобы завершить авторизацию."
@@ -8438,7 +8406,7 @@
"message": "Доверенные устройства"
},
"memberDecryptionOptionTdeDescPart1": {
- "message": "Пользователям не понадобится мастер-пароль при входе в систему с SSO. Мастер-пароль заменяется ключом шифрования, хранящимся на устройстве, который делает его надёжным. Первое устройство, в которое участник создаёт свой аккаунт и входит в первый раз, будет доверенным. Новые устройства должны быть утверждены существующим доверенным устройством или администратором.",
+ "message": "Пользователям не нужен мастер-пароль при авторизации с помощью SSO. Мастер-пароль будет заменен ключом шифрования, хранящимся на устройстве, что сделает это устройство доверенным. Первое устройство, на котором участник создает аккаунт и авторизуется, будет доверенным. Новые устройства должны быть одобрены существующим доверенным устройством или администратором.",
"description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'"
},
"memberDecryptionOptionTdeDescLink1": {
@@ -8458,11 +8426,11 @@
"description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'"
},
"memberDecryptionOptionTdeDescLink3": {
- "message": "account recovery administration",
+ "message": "политика администрирования восстановления аккаунтов",
"description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'"
},
"memberDecryptionOptionTdeDescPart4": {
- "message": "policy will turn on when this option is used.",
+ "message": "будет включена при использовании этой опции.",
"description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'"
},
"orgPermissionsUpdatedMustSetPassword": {
@@ -8759,7 +8727,7 @@
"message": "Ограничить удаление коллекций владельцам и администраторам"
},
"limitItemDeletionDesc": {
- "message": "Ограничить удаление элементов для пользователей с разрешением «Может управлять»"
+ "message": "Ограничить удаление элементов для пользователей с разрешением 'Может управлять'"
},
"allowAdminAccessToAllCollectionItemsDesc": {
"message": "Владельцы и администраторы могут управлять всеми коллекциями и элементами"
@@ -10501,7 +10469,7 @@
}
},
"accountDeprovisioningNotification": {
- "message": "Администраторы теперь могут удалять аккаунты пользователей, принадлежащие заявленному домену."
+ "message": "Администраторы теперь могут удалять аккаунты пользователей, принадлежащие зарегистрированному домену."
},
"deleteManagedUserWarningDesc": {
"message": "Это действие удалит аккаунт пользователя, включая все элементы в его хранилище. Это действие заменяет предыдущее действие Удалить."
@@ -10532,7 +10500,7 @@
"message": "Нет организаций для отображения"
},
"yourProviderSubscriptionCredit": {
- "message": "Your provider subscription will receive a credit for any remaining time in the organization's subscription."
+ "message": "Ваш провайдер получит кредит на любое время, оставшееся в подписке организации."
},
"doYouWantToAddThisOrg": {
"message": "Вы хотите добавить эту организацию в $PROVIDER$?",
@@ -10547,10 +10515,10 @@
"message": "Добавлена существующая организация"
},
"assignedExceedsAvailable": {
- "message": "Assigned seats exceed available seats."
+ "message": "Количество назначенных мест превышает количество доступных."
},
"changeAtRiskPassword": {
- "message": "Изменить пароль, находящийся под угрозой"
+ "message": "Изменить пароль, подверженный риску"
},
"removeUnlockWithPinPolicyTitle": {
"message": "Отключить разблокировку PIN-кодом"
@@ -10559,7 +10527,7 @@
"message": "Не разрешать пользователям разблокировать свои аккаунты с помощью PIN-кода."
},
"limitedEventLogs": {
- "message": "Планы $PRODUCT_TYPE$ не имеют доступа к реальным журналам событий",
+ "message": "Планы $PRODUCT_TYPE$ не имеют доступа к журналам текущих событий",
"placeholders": {
"product_type": {
"content": "$1",
@@ -10571,10 +10539,10 @@
"message": "Получите полный доступ к журналам событий организации, перейдя на план Teams или Enterprise."
},
"upgradeEventLogTitle": {
- "message": "Upgrade for real event log data"
+ "message": "Переудите на более высокий тариф для просмотра журнала реальных событий"
},
"upgradeEventLogMessage": {
- "message": "Эти события являются лишь примерами и не отражают реальных событий в вашей организации Bitwarden."
+ "message": "Эти события являются лишь примерами и не отражают текущих событий вашей организации Bitwarden."
},
"cannotCreateCollection": {
"message": "В бесплатных организациях может быть до 2 коллекций. Перейдите на платный план, чтобы добавить больше коллекций."
diff --git a/apps/web/src/locales/si/messages.json b/apps/web/src/locales/si/messages.json
index 4dd1d214bfb..fc79da1352b 100644
--- a/apps/web/src/locales/si/messages.json
+++ b/apps/web/src/locales/si/messages.json
@@ -879,9 +879,6 @@
"filter": {
"message": "Filter"
},
- "moveSelectedToOrg": {
- "message": "Move selected to organization"
- },
"deleteSelected": {
"message": "Delete selected"
},
@@ -937,15 +934,6 @@
}
}
},
- "movedItemsToOrg": {
- "message": "Selected items moved to $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Items moved to $ORGNAME$",
"placeholders": {
@@ -1571,9 +1559,6 @@
"moveToOrgDesc": {
"message": "Choose an organization that you wish to move this item to. Moving to an organization transfers ownership of the item to that organization. You will no longer be the direct owner of this item once it has been moved."
},
- "moveManyToOrgDesc": {
- "message": "Choose an organization that you wish to move these items to. Moving to an organization transfers ownership of the items to that organization. You will no longer be the direct owner of these items once they have been moved."
- },
"collectionsDesc": {
"message": "Edit the collections that this item is being shared with. Only organization users with access to these collections will be able to see this item."
},
@@ -1607,23 +1592,6 @@
}
}
},
- "moveSelectedItemsCountDesc": {
- "message": "You have selected $COUNT$ item(s). $MOVEABLE_COUNT$ item(s) can be moved to an organization, $NONMOVEABLE_COUNT$ cannot.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "10"
- },
- "moveable_count": {
- "content": "$2",
- "example": "8"
- },
- "nonmoveable_count": {
- "content": "$3",
- "example": "2"
- }
- }
- },
"verificationCodeTotp": {
"message": "Verification code (TOTP)"
},
diff --git a/apps/web/src/locales/sk/messages.json b/apps/web/src/locales/sk/messages.json
index ca15d6715eb..fdf57456a4c 100644
--- a/apps/web/src/locales/sk/messages.json
+++ b/apps/web/src/locales/sk/messages.json
@@ -879,9 +879,6 @@
"filter": {
"message": "Filter"
},
- "moveSelectedToOrg": {
- "message": "Presunúť vybraté do organizácie"
- },
"deleteSelected": {
"message": "Odstrániť vybrané"
},
@@ -937,15 +934,6 @@
}
}
},
- "movedItemsToOrg": {
- "message": "Vybraté položky boli presunuté do $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Položky presunuté do $ORGNAME$",
"placeholders": {
@@ -1571,9 +1559,6 @@
"moveToOrgDesc": {
"message": "Vyberte organizáciu, do ktorej chcete presunúť túto položku. Presunom do organizácie sa vlastníctvo položky prenáša na túto organizáciu. Po presunutí už nebudete priamym vlastníkom danej položky."
},
- "moveManyToOrgDesc": {
- "message": "Vyberte organizáciu, do ktorej chcete presunúť tieto položky. Presunom do organizácie sa vlastníctvo položky prenáša na túto organizáciu. Po presunutí už nebudete priamym vlastníkom daných položiek."
- },
"collectionsDesc": {
"message": "Upravte zbierky s ktorými bude táto položka zdieľaná. Iba členovia organizácie s prístupom k vybraným zbierkam budú vidieť túto položku."
},
@@ -1607,23 +1592,6 @@
}
}
},
- "moveSelectedItemsCountDesc": {
- "message": "Vybrali ste $COUNT$ položku(iek). $MOVEABLE_COUNT$ položka(iek) môže(u) byť presunuté do organizácie, $NONMOVEABLE_COUNT$ nemôže(u).",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "10"
- },
- "moveable_count": {
- "content": "$2",
- "example": "8"
- },
- "nonmoveable_count": {
- "content": "$3",
- "example": "2"
- }
- }
- },
"verificationCodeTotp": {
"message": "Overovací kód (TOTP)"
},
diff --git a/apps/web/src/locales/sl/messages.json b/apps/web/src/locales/sl/messages.json
index 575937084ae..1652841b650 100644
--- a/apps/web/src/locales/sl/messages.json
+++ b/apps/web/src/locales/sl/messages.json
@@ -879,9 +879,6 @@
"filter": {
"message": "Filter"
},
- "moveSelectedToOrg": {
- "message": "Premakni označeno v organizacijo"
- },
"deleteSelected": {
"message": "Izbriši izbrano"
},
@@ -937,15 +934,6 @@
}
}
},
- "movedItemsToOrg": {
- "message": "Izbrani elementi premaknjeni v $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Items moved to $ORGNAME$",
"placeholders": {
@@ -1571,9 +1559,6 @@
"moveToOrgDesc": {
"message": "Izberite organizacijo, v katero želite premakniti ta element. To bo preneslo lasništvo elementa na to organizacijo. Potem ne boste več neposredni lastnik tega elementa."
},
- "moveManyToOrgDesc": {
- "message": "Izberite organizacijo, v katero želite premakniti te elemente. To bo preneslo lasništvo elementov na to organizacijo. Potem ne boste več neposredni lastnik teh elementov."
- },
"collectionsDesc": {
"message": "Uredite zbirke s katerimi želite deliti ta predmet. Predmet bodo lahko videli le uporabniki orgnanizacije, ki bodo imajo dostop do teh zbirk."
},
@@ -1607,23 +1592,6 @@
}
}
},
- "moveSelectedItemsCountDesc": {
- "message": "Izbrali ste toliko elementov: $COUNT$. Toliko jih je mogoče premakniti v organizacijo: $MOVEABLE_COUNT$, toliko pa ne: $NONMOVEABLE_COUNT$.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "10"
- },
- "moveable_count": {
- "content": "$2",
- "example": "8"
- },
- "nonmoveable_count": {
- "content": "$3",
- "example": "2"
- }
- }
- },
"verificationCodeTotp": {
"message": "Verifikacijska koda (TOTP)"
},
diff --git a/apps/web/src/locales/sr/messages.json b/apps/web/src/locales/sr/messages.json
index b3dc1bd687f..005fa7602b5 100644
--- a/apps/web/src/locales/sr/messages.json
+++ b/apps/web/src/locales/sr/messages.json
@@ -879,9 +879,6 @@
"filter": {
"message": "Филтер"
},
- "moveSelectedToOrg": {
- "message": "Премести одабрано у организацију"
- },
"deleteSelected": {
"message": "Избриши изабрано"
},
@@ -937,15 +934,6 @@
}
}
},
- "movedItemsToOrg": {
- "message": "Одабране ставке премештене у $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Ставке премештене у $ORGNAME$",
"placeholders": {
@@ -1571,9 +1559,6 @@
"moveToOrgDesc": {
"message": "Изаберите организацију коју желите да преместите овај предмет. Прелазак на организацију преноси власништво над ставком у ту организацију. Више нећете бити директни власник ове ставке након што је премештена."
},
- "moveManyToOrgDesc": {
- "message": "Изаберите организацију коју желите да преместите ове ставке. Прелазак на организацију преноси власништво над ставкама у ту организацију. Више нећете бити директни власник ове ставки након што су премештене."
- },
"collectionsDesc": {
"message": "Уредите колекције са којима се ова ставка дели. Само корисници организације који имају приступ овим колекцијама моћи ће да виде ову ставку."
},
@@ -1607,23 +1592,6 @@
}
}
},
- "moveSelectedItemsCountDesc": {
- "message": "Одабрали сте $COUNT$ ставку(и). $MOVEABLE_COUNT$ ставка(и) може да се преместе у организацију, $NONMOVEABLE_COUNT$ не.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "10"
- },
- "moveable_count": {
- "content": "$2",
- "example": "8"
- },
- "nonmoveable_count": {
- "content": "$3",
- "example": "2"
- }
- }
- },
"verificationCodeTotp": {
"message": "Једнократни код"
},
diff --git a/apps/web/src/locales/sr_CS/messages.json b/apps/web/src/locales/sr_CS/messages.json
index 04f63f6e0e7..d141d8301fc 100644
--- a/apps/web/src/locales/sr_CS/messages.json
+++ b/apps/web/src/locales/sr_CS/messages.json
@@ -879,9 +879,6 @@
"filter": {
"message": "Filter"
},
- "moveSelectedToOrg": {
- "message": "Move selected to organization"
- },
"deleteSelected": {
"message": "Obriši izabrane stavke"
},
@@ -937,15 +934,6 @@
}
}
},
- "movedItemsToOrg": {
- "message": "Selected items moved to $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Items moved to $ORGNAME$",
"placeholders": {
@@ -1571,9 +1559,6 @@
"moveToOrgDesc": {
"message": "Choose an organization that you wish to move this item to. Moving to an organization transfers ownership of the item to that organization. You will no longer be the direct owner of this item once it has been moved."
},
- "moveManyToOrgDesc": {
- "message": "Choose an organization that you wish to move these items to. Moving to an organization transfers ownership of the items to that organization. You will no longer be the direct owner of these items once they have been moved."
- },
"collectionsDesc": {
"message": "Edit the collections that this item is being shared with. Only organization users with access to these collections will be able to see this item."
},
@@ -1607,23 +1592,6 @@
}
}
},
- "moveSelectedItemsCountDesc": {
- "message": "You have selected $COUNT$ item(s). $MOVEABLE_COUNT$ item(s) can be moved to an organization, $NONMOVEABLE_COUNT$ cannot.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "10"
- },
- "moveable_count": {
- "content": "$2",
- "example": "8"
- },
- "nonmoveable_count": {
- "content": "$3",
- "example": "2"
- }
- }
- },
"verificationCodeTotp": {
"message": "Verification code (TOTP)"
},
diff --git a/apps/web/src/locales/sv/messages.json b/apps/web/src/locales/sv/messages.json
index b9b9f5a2dc5..f7d6414c36c 100644
--- a/apps/web/src/locales/sv/messages.json
+++ b/apps/web/src/locales/sv/messages.json
@@ -879,9 +879,6 @@
"filter": {
"message": "Filter"
},
- "moveSelectedToOrg": {
- "message": "Flytta valda till organisation"
- },
"deleteSelected": {
"message": "Radera markerade"
},
@@ -937,15 +934,6 @@
}
}
},
- "movedItemsToOrg": {
- "message": "De valda objekten flyttades till $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Items moved to $ORGNAME$",
"placeholders": {
@@ -1571,9 +1559,6 @@
"moveToOrgDesc": {
"message": "Välj en organisation som du vill flytta detta objektet till. Flytt till en organisation överför ägandet av objektet till den organisationen. Du kommer inte längre att vara direkt ägare till detta objekt när det har flyttats."
},
- "moveManyToOrgDesc": {
- "message": "Välj en organisation som du vill flytta dessa objekt till. Flytt till en organisation överför ägandet av objekten till den organisationen. Du kommer inte längre att vara direkt ägare till dessa objekt när de har flyttats."
- },
"collectionsDesc": {
"message": "Redigera de samlingar som detta objekt delas med. Endast organisationsanvändare med tillgång till dessa samlingar kommer att kunna se detta objekt."
},
@@ -1607,23 +1592,6 @@
}
}
},
- "moveSelectedItemsCountDesc": {
- "message": "Du har valt $COUNT$ objekt. $MOVEABLE_COUNT$ objekt kan flyttas till en organisation, $NONMOVEABLE_COUNT$ kan det inte.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "10"
- },
- "moveable_count": {
- "content": "$2",
- "example": "8"
- },
- "nonmoveable_count": {
- "content": "$3",
- "example": "2"
- }
- }
- },
"verificationCodeTotp": {
"message": "Verifieringskod (TOTP)"
},
diff --git a/apps/web/src/locales/te/messages.json b/apps/web/src/locales/te/messages.json
index 9105ec373c3..f334131b69b 100644
--- a/apps/web/src/locales/te/messages.json
+++ b/apps/web/src/locales/te/messages.json
@@ -879,9 +879,6 @@
"filter": {
"message": "Filter"
},
- "moveSelectedToOrg": {
- "message": "Move selected to organization"
- },
"deleteSelected": {
"message": "Delete selected"
},
@@ -937,15 +934,6 @@
}
}
},
- "movedItemsToOrg": {
- "message": "Selected items moved to $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Items moved to $ORGNAME$",
"placeholders": {
@@ -1571,9 +1559,6 @@
"moveToOrgDesc": {
"message": "Choose an organization that you wish to move this item to. Moving to an organization transfers ownership of the item to that organization. You will no longer be the direct owner of this item once it has been moved."
},
- "moveManyToOrgDesc": {
- "message": "Choose an organization that you wish to move these items to. Moving to an organization transfers ownership of the items to that organization. You will no longer be the direct owner of these items once they have been moved."
- },
"collectionsDesc": {
"message": "Edit the collections that this item is being shared with. Only organization users with access to these collections will be able to see this item."
},
@@ -1607,23 +1592,6 @@
}
}
},
- "moveSelectedItemsCountDesc": {
- "message": "You have selected $COUNT$ item(s). $MOVEABLE_COUNT$ item(s) can be moved to an organization, $NONMOVEABLE_COUNT$ cannot.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "10"
- },
- "moveable_count": {
- "content": "$2",
- "example": "8"
- },
- "nonmoveable_count": {
- "content": "$3",
- "example": "2"
- }
- }
- },
"verificationCodeTotp": {
"message": "Verification code (TOTP)"
},
diff --git a/apps/web/src/locales/th/messages.json b/apps/web/src/locales/th/messages.json
index 737ecf580cd..7d2685807b3 100644
--- a/apps/web/src/locales/th/messages.json
+++ b/apps/web/src/locales/th/messages.json
@@ -879,9 +879,6 @@
"filter": {
"message": "Filter"
},
- "moveSelectedToOrg": {
- "message": "Move selected to organization"
- },
"deleteSelected": {
"message": "ลบรายการที่เลือก"
},
@@ -937,15 +934,6 @@
}
}
},
- "movedItemsToOrg": {
- "message": "Selected items moved to $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Items moved to $ORGNAME$",
"placeholders": {
@@ -1571,9 +1559,6 @@
"moveToOrgDesc": {
"message": "เลือกองค์กรที่คุณต้องการย้ายรายการนี้ไป การย้ายไปยังองค์กรจะโอนความเป็นเจ้าของรายการไปยังองค์กรนั้น คุณจะไม่ได้เป็นเจ้าของโดยตรงของรายการนี้อีกต่อไปเมื่อมีการย้ายแล้ว"
},
- "moveManyToOrgDesc": {
- "message": "Choose an organization that you wish to move these items to. Moving to an organization transfers ownership of the items to that organization. You will no longer be the direct owner of these items once they have been moved."
- },
"collectionsDesc": {
"message": "Edit the collections that this item is being shared with. Only organization users with access to these collections will be able to see this item."
},
@@ -1607,23 +1592,6 @@
}
}
},
- "moveSelectedItemsCountDesc": {
- "message": "You have selected $COUNT$ item(s). $MOVEABLE_COUNT$ item(s) can be moved to an organization, $NONMOVEABLE_COUNT$ cannot.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "10"
- },
- "moveable_count": {
- "content": "$2",
- "example": "8"
- },
- "nonmoveable_count": {
- "content": "$3",
- "example": "2"
- }
- }
- },
"verificationCodeTotp": {
"message": "Verification code (TOTP)"
},
diff --git a/apps/web/src/locales/tr/messages.json b/apps/web/src/locales/tr/messages.json
index d752d2886d1..030fd0dd9b2 100644
--- a/apps/web/src/locales/tr/messages.json
+++ b/apps/web/src/locales/tr/messages.json
@@ -879,9 +879,6 @@
"filter": {
"message": "Filtre"
},
- "moveSelectedToOrg": {
- "message": "Seçilenleri kuruluşa taşı"
- },
"deleteSelected": {
"message": "Seçilenleri sil"
},
@@ -937,15 +934,6 @@
}
}
},
- "movedItemsToOrg": {
- "message": "Seçilen kayıtlar $ORGNAME$ kuruluşuna taşındı",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Kayıtlar $ORGNAME$ kuruluşuna taşındı",
"placeholders": {
@@ -1571,9 +1559,6 @@
"moveToOrgDesc": {
"message": "Bu kaydı taşımak istediğiniz kuruluşu seçin. Taşıdığınız kaydın sahipliği seçtiğiniz kuruluşa aktarılacak. Artık bu kaydın doğrudan sahibi olmayacaksınız."
},
- "moveManyToOrgDesc": {
- "message": "Bu kayıtları taşımak istediğiniz kuruluşu seçin. Taşıdığınız kayıtların sahipliği seçtiğiniz kuruluşa aktarılacak. Artık bu kayıtların doğrudan sahibi olmayacaksınız."
- },
"collectionsDesc": {
"message": "Bu kaydın şu anda paylaşıldığı koleksiyonları düzenler. Kuruluştaki kullanıcılardan yalnızca bu koleksiyonlara erişimi olanlar bu kaydı görebilir."
},
@@ -1607,23 +1592,6 @@
}
}
},
- "moveSelectedItemsCountDesc": {
- "message": "$COUNT$ kayıt seçtiniz. $MOVEABLE_COUNT$ kayıt bir kuruluşa taşınabilir, $NONMOVEABLE_COUNT$ kayıt taşınamaz.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "10"
- },
- "moveable_count": {
- "content": "$2",
- "example": "8"
- },
- "nonmoveable_count": {
- "content": "$3",
- "example": "2"
- }
- }
- },
"verificationCodeTotp": {
"message": "Doğrulama kodu (TOTP)"
},
diff --git a/apps/web/src/locales/uk/messages.json b/apps/web/src/locales/uk/messages.json
index 399627a49b3..7d9eb316821 100644
--- a/apps/web/src/locales/uk/messages.json
+++ b/apps/web/src/locales/uk/messages.json
@@ -879,9 +879,6 @@
"filter": {
"message": "Фільтр"
},
- "moveSelectedToOrg": {
- "message": "Перемістити вибране до організації"
- },
"deleteSelected": {
"message": "Видалити вибране"
},
@@ -937,15 +934,6 @@
}
}
},
- "movedItemsToOrg": {
- "message": "Вибрані записи переміщено до $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Записи переміщено до $ORGNAME$",
"placeholders": {
@@ -1571,9 +1559,6 @@
"moveToOrgDesc": {
"message": "Виберіть організацію, до якої ви бажаєте перемістити цей запис. Переміщуючи до організації, власність запису передається тій організації. Ви більше не будете єдиним власником цього запису після переміщення."
},
- "moveManyToOrgDesc": {
- "message": "Виберіть організацію, до якої ви бажаєте перемістити ці записи. Переміщуючи до організації, власність записів передається тій організації. Ви більше не будете єдиним власником цих записів після переміщення."
- },
"collectionsDesc": {
"message": "Редагуйте збірки, з якими цей запис знаходиться в спільному доступі. Лише учасники організацій з доступом до цих збірок матимуть можливість бачити цей запис."
},
@@ -1607,23 +1592,6 @@
}
}
},
- "moveSelectedItemsCountDesc": {
- "message": "Ви вибрали $COUNT$ запис(ів). $MOVEABLE_COUNT$ запис(ів) можна перемістити до організації, $NONMOVEABLE_COUNT$ не можна.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "10"
- },
- "moveable_count": {
- "content": "$2",
- "example": "8"
- },
- "nonmoveable_count": {
- "content": "$3",
- "example": "2"
- }
- }
- },
"verificationCodeTotp": {
"message": "Код підтвердження (TOTP)"
},
@@ -10435,11 +10403,11 @@
"message": "Розширення браузера Bitwarden успішно відкрито. Тепер ви можете переглянути свої ризиковані паролі."
},
"openExtensionManuallyPart1": {
- "message": "We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon",
+ "message": "Виникли проблеми з відкриттям розширення браузера Bitwarden. Відкрийте піктограму Bitwarden",
"description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'"
},
"openExtensionManuallyPart2": {
- "message": "from the toolbar.",
+ "message": "з панелі інструментів.",
"description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'"
},
"resellerRenewalWarningMsg": {
@@ -10501,16 +10469,16 @@
}
},
"accountDeprovisioningNotification": {
- "message": "Administrators now have the ability to delete member accounts that belong to a claimed domain."
+ "message": "Адміністратори тепер мають можливість видаляти облікові записи учасників, що належать до заявленого домену."
},
"deleteManagedUserWarningDesc": {
- "message": "This action will delete the member account including all items in their vault. This replaces the previous Remove action."
+ "message": "Ця дія видалить обліковий запис учасника, включно з усіма записами в сховищі. Ця функція замінює попередню дію вилучення."
},
"deleteManagedUserWarning": {
- "message": "Delete is a new action!"
+ "message": "Видалення – нова дія!"
},
"seatsRemaining": {
- "message": "You have $REMAINING$ seats remaining out of $TOTAL$ seats assigned to this organization. Contact your provider to manage your subscription.",
+ "message": "У вас залишилося $REMAINING$ місць із загальної кількості ($TOTAL$) місць, призначених цій організації. Зверніться до свого провайдера, щоб керувати передплатою.",
"placeholders": {
"remaining": {
"content": "$1",
@@ -10523,19 +10491,19 @@
}
},
"existingOrganization": {
- "message": "Existing organization"
+ "message": "Наявна організація"
},
"selectOrganizationProviderPortal": {
- "message": "Select an organization to add to your Provider Portal."
+ "message": "Виберіть організацію, щоб додати до вашого порталу провайдера."
},
"noOrganizations": {
- "message": "There are no organizations to list"
+ "message": "Немає організацій для показу"
},
"yourProviderSubscriptionCredit": {
- "message": "Your provider subscription will receive a credit for any remaining time in the organization's subscription."
+ "message": "Ваш провайдер отримає кредит за будь-який час, що залишився у передплаті організації."
},
"doYouWantToAddThisOrg": {
- "message": "Do you want to add this organization to $PROVIDER$?",
+ "message": "Ви хочете додати цю організацію до $PROVIDER$?",
"placeholders": {
"provider": {
"content": "$1",
@@ -10544,22 +10512,22 @@
}
},
"addedExistingOrganization": {
- "message": "Added existing organization"
+ "message": "Додано наявну організацію"
},
"assignedExceedsAvailable": {
- "message": "Assigned seats exceed available seats."
+ "message": "Призначені місця перевищують доступні місця."
},
"changeAtRiskPassword": {
"message": "Змінити ризикований пароль"
},
"removeUnlockWithPinPolicyTitle": {
- "message": "Remove Unlock with PIN"
+ "message": "Вилучити розблокування з PIN-кодом"
},
"removeUnlockWithPinPolicyDesc": {
- "message": "Do not allow members to unlock their account with a PIN."
+ "message": "Не дозволяти учасникам розблокувати свій обліковий запис за допомогою PIN-коду."
},
"limitedEventLogs": {
- "message": "$PRODUCT_TYPE$ plans do not have access to real event logs",
+ "message": "Тарифні плани $PRODUCT_TYPE$ не мають доступу до реальних журналів подій",
"placeholders": {
"product_type": {
"content": "$1",
@@ -10568,15 +10536,15 @@
}
},
"upgradeForFullEvents": {
- "message": "Get full access to organization event logs by upgrading to a Teams or Enterprise plan."
+ "message": "Отримайте повний доступ до журналу подій організації, передплативши тарифний план Teams або Enterprise."
},
"upgradeEventLogTitle": {
- "message": "Upgrade for real event log data"
+ "message": "Оновіть тарифний план, щоб переглядати реальні дані журналу подій"
},
"upgradeEventLogMessage": {
- "message": "These events are examples only and do not reflect real events within your Bitwarden organization."
+ "message": "Ці події є лише зразками. Вони не відображають реальних подій у вашій організації Bitwarden."
},
"cannotCreateCollection": {
- "message": "Free organizations may have up to 2 collections. Upgrade to a paid plan to add more collections."
+ "message": "Безплатні організації можуть мати до 2 збірок. Передплатіть тарифний план, щоб додати більше збірок."
}
}
diff --git a/apps/web/src/locales/vi/messages.json b/apps/web/src/locales/vi/messages.json
index 2bd2af7c882..f0a0215f823 100644
--- a/apps/web/src/locales/vi/messages.json
+++ b/apps/web/src/locales/vi/messages.json
@@ -879,9 +879,6 @@
"filter": {
"message": "Bộ lọc"
},
- "moveSelectedToOrg": {
- "message": "Chuyển mục đã chọn tới Tổ chức"
- },
"deleteSelected": {
"message": "Xóa mục đã chọn"
},
@@ -937,15 +934,6 @@
}
}
},
- "movedItemsToOrg": {
- "message": "Đã di chuyển các mục đã chọn đến $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Items moved to $ORGNAME$",
"placeholders": {
@@ -1571,9 +1559,6 @@
"moveToOrgDesc": {
"message": "Choose an organization that you wish to move this item to. Moving to an organization transfers ownership of the item to that organization. You will no longer be the direct owner of this item once it has been moved."
},
- "moveManyToOrgDesc": {
- "message": "Choose an organization that you wish to move these items to. Moving to an organization transfers ownership of the items to that organization. You will no longer be the direct owner of these items once they have been moved."
- },
"collectionsDesc": {
"message": "Chỉnh sửa những bộ sưu tập mà bạn có chia sẻ mục này. Chỉ những thành viên của tổ chức có quyền quản lý những bộ sưu tập đó mới có thể xem được mục này."
},
@@ -1607,23 +1592,6 @@
}
}
},
- "moveSelectedItemsCountDesc": {
- "message": "You have selected $COUNT$ item(s). $MOVEABLE_COUNT$ item(s) can be moved to an organization, $NONMOVEABLE_COUNT$ cannot.",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "10"
- },
- "moveable_count": {
- "content": "$2",
- "example": "8"
- },
- "nonmoveable_count": {
- "content": "$3",
- "example": "2"
- }
- }
- },
"verificationCodeTotp": {
"message": "Mã xác thực (TOTP)"
},
diff --git a/apps/web/src/locales/zh_CN/messages.json b/apps/web/src/locales/zh_CN/messages.json
index ac9e80e0b25..08a610c47ce 100644
--- a/apps/web/src/locales/zh_CN/messages.json
+++ b/apps/web/src/locales/zh_CN/messages.json
@@ -879,9 +879,6 @@
"filter": {
"message": "筛选"
},
- "moveSelectedToOrg": {
- "message": "移动所选到组织"
- },
"deleteSelected": {
"message": "删除所选"
},
@@ -937,15 +934,6 @@
}
}
},
- "movedItemsToOrg": {
- "message": "所选项目已移动到 $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "项目已移动到 $ORGNAME$",
"placeholders": {
@@ -1571,9 +1559,6 @@
"moveToOrgDesc": {
"message": "选择一个您想将此项目移至的组织。移动到组织会将该项目的所有权转让给该组织。移动后,您将不再是此项目的直接所有者。"
},
- "moveManyToOrgDesc": {
- "message": "选择一个您想将这些项目移至的组织。移动到组织会将这些项目的所有权转让给该组织。移动后,您将不再是这些项目的直接所有者。"
- },
"collectionsDesc": {
"message": "编辑与其共享此项目的集合。只有具有这些集合访问权限的组织用户才能看到此项目。"
},
@@ -1607,23 +1592,6 @@
}
}
},
- "moveSelectedItemsCountDesc": {
- "message": "您选择了 $COUNT$ 个项目。$MOVEABLE_COUNT$ 个项目可以移动到组织,$NONMOVEABLE_COUNT$ 不能。",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "10"
- },
- "moveable_count": {
- "content": "$2",
- "example": "8"
- },
- "nonmoveable_count": {
- "content": "$3",
- "example": "2"
- }
- }
- },
"verificationCodeTotp": {
"message": "验证码 (TOTP)"
},
@@ -2723,7 +2691,7 @@
"description": "Add more credit to your account's balance."
},
"amount": {
- "message": "合计",
+ "message": "金额",
"description": "Dollar amount, or quantity."
},
"creditDelayed": {
@@ -3030,7 +2998,7 @@
"message": "移除存储空间将会调整计费总金额,这笔调整费用将按比例作为信用额度抵扣您的下一笔账单费用。"
},
"adjustedStorage": {
- "message": "已调整 $AMOUNT$ GB 的存储空间。",
+ "message": "调整了 $AMOUNT$ GB 存储空间。",
"placeholders": {
"amount": {
"content": "$1",
@@ -6112,7 +6080,7 @@
"message": "为允许的应用程序自动登录用户"
},
"automaticAppLoginDesc": {
- "message": "从您配置的身份提供程序启动的 App 的登录表单将自动填写并提交。"
+ "message": "从您配置的身份提供程序启动的 App 的登录表单将自动填充并提交。"
},
"automaticAppLoginIdpHostLabel": {
"message": "身份提供程序主机"
@@ -7195,7 +7163,7 @@
"message": "输入的不是电子邮箱地址。"
},
"inputMinLength": {
- "message": "至少输入 $COUNT$ 个字符。",
+ "message": "输入长度不能低于 $COUNT$ 个字符。",
"placeholders": {
"count": {
"content": "$1",
@@ -7863,7 +7831,7 @@
"message": "自动域名验证"
},
"automaticDomainVerificationProcess": {
- "message": "Bitwarden 将尝试在 72 小时内验证此域名 3 次。如果此域名无法验证,请检查您的主机中的 DNS 记录并手动验证。如果此域名未通过验证,7 天内将从您的组织中移除。"
+ "message": "Bitwarden 将在最初的 72 小时内尝试验证域名 3 次。如果此域名无法验证,请检查您主机中的 DNS 记录并手动验证。如果此域名在 7 天内未验证,它将被从您的组织中移除。"
},
"invalidDomainNameMessage": {
"message": "输入的格式无效。格式:mydomain.com。子域名需要单独的条目进行验证。"
@@ -7953,7 +7921,7 @@
}
},
"domainNotVerifiedEvent": {
- "message": "$DOMAIN$ 无法验证",
+ "message": "$DOMAIN$ 未验证",
"placeholders": {
"DOMAIN": {
"content": "$1",
@@ -10305,16 +10273,16 @@
"message": "重新声明域名"
},
"claimDomainNameInputHint": {
- "message": "示例:mydomain.com。子域名需要单独的条目才能声明。"
+ "message": "示例:mydomain.com。子域名需要单独的条目进行声明。"
},
"automaticClaimedDomains": {
"message": "自动声明域名"
},
"automaticDomainClaimProcess": {
- "message": "Bitwarden 将在前 72 小时内尝试声明域名 3 次。如果无法声明此域名,请检查主机中的 DNS 记录并手动声明。如果未声明,该域名将在 7 天内从您的组织中移除。"
+ "message": "Bitwarden 将在最初的 72 小时内尝试声明域名 3 次。如果此域名无法声明,请检查您主机中的 DNS 记录并手动声明。如果此域名在 7 天内未声明,它将被从您的组织中移除。"
},
"domainNotClaimed": {
- "message": "$DOMAIN$ 未声明。请检查 DNS 记录。",
+ "message": "$DOMAIN$ 无法声明。请检查您的 DNS 记录。",
"placeholders": {
"DOMAIN": {
"content": "$1",
@@ -10332,7 +10300,7 @@
"message": "声明一个域名,以拥有电子邮箱地址与该域名匹配的所有成员账户。成员登录时将可以跳过 SSO 标识符。管理员也可以删除成员账户。"
},
"invalidDomainNameClaimMessage": {
- "message": "输入的格式无效。格式:mydomain.com。子域名需要单独的条目才能声明。"
+ "message": "输入的格式无效。格式:mydomain.com。子域名需要单独的条目进行声明。"
},
"domainClaimedEvent": {
"message": "$DOMAIN$ 已声明",
diff --git a/apps/web/src/locales/zh_TW/messages.json b/apps/web/src/locales/zh_TW/messages.json
index 6631ee1264c..beca8f74485 100644
--- a/apps/web/src/locales/zh_TW/messages.json
+++ b/apps/web/src/locales/zh_TW/messages.json
@@ -879,9 +879,6 @@
"filter": {
"message": "篩選"
},
- "moveSelectedToOrg": {
- "message": "移動所選至組織"
- },
"deleteSelected": {
"message": "刪除所選"
},
@@ -937,15 +934,6 @@
}
}
},
- "movedItemsToOrg": {
- "message": "將已選取項目移動至 $ORGNAME$",
- "placeholders": {
- "orgname": {
- "content": "$1",
- "example": "Company Name"
- }
- }
- },
"itemsMovedToOrg": {
"message": "Items moved to $ORGNAME$",
"placeholders": {
@@ -1571,9 +1559,6 @@
"moveToOrgDesc": {
"message": "選擇您希望將這個項目移動至哪個組織。項目的擁有權將會轉移至該組織。轉移之後,您將不再是此項目的直接擁有者。"
},
- "moveManyToOrgDesc": {
- "message": "選擇您希望將這些項目移動至哪個組織。項目的擁有權將會轉移至該組織。轉移之後,您將不再是這些項目的直接擁有者。"
- },
"collectionsDesc": {
"message": "編輯與此項目共享的集合。只有具有這些集合存取權限的組織使用者才能夠看到此項目。"
},
@@ -1607,23 +1592,6 @@
}
}
},
- "moveSelectedItemsCountDesc": {
- "message": "你選擇了 $COUNT$ 個項目。當中有 $MOVEABLE_COUNT$ 個項目可移動到組織當中,而有 $NONMOVEABLE_COUNT$ 個無法移動。",
- "placeholders": {
- "count": {
- "content": "$1",
- "example": "10"
- },
- "moveable_count": {
- "content": "$2",
- "example": "8"
- },
- "nonmoveable_count": {
- "content": "$3",
- "example": "2"
- }
- }
- },
"verificationCodeTotp": {
"message": "驗證碼 (TOTP)"
},
From 4c4019c35fd47ab8e4641cd417d170a17b305a0c Mon Sep 17 00:00:00 2001
From: Jason Ng
Date: Wed, 19 Mar 2025 14:49:03 -0400
Subject: [PATCH 013/113] [PM]19342] Onboarding Nudges Feature Flag (#13906)
---
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 dda27d202de..280e8d8a989 100644
--- a/libs/common/src/enums/feature-flag.enum.ts
+++ b/libs/common/src/enums/feature-flag.enum.ts
@@ -29,6 +29,7 @@ export enum FeatureFlag {
ExportAttachments = "export-attachments",
/* Vault */
+ PM8851_BrowserOnboardingNudge = "pm-8851-browser-onboarding-nudge",
PM9111ExtensionPersistAddEditForm = "pm-9111-extension-persist-add-edit-form",
NewDeviceVerificationTemporaryDismiss = "new-device-temporary-dismiss",
NewDeviceVerificationPermanentDismiss = "new-device-permanent-dismiss",
@@ -85,6 +86,7 @@ export const DefaultFeatureFlagValue = {
[FeatureFlag.ExportAttachments]: FALSE,
/* Vault */
+ [FeatureFlag.PM8851_BrowserOnboardingNudge]: FALSE,
[FeatureFlag.PM9111ExtensionPersistAddEditForm]: FALSE,
[FeatureFlag.NewDeviceVerificationTemporaryDismiss]: FALSE,
[FeatureFlag.NewDeviceVerificationPermanentDismiss]: FALSE,
From 2e0c991f839f97ee1ecac3f9a1d48b97b4345e82 Mon Sep 17 00:00:00 2001
From: Patrick-Pimentel-Bitwarden
Date: Wed, 19 Mar 2025 15:26:10 -0400
Subject: [PATCH 014/113] fix(device-approval-persistence): [PM-9112] Device
Approval Persistence (#13680)
* feat(device-approval-persistence): [PM-9112] Device Approval Persistence - Added in view cache data needed to persist the approval process. Clears after 2 minutes.
---
.../login-via-auth-request.component.html | 2 +-
.../login-via-auth-request.component.ts | 255 +++++++++++++++---
.../common/models/domain/login-credentials.ts | 6 +-
...gin-via-auth-request-cache.service.spec.ts | 111 ++++++++
...lt-login-via-auth-request-cache.service.ts | 88 ++++++
.../view/login-via-auth-request.view.ts | 17 ++
libs/common/src/enums/feature-flag.enum.ts | 6 +
7 files changed, 446 insertions(+), 39 deletions(-)
create mode 100644 libs/auth/src/common/services/auth-request/default-login-via-auth-request-cache.service.spec.ts
create mode 100644 libs/auth/src/common/services/auth-request/default-login-via-auth-request-cache.service.ts
create mode 100644 libs/common/src/auth/models/view/login-via-auth-request.view.ts
diff --git a/libs/auth/src/angular/login-via-auth-request/login-via-auth-request.component.html b/libs/auth/src/angular/login-via-auth-request/login-via-auth-request.component.html
index 22cf8320036..d6b91b960b0 100644
--- a/libs/auth/src/angular/login-via-auth-request/login-via-auth-request.component.html
+++ b/libs/auth/src/angular/login-via-auth-request/login-via-auth-request.component.html
@@ -26,7 +26,7 @@
block
buttonType="secondary"
class="tw-mt-4"
- (click)="startStandardAuthRequestLogin()"
+ (click)="startStandardAuthRequestLogin(true)"
>
{{ "resendNotification" | i18n }}
diff --git a/libs/auth/src/angular/login-via-auth-request/login-via-auth-request.component.ts b/libs/auth/src/angular/login-via-auth-request/login-via-auth-request.component.ts
index 266ee3c4acc..bb2822d67e9 100644
--- a/libs/auth/src/angular/login-via-auth-request/login-via-auth-request.component.ts
+++ b/libs/auth/src/angular/login-via-auth-request/login-via-auth-request.component.ts
@@ -1,5 +1,3 @@
-// FIXME: Update this file to be type safe and remove this and next line
-// @ts-strict-ignore
import { CommonModule } from "@angular/common";
import { Component, OnDestroy, OnInit } from "@angular/core";
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
@@ -24,10 +22,13 @@ import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result";
import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason";
import { AuthRequest } from "@bitwarden/common/auth/models/request/auth.request";
import { AuthRequestResponse } from "@bitwarden/common/auth/models/response/auth-request.response";
+import { LoginViaAuthRequestView } from "@bitwarden/common/auth/models/view/login-via-auth-request.view";
import { ClientType, HttpStatusCode } 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 { ErrorResponse } from "@bitwarden/common/models/response/error.response";
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
+import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
@@ -40,6 +41,7 @@ import { ButtonModule, LinkModule, ToastService } from "@bitwarden/components";
import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy";
import { AuthRequestApiService } from "../../common/abstractions/auth-request-api.service";
+import { LoginViaAuthRequestCacheService } from "../../common/services/auth-request/default-login-via-auth-request-cache.service";
enum Flow {
StandardAuthRequest, // when user clicks "Login with device" from /login or "Approve from your other device" from /login-initiated
@@ -57,23 +59,26 @@ const matchOptions: IsActiveMatchOptions = {
standalone: true,
templateUrl: "./login-via-auth-request.component.html",
imports: [ButtonModule, CommonModule, JslibModule, LinkModule, RouterModule],
+ providers: [{ provide: LoginViaAuthRequestCacheService }],
})
export class LoginViaAuthRequestComponent implements OnInit, OnDestroy {
- private authRequest: AuthRequest;
- private authRequestKeyPair: { publicKey: Uint8Array; privateKey: Uint8Array };
- private authStatus: AuthenticationStatus;
+ private authRequest: AuthRequest | undefined = undefined;
+ private authRequestKeyPair:
+ | { publicKey: Uint8Array | undefined; privateKey: Uint8Array | undefined }
+ | undefined = undefined;
+ private authStatus: AuthenticationStatus | undefined = undefined;
private showResendNotificationTimeoutSeconds = 12;
protected backToRoute = "/login";
protected clientType: ClientType;
protected ClientType = ClientType;
- protected email: string;
- protected fingerprintPhrase: string;
+ protected email: string | undefined = undefined;
+ protected fingerprintPhrase: string | undefined = undefined;
protected showResendNotification = false;
protected Flow = Flow;
protected flow = Flow.StandardAuthRequest;
- protected webVaultUrl: string;
- protected deviceManagementUrl: string;
+ protected webVaultUrl: string | undefined = undefined;
+ protected deviceManagementUrl: string | undefined;
constructor(
private accountService: AccountService,
@@ -95,6 +100,8 @@ export class LoginViaAuthRequestComponent implements OnInit, OnDestroy {
private toastService: ToastService,
private validationService: ValidationService,
private loginSuccessHandlerService: LoginSuccessHandlerService,
+ private loginViaAuthRequestCacheService: LoginViaAuthRequestCacheService,
+ private configService: ConfigService,
) {
this.clientType = this.platformUtilsService.getClientType();
@@ -124,6 +131,7 @@ export class LoginViaAuthRequestComponent implements OnInit, OnDestroy {
async ngOnInit(): Promise {
// Get the authStatus early because we use it in both flows
this.authStatus = await firstValueFrom(this.authService.activeAccountStatus$);
+ await this.loginViaAuthRequestCacheService.init();
const userHasAuthenticatedViaSSO = this.authStatus === AuthenticationStatus.Locked;
@@ -133,7 +141,7 @@ export class LoginViaAuthRequestComponent implements OnInit, OnDestroy {
/**
* The LoginViaAuthRequestComponent handles both the `login-with-device` and
- * the `admin-approval-requested` routes. Therefore we check the route to determine
+ * the `admin-approval-requested` routes. Therefore, we check the route to determine
* which flow to initialize.
*/
if (this.router.isActive("admin-approval-requested", matchOptions)) {
@@ -159,7 +167,14 @@ export class LoginViaAuthRequestComponent implements OnInit, OnDestroy {
// We only allow a single admin approval request to be active at a time
// so we must check state to see if we have an existing one or not
- const userId = (await firstValueFrom(this.accountService.activeAccount$)).id;
+ const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id;
+ if (!userId) {
+ this.logService.error(
+ "Not able to get a user id from the account service active account observable.",
+ );
+ return;
+ }
+
const existingAdminAuthRequest = await this.authRequestService.getAdminAuthRequest(userId);
if (existingAdminAuthRequest) {
@@ -172,7 +187,9 @@ export class LoginViaAuthRequestComponent implements OnInit, OnDestroy {
private async initStandardAuthRequestFlow(): Promise {
this.flow = Flow.StandardAuthRequest;
- this.email = await firstValueFrom(this.loginEmailService.loginEmail$);
+ this.email = await firstValueFrom(
+ this.accountService.activeAccount$.pipe(map((a) => a?.email)),
+ );
if (!this.email) {
await this.handleMissingEmail();
@@ -185,7 +202,6 @@ export class LoginViaAuthRequestComponent implements OnInit, OnDestroy {
private async handleMissingEmail(): Promise {
this.toastService.showToast({
variant: "error",
- title: null,
message: this.i18nService.t("userEmailMissing"),
});
@@ -194,21 +210,41 @@ export class LoginViaAuthRequestComponent implements OnInit, OnDestroy {
async ngOnDestroy(): Promise {
await this.anonymousHubService.stopHubConnection();
+
+ this.loginViaAuthRequestCacheService.clearCacheLoginView();
}
private async startAdminAuthRequestLogin(): Promise {
try {
await this.buildAuthRequest(AuthRequestType.AdminApproval);
+ if (!this.authRequest) {
+ this.logService.error("Auth request failed to build.");
+ return;
+ }
+
+ if (!this.authRequestKeyPair) {
+ this.logService.error("Key pairs failed to initialize from buildAuthRequest.");
+ return;
+ }
+
const authRequestResponse = await this.authRequestApiService.postAdminAuthRequest(
- this.authRequest,
+ this.authRequest as AuthRequest,
);
const adminAuthReqStorable = new AdminAuthRequestStorable({
id: authRequestResponse.id,
privateKey: this.authRequestKeyPair.privateKey,
});
- const userId = (await firstValueFrom(this.accountService.activeAccount$)).id;
+ const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id;
+
+ if (!userId) {
+ this.logService.error(
+ "Not able to get a user id from the account service active account observable.",
+ );
+ return;
+ }
+
await this.authRequestService.setAdminAuthRequest(adminAuthReqStorable, userId);
if (authRequestResponse.id) {
@@ -219,21 +255,104 @@ export class LoginViaAuthRequestComponent implements OnInit, OnDestroy {
}
}
- protected async startStandardAuthRequestLogin(): Promise {
+ protected async startStandardAuthRequestLogin(
+ clearCachedRequest: boolean = false,
+ ): Promise {
this.showResendNotification = false;
- try {
- await this.buildAuthRequest(AuthRequestType.AuthenticateAndUnlock);
-
- const authRequestResponse = await this.authRequestApiService.postAuthRequest(
- this.authRequest,
- );
-
- if (authRequestResponse.id) {
- await this.anonymousHubService.createHubConnection(authRequestResponse.id);
+ if (await this.configService.getFeatureFlag(FeatureFlag.PM9112_DeviceApprovalPersistence)) {
+ // Used for manually refreshing the auth request when clicking the resend auth request
+ // on the ui.
+ if (clearCachedRequest) {
+ this.loginViaAuthRequestCacheService.clearCacheLoginView();
+ }
+
+ try {
+ const loginAuthRequestView: LoginViaAuthRequestView | null =
+ this.loginViaAuthRequestCacheService.getCachedLoginViaAuthRequestView();
+
+ if (!loginAuthRequestView) {
+ await this.buildAuthRequest(AuthRequestType.AuthenticateAndUnlock);
+
+ // I tried several ways to get the IDE/linter to play nice with checking for null values
+ // in less code / more efficiently, but it struggles to identify code paths that
+ // are more complicated than this.
+ if (!this.authRequest) {
+ this.logService.error("AuthRequest failed to initialize from buildAuthRequest.");
+ return;
+ }
+
+ if (!this.fingerprintPhrase) {
+ this.logService.error("FingerprintPhrase failed to initialize from buildAuthRequest.");
+ return;
+ }
+
+ if (!this.authRequestKeyPair) {
+ this.logService.error("KeyPair failed to initialize from buildAuthRequest.");
+ return;
+ }
+
+ const authRequestResponse: AuthRequestResponse =
+ await this.authRequestApiService.postAuthRequest(this.authRequest);
+
+ this.loginViaAuthRequestCacheService.cacheLoginView(
+ this.authRequest,
+ authRequestResponse,
+ this.fingerprintPhrase,
+ this.authRequestKeyPair,
+ );
+
+ if (authRequestResponse.id) {
+ await this.anonymousHubService.createHubConnection(authRequestResponse.id);
+ }
+ } else {
+ // Grab the cached information and store it back in component state.
+ // We don't need the public key for handling the authentication request because
+ // the verifyAndHandleApprovedAuthReq function will receive the public key back
+ // from the looked up auth request and all we need is to make sure that
+ // we can use the cached private key that is associated with it.
+ this.authRequest = loginAuthRequestView.authRequest;
+ this.fingerprintPhrase = loginAuthRequestView.fingerprintPhrase;
+ this.authRequestKeyPair = {
+ privateKey: loginAuthRequestView.privateKey
+ ? Utils.fromB64ToArray(loginAuthRequestView.privateKey)
+ : undefined,
+ publicKey: undefined,
+ };
+
+ if (!loginAuthRequestView.authRequestResponse) {
+ this.logService.error("No cached auth request response.");
+ return;
+ }
+
+ if (loginAuthRequestView.authRequestResponse.id) {
+ await this.anonymousHubService.createHubConnection(
+ loginAuthRequestView.authRequestResponse.id,
+ );
+ }
+ }
+ } catch (e) {
+ this.logService.error(e);
+ }
+ } else {
+ try {
+ await this.buildAuthRequest(AuthRequestType.AuthenticateAndUnlock);
+
+ if (!this.authRequest) {
+ this.logService.error("No auth request found.");
+ return;
+ }
+
+ const authRequestResponse = await this.authRequestApiService.postAuthRequest(
+ this.authRequest,
+ );
+
+ if (authRequestResponse.id) {
+ await this.anonymousHubService.createHubConnection(authRequestResponse.id);
+ }
+ } catch (e) {
+ this.logService.error(e);
}
- } catch (e) {
- this.logService.error(e);
}
setTimeout(() => {
@@ -250,12 +369,23 @@ export class LoginViaAuthRequestComponent implements OnInit, OnDestroy {
};
const deviceIdentifier = await this.appIdService.getAppId();
+
+ if (!this.authRequestKeyPair.publicKey) {
+ this.logService.error("AuthRequest public key not set to value in building auth request.");
+ return;
+ }
+
const publicKey = Utils.fromBufferToB64(this.authRequestKeyPair.publicKey);
const accessCode = await this.passwordGenerationService.generatePassword({
type: "password",
length: 25,
});
+ if (!this.email) {
+ this.logService.error("Email not defined when building auth request.");
+ return;
+ }
+
this.fingerprintPhrase = await this.authRequestService.getFingerprintPhrase(
this.email,
this.authRequestKeyPair.publicKey,
@@ -288,6 +418,8 @@ export class LoginViaAuthRequestComponent implements OnInit, OnDestroy {
if (error instanceof ErrorResponse && error.statusCode === HttpStatusCode.NotFound) {
return await this.handleExistingAdminAuthReqDeletedOrDenied(userId);
}
+ this.logService.error(error);
+ return;
}
// Request doesn't exist anymore
@@ -300,6 +432,12 @@ export class LoginViaAuthRequestComponent implements OnInit, OnDestroy {
const derivedPublicKeyArrayBuffer = await this.cryptoFunctionService.rsaExtractPublicKey(
adminAuthRequestStorable.privateKey,
);
+
+ if (!this.email) {
+ this.logService.error("Email not defined when handling an existing an admin auth request.");
+ return;
+ }
+
this.fingerprintPhrase = await this.authRequestService.getFingerprintPhrase(
this.email,
derivedPublicKeyArrayBuffer,
@@ -319,9 +457,12 @@ export class LoginViaAuthRequestComponent implements OnInit, OnDestroy {
);
}
- // Request still pending response from admin
- // set keypair and create hub connection so that any approvals will be received via push notification
- this.authRequestKeyPair = { privateKey: adminAuthRequestStorable.privateKey, publicKey: null };
+ // Request still pending response from admin set keypair and create hub connection
+ // so that any approvals will be received via push notification
+ this.authRequestKeyPair = {
+ privateKey: adminAuthRequestStorable.privateKey,
+ publicKey: undefined,
+ };
await this.anonymousHubService.createHubConnection(adminAuthRequestStorable.id);
}
@@ -403,6 +544,11 @@ export class LoginViaAuthRequestComponent implements OnInit, OnDestroy {
await this.handleAuthenticatedFlows(authRequestResponse);
}
} else {
+ if (!this.authRequest) {
+ this.logService.error("No auth request defined when handling approved auth request.");
+ return;
+ }
+
// Get the auth request from the server
// User is unauthenticated, therefore the endpoint requires an access code for user verification.
const authRequestResponse = await this.authRequestApiService.getAuthResponse(
@@ -423,11 +569,26 @@ export class LoginViaAuthRequestComponent implements OnInit, OnDestroy {
}
this.logService.error(error);
+ } finally {
+ // Manually clean out the cache to make sure sensitive
+ // data does not persist longer than it needs to.
+ this.loginViaAuthRequestCacheService.clearCacheLoginView();
}
}
private async handleAuthenticatedFlows(authRequestResponse: AuthRequestResponse) {
- const userId = (await firstValueFrom(this.accountService.activeAccount$)).id;
+ const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id;
+ if (!userId) {
+ this.logService.error(
+ "Not able to get a user id from the account service active account observable.",
+ );
+ return;
+ }
+
+ if (!this.authRequestKeyPair || !this.authRequestKeyPair.privateKey) {
+ this.logService.error("No private key set when handling the authenticated flows.");
+ return;
+ }
await this.decryptViaApprovedAuthRequest(
authRequestResponse,
@@ -445,6 +606,11 @@ export class LoginViaAuthRequestComponent implements OnInit, OnDestroy {
authRequestResponse,
);
+ if (!authRequestLoginCredentials) {
+ this.logService.error("Didn't set up auth request login credentials properly.");
+ return;
+ }
+
// Note: keys are set by AuthRequestLoginStrategy success handling
const authResult = await this.loginStrategyService.logIn(authRequestLoginCredentials);
@@ -463,7 +629,6 @@ export class LoginViaAuthRequestComponent implements OnInit, OnDestroy {
* - If `masterPasswordHash` has a value, we receive the `key` as an authRequestPublicKey(masterKey) [plus we have authRequestPublicKey(masterPasswordHash)]
* - If `masterPasswordHash` does not have a value, we receive the `key` as an authRequestPublicKey(userKey)
*/
-
if (authRequestResponse.masterPasswordHash) {
// ...in Standard Auth Request Flow 3
await this.authRequestService.setKeysAfterDecryptingSharedMasterKeyAndHash(
@@ -486,13 +651,17 @@ export class LoginViaAuthRequestComponent implements OnInit, OnDestroy {
this.toastService.showToast({
variant: "success",
- title: null,
message: this.i18nService.t("loginApproved"),
});
// Now that we have a decrypted user key in memory, we can check if we
// need to establish trust on the current device
const activeAccount = await firstValueFrom(this.accountService.activeAccount$);
+ if (!activeAccount) {
+ this.logService.error("No active account defined from the account service.");
+ return;
+ }
+
await this.deviceTrustService.trustDeviceIfRequired(activeAccount.id);
await this.handleSuccessfulLoginNavigation(userId);
@@ -508,7 +677,24 @@ export class LoginViaAuthRequestComponent implements OnInit, OnDestroy {
private async buildAuthRequestLoginCredentials(
requestId: string,
authRequestResponse: AuthRequestResponse,
- ): Promise {
+ ): Promise {
+ if (!this.authRequestKeyPair || !this.authRequestKeyPair.privateKey) {
+ this.logService.error("No private key set when building auth request login credentials.");
+ return;
+ }
+
+ if (!this.email) {
+ this.logService.error("Email not defined.");
+ return;
+ }
+
+ if (!this.authRequest) {
+ this.logService.error(
+ "AuthRequest not defined when building auth request login credentials.",
+ );
+ return;
+ }
+
/**
* See verifyAndHandleApprovedAuthReq() for flow details.
*
@@ -516,7 +702,6 @@ export class LoginViaAuthRequestComponent implements OnInit, OnDestroy {
* - If `masterPasswordHash` has a value, we receive the `key` as an authRequestPublicKey(masterKey) [plus we have authRequestPublicKey(masterPasswordHash)]
* - If `masterPasswordHash` does not have a value, we receive the `key` as an authRequestPublicKey(userKey)
*/
-
if (authRequestResponse.masterPasswordHash) {
// ...in Standard Auth Request Flow 1
const { masterKey, masterKeyHash } =
diff --git a/libs/auth/src/common/models/domain/login-credentials.ts b/libs/auth/src/common/models/domain/login-credentials.ts
index 72cc7413bec..cc21e5b2505 100644
--- a/libs/auth/src/common/models/domain/login-credentials.ts
+++ b/libs/auth/src/common/models/domain/login-credentials.ts
@@ -53,9 +53,9 @@ export class AuthRequestLoginCredentials {
public email: string,
public accessCode: string,
public authRequestId: string,
- public decryptedUserKey: UserKey,
- public decryptedMasterKey: MasterKey,
- public decryptedMasterKeyHash: string,
+ public decryptedUserKey: UserKey | null,
+ public decryptedMasterKey: MasterKey | null,
+ public decryptedMasterKeyHash: string | null,
public twoFactor?: TokenTwoFactorRequest,
) {}
diff --git a/libs/auth/src/common/services/auth-request/default-login-via-auth-request-cache.service.spec.ts b/libs/auth/src/common/services/auth-request/default-login-via-auth-request-cache.service.spec.ts
new file mode 100644
index 00000000000..82ac0f1006d
--- /dev/null
+++ b/libs/auth/src/common/services/auth-request/default-login-via-auth-request-cache.service.spec.ts
@@ -0,0 +1,111 @@
+import { signal } from "@angular/core";
+import { TestBed } from "@angular/core/testing";
+
+import { ViewCacheService } from "@bitwarden/angular/platform/abstractions/view-cache.service";
+import { AuthRequestType } from "@bitwarden/common/auth/enums/auth-request-type";
+import { AuthRequest } from "@bitwarden/common/auth/models/request/auth.request";
+import { AuthRequestResponse } from "@bitwarden/common/auth/models/response/auth-request.response";
+import { LoginViaAuthRequestView } from "@bitwarden/common/auth/models/view/login-via-auth-request.view";
+import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
+
+import { LoginViaAuthRequestCacheService } from "./default-login-via-auth-request-cache.service";
+
+describe("LoginViaAuthRequestCache", () => {
+ let service: LoginViaAuthRequestCacheService;
+ let testBed: TestBed;
+
+ const cacheSignal = signal(null);
+ const getCacheSignal = jest.fn().mockReturnValue(cacheSignal);
+ const getFeatureFlag = jest.fn().mockResolvedValue(false);
+ const cacheSetMock = jest.spyOn(cacheSignal, "set");
+
+ beforeEach(() => {
+ getCacheSignal.mockClear();
+ getFeatureFlag.mockClear();
+ cacheSetMock.mockClear();
+
+ testBed = TestBed.configureTestingModule({
+ providers: [
+ { provide: ViewCacheService, useValue: { signal: getCacheSignal } },
+ { provide: ConfigService, useValue: { getFeatureFlag } },
+ LoginViaAuthRequestCacheService,
+ ],
+ });
+ });
+
+ describe("feature enabled", () => {
+ beforeEach(() => {
+ getFeatureFlag.mockResolvedValue(true);
+ });
+
+ it("`getCachedLoginViaAuthRequestView` returns the cached data", async () => {
+ cacheSignal.set({ ...buildAuthenticMockAuthView() });
+ service = testBed.inject(LoginViaAuthRequestCacheService);
+ await service.init();
+
+ expect(service.getCachedLoginViaAuthRequestView()).toEqual({
+ ...buildAuthenticMockAuthView(),
+ });
+ });
+
+ it("updates the signal value", async () => {
+ service = testBed.inject(LoginViaAuthRequestCacheService);
+ await service.init();
+
+ const parameters = buildAuthenticMockAuthView();
+
+ service.cacheLoginView(
+ parameters.authRequest,
+ parameters.authRequestResponse,
+ parameters.fingerprintPhrase,
+ { publicKey: new Uint8Array(), privateKey: new Uint8Array() },
+ );
+
+ expect(cacheSignal.set).toHaveBeenCalledWith(parameters);
+ });
+ });
+
+ describe("feature disabled", () => {
+ beforeEach(async () => {
+ cacheSignal.set({ ...buildAuthenticMockAuthView() } as LoginViaAuthRequestView);
+ getFeatureFlag.mockResolvedValue(false);
+ cacheSetMock.mockClear();
+
+ service = testBed.inject(LoginViaAuthRequestCacheService);
+ await service.init();
+ });
+
+ it("`getCachedCipherView` returns null", () => {
+ expect(service.getCachedLoginViaAuthRequestView()).toBeNull();
+ });
+
+ it("does not update the signal value", () => {
+ const params = buildAuthenticMockAuthView();
+
+ service.cacheLoginView(
+ params.authRequest,
+ params.authRequestResponse,
+ params.fingerprintPhrase,
+ { publicKey: new Uint8Array(), privateKey: new Uint8Array() },
+ );
+
+ expect(cacheSignal.set).not.toHaveBeenCalled();
+ });
+ });
+
+ const buildAuthenticMockAuthView = () => {
+ return {
+ fingerprintPhrase: "",
+ privateKey: "",
+ publicKey: "",
+ authRequest: new AuthRequest(
+ "test@gmail.com",
+ "deviceIdentifier",
+ "publicKey",
+ AuthRequestType.Unlock,
+ "accessCode",
+ ),
+ authRequestResponse: new AuthRequestResponse({}),
+ };
+ };
+});
diff --git a/libs/auth/src/common/services/auth-request/default-login-via-auth-request-cache.service.ts b/libs/auth/src/common/services/auth-request/default-login-via-auth-request-cache.service.ts
new file mode 100644
index 00000000000..30ba8879546
--- /dev/null
+++ b/libs/auth/src/common/services/auth-request/default-login-via-auth-request-cache.service.ts
@@ -0,0 +1,88 @@
+import { inject, Injectable, WritableSignal } from "@angular/core";
+
+import { ViewCacheService } from "@bitwarden/angular/platform/abstractions/view-cache.service";
+import { AuthRequest } from "@bitwarden/common/auth/models/request/auth.request";
+import { AuthRequestResponse } from "@bitwarden/common/auth/models/response/auth-request.response";
+import { LoginViaAuthRequestView } from "@bitwarden/common/auth/models/view/login-via-auth-request.view";
+import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
+import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
+import { Utils } from "@bitwarden/common/platform/misc/utils";
+
+const LOGIN_VIA_AUTH_CACHE_KEY = "login-via-auth-request-form-cache";
+
+/**
+ * This is a cache service used for the login via auth request component.
+ *
+ * There is sensitive information stored temporarily here. Cache will be cleared
+ * after 2 minutes.
+ */
+@Injectable()
+export class LoginViaAuthRequestCacheService {
+ private viewCacheService: ViewCacheService = inject(ViewCacheService);
+ private configService: ConfigService = inject(ConfigService);
+
+ /** True when the `PM9112_DeviceApproval` flag is enabled */
+ private featureEnabled: boolean = false;
+
+ private defaultLoginViaAuthRequestCache: WritableSignal =
+ this.viewCacheService.signal({
+ key: LOGIN_VIA_AUTH_CACHE_KEY,
+ initialValue: null,
+ deserializer: LoginViaAuthRequestView.fromJSON,
+ });
+
+ constructor() {}
+
+ /**
+ * Must be called once before interacting with the cached data, otherwise methods will be noop.
+ */
+ async init() {
+ this.featureEnabled = await this.configService.getFeatureFlag(
+ FeatureFlag.PM9112_DeviceApprovalPersistence,
+ );
+ }
+
+ /**
+ * Update the cache with the new LoginView.
+ */
+ cacheLoginView(
+ authRequest: AuthRequest,
+ authRequestResponse: AuthRequestResponse,
+ fingerprintPhrase: string,
+ keys: { privateKey: Uint8Array | undefined; publicKey: Uint8Array | undefined },
+ ): void {
+ if (!this.featureEnabled) {
+ return;
+ }
+
+ // When the keys get stored they should be converted to a B64 string to ensure
+ // data can be properly formed when json-ified. If not done, they are not stored properly and
+ // will not be parsable by the cryptography library after coming out of storage.
+ this.defaultLoginViaAuthRequestCache.set({
+ authRequest,
+ authRequestResponse,
+ fingerprintPhrase,
+ privateKey: keys.privateKey ? Utils.fromBufferToB64(keys.privateKey.buffer) : undefined,
+ publicKey: keys.publicKey ? Utils.fromBufferToB64(keys.publicKey.buffer) : undefined,
+ } as LoginViaAuthRequestView);
+ }
+
+ clearCacheLoginView(): void {
+ if (!this.featureEnabled) {
+ return;
+ }
+
+ this.defaultLoginViaAuthRequestCache.set(null);
+ }
+
+ /**
+ * Returns the cached LoginViaAuthRequestView when available.
+ */
+ getCachedLoginViaAuthRequestView(): LoginViaAuthRequestView | null {
+ if (!this.featureEnabled) {
+ return null;
+ }
+
+ return this.defaultLoginViaAuthRequestCache();
+ }
+}
diff --git a/libs/common/src/auth/models/view/login-via-auth-request.view.ts b/libs/common/src/auth/models/view/login-via-auth-request.view.ts
new file mode 100644
index 00000000000..0691b8efd86
--- /dev/null
+++ b/libs/common/src/auth/models/view/login-via-auth-request.view.ts
@@ -0,0 +1,17 @@
+import { Jsonify } from "type-fest";
+
+import { AuthRequest } from "@bitwarden/common/auth/models/request/auth.request";
+import { AuthRequestResponse } from "@bitwarden/common/auth/models/response/auth-request.response";
+import { View } from "@bitwarden/common/models/view/view";
+
+export class LoginViaAuthRequestView implements View {
+ authRequest: AuthRequest | undefined = undefined;
+ authRequestResponse: AuthRequestResponse | undefined = undefined;
+ fingerprintPhrase: string | undefined = undefined;
+ privateKey: string | undefined = undefined;
+ publicKey: string | undefined = undefined;
+
+ static fromJSON(obj: Partial>): LoginViaAuthRequestView {
+ return Object.assign(new LoginViaAuthRequestView(), obj);
+ }
+}
diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts
index 280e8d8a989..d35a1854653 100644
--- a/libs/common/src/enums/feature-flag.enum.ts
+++ b/libs/common/src/enums/feature-flag.enum.ts
@@ -36,6 +36,9 @@ export enum FeatureFlag {
VaultBulkManagementAction = "vault-bulk-management-action",
SecurityTasks = "security-tasks",
+ /* Auth */
+ PM9112_DeviceApprovalPersistence = "pm-9112-device-approval-persistence",
+
PM4154_BulkEncryptionService = "PM-4154-bulk-encryption-service",
UnauthenticatedExtensionUIRefresh = "unauth-ui-refresh",
CipherKeyEncryption = "cipher-key-encryption",
@@ -93,6 +96,9 @@ export const DefaultFeatureFlagValue = {
[FeatureFlag.VaultBulkManagementAction]: FALSE,
[FeatureFlag.SecurityTasks]: FALSE,
+ /* Auth */
+ [FeatureFlag.PM9112_DeviceApprovalPersistence]: FALSE,
+
[FeatureFlag.PM4154_BulkEncryptionService]: FALSE,
[FeatureFlag.UnauthenticatedExtensionUIRefresh]: FALSE,
[FeatureFlag.CipherKeyEncryption]: FALSE,
From 2c41e497f7da4e231c0aaa74a5b929f57f624239 Mon Sep 17 00:00:00 2001
From: Shane Melton
Date: Wed, 19 Mar 2025 14:30:54 -0700
Subject: [PATCH 015/113] Add missing mocks (#13910)
---
.../app/vault/components/vault-items/vault-items.stories.ts | 6 ++++++
1 file changed, 6 insertions(+)
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 f2529bc7a5c..55807ed855f 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
@@ -117,6 +117,12 @@ export default {
canDeleteCipher$() {
return of(true);
},
+ canRestoreCipher$() {
+ return of(true);
+ },
+ canCloneCipher$() {
+ return of(true);
+ },
},
},
],
From 886f85df48afb8e08e8682509e7910639bd9154f Mon Sep 17 00:00:00 2001
From: Vicki League
Date: Wed, 19 Mar 2025 21:20:07 -0400
Subject: [PATCH 016/113] [CL-605] Fix whitespace between elements (#13644)
---
.../components/approve-ssh-request.html | 4 +-
.../members/members.component.html | 11 ++++--
.../webauthn-login-settings.component.html | 38 ++++++++++---------
.../exposed-passwords-report.component.html | 3 +-
.../domain-add-edit-dialog.component.html | 38 ++++++++++---------
5 files changed, 51 insertions(+), 43 deletions(-)
diff --git a/apps/desktop/src/platform/components/approve-ssh-request.html b/apps/desktop/src/platform/components/approve-ssh-request.html
index 952e3344e9c..b7005872f25 100644
--- a/apps/desktop/src/platform/components/approve-ssh-request.html
+++ b/apps/desktop/src/platform/components/approve-ssh-request.html
@@ -14,13 +14,13 @@
{{params.cipherName}}
{{ "sshkeyApprovalMessageSuffix" | i18n }} {{ params.action | i18n }}
-
+
{{ "authorize" | i18n }}
{{ "deny" | i18n }}
-
+
diff --git a/apps/web/src/app/admin-console/organizations/members/members.component.html b/apps/web/src/app/admin-console/organizations/members/members.component.html
index f2995e31f12..3856f6f4e28 100644
--- a/apps/web/src/app/admin-console/organizations/members/members.component.html
+++ b/apps/web/src/app/admin-console/organizations/members/members.component.html
@@ -187,7 +187,7 @@
class="tw-mr-3"
>
-
+
{{ u.name ?? u.email }}
@@ -196,22 +196,25 @@
class="tw-text-xs"
variant="secondary"
*ngIf="u.status === userStatusType.Invited"
- >{{ "invited" | i18n }}
+ {{ "invited" | i18n }}
+
{{ "needsConfirmation" | i18n }}
+ {{ "needsConfirmation" | i18n }}
+
{{ "revoked" | i18n }}
+ {{ "revoked" | i18n }}
+
{{ u.email }}
diff --git a/apps/web/src/app/auth/settings/webauthn-login-settings/webauthn-login-settings.component.html b/apps/web/src/app/auth/settings/webauthn-login-settings/webauthn-login-settings.component.html
index 9965302d15a..7b1d859fb69 100644
--- a/apps/web/src/app/auth/settings/webauthn-login-settings/webauthn-login-settings.component.html
+++ b/apps/web/src/app/auth/settings/webauthn-login-settings/webauthn-login-settings.component.html
@@ -1,24 +1,26 @@
{{ "logInWithPasskey" | i18n }}
-
-
- {{ "off" | i18n }} - {{ "ssoLoginIsRequired" | i18n }}
-
-
- {{
- "on" | i18n
- }}
- {{
- "off" | i18n
- }}
+
+
+
+ {{ "off" | i18n }} - {{ "ssoLoginIsRequired" | i18n }}
+
+
+
+ {{ "on" | i18n }}
+
+
+ {{ "off" | i18n }}
+
+
-
- {{ "beta" | i18n }}
+ {{ "beta" | i18n }}
+
diff --git a/apps/web/src/app/tools/reports/pages/exposed-passwords-report.component.html b/apps/web/src/app/tools/reports/pages/exposed-passwords-report.component.html
index 589704a409a..eb8e2c56527 100644
--- a/apps/web/src/app/tools/reports/pages/exposed-passwords-report.component.html
+++ b/apps/web/src/app/tools/reports/pages/exposed-passwords-report.component.html
@@ -49,8 +49,9 @@
appStopClick
(click)="selectCipher(row)"
title="{{ 'editItemWithName' | i18n: row.name }}"
- >{{ row.name }}
+ {{ row.name }}
+
{{ row.name }}
diff --git a/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/domain-verification/domain-add-edit-dialog/domain-add-edit-dialog.component.html b/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/domain-verification/domain-add-edit-dialog/domain-add-edit-dialog.component.html
index 7226c957598..7df5953c56a 100644
--- a/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/domain-verification/domain-add-edit-dialog/domain-add-edit-dialog.component.html
+++ b/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/domain-verification/domain-add-edit-dialog/domain-add-edit-dialog.component.html
@@ -7,25 +7,27 @@
{{ "newDomain" | i18n }}
+ {{ ((accountDeprovisioningEnabled$ | async) ? "claimDomain" : "verifyDomain") | i18n }}
+
+
+
+ {{ data.orgDomain.domainName }}
+
+
+
{{
- ((accountDeprovisioningEnabled$ | async) ? "claimDomain" : "verifyDomain") | i18n
- }}
-
- {{
- data.orgDomain.domainName
- }}
-
- {{
- ((accountDeprovisioningEnabled$ | async)
- ? "domainStatusUnderVerification"
- : "domainStatusUnverified"
- ) | i18n
- }}
- {{
- ((accountDeprovisioningEnabled$ | async) ? "domainStatusClaimed" : "domainStatusVerified")
- | i18n
- }}
+ ((accountDeprovisioningEnabled$ | async)
+ ? "domainStatusUnderVerification"
+ : "domainStatusUnverified"
+ ) | i18n
+ }}
+
+
+ {{
+ ((accountDeprovisioningEnabled$ | async) ? "domainStatusClaimed" : "domainStatusVerified")
+ | i18n
+ }}
+
From 92587a1dd83913c323429245fa6ea1c6a6f6dc75 Mon Sep 17 00:00:00 2001
From: Oscar Hinton
Date: Thu, 20 Mar 2025 02:20:55 +0100
Subject: [PATCH 017/113] [CL-611] Fix whitespaces for about dialog & emergency
access (#13855)
---
.../popup/settings/about-dialog/about-dialog.component.html | 4 ++--
.../settings/emergency-access/emergency-access.component.html | 4 ++--
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/apps/browser/src/tools/popup/settings/about-dialog/about-dialog.component.html b/apps/browser/src/tools/popup/settings/about-dialog/about-dialog.component.html
index b3bf06cfbe7..40dad4cde4b 100644
--- a/apps/browser/src/tools/popup/settings/about-dialog/about-dialog.component.html
+++ b/apps/browser/src/tools/popup/settings/about-dialog/about-dialog.component.html
@@ -42,12 +42,12 @@
-
+
{{ "close" | i18n }}
{{ "copy" | i18n }}
-
+
diff --git a/apps/web/src/app/auth/settings/emergency-access/emergency-access.component.html b/apps/web/src/app/auth/settings/emergency-access/emergency-access.component.html
index 524ebbc28cf..ab93f0be3bc 100644
--- a/apps/web/src/app/auth/settings/emergency-access/emergency-access.component.html
+++ b/apps/web/src/app/auth/settings/emergency-access/emergency-access.component.html
@@ -52,7 +52,7 @@
[color]="c.avatarColor"
size="small"
>
-
+
{{ c.email }}
-
+
{{ c.email }}
{{
"invited" | i18n
From 57c15a26eb1cf035593df677420b677cd8ac3ecd Mon Sep 17 00:00:00 2001
From: Oscar Hinton
Date: Thu, 20 Mar 2025 11:17:46 +0100
Subject: [PATCH 018/113] [PM-18657] Fix clipboard tests calling console.warn
(#13580)
* Fix clipboard tests calling console.warn
* Change to jest.SpyInstance
---
.../offscreen-document.spec.ts | 21 ++++++++++++-------
.../browser-clipboard.service.spec.ts | 5 ++---
.../browser-platform-utils.service.spec.ts | 8 +++++--
3 files changed, 22 insertions(+), 12 deletions(-)
diff --git a/apps/browser/src/platform/offscreen-document/offscreen-document.spec.ts b/apps/browser/src/platform/offscreen-document/offscreen-document.spec.ts
index 67fa920d18d..37731f17fbe 100644
--- a/apps/browser/src/platform/offscreen-document/offscreen-document.spec.ts
+++ b/apps/browser/src/platform/offscreen-document/offscreen-document.spec.ts
@@ -3,14 +3,19 @@ import { BrowserApi } from "../browser/browser-api";
import BrowserClipboardService from "../services/browser-clipboard.service";
describe("OffscreenDocument", () => {
- const browserApiMessageListenerSpy = jest.spyOn(BrowserApi, "messageListener");
- const browserClipboardServiceCopySpy = jest.spyOn(BrowserClipboardService, "copy");
- const browserClipboardServiceReadSpy = jest.spyOn(BrowserClipboardService, "read");
- const consoleErrorSpy = jest.spyOn(console, "error");
+ let browserClipboardServiceCopySpy: jest.SpyInstance;
+ let browserClipboardServiceReadSpy: jest.SpyInstance;
+ let browserApiMessageListenerSpy: jest.SpyInstance;
+ let consoleErrorSpy: jest.SpyInstance;
- // FIXME: Remove when updating file. Eslint update
- // eslint-disable-next-line @typescript-eslint/no-require-imports
- require("../offscreen-document/offscreen-document");
+ beforeEach(async () => {
+ browserApiMessageListenerSpy = jest.spyOn(BrowserApi, "messageListener");
+ browserClipboardServiceCopySpy = jest.spyOn(BrowserClipboardService, "copy");
+ browserClipboardServiceReadSpy = jest.spyOn(BrowserClipboardService, "read");
+ consoleErrorSpy = jest.spyOn(console, "error").mockImplementation();
+
+ await import("./offscreen-document");
+ });
describe("init", () => {
it("sets up a `chrome.runtime.onMessage` listener", () => {
@@ -47,6 +52,7 @@ describe("OffscreenDocument", () => {
it("copies the message text", async () => {
const text = "test";
+ browserClipboardServiceCopySpy.mockResolvedValueOnce(undefined);
sendMockExtensionMessage({ command: "offscreenCopyToClipboard", text });
await flushPromises();
@@ -56,6 +62,7 @@ describe("OffscreenDocument", () => {
describe("handleOffscreenReadFromClipboard", () => {
it("reads the value from the clipboard service", async () => {
+ browserClipboardServiceReadSpy.mockResolvedValueOnce("");
sendMockExtensionMessage({ command: "offscreenReadFromClipboard" });
await flushPromises();
diff --git a/apps/browser/src/platform/services/browser-clipboard.service.spec.ts b/apps/browser/src/platform/services/browser-clipboard.service.spec.ts
index cf0d7c46004..1d6904c7eb0 100644
--- a/apps/browser/src/platform/services/browser-clipboard.service.spec.ts
+++ b/apps/browser/src/platform/services/browser-clipboard.service.spec.ts
@@ -2,9 +2,10 @@ import BrowserClipboardService from "./browser-clipboard.service";
describe("BrowserClipboardService", () => {
let windowMock: any;
- const consoleWarnSpy = jest.spyOn(console, "warn");
+ let consoleWarnSpy: any;
beforeEach(() => {
+ consoleWarnSpy = jest.spyOn(console, "warn").mockImplementation();
windowMock = {
navigator: {
clipboard: {
@@ -104,8 +105,6 @@ describe("BrowserClipboardService", () => {
});
await BrowserClipboardService.read(windowMock as Window);
-
- expect(consoleWarnSpy).toHaveBeenCalled();
});
});
});
diff --git a/apps/browser/src/platform/services/platform-utils/browser-platform-utils.service.spec.ts b/apps/browser/src/platform/services/platform-utils/browser-platform-utils.service.spec.ts
index fe049c4f1db..38166d10a08 100644
--- a/apps/browser/src/platform/services/platform-utils/browser-platform-utils.service.spec.ts
+++ b/apps/browser/src/platform/services/platform-utils/browser-platform-utils.service.spec.ts
@@ -185,7 +185,9 @@ describe("Browser Utils Service", () => {
describe("copyToClipboard", () => {
const getManifestVersionSpy = jest.spyOn(BrowserApi, "manifestVersion", "get");
const sendMessageToAppSpy = jest.spyOn(SafariApp, "sendMessageToApp");
- const clipboardServiceCopySpy = jest.spyOn(BrowserClipboardService, "copy");
+ const clipboardServiceCopySpy = jest
+ .spyOn(BrowserClipboardService, "copy")
+ .mockResolvedValue(undefined);
let triggerOffscreenCopyToClipboardSpy: jest.SpyInstance;
beforeEach(() => {
@@ -281,7 +283,9 @@ describe("Browser Utils Service", () => {
describe("readFromClipboard", () => {
const getManifestVersionSpy = jest.spyOn(BrowserApi, "manifestVersion", "get");
const sendMessageToAppSpy = jest.spyOn(SafariApp, "sendMessageToApp");
- const clipboardServiceReadSpy = jest.spyOn(BrowserClipboardService, "read");
+ const clipboardServiceReadSpy = jest
+ .spyOn(BrowserClipboardService, "read")
+ .mockResolvedValue("");
beforeEach(() => {
getManifestVersionSpy.mockReturnValue(2);
From 45d5b171b857a61bf94b85aaf1a4afb7f03e3eb9 Mon Sep 17 00:00:00 2001
From: Daniel Riera
Date: Thu, 20 Mar 2025 09:54:56 -0400
Subject: [PATCH 019/113] PM-19291 Pass relevant folder and vault data to drop
down component within notification footer (#13901)
* PM-19291
- Pass relevant data into dropdown component
- Clean up some files
- Pass all data into notificationBarIframeInitData using promise all
* fix tests
---
.../notification.background.spec.ts | 15 ++--
.../background/notification.background.ts | 69 ++++++++++++-------
.../content/components/notification/body.ts | 4 +-
.../components/notification/container.ts | 21 ++++--
.../abstractions/notification-bar.ts | 18 +++--
apps/browser/src/autofill/notification/bar.ts | 23 ++++++-
.../browser/src/background/main.background.ts | 13 ++--
7 files changed, 110 insertions(+), 53 deletions(-)
diff --git a/apps/browser/src/autofill/background/notification.background.spec.ts b/apps/browser/src/autofill/background/notification.background.spec.ts
index e02e3d8d951..d474e303336 100644
--- a/apps/browser/src/autofill/background/notification.background.spec.ts
+++ b/apps/browser/src/autofill/background/notification.background.spec.ts
@@ -1,6 +1,7 @@
import { mock, MockProxy } from "jest-mock-extended";
import { BehaviorSubject, firstValueFrom } from "rxjs";
+import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { PolicyService } from "@bitwarden/common/admin-console/services/policy/policy.service";
import { AccountInfo, AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
@@ -59,6 +60,7 @@ describe("NotificationBackground", () => {
const themeStateService = mock();
const configService = mock();
const accountService = mock();
+ const organizationService = mock();
const activeAccountSubject = new BehaviorSubject<{ id: UserId } & AccountInfo>({
id: "testId" as UserId,
@@ -73,18 +75,19 @@ describe("NotificationBackground", () => {
authService.activeAccountStatus$ = activeAccountStatusMock$;
accountService.activeAccount$ = activeAccountSubject;
notificationBackground = new NotificationBackground(
+ accountService,
+ authService,
autofillService,
cipherService,
- authService,
- policyService,
- folderService,
- userNotificationSettingsService,
+ configService,
domainSettingsService,
environmentService,
+ folderService,
logService,
+ organizationService,
+ policyService,
themeStateService,
- configService,
- accountService,
+ userNotificationSettingsService,
);
});
diff --git a/apps/browser/src/autofill/background/notification.background.ts b/apps/browser/src/autofill/background/notification.background.ts
index 11037e7e261..50e0ee0aa75 100644
--- a/apps/browser/src/autofill/background/notification.background.ts
+++ b/apps/browser/src/autofill/background/notification.background.ts
@@ -2,6 +2,7 @@
// @ts-strict-ignore
import { firstValueFrom } from "rxjs";
+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 { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
@@ -63,46 +64,48 @@ export default class NotificationBackground {
ExtensionCommand.AutofillIdentity,
]);
private readonly extensionMessageHandlers: NotificationBackgroundExtensionMessageHandlers = {
- unlockCompleted: ({ message, sender }) => this.handleUnlockCompleted(message, sender),
- bgGetFolderData: () => this.getFolderData(),
- bgCloseNotificationBar: ({ message, sender }) =>
- this.handleCloseNotificationBarMessage(message, sender),
+ bgAddLogin: ({ message, sender }) => this.addLogin(message, sender),
bgAdjustNotificationBar: ({ message, sender }) =>
this.handleAdjustNotificationBarMessage(message, sender),
- bgAddLogin: ({ message, sender }) => this.addLogin(message, sender),
bgChangedPassword: ({ message, sender }) => this.changedPassword(message, sender),
- bgRemoveTabFromNotificationQueue: ({ sender }) =>
- this.removeTabFromNotificationQueue(sender.tab),
- bgSaveCipher: ({ message, sender }) => this.handleSaveCipherMessage(message, sender),
- bgNeverSave: ({ sender }) => this.saveNever(sender.tab),
- collectPageDetailsResponse: ({ message }) =>
- this.handleCollectPageDetailsResponseMessage(message),
- bgUnlockPopoutOpened: ({ message, sender }) => this.unlockVault(message, sender.tab),
- checkNotificationQueue: ({ sender }) => this.checkNotificationQueue(sender.tab),
- bgReopenUnlockPopout: ({ sender }) => this.openUnlockPopout(sender.tab),
+ bgCloseNotificationBar: ({ message, sender }) =>
+ this.handleCloseNotificationBarMessage(message, sender),
+ bgGetActiveUserServerConfig: () => this.getActiveUserServerConfig(),
+ bgGetDecryptedCiphers: () => this.getNotificationCipherData(),
bgGetEnableChangedPasswordPrompt: () => this.getEnableChangedPasswordPrompt(),
bgGetEnableAddedLoginPrompt: () => this.getEnableAddedLoginPrompt(),
bgGetExcludedDomains: () => this.getExcludedDomains(),
- bgGetActiveUserServerConfig: () => this.getActiveUserServerConfig(),
+ bgGetFolderData: () => this.getFolderData(),
+ bgGetOrgData: () => this.getOrgData(),
+ bgNeverSave: ({ sender }) => this.saveNever(sender.tab),
+ bgOpenVault: ({ message, sender }) => this.openVault(message, sender.tab),
+ bgRemoveTabFromNotificationQueue: ({ sender }) =>
+ this.removeTabFromNotificationQueue(sender.tab),
+ bgReopenUnlockPopout: ({ sender }) => this.openUnlockPopout(sender.tab),
+ bgSaveCipher: ({ message, sender }) => this.handleSaveCipherMessage(message, sender),
+ bgUnlockPopoutOpened: ({ message, sender }) => this.unlockVault(message, sender.tab),
+ checkNotificationQueue: ({ sender }) => this.checkNotificationQueue(sender.tab),
+ collectPageDetailsResponse: ({ message }) =>
+ this.handleCollectPageDetailsResponseMessage(message),
getWebVaultUrlForNotification: () => this.getWebVaultUrl(),
notificationRefreshFlagValue: () => this.getNotificationFlag(),
- bgGetDecryptedCiphers: () => this.getNotificationCipherData(),
- bgOpenVault: ({ message, sender }) => this.openVault(message, sender.tab),
+ unlockCompleted: ({ message, sender }) => this.handleUnlockCompleted(message, sender),
};
constructor(
+ private accountService: AccountService,
+ private authService: AuthService,
private autofillService: AutofillService,
private cipherService: CipherService,
- private authService: AuthService,
- private policyService: PolicyService,
- private folderService: FolderService,
- private userNotificationSettingsService: UserNotificationSettingsServiceAbstraction,
+ private configService: ConfigService,
private domainSettingsService: DomainSettingsService,
private environmentService: EnvironmentService,
+ private folderService: FolderService,
private logService: LogService,
+ private organizationService: OrganizationService,
+ private policyService: PolicyService,
private themeStateService: ThemeStateService,
- private configService: ConfigService,
- private accountService: AccountService,
+ private userNotificationSettingsService: UserNotificationSettingsServiceAbstraction,
) {}
init() {
@@ -744,6 +747,26 @@ export default class NotificationBackground {
);
}
+ /**
+ * Returns the first value found from the organization service organizations$ observable.
+ */
+ private async getOrgData() {
+ const activeUserId = await firstValueFrom(
+ this.accountService.activeAccount$.pipe(getOptionalUserId),
+ );
+ const organizations = await firstValueFrom(
+ this.organizationService.organizations$(activeUserId),
+ );
+ return organizations.map((org) => {
+ const { id, name, productTierType } = org;
+ return {
+ id,
+ name,
+ productTierType,
+ };
+ });
+ }
+
/**
* Handles the unlockCompleted extension message. Will close the notification bar
* after an attempted autofill action, and retry the autofill action if the message
diff --git a/apps/browser/src/autofill/content/components/notification/body.ts b/apps/browser/src/autofill/content/components/notification/body.ts
index 2433381dfba..66b580bde43 100644
--- a/apps/browser/src/autofill/content/components/notification/body.ts
+++ b/apps/browser/src/autofill/content/components/notification/body.ts
@@ -16,12 +16,12 @@ const { css } = createEmotion({
});
export function NotificationBody({
- ciphers,
+ ciphers = [],
notificationType,
theme = ThemeTypes.Light,
handleEditOrUpdateAction,
}: {
- ciphers: NotificationCipherData[];
+ ciphers?: NotificationCipherData[];
customClasses?: string[];
notificationType?: NotificationType;
theme: Theme;
diff --git a/apps/browser/src/autofill/content/components/notification/container.ts b/apps/browser/src/autofill/content/components/notification/container.ts
index f98ef795749..8d80dc9fb50 100644
--- a/apps/browser/src/autofill/content/components/notification/container.ts
+++ b/apps/browser/src/autofill/content/components/notification/container.ts
@@ -9,6 +9,7 @@ import {
NotificationType,
} from "../../../notification/abstractions/notification-bar";
import { NotificationCipherData } from "../cipher/types";
+import { FolderView, OrgView } from "../common-types";
import { themes, spacing } from "../constants/styles";
import { NotificationBody, componentClassPrefix as notificationBodyClassPrefix } from "./body";
@@ -20,20 +21,24 @@ import {
export function NotificationContainer({
handleCloseNotification,
+ handleEditOrUpdateAction,
+ handleSaveAction,
+ ciphers,
+ folders,
i18n,
+ organizations,
theme = ThemeTypes.Light,
type,
- ciphers,
- handleSaveAction,
- handleEditOrUpdateAction,
}: NotificationBarIframeInitData & {
handleCloseNotification: (e: Event) => void;
handleSaveAction: (e: Event) => void;
handleEditOrUpdateAction: (e: Event) => void;
} & {
+ ciphers?: NotificationCipherData[];
+ folders?: FolderView[];
i18n: { [key: string]: string };
+ organizations?: OrgView[];
type: NotificationType; // @TODO typing override for generic `NotificationBarIframeInitData.type`
- ciphers: NotificationCipherData[];
}) {
const headerMessage = getHeaderMessage(i18n, type);
const showBody = true;
@@ -42,8 +47,8 @@ export function NotificationContainer({
${NotificationHeader({
handleCloseNotification,
- standalone: showBody,
message: headerMessage,
+ standalone: showBody,
theme,
})}
${showBody
@@ -56,9 +61,11 @@ export function NotificationContainer({
: null}
${NotificationFooter({
handleSaveAction,
- theme,
- notificationType: type,
+ folders,
i18n,
+ notificationType: type,
+ organizations,
+ theme,
})}
`;
diff --git a/apps/browser/src/autofill/notification/abstractions/notification-bar.ts b/apps/browser/src/autofill/notification/abstractions/notification-bar.ts
index cb14a86dffa..6e7427e3a38 100644
--- a/apps/browser/src/autofill/notification/abstractions/notification-bar.ts
+++ b/apps/browser/src/autofill/notification/abstractions/notification-bar.ts
@@ -1,5 +1,8 @@
import { Theme } from "@bitwarden/common/platform/enums";
+import { NotificationCipherData } from "../../../autofill/content/components/cipher/types";
+import { FolderView, OrgView } from "../../../autofill/content/components/common-types";
+
const NotificationTypes = {
Add: "add",
Change: "change",
@@ -9,21 +12,24 @@ const NotificationTypes = {
type NotificationType = (typeof NotificationTypes)[keyof typeof NotificationTypes];
type NotificationBarIframeInitData = {
- type?: string; // @TODO use `NotificationType`
- isVaultLocked?: boolean;
- theme?: Theme;
- removeIndividualVault?: boolean;
- importType?: string;
applyRedesign?: boolean;
+ ciphers?: NotificationCipherData[];
+ folders?: FolderView[];
+ importType?: string;
+ isVaultLocked?: boolean;
launchTimestamp?: number;
+ organizations?: OrgView[];
+ removeIndividualVault?: boolean;
+ theme?: Theme;
+ type?: string; // @TODO use `NotificationType`
};
type NotificationBarWindowMessage = {
+ cipherId?: string;
command: string;
error?: string;
initData?: NotificationBarIframeInitData;
username?: string;
- cipherId?: string;
};
type NotificationBarWindowMessageHandlers = {
diff --git a/apps/browser/src/autofill/notification/bar.ts b/apps/browser/src/autofill/notification/bar.ts
index 617b1e58c14..d17c008372d 100644
--- a/apps/browser/src/autofill/notification/bar.ts
+++ b/apps/browser/src/autofill/notification/bar.ts
@@ -5,6 +5,8 @@ import { ConsoleLogService } from "@bitwarden/common/platform/services/console-l
import type { FolderView } from "@bitwarden/common/vault/models/view/folder.view";
import { AdjustNotificationBarMessageData } from "../background/abstractions/notification.background";
+import { NotificationCipherData } from "../content/components/cipher/types";
+import { OrgView } from "../content/components/common-types";
import { NotificationConfirmationContainer } from "../content/components/notification/confirmation-container";
import { NotificationContainer } from "../content/components/notification/container";
import { buildSvgDomElement } from "../utils";
@@ -115,7 +117,7 @@ function setElementText(template: HTMLTemplateElement, elementId: string, text:
}
}
-function initNotificationBar(message: NotificationBarWindowMessage) {
+async function initNotificationBar(message: NotificationBarWindowMessage) {
const { initData } = message;
if (!initData) {
return;
@@ -131,7 +133,23 @@ function initNotificationBar(message: NotificationBarWindowMessage) {
// Current implementations utilize a require for scss files which creates the need to remove the node.
document.head.querySelectorAll('link[rel="stylesheet"]').forEach((node) => node.remove());
- sendPlatformMessage({ command: "bgGetDecryptedCiphers" }, (cipherData) => {
+ await Promise.all([
+ new Promise((resolve) =>
+ sendPlatformMessage({ command: "bgGetOrgData" }, resolve),
+ ),
+ new Promise((resolve) =>
+ sendPlatformMessage({ command: "bgGetFolderData" }, resolve),
+ ),
+ new Promise((resolve) =>
+ sendPlatformMessage({ command: "bgGetDecryptedCiphers" }, resolve),
+ ),
+ ]).then(([organizations, folders, ciphers]) => {
+ notificationBarIframeInitData = {
+ ...notificationBarIframeInitData,
+ folders,
+ ciphers,
+ organizations,
+ };
// @TODO use context to avoid prop drilling
return render(
NotificationContainer({
@@ -142,7 +160,6 @@ function initNotificationBar(message: NotificationBarWindowMessage) {
handleSaveAction,
handleEditOrUpdateAction,
i18n,
- ciphers: cipherData,
}),
document.body,
);
diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts
index f8f86e6a277..74fa6acdf79 100644
--- a/apps/browser/src/background/main.background.ts
+++ b/apps/browser/src/background/main.background.ts
@@ -1173,18 +1173,19 @@ export default class MainBackground {
() => this.generatePasswordToClipboard(),
);
this.notificationBackground = new NotificationBackground(
+ this.accountService,
+ this.authService,
this.autofillService,
this.cipherService,
- this.authService,
- this.policyService,
- this.folderService,
- this.userNotificationSettingsService,
+ this.configService,
this.domainSettingsService,
this.environmentService,
+ this.folderService,
this.logService,
+ this.organizationService,
+ this.policyService,
this.themeStateService,
- this.configService,
- this.accountService,
+ this.userNotificationSettingsService,
);
this.overlayNotificationsBackground = new OverlayNotificationsBackground(
From 23fbb56248cde0a5d6f4d11e707d8baec617b204 Mon Sep 17 00:00:00 2001
From: Justin Baur <19896123+justindbaur@users.noreply.github.com>
Date: Thu, 20 Mar 2025 10:56:30 -0400
Subject: [PATCH 020/113] Switch Notifications to only connect on unlocked
(#13913)
---
.../internal/default-notifications.service.spec.ts | 13 +++++++++----
.../internal/default-notifications.service.ts | 10 +++++-----
2 files changed, 14 insertions(+), 9 deletions(-)
diff --git a/libs/common/src/platform/notifications/internal/default-notifications.service.spec.ts b/libs/common/src/platform/notifications/internal/default-notifications.service.spec.ts
index e24069a9fbe..bf834e8dd93 100644
--- a/libs/common/src/platform/notifications/internal/default-notifications.service.spec.ts
+++ b/libs/common/src/platform/notifications/internal/default-notifications.service.spec.ts
@@ -225,9 +225,10 @@ describe("NotificationsService", () => {
});
it.each([
- { initialStatus: AuthenticationStatus.Locked, updatedStatus: AuthenticationStatus.Unlocked },
- { initialStatus: AuthenticationStatus.Unlocked, updatedStatus: AuthenticationStatus.Locked },
- { initialStatus: AuthenticationStatus.Locked, updatedStatus: AuthenticationStatus.Locked },
+ // Temporarily rolling back notifications being connected while locked
+ // { initialStatus: AuthenticationStatus.Locked, updatedStatus: AuthenticationStatus.Unlocked },
+ // { initialStatus: AuthenticationStatus.Unlocked, updatedStatus: AuthenticationStatus.Locked },
+ // { initialStatus: AuthenticationStatus.Locked, updatedStatus: AuthenticationStatus.Locked },
{ initialStatus: AuthenticationStatus.Unlocked, updatedStatus: AuthenticationStatus.Unlocked },
])(
"does not re-connect when the user transitions from $initialStatus to $updatedStatus",
@@ -252,7 +253,11 @@ describe("NotificationsService", () => {
},
);
- it.each([AuthenticationStatus.Locked, AuthenticationStatus.Unlocked])(
+ it.each([
+ // Temporarily disabling notifications connecting while in a locked state
+ // AuthenticationStatus.Locked,
+ AuthenticationStatus.Unlocked,
+ ])(
"connects when a user transitions from logged out to %s",
async (newStatus: AuthenticationStatus) => {
emitActiveUser(mockUser1);
diff --git a/libs/common/src/platform/notifications/internal/default-notifications.service.ts b/libs/common/src/platform/notifications/internal/default-notifications.service.ts
index f0586e37ff7..fc505b018ce 100644
--- a/libs/common/src/platform/notifications/internal/default-notifications.service.ts
+++ b/libs/common/src/platform/notifications/internal/default-notifications.service.ts
@@ -123,13 +123,13 @@ export class DefaultNotificationsService implements NotificationsServiceAbstract
);
}
+ // This method name is a lie currently as we also have an access token
+ // when locked, this is eventually where we want to be but it increases load
+ // on signalR so we are rolling back until we can move the load of browser to
+ // web push.
private hasAccessToken$(userId: UserId) {
return this.authService.authStatusFor$(userId).pipe(
- map(
- (authStatus) =>
- authStatus === AuthenticationStatus.Locked ||
- authStatus === AuthenticationStatus.Unlocked,
- ),
+ map((authStatus) => authStatus === AuthenticationStatus.Unlocked),
distinctUntilChanged(),
);
}
From bef0e0f5b7c45e345e408f053ff465df0baeb975 Mon Sep 17 00:00:00 2001
From: Github Actions
Date: Thu, 20 Mar 2025 14:58:59 +0000
Subject: [PATCH 021/113] Bumped client version(s)
---
apps/browser/package.json | 2 +-
apps/browser/src/manifest.json | 2 +-
apps/browser/src/manifest.v3.json | 2 +-
package-lock.json | 2 +-
4 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/apps/browser/package.json b/apps/browser/package.json
index e3bccf3f0df..5a8ddd03b41 100644
--- a/apps/browser/package.json
+++ b/apps/browser/package.json
@@ -1,6 +1,6 @@
{
"name": "@bitwarden/browser",
- "version": "2025.3.0",
+ "version": "2025.3.1",
"scripts": {
"build": "npm run build:chrome",
"build:chrome": "cross-env BROWSER=chrome MANIFEST_VERSION=3 NODE_OPTIONS=\"--max-old-space-size=8192\" webpack",
diff --git a/apps/browser/src/manifest.json b/apps/browser/src/manifest.json
index 5bfca440b99..4510c2f342d 100644
--- a/apps/browser/src/manifest.json
+++ b/apps/browser/src/manifest.json
@@ -2,7 +2,7 @@
"manifest_version": 2,
"name": "__MSG_extName__",
"short_name": "__MSG_appName__",
- "version": "2025.3.0",
+ "version": "2025.3.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 1e2ac1812ca..fc897c1b1c3 100644
--- a/apps/browser/src/manifest.v3.json
+++ b/apps/browser/src/manifest.v3.json
@@ -3,7 +3,7 @@
"minimum_chrome_version": "102.0",
"name": "__MSG_extName__",
"short_name": "__MSG_appName__",
- "version": "2025.3.0",
+ "version": "2025.3.1",
"description": "__MSG_extDesc__",
"default_locale": "en",
"author": "Bitwarden Inc.",
diff --git a/package-lock.json b/package-lock.json
index eaf5c0f24ed..cb51c157b09 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -189,7 +189,7 @@
},
"apps/browser": {
"name": "@bitwarden/browser",
- "version": "2025.3.0"
+ "version": "2025.3.1"
},
"apps/cli": {
"name": "@bitwarden/cli",
From e31ffd9b6603939e45b80af4a935c1e8485e51e3 Mon Sep 17 00:00:00 2001
From: Matt Andreko
Date: Thu, 20 Mar 2025 12:29:24 -0400
Subject: [PATCH 022/113] Update SARIF upload to use proper branch (#13917)
---
.github/workflows/build-web.yml | 2 ++
.github/workflows/scan.yml | 2 ++
2 files changed, 4 insertions(+)
diff --git a/.github/workflows/build-web.yml b/.github/workflows/build-web.yml
index bd96b388c6a..3cc0df5103c 100644
--- a/.github/workflows/build-web.yml
+++ b/.github/workflows/build-web.yml
@@ -323,6 +323,8 @@ jobs:
uses: github/codeql-action/upload-sarif@d68b2d4edb4189fd2a5366ac14e72027bd4b37dd # v3.28.2
with:
sarif_file: ${{ steps.container-scan.outputs.sarif }}
+ sha: ${{ contains(github.event_name, 'pull_request') && github.event.pull_request.head.sha || github.sha }}
+ ref: ${{ contains(github.event_name, 'pull_request') && format('refs/pull/{0}/head', github.event.pull_request.number) || github.ref }}
- name: Log out of Docker
run: docker logout
diff --git a/.github/workflows/scan.yml b/.github/workflows/scan.yml
index c5e189c4666..77b66ba8bf1 100644
--- a/.github/workflows/scan.yml
+++ b/.github/workflows/scan.yml
@@ -49,6 +49,8 @@ jobs:
uses: github/codeql-action/upload-sarif@d68b2d4edb4189fd2a5366ac14e72027bd4b37dd # v3.28.2
with:
sarif_file: cx_result.sarif
+ sha: ${{ contains(github.event_name, 'pull_request') && github.event.pull_request.head.sha || github.sha }}
+ ref: ${{ contains(github.event_name, 'pull_request') && format('refs/pull/{0}/head', github.event.pull_request.number) || github.ref }}
quality:
name: Quality scan
From c999c19f07eb7014208035bdedd3c31ac5737904 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Micha=C5=82=20Ch=C4=99ci=C5=84ski?=
Date: Thu, 20 Mar 2025 17:38:51 +0100
Subject: [PATCH 023/113] fix(workflow): add conditional checks for Docker
image scanning and result upload (#13898)
---
.github/workflows/build-web.yml | 2 ++
1 file changed, 2 insertions(+)
diff --git a/.github/workflows/build-web.yml b/.github/workflows/build-web.yml
index 3cc0df5103c..e91fba2e87a 100644
--- a/.github/workflows/build-web.yml
+++ b/.github/workflows/build-web.yml
@@ -312,6 +312,7 @@ jobs:
cosign sign --yes ${images}
- name: Scan Docker image
+ if: ${{ needs.setup.outputs.has_secrets == 'true' }}
id: container-scan
uses: anchore/scan-action@869c549e657a088dc0441b08ce4fc0ecdac2bb65 # v5.3.0
with:
@@ -320,6 +321,7 @@ jobs:
output-format: sarif
- name: Upload Grype results to GitHub
+ if: ${{ needs.setup.outputs.has_secrets == 'true' }}
uses: github/codeql-action/upload-sarif@d68b2d4edb4189fd2a5366ac14e72027bd4b37dd # v3.28.2
with:
sarif_file: ${{ steps.container-scan.outputs.sarif }}
From bd0fedc5ce18fc412b6f621dff2a9199503cd2ed Mon Sep 17 00:00:00 2001
From: Alex Rosenfeld
Date: Thu, 20 Mar 2025 13:53:17 -0400
Subject: [PATCH 024/113] [PM-18153] add support for importing some older /
wonky card formats from msecure (#13328)
* add support for importing some older / wonky card formats from msecure
* slightly less fuzzy logic
---------
Co-authored-by: Daniel James Smith <2670567+djsmith85@users.noreply.github.com>
---
.../importers/msecure-csv-importer.spec.ts | 18 ++++++++
.../src/importers/msecure-csv-importer.ts | 45 ++++++++++++-------
2 files changed, 46 insertions(+), 17 deletions(-)
diff --git a/libs/importer/src/importers/msecure-csv-importer.spec.ts b/libs/importer/src/importers/msecure-csv-importer.spec.ts
index 83e35802fac..3cf7cc713a8 100644
--- a/libs/importer/src/importers/msecure-csv-importer.spec.ts
+++ b/libs/importer/src/importers/msecure-csv-importer.spec.ts
@@ -8,6 +8,24 @@ describe("MSecureCsvImporter.parse", () => {
importer = new MSecureCsvImporter();
});
+ it("should correctly parse legacy formatted cards", async () => {
+ const mockCsvData =
+ `aWeirdOldStyleCard|1032,Credit Card,,Security code 1234,Card Number|12|5555 4444 3333 2222,Expiration Date|11|04/0029,Name on Card|9|Obi Wan Kenobi,Security Code|9|444,`.trim();
+ const result = await importer.parse(mockCsvData);
+
+ expect(result.success).toBe(true);
+ expect(result.ciphers.length).toBe(1);
+ const cipher = result.ciphers[0];
+ expect(cipher.name).toBe("aWeirdOldStyleCard");
+ expect(cipher.type).toBe(CipherType.Card);
+ expect(cipher.card.number).toBe("5555 4444 3333 2222");
+ expect(cipher.card.expiration).toBe("04 / 2029");
+ expect(cipher.card.code).toBe("444");
+ expect(cipher.card.cardholderName).toBe("Obi Wan Kenobi");
+ expect(cipher.notes).toBe("Security code 1234");
+ expect(cipher.card.brand).toBe("");
+ });
+
it("should correctly parse credit card entries as Secret Notes", async () => {
const mockCsvData =
`myCreditCard|155089404,Credit Card,,,Card Number|12|41111111111111111,Expiration Date|11|05/2026,Security Code|9|123,Name on Card|0|John Doe,PIN|9|1234,Issuing Bank|0|Visa,Phone Number|4|,Billing Address|0|,`.trim();
diff --git a/libs/importer/src/importers/msecure-csv-importer.ts b/libs/importer/src/importers/msecure-csv-importer.ts
index 322764fa8dc..e78c715976f 100644
--- a/libs/importer/src/importers/msecure-csv-importer.ts
+++ b/libs/importer/src/importers/msecure-csv-importer.ts
@@ -43,23 +43,34 @@ export class MSecureCsvImporter extends BaseImporter implements Importer {
).split("/");
cipher.card.expMonth = month.trim();
cipher.card.expYear = year.trim();
- cipher.card.code = this.getValueOrDefault(this.splitValueRetainingLastPart(value[6]));
- cipher.card.cardholderName = this.getValueOrDefault(
- this.splitValueRetainingLastPart(value[7]),
+ const securityCodeRegex = RegExp("^Security Code\\|\\d*\\|");
+ const securityCodeEntry = value.find((entry: string) => securityCodeRegex.test(entry));
+ cipher.card.code = this.getValueOrDefault(
+ this.splitValueRetainingLastPart(securityCodeEntry),
);
- cipher.card.brand = this.getValueOrDefault(this.splitValueRetainingLastPart(value[9]));
- cipher.notes =
- this.getValueOrDefault(value[8].split("|")[0]) +
- ": " +
- this.getValueOrDefault(this.splitValueRetainingLastPart(value[8]), "") +
- "\n" +
- this.getValueOrDefault(value[10].split("|")[0]) +
- ": " +
- this.getValueOrDefault(this.splitValueRetainingLastPart(value[10]), "") +
- "\n" +
- this.getValueOrDefault(value[11].split("|")[0]) +
- ": " +
- this.getValueOrDefault(this.splitValueRetainingLastPart(value[11]), "");
+
+ const cardNameRegex = RegExp("^Name on Card\\|\\d*\\|");
+ const nameOnCardEntry = value.find((entry: string) => entry.match(cardNameRegex));
+ cipher.card.cardholderName = this.getValueOrDefault(
+ this.splitValueRetainingLastPart(nameOnCardEntry),
+ );
+
+ cipher.card.brand = this.getValueOrDefault(this.splitValueRetainingLastPart(value[9]), "");
+
+ const noteRegex = RegExp("\\|\\d*\\|");
+ const rawNotes = value
+ .slice(2)
+ .filter((entry: string) => !this.isNullOrWhitespace(entry) && !noteRegex.test(entry));
+ const noteIndexes = [8, 10, 11];
+ const indexedNotes = noteIndexes
+ .filter((idx) => value[idx] && noteRegex.test(value[idx]))
+ .map((idx) => value[idx])
+ .map((val) => {
+ const key = val.split("|")[0];
+ const value = this.getValueOrDefault(this.splitValueRetainingLastPart(val), "");
+ return `${key}: ${value}`;
+ });
+ cipher.notes = [...rawNotes, ...indexedNotes].join("\n");
} else if (value.length > 3) {
cipher.type = CipherType.SecureNote;
cipher.secureNote = new SecureNoteView();
@@ -95,6 +106,6 @@ export class MSecureCsvImporter extends BaseImporter implements Importer {
// like "Password|8|myPassword", we want to keep the "myPassword" but also ensure that if
// the value contains any "|" it works fine
private splitValueRetainingLastPart(value: string) {
- return value.split("|").slice(0, 2).concat(value.split("|").slice(2).join("|")).pop();
+ return value && value.split("|").slice(0, 2).concat(value.split("|").slice(2).join("|")).pop();
}
}
From cf827981af3abf9d12d88bdad214b5b9f09b6243 Mon Sep 17 00:00:00 2001
From: Jason Ng
Date: Thu, 20 Mar 2025 15:16:18 -0400
Subject: [PATCH 025/113] [PM-19240] Do not show task unless Manage or Edit
Permission (#13880)
* do not show task for edit except pw
---
.../view/emergency-view-dialog.component.spec.ts | 3 +++
libs/vault/src/cipher-view/cipher-view.component.ts | 11 +++++++++--
2 files changed, 12 insertions(+), 2 deletions(-)
diff --git a/apps/web/src/app/auth/settings/emergency-access/view/emergency-view-dialog.component.spec.ts b/apps/web/src/app/auth/settings/emergency-access/view/emergency-view-dialog.component.spec.ts
index 0021d938f82..6e96b357e3e 100644
--- a/apps/web/src/app/auth/settings/emergency-access/view/emergency-view-dialog.component.spec.ts
+++ b/apps/web/src/app/auth/settings/emergency-access/view/emergency-view-dialog.component.spec.ts
@@ -13,6 +13,7 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec";
import { UserId } from "@bitwarden/common/types/guid";
+import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
import { CipherType } from "@bitwarden/common/vault/enums";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
@@ -65,6 +66,7 @@ describe("EmergencyViewDialogComponent", () => {
useValue: ChangeLoginPasswordService,
},
{ provide: ConfigService, useValue: ConfigService },
+ { provide: CipherService, useValue: mock() },
],
},
add: {
@@ -79,6 +81,7 @@ describe("EmergencyViewDialogComponent", () => {
useValue: mock(),
},
{ provide: ConfigService, useValue: mock() },
+ { provide: CipherService, useValue: mock() },
],
},
})
diff --git a/libs/vault/src/cipher-view/cipher-view.component.ts b/libs/vault/src/cipher-view/cipher-view.component.ts
index 1df96656da5..57c2b4dbae4 100644
--- a/libs/vault/src/cipher-view/cipher-view.component.ts
+++ b/libs/vault/src/cipher-view/cipher-view.component.ts
@@ -15,7 +15,8 @@ import { isCardExpired } from "@bitwarden/common/autofill/utils";
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 { CollectionId, UserId } from "@bitwarden/common/types/guid";
+import { CipherId, CollectionId, UserId } from "@bitwarden/common/types/guid";
+import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { FolderView } from "@bitwarden/common/vault/models/view/folder.view";
@@ -87,6 +88,7 @@ export class CipherViewComponent implements OnChanges, OnDestroy {
private platformUtilsService: PlatformUtilsService,
private changeLoginPasswordService: ChangeLoginPasswordService,
private configService: ConfigService,
+ private cipherService: CipherService,
) {}
async ngOnChanges() {
@@ -152,7 +154,12 @@ export class CipherViewComponent implements OnChanges, OnDestroy {
const userId = await firstValueFrom(this.activeUserId$);
- if (this.cipher.edit && this.cipher.viewPassword) {
+ // Show Tasks for Manage and Edit permissions
+ // Using cipherService to see if user has access to cipher in a non-AC context to address with Edit Except Password permissions
+ const allCiphers = await firstValueFrom(this.cipherService.ciphers$(userId));
+ const cipherServiceCipher = allCiphers[this.cipher?.id as CipherId];
+
+ if (cipherServiceCipher?.edit && cipherServiceCipher?.viewPassword) {
await this.checkPendingChangePasswordTasks(userId);
}
From 85c71351fce911af516dd10557bcd62ed34d154f Mon Sep 17 00:00:00 2001
From: Daniel Riera
Date: Thu, 20 Mar 2025 15:37:46 -0400
Subject: [PATCH 026/113] PM-19361 Notification bar dropdown folder component
displays "No Folder" twice (#13924)
* PM-19361
- Remove default folder option
- Edit iFrame height
* revert testing change
---
.../content/components/notification/button-row.ts | 13 ++++---------
.../src/autofill/content/notification-bar.ts | 4 ++--
2 files changed, 6 insertions(+), 11 deletions(-)
diff --git a/apps/browser/src/autofill/content/components/notification/button-row.ts b/apps/browser/src/autofill/content/components/notification/button-row.ts
index 1eb0a4ac5f4..8661f5957e1 100644
--- a/apps/browser/src/autofill/content/components/notification/button-row.ts
+++ b/apps/browser/src/autofill/content/components/notification/button-row.ts
@@ -60,23 +60,18 @@ export function NotificationButtonRow({
)
: ([] as Option[]);
- const noFolderOption: Option = {
- default: true,
- icon: Folder,
- text: "No folder", // @TODO localize
- value: "0",
- };
const folderOptions: Option[] = folders?.length
- ? folders.reduce(
+ ? folders.reduce(
(options, { id, name }: FolderView) => [
...options,
{
icon: Folder,
text: name,
- value: id,
+ value: id === null ? "0" : id,
+ default: id === null,
},
],
- [noFolderOption],
+ [],
)
: [];
diff --git a/apps/browser/src/autofill/content/notification-bar.ts b/apps/browser/src/autofill/content/notification-bar.ts
index ae489ea956b..bf3d562a0ef 100644
--- a/apps/browser/src/autofill/content/notification-bar.ts
+++ b/apps/browser/src/autofill/content/notification-bar.ts
@@ -880,7 +880,7 @@ async function loadNotificationBar() {
const baseStyle = useComponentBar
? isNotificationFresh
- ? "height: calc(276px + 25px); width: 450px; right: 0; transform:translateX(100%); opacity:0;"
+ ? "height: calc(276px + 50px); width: 450px; right: 0; transform:translateX(100%); opacity:0;"
: "height: calc(276px + 25px); width: 450px; right: 0; transform:translateX(0%); opacity:1;"
: "height: 42px; width: 100%;";
@@ -910,7 +910,7 @@ async function loadNotificationBar() {
function getFrameStyle(useComponentBar: boolean): string {
return (
(useComponentBar
- ? "height: calc(276px + 25px); width: 450px; right: 0;"
+ ? "height: calc(276px + 50px); width: 450px; right: 0;"
: "height: 42px; width: 100%; left: 0;") +
" top: 0; padding: 0; position: fixed;" +
" z-index: 2147483647; visibility: visible;"
From 79fd1b32639e2e89ce4648185e7675dcfbd246b4 Mon Sep 17 00:00:00 2001
From: Jeffrey Holland <124393578+jholland-livefront@users.noreply.github.com>
Date: Thu, 20 Mar 2025 20:54:33 +0100
Subject: [PATCH 027/113] PM-17187 Autofill new card information in the popout
(#13688)
---
.../add-edit/add-edit-v2.component.ts | 26 +++++++++++++++++
.../cipher-form-config.service.ts | 5 ++++
.../card-details-section.component.spec.ts | 6 ++++
.../card-details-section.component.ts | 28 ++++++++++++++++++-
4 files changed, 64 insertions(+), 1 deletion(-)
diff --git a/apps/browser/src/vault/popup/components/vault-v2/add-edit/add-edit-v2.component.ts b/apps/browser/src/vault/popup/components/vault-v2/add-edit/add-edit-v2.component.ts
index b7ff0718b35..f986bdfca31 100644
--- a/apps/browser/src/vault/popup/components/vault-v2/add-edit/add-edit-v2.component.ts
+++ b/apps/browser/src/vault/popup/components/vault-v2/add-edit/add-edit-v2.component.ts
@@ -440,6 +440,32 @@ const mapAddEditCipherInfoToInitialValues = (
initialValues.name = cipher.name;
}
+ if (cipher.type === CipherType.Card) {
+ const card = cipher.card;
+
+ if (card != null) {
+ if (card.cardholderName != null) {
+ initialValues.cardholderName = card.cardholderName;
+ }
+
+ if (card.number != null) {
+ initialValues.number = card.number;
+ }
+
+ if (card.expMonth != null) {
+ initialValues.expMonth = card.expMonth;
+ }
+
+ if (card.expYear != null) {
+ initialValues.expYear = card.expYear;
+ }
+
+ if (card.code != null) {
+ initialValues.code = card.code;
+ }
+ }
+ }
+
if (cipher.type === CipherType.Login) {
const login = cipher.login;
diff --git a/libs/vault/src/cipher-form/abstractions/cipher-form-config.service.ts b/libs/vault/src/cipher-form/abstractions/cipher-form-config.service.ts
index 3fc473c4465..8a16050804b 100644
--- a/libs/vault/src/cipher-form/abstractions/cipher-form-config.service.ts
+++ b/libs/vault/src/cipher-form/abstractions/cipher-form-config.service.ts
@@ -25,6 +25,11 @@ export type OptionalInitialValues = {
username?: string;
password?: string;
name?: string;
+ cardholderName?: string;
+ number?: string;
+ expMonth?: string;
+ expYear?: string;
+ code?: string;
};
/**
diff --git a/libs/vault/src/cipher-form/components/card-details-section/card-details-section.component.spec.ts b/libs/vault/src/cipher-form/components/card-details-section/card-details-section.component.spec.ts
index 39a59192985..32baad189cf 100644
--- a/libs/vault/src/cipher-form/components/card-details-section/card-details-section.component.spec.ts
+++ b/libs/vault/src/cipher-form/components/card-details-section/card-details-section.component.spec.ts
@@ -65,6 +65,8 @@ describe("CardDetailsSectionComponent", () => {
cardView.cardholderName = "Ron Burgundy";
cardView.number = "4242 4242 4242 4242";
cardView.brand = "Visa";
+ cardView.expMonth = "";
+ cardView.code = "";
expect(patchCipherSpy).toHaveBeenCalled();
const patchFn = patchCipherSpy.mock.lastCall[0];
@@ -79,6 +81,10 @@ describe("CardDetailsSectionComponent", () => {
});
const cardView = new CardView();
+ cardView.cardholderName = "";
+ cardView.number = "";
+ cardView.expMonth = "";
+ cardView.code = "";
cardView.expYear = "2022";
expect(patchCipherSpy).toHaveBeenCalled();
diff --git a/libs/vault/src/cipher-form/components/card-details-section/card-details-section.component.ts b/libs/vault/src/cipher-form/components/card-details-section/card-details-section.component.ts
index c2b3ebb59aa..cb00c7d24f5 100644
--- a/libs/vault/src/cipher-form/components/card-details-section/card-details-section.component.ts
+++ b/libs/vault/src/cipher-form/components/card-details-section/card-details-section.component.ts
@@ -97,6 +97,10 @@ export class CardDetailsSectionComponent implements OnInit {
EventType = EventType;
+ get initialValues() {
+ return this.cipherFormContainer.config.initialValues;
+ }
+
constructor(
private cipherFormContainer: CipherFormContainer,
private formBuilder: FormBuilder,
@@ -139,7 +143,9 @@ export class CardDetailsSectionComponent implements OnInit {
const prefillCipher = this.cipherFormContainer.getInitialCipherView();
if (prefillCipher) {
- this.setInitialValues(prefillCipher);
+ this.initFromExistingCipher(prefillCipher.card);
+ } else {
+ this.initNewCipher();
}
if (this.disabled) {
@@ -147,6 +153,26 @@ export class CardDetailsSectionComponent implements OnInit {
}
}
+ private initFromExistingCipher(existingCard: CardView) {
+ this.cardDetailsForm.patchValue({
+ cardholderName: this.initialValues?.cardholderName ?? existingCard.cardholderName,
+ number: this.initialValues?.number ?? existingCard.number,
+ expMonth: this.initialValues?.expMonth ?? existingCard.expMonth,
+ expYear: this.initialValues?.expYear ?? existingCard.expYear,
+ code: this.initialValues?.code ?? existingCard.code,
+ });
+ }
+
+ private initNewCipher() {
+ this.cardDetailsForm.patchValue({
+ cardholderName: this.initialValues?.cardholderName || "",
+ number: this.initialValues?.number || "",
+ expMonth: this.initialValues?.expMonth || "",
+ expYear: this.initialValues?.expYear || "",
+ code: this.initialValues?.code || "",
+ });
+ }
+
/** Get the section heading based on the card brand */
getSectionHeading(): string {
const { brand } = this.cardDetailsForm.value;
From 87847dc8067462418acdb29f9b56bbe5444f5b12 Mon Sep 17 00:00:00 2001
From: Alec Rippberger <127791530+alec-livefront@users.noreply.github.com>
Date: Thu, 20 Mar 2025 15:23:19 -0500
Subject: [PATCH 028/113] fix: check device id and creationDate for falsey
values
This commit adds validation to check for falsey values in device 'id' and 'creationDate' fields in the device management component. This prevents potential issues when these string values are empty or otherwise evaluate to false.
Resolves PM-18757
---
.../app/auth/settings/security/device-management.component.ts | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/apps/web/src/app/auth/settings/security/device-management.component.ts b/apps/web/src/app/auth/settings/security/device-management.component.ts
index 0f31b8d4639..631ab02db7d 100644
--- a/apps/web/src/app/auth/settings/security/device-management.component.ts
+++ b/apps/web/src/app/auth/settings/security/device-management.component.ts
@@ -180,7 +180,7 @@ export class DeviceManagementComponent {
private updateDeviceTable(devices: Array): void {
this.dataSource.data = devices
.map((device: DeviceView): DeviceTableData | null => {
- if (device.id == undefined) {
+ if (!device.id) {
this.validationService.showError(new Error(this.i18nService.t("deviceIdMissing")));
return null;
}
@@ -190,7 +190,7 @@ export class DeviceManagementComponent {
return null;
}
- if (device.creationDate == undefined) {
+ if (!device.creationDate) {
this.validationService.showError(
new Error(this.i18nService.t("deviceCreationDateMissing")),
);
From 1551ab5be97331a9a4d1686ef74775d4c392d4d1 Mon Sep 17 00:00:00 2001
From: "bw-ghapp[bot]" <178206702+bw-ghapp[bot]@users.noreply.github.com>
Date: Fri, 21 Mar 2025 11:14:45 +0100
Subject: [PATCH 029/113] Autosync the updated translations (#13937)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
---
apps/web/src/locales/de/messages.json | 2 +-
apps/web/src/locales/hr/messages.json | 122 +++++++++++------------
apps/web/src/locales/zh_CN/messages.json | 2 +-
3 files changed, 63 insertions(+), 63 deletions(-)
diff --git a/apps/web/src/locales/de/messages.json b/apps/web/src/locales/de/messages.json
index e0a25e99ae2..135ab8af923 100644
--- a/apps/web/src/locales/de/messages.json
+++ b/apps/web/src/locales/de/messages.json
@@ -429,7 +429,7 @@
"message": "Zum Sortieren ziehen"
},
"dragToReorder": {
- "message": "Ziehen zum umsortieren"
+ "message": "Ziehen zum Umsortieren"
},
"cfTypeText": {
"message": "Text"
diff --git a/apps/web/src/locales/hr/messages.json b/apps/web/src/locales/hr/messages.json
index eaecafa5c0d..65c42a0cbf0 100644
--- a/apps/web/src/locales/hr/messages.json
+++ b/apps/web/src/locales/hr/messages.json
@@ -6,7 +6,7 @@
"message": "Kritične aplikacije"
},
"noCriticalAppsAtRisk": {
- "message": "No critical applications at risk"
+ "message": "Nema kritičnih aplikacija u opasnosti"
},
"accessIntelligence": {
"message": "Pristup inteligenciji"
@@ -202,7 +202,7 @@
"message": "Bilješke"
},
"privateNote": {
- "message": "Private note"
+ "message": "Privatna bilješka"
},
"note": {
"message": "Bilješka"
@@ -429,7 +429,7 @@
"message": "Povuci za sortiranje"
},
"dragToReorder": {
- "message": "Drag to reorder"
+ "message": "Povuci za premještanje"
},
"cfTypeText": {
"message": "Tekst"
@@ -474,7 +474,7 @@
"message": "Uredi mapu"
},
"editWithName": {
- "message": "Edit $ITEM$: $NAME$",
+ "message": "Uredi $ITEM$: $NAME$",
"placeholders": {
"item": {
"content": "$1",
@@ -1037,7 +1037,7 @@
"message": "Ne"
},
"location": {
- "message": "Location"
+ "message": "Lokacija"
},
"loginOrCreateNewAccount": {
"message": "Prijavi se ili stvori novi račun za pristup svojem sigurnom trezoru."
@@ -1175,13 +1175,13 @@
"message": "Prijavi se u Bitwarden"
},
"enterTheCodeSentToYourEmail": {
- "message": "Enter the code sent to your email"
+ "message": "Unesi kôd poslan e-poštom"
},
"enterTheCodeFromYourAuthenticatorApp": {
- "message": "Enter the code from your authenticator app"
+ "message": "Unesi kôd iz svoje aplikacije za autentifikaciju"
},
"pressYourYubiKeyToAuthenticate": {
- "message": "Press your YubiKey to authenticate"
+ "message": "Za autentifikaciju dodirni svoj YubiKey"
},
"authenticationTimeout": {
"message": "Istek vremena za autentifikaciju"
@@ -1190,7 +1190,7 @@
"message": "Sesija za autentifikaciju je istekla. Ponovi proces prijave."
},
"verifyYourIdentity": {
- "message": "Verify your Identity"
+ "message": "Potvrdi svoj identitet"
},
"weDontRecognizeThisDevice": {
"message": "Ne prepoznajemo ovaj uređaj. Za potvrdu identiteta unesi kôd poslan e-poštom."
@@ -1460,7 +1460,7 @@
"message": "Zapamti me"
},
"dontAskAgainOnThisDeviceFor30Days": {
- "message": "Don't ask again on this device for 30 days"
+ "message": "Ne pitaj na ovom uređaju idućih 30 dana"
},
"sendVerificationCodeEmailAgain": {
"message": "Ponovno slanje kontrolnog koda e-poštom"
@@ -1469,11 +1469,11 @@
"message": "Koristiti drugi način prijave dvostrukom autentifikacijom"
},
"selectAnotherMethod": {
- "message": "Select another method",
+ "message": "Odaberi drugi način",
"description": "Select another two-step login method"
},
"useYourRecoveryCode": {
- "message": "Use your recovery code"
+ "message": "Koristi kôd za oporavak"
},
"insertYubiKey": {
"message": "Umetni svoj YubiKey u USB priključak računala, a zatim dodirni njegovu tipku."
@@ -1494,7 +1494,7 @@
"message": "Mogućnosti prijave dvostrukom autentifikacijom"
},
"selectTwoStepLoginMethod": {
- "message": "Select two-step login method"
+ "message": "Odaberi način prijave dvostrukom autentifikacijom"
},
"recoveryCodeDesc": {
"message": "Izgubljen je pristup uređaju za prijavu dvostrukom autentifikacijom? Koristi svoj kôd za oporavak za onemogućavanje svih pružatelja usluga prijave dvostrukom autentifikacijom na svom računu."
@@ -1539,7 +1539,7 @@
"message": "(migrirano s FIDO)"
},
"openInNewTab": {
- "message": "Open in new tab"
+ "message": "Otvori u novoj kartici"
},
"emailTitle": {
"message": "E-pošta"
@@ -2237,7 +2237,7 @@
"message": "Opozovi pristup"
},
"revoke": {
- "message": "Revoke"
+ "message": "Opozovi"
},
"twoStepLoginProviderEnabled": {
"message": "Ovaj pružatelj prijave dvostrukom autentifikacijom je omogućen na tvojem računu."
@@ -4097,10 +4097,10 @@
"message": "Koristiš nepodržani preglednik. Web trezor možda neće ispravno raditi."
},
"youHaveAPendingLoginRequest": {
- "message": "You have a pending login request from another device."
+ "message": "Na čekanju je zahtjev za prijavu s drugog uređaja."
},
"reviewLoginRequest": {
- "message": "Review login request"
+ "message": "Pregledaj zahtjev za prijavu"
},
"freeTrialEndPromptCount": {
"message": "Besplatno probno razdoblje završava za $COUNT$ dan/a.",
@@ -4491,7 +4491,7 @@
"message": "Ažuriranje ključa za šifriranje ne može se nastaviti"
},
"editFieldLabel": {
- "message": "Edit $LABEL$",
+ "message": "Uredi $LABEL$",
"placeholders": {
"label": {
"content": "$1",
@@ -4500,7 +4500,7 @@
}
},
"reorderToggleButton": {
- "message": "Reorder $LABEL$. Use arrow key to move item up or down.",
+ "message": "Ponovno poredaj $LABEL$. Koristi tipke sa strelicom za pomicanje stavke gore ili dolje.",
"placeholders": {
"label": {
"content": "$1",
@@ -4509,7 +4509,7 @@
}
},
"reorderFieldUp": {
- "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$",
+ "message": "$LABEL$ pomaknuto gore, pozicija $INDEX$ od $LENGTH$",
"placeholders": {
"label": {
"content": "$1",
@@ -4526,7 +4526,7 @@
}
},
"reorderFieldDown": {
- "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$",
+ "message": "$LABEL$ pomaknuto dolje, pozicija $INDEX$ od$LENGTH$",
"placeholders": {
"label": {
"content": "$1",
@@ -4814,7 +4814,7 @@
"message": "Postavi pravila sigurnosti koja mora zadovoljiti glavna lozinka."
},
"passwordStrengthScore": {
- "message": "Password strength score $SCORE$",
+ "message": "Ocjena jačine lozinke: $SCORE$",
"placeholders": {
"score": {
"content": "$1",
@@ -5103,14 +5103,14 @@
"message": "Pravilo neće biti primjenjeno na Vlasnike i Administratore."
},
"limitSendViews": {
- "message": "Limit views"
+ "message": "Ograniči broj pogleda"
},
"limitSendViewsHint": {
- "message": "No one can view this Send after the limit is reached.",
+ "message": "Nakon dosegnutog broja, nitko neće moći pogledati Send.",
"description": "Displayed under the limit views field on Send"
},
"limitSendViewsCount": {
- "message": "$ACCESSCOUNT$ views left",
+ "message": "Preostalo pogleda: $ACCESSCOUNT$",
"description": "Displayed under the limit views field on Send",
"placeholders": {
"accessCount": {
@@ -5120,11 +5120,11 @@
}
},
"sendDetails": {
- "message": "Send details",
+ "message": "Detalji Senda",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"sendTypeTextToShare": {
- "message": "Text to share"
+ "message": "Tekst za dijeljenje"
},
"sendTypeFile": {
"message": "Datoteka"
@@ -5133,7 +5133,7 @@
"message": "Tekst"
},
"sendPasswordDescV3": {
- "message": "Add an optional password for recipients to access this Send.",
+ "message": "Dodaj neobaveznu lozinku za pristup ovom Sendu.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"createSend": {
@@ -5161,14 +5161,14 @@
"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": "Sigurno želiš trajno izbrisati ovaj Send?",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"deletionDate": {
"message": "Datum brisanja"
},
"deletionDateDescV2": {
- "message": "The Send will be permanently deleted on this date.",
+ "message": "Send će na ovaj datum biti trajno izbrisan.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"expirationDate": {
@@ -5218,7 +5218,7 @@
"message": "Čeka brisanje"
},
"hideTextByDefault": {
- "message": "Hide text by default"
+ "message": "Zadano sakrij tekst"
},
"expired": {
"message": "Isteklo"
@@ -5689,7 +5689,7 @@
"message": "Došlo je do greške kod spremanja vaših datuma isteka i brisanja."
},
"hideYourEmail": {
- "message": "Hide your email address from viewers."
+ "message": "Sakriti adresu e-pošte od primatelja."
},
"webAuthnFallbackMsg": {
"message": "Za ovjeru tvoje 2FA, odaberi donju tipku."
@@ -5698,10 +5698,10 @@
"message": "Ovjeri WebAuthn"
},
"readSecurityKey": {
- "message": "Read security key"
+ "message": "Pročitaj sigurnosni ključ"
},
"awaitingSecurityKeyInteraction": {
- "message": "Awaiting security key interaction..."
+ "message": "Čekanje na interakciju sa sigurnosnim ključem..."
},
"webAuthnNotSupported": {
"message": "WebAuthn nije podržan u ovom pregledniku."
@@ -6997,7 +6997,7 @@
}
},
"forwaderInvalidOperation": {
- "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.",
+ "message": "$SERVICENAME$ je odbio tvoj zahtjev. Obrati se svom pružatelju usluga za pomoć.",
"description": "Displayed when the user is forbidden from using the API by the forwarding service.",
"placeholders": {
"servicename": {
@@ -7007,7 +7007,7 @@
}
},
"forwaderInvalidOperationWithMessage": {
- "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$",
+ "message": "$SERVICENAME$ je odbio tvoj zahtjev: $ERRORMESSAGE$",
"description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.",
"placeholders": {
"servicename": {
@@ -7250,10 +7250,10 @@
"message": "Za tvoj račun je potrebna Duo dvostruka autentifikacija."
},
"duoTwoFactorRequiredPageSubtitle": {
- "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in."
+ "message": "Za tvoj je račun potrebna Duo prijava u dva koraka. Za dovršetak prijave, slijedi daljnje korake."
},
"followTheStepsBelowToFinishLoggingIn": {
- "message": "Follow the steps below to finish logging in."
+ "message": "Prati korake za dovršetak prijave."
},
"launchDuo": {
"message": "Pokreni Duo"
@@ -9339,13 +9339,13 @@
"message": "Konfiguriraj upravljanje uređajima za Bitwarden pomoću vodiča za implementaciju za svoju platformu."
},
"deviceIdMissing": {
- "message": "Device ID is missing"
+ "message": "Nedostaje ID uređaja"
},
"deviceTypeMissing": {
- "message": "Device type is missing"
+ "message": "Nedostaje vrsta uređaja"
},
"deviceCreationDateMissing": {
- "message": "Device creation date is missing"
+ "message": "Nedostaje datum stvaranja uređaja"
},
"desktopRequired": {
"message": "Potrebno stolno računalo"
@@ -9847,13 +9847,13 @@
"message": "Saznaj više o Bitwarden API"
},
"fileSend": {
- "message": "File Send"
+ "message": "Send datoteke"
},
"fileSends": {
"message": "Send datoteke"
},
"textSend": {
- "message": "Text Send"
+ "message": "Send teksta"
},
"textSends": {
"message": "Send tekstovi"
@@ -10349,34 +10349,34 @@
"message": "Naziv organizacije ne može biti duži od 50 znakova."
},
"sshKeyWrongPassword": {
- "message": "The password you entered is incorrect."
+ "message": "Unesena lozinka nije ispravna."
},
"importSshKey": {
- "message": "Import"
+ "message": "Uvoz"
},
"confirmSshKeyPassword": {
- "message": "Confirm password"
+ "message": "Potvrdi lozinku"
},
"enterSshKeyPasswordDesc": {
- "message": "Enter the password for the SSH key."
+ "message": "Unesi lozinku za SSH ključ."
},
"enterSshKeyPassword": {
- "message": "Enter password"
+ "message": "Unesi lozinku"
},
"invalidSshKey": {
- "message": "The SSH key is invalid"
+ "message": "SSH ključ nije valjan"
},
"sshKeyTypeUnsupported": {
- "message": "The SSH key type is not supported"
+ "message": "Tip SSH ključa nije podržan"
},
"importSshKeyFromClipboard": {
- "message": "Import key from clipboard"
+ "message": "Uvezi ključ iz međuspremnika"
},
"sshKeyImported": {
- "message": "SSH key imported successfully"
+ "message": "SSH ključ uspješno uvezen"
},
"copySSHPrivateKey": {
- "message": "Copy private key"
+ "message": "Kopiraj privatni ključ"
},
"openingExtension": {
"message": "Otvaranje Bitwarden proširenja preglednika"
@@ -10518,16 +10518,16 @@
"message": "Dodijeljene licence premašuju dostupne licence."
},
"changeAtRiskPassword": {
- "message": "Change at-risk password"
+ "message": "Promijeni rizičnu lozinku"
},
"removeUnlockWithPinPolicyTitle": {
- "message": "Remove Unlock with PIN"
+ "message": "Ukloni otključavanje PIN-om"
},
"removeUnlockWithPinPolicyDesc": {
- "message": "Do not allow members to unlock their account with a PIN."
+ "message": "Ne dozvoli članovima otključavanje računa PIN-om."
},
"limitedEventLogs": {
- "message": "$PRODUCT_TYPE$ plans do not have access to real event logs",
+ "message": "$PRODUCT_TYPE$ planovi nemaju pristup stvarnim zapisima događaja",
"placeholders": {
"product_type": {
"content": "$1",
@@ -10536,15 +10536,15 @@
}
},
"upgradeForFullEvents": {
- "message": "Get full access to organization event logs by upgrading to a Teams or Enterprise plan."
+ "message": "Omogući puni pristup zapisnicima događaja organizacije nadogradnjom na plan Teams ili Enterprise."
},
"upgradeEventLogTitle": {
- "message": "Upgrade for real event log data"
+ "message": "Nadogradi za stvarne podatke dnevnika događaja"
},
"upgradeEventLogMessage": {
- "message": "These events are examples only and do not reflect real events within your Bitwarden organization."
+ "message": "Ovi događaji su samo primjeri i ne odražavaju stvarne događaje unutar tvoje Bitwarden organizacije."
},
"cannotCreateCollection": {
- "message": "Free organizations may have up to 2 collections. Upgrade to a paid plan to add more collections."
+ "message": "Besplatne organizacije mogu imati do 2 zbirke. Nadogradi na plaćeni plan za dodavanje više zbirki."
}
}
diff --git a/apps/web/src/locales/zh_CN/messages.json b/apps/web/src/locales/zh_CN/messages.json
index 08a610c47ce..bce969ee722 100644
--- a/apps/web/src/locales/zh_CN/messages.json
+++ b/apps/web/src/locales/zh_CN/messages.json
@@ -10264,7 +10264,7 @@
"message": "您的账户在以下设备上登录过。"
},
"claimedDomains": {
- "message": "已声明的域名"
+ "message": "声明域名"
},
"claimDomain": {
"message": "声明域名"
From 266d6cc8dc8a9642acd7889a114a83299b2dd3c8 Mon Sep 17 00:00:00 2001
From: "bw-ghapp[bot]" <178206702+bw-ghapp[bot]@users.noreply.github.com>
Date: Fri, 21 Mar 2025 11:14:54 +0100
Subject: [PATCH 030/113] Autosync the updated translations (#13938)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
---
apps/desktop/src/locales/hr/messages.json | 52 +++++++++++------------
1 file changed, 26 insertions(+), 26 deletions(-)
diff --git a/apps/desktop/src/locales/hr/messages.json b/apps/desktop/src/locales/hr/messages.json
index e586b2e062e..cb66437bedd 100644
--- a/apps/desktop/src/locales/hr/messages.json
+++ b/apps/desktop/src/locales/hr/messages.json
@@ -649,13 +649,13 @@
"message": "Prijavi se u Bitwarden"
},
"enterTheCodeSentToYourEmail": {
- "message": "Enter the code sent to your email"
+ "message": "Unesi kôd poslan e-poštom"
},
"enterTheCodeFromYourAuthenticatorApp": {
- "message": "Enter the code from your authenticator app"
+ "message": "Unesi kôd iz svoje aplikacije za autentifikaciju"
},
"pressYourYubiKeyToAuthenticate": {
- "message": "Press your YubiKey to authenticate"
+ "message": "Za autentifikaciju dodirni svoj YubiKey"
},
"logInWithPasskey": {
"message": "Prijava pristupnim ključem"
@@ -710,7 +710,7 @@
"message": "Podsjetnik glavne lozinke"
},
"passwordStrengthScore": {
- "message": "Password strength score $SCORE$",
+ "message": "Ocjena jačine lozinke: $SCORE$",
"placeholders": {
"score": {
"content": "$1",
@@ -825,7 +825,7 @@
"message": "Autentifikacija je otkazana ili je trajala predugo. Molimo pokušaj ponovno."
},
"openInNewTab": {
- "message": "Open in new tab"
+ "message": "Otvori u novoj kartici"
},
"invalidVerificationCode": {
"message": "Nevažeći kôd za provjeru"
@@ -858,7 +858,7 @@
"message": "Zapamti me"
},
"dontAskAgainOnThisDeviceFor30Days": {
- "message": "Don't ask again on this device for 30 days"
+ "message": "Ne pitaj na ovom uređaju idućih 30 dana"
},
"sendVerificationCodeEmailAgain": {
"message": "Ponovno slanje kontrolnog koda e-poštom"
@@ -867,11 +867,11 @@
"message": "Koristiti drugi način prijave dvostrukom autentifikacijom"
},
"selectAnotherMethod": {
- "message": "Select another method",
+ "message": "Odaberi drugi način",
"description": "Select another two-step login method"
},
"useYourRecoveryCode": {
- "message": "Use your recovery code"
+ "message": "Koristi kôd za oporavak"
},
"insertYubiKey": {
"message": "Umetni svoj YubiKey u USB priključak računala, a zatim dodirni njegovu tipku."
@@ -907,7 +907,7 @@
"description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated."
},
"verifyYourIdentity": {
- "message": "Verify your Identity"
+ "message": "Potvrdi svoj identitet"
},
"weDontRecognizeThisDevice": {
"message": "Ne prepoznajemo ovaj uređaj. Za potvrdu identiteta unesi kôd poslan e-poštom."
@@ -940,7 +940,7 @@
"message": "Mogućnosti prijave dvostrukom autentifikacijom"
},
"selectTwoStepLoginMethod": {
- "message": "Select two-step login method"
+ "message": "Odaberi način prijave dvostrukom autentifikacijom"
},
"selfHostedEnvironment": {
"message": "Vlastito hosting okruženje"
@@ -998,7 +998,7 @@
"message": "Ne"
},
"location": {
- "message": "Location"
+ "message": "Lokacija"
},
"overwritePassword": {
"message": "Prebriši lozinku"
@@ -1797,7 +1797,7 @@
"message": "Zahtijevaj lozinku ili PIN pri pokretanju"
},
"requirePasswordWithoutPinOnStart": {
- "message": "Require password on app start"
+ "message": "Zahtijevaj lozinku pri pokretanju"
},
"recommendedForSecurity": {
"message": "Preporučeno za sigurnost."
@@ -2269,10 +2269,10 @@
"message": "Ovjeri WebAuthn"
},
"readSecurityKey": {
- "message": "Read security key"
+ "message": "Pročitaj sigurnosni ključ"
},
"awaitingSecurityKeyInteraction": {
- "message": "Awaiting security key interaction..."
+ "message": "Čekanje na interakciju sa sigurnosnim ključem..."
},
"hideEmail": {
"message": "Sakrij moju adresu e-pošte od primatelja."
@@ -2671,7 +2671,7 @@
}
},
"forwaderInvalidOperation": {
- "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.",
+ "message": "$SERVICENAME$ je odbio tvoj zahtjev. Obrati se svom pružatelju usluga za pomoć.",
"description": "Displayed when the user is forbidden from using the API by the forwarding service.",
"placeholders": {
"servicename": {
@@ -2681,7 +2681,7 @@
}
},
"forwaderInvalidOperationWithMessage": {
- "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$",
+ "message": "$SERVICENAME$ je odbio tvoj zahtjev: $ERRORMESSAGE$",
"description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.",
"placeholders": {
"servicename": {
@@ -3222,10 +3222,10 @@
"message": "Za tvoj račun je potrebna Duo dvostruka autentifikacija."
},
"duoTwoFactorRequiredPageSubtitle": {
- "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in."
+ "message": "Za tvoj je račun potrebna Duo prijava u dva koraka. Za dovršetak prijave, slijedi daljnje korake."
},
"followTheStepsBelowToFinishLoggingIn": {
- "message": "Follow the steps below to finish logging in."
+ "message": "Prati korake za dovršetak prijave."
},
"launchDuo": {
"message": "Pokreni Duo u pregledniku"
@@ -3485,25 +3485,25 @@
"message": "Potvrdi korištenje SSH ključa"
},
"agentForwardingWarningTitle": {
- "message": "Warning: Agent Forwarding"
+ "message": "Upozorenje: Agent proslijeđivanje"
},
"agentForwardingWarningText": {
- "message": "This request comes from a remote device that you are logged into"
+ "message": "Ovaj je zahtjev došao s prijavljenog udaljenog uređaja"
},
"sshkeyApprovalMessageInfix": {
"message": "traži pristup za"
},
"sshkeyApprovalMessageSuffix": {
- "message": "in order to"
+ "message": "za"
},
"sshActionLogin": {
- "message": "authenticate to a server"
+ "message": "autentifikaciju poslužitelju"
},
"sshActionSign": {
- "message": "sign a message"
+ "message": "potpis poruke"
},
"sshActionGitSign": {
- "message": "sign a git commit"
+ "message": "potpis git commita"
},
"unknownApplication": {
"message": "Aplikacija"
@@ -3518,7 +3518,7 @@
"message": "Uvezi ključ iz međuspremnika"
},
"sshKeyImported": {
- "message": "SSH key imported successfully"
+ "message": "SSH ključ uspješno uvezen"
},
"fileSavedToDevice": {
"message": "Datoteka spremljena na uređaj. Upravljaj u preuzimanjima svog uređaja."
@@ -3578,6 +3578,6 @@
"message": "Proširenje preglednika koje koristitš zastarjelo. Ažuriraj ga ili onemogući provjeru otiska prsta u pregledniku u postavkama aplikacije za stolno računalo."
},
"changeAtRiskPassword": {
- "message": "Change at-risk password"
+ "message": "Promijeni rizičnu lozinku"
}
}
From a3d037de3c2efa5430b8efc588947960323a5e33 Mon Sep 17 00:00:00 2001
From: "bw-ghapp[bot]" <178206702+bw-ghapp[bot]@users.noreply.github.com>
Date: Fri, 21 Mar 2025 10:26:39 +0000
Subject: [PATCH 031/113] Autosync the updated translations (#13939)
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/de/messages.json | 2 +-
apps/browser/src/_locales/hr/messages.json | 120 ++++++++++-----------
2 files changed, 61 insertions(+), 61 deletions(-)
diff --git a/apps/browser/src/_locales/de/messages.json b/apps/browser/src/_locales/de/messages.json
index 4df2fd25b3c..a7439db6432 100644
--- a/apps/browser/src/_locales/de/messages.json
+++ b/apps/browser/src/_locales/de/messages.json
@@ -1668,7 +1668,7 @@
"message": "Zum Sortieren ziehen"
},
"dragToReorder": {
- "message": "Ziehen zum umsortieren"
+ "message": "Ziehen zum Umsortieren"
},
"cfTypeText": {
"message": "Text"
diff --git a/apps/browser/src/_locales/hr/messages.json b/apps/browser/src/_locales/hr/messages.json
index 400e70b864f..b88fc45493f 100644
--- a/apps/browser/src/_locales/hr/messages.json
+++ b/apps/browser/src/_locales/hr/messages.json
@@ -81,7 +81,7 @@
"message": "Podsjetnik glavne lozinke (neobavezno)"
},
"passwordStrengthScore": {
- "message": "Password strength score $SCORE$",
+ "message": "Ocjena jačine lozinke: $SCORE$",
"placeholders": {
"score": {
"content": "$1",
@@ -186,7 +186,7 @@
"message": "Kopiraj bilješke"
},
"copy": {
- "message": "Copy",
+ "message": "Kopiraj",
"description": "Copy to clipboard"
},
"fill": {
@@ -380,7 +380,7 @@
"message": "Uredi mapu"
},
"editFolderWithName": {
- "message": "Edit folder: $FOLDERNAME$",
+ "message": "Uredi mapu: $FOLDERNAME$",
"placeholders": {
"foldername": {
"content": "$1",
@@ -653,7 +653,7 @@
"message": "Web preglednik ne podržava jednostavno kopiranje međuspremnika. Umjesto toga ručno kopirajte."
},
"verifyYourIdentity": {
- "message": "Verify your identity"
+ "message": "Potvrdi svoj identitet"
},
"weDontRecognizeThisDevice": {
"message": "Ne prepoznajemo ovaj uređaj. Za potvrdu identiteta unesi kôd poslan e-poštom."
@@ -869,19 +869,19 @@
"message": "Prijavi se u Bitwarden"
},
"enterTheCodeSentToYourEmail": {
- "message": "Enter the code sent to your email"
+ "message": "Unesi kôd poslan e-poštom"
},
"enterTheCodeFromYourAuthenticatorApp": {
- "message": "Enter the code from your authenticator app"
+ "message": "Unesi kôd iz svoje aplikacije za autentifikaciju"
},
"pressYourYubiKeyToAuthenticate": {
- "message": "Press your YubiKey to authenticate"
+ "message": "Za autentifikaciju dodirni svoj YubiKey"
},
"duoTwoFactorRequiredPageSubtitle": {
- "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in."
+ "message": "Za tvoj je račun potrebna Duo prijava u dva koraka. Za dovršetak prijave, slijedi daljnje korake."
},
"followTheStepsBelowToFinishLoggingIn": {
- "message": "Follow the steps below to finish logging in."
+ "message": "Prati korake za dovršetak prijave."
},
"restartRegistration": {
"message": "Ponovno pokreni registraciju"
@@ -905,7 +905,7 @@
"message": "Ne"
},
"location": {
- "message": "Location"
+ "message": "Lokacija"
},
"unexpectedError": {
"message": "Došlo je do neočekivane pogreške."
@@ -1040,7 +1040,7 @@
"message": "Klikni stavke za auto-ispunu na prikazu trezora"
},
"clickToAutofill": {
- "message": "Click items in autofill suggestion to fill"
+ "message": "Kliknite stavku u prijedlogu auto-ispune za popunjavanje"
},
"clearClipboard": {
"message": "Očisti međuspremnik",
@@ -1057,7 +1057,7 @@
"message": "Spremi"
},
"loginSaveSuccessDetails": {
- "message": "$USERNAME$ saved to Bitwarden.",
+ "message": "$USERNAME$ spremljeno u Bitwarden.",
"placeholders": {
"username": {
"content": "$1"
@@ -1066,7 +1066,7 @@
"description": "Shown to user after login is saved."
},
"loginUpdatedSuccessDetails": {
- "message": "$USERNAME$ updated in Bitwarden.",
+ "message": "$USERNAME$ ažurirano u Bitwardenu.",
"placeholders": {
"username": {
"content": "$1"
@@ -1075,35 +1075,35 @@
"description": "Shown to user after login is updated."
},
"saveAsNewLoginAction": {
- "message": "Save as new login",
+ "message": "Spremi novu prijavu",
"description": "Button text for saving login details as a new entry."
},
"updateLoginAction": {
- "message": "Update login",
+ "message": "Ažuriraj prijavu",
"description": "Button text for updating an existing login entry."
},
"saveLoginPrompt": {
- "message": "Save login?",
+ "message": "Spremiti prijavu?",
"description": "Prompt asking the user if they want to save their login details."
},
"updateLoginPrompt": {
- "message": "Update existing login?",
+ "message": "Ažurirati postojeću prijavu?",
"description": "Prompt asking the user if they want to update an existing login entry."
},
"loginSaveSuccess": {
- "message": "Login saved",
+ "message": "Prijava spremljena",
"description": "Message displayed when login details are successfully saved."
},
"loginUpdateSuccess": {
- "message": "Login updated",
+ "message": "Prijava ažurirana",
"description": "Message displayed when login details are successfully updated."
},
"saveFailure": {
- "message": "Error saving",
+ "message": "Greška kod spremanja",
"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": "Ups! Nismo mogli ovo spasiti. Pokušaj ručno unijeti detalje.",
"description": "Detailed error message shown when saving login details fails."
},
"enableChangedPasswordNotification": {
@@ -1422,7 +1422,7 @@
"message": "Zapamti me"
},
"dontAskAgainOnThisDeviceFor30Days": {
- "message": "Don't ask again on this device for 30 days"
+ "message": "Ne pitaj na ovom uređaju idućih 30 dana"
},
"sendVerificationCodeEmailAgain": {
"message": "Ponovno slanje kontrolnog koda e-poštom"
@@ -1431,11 +1431,11 @@
"message": "Koristiti drugi način prijave dvostrukom autentifikacijom"
},
"selectAnotherMethod": {
- "message": "Select another method",
+ "message": "Odaberi drugi način",
"description": "Select another two-step login method"
},
"useYourRecoveryCode": {
- "message": "Use your recovery code"
+ "message": "Koristi kôd za oporavak"
},
"insertYubiKey": {
"message": "Umetni svoj YubiKey u USB priključak računala, a zatim dodirni njegovu tipku."
@@ -1450,16 +1450,16 @@
"message": "Otvori novu karticu"
},
"openInNewTab": {
- "message": "Open in new tab"
+ "message": "Otvori u novoj kartici"
},
"webAuthnAuthenticate": {
"message": "Ovjeri WebAuthn"
},
"readSecurityKey": {
- "message": "Read security key"
+ "message": "Pročitaj sigurnosni ključ"
},
"awaitingSecurityKeyInteraction": {
- "message": "Awaiting security key interaction..."
+ "message": "Čekanje na interakciju sa sigurnosnim ključem..."
},
"loginUnavailable": {
"message": "Prijava nije dostupna"
@@ -1474,7 +1474,7 @@
"message": "Mogućnosti prijave dvostrukom autentifikacijom"
},
"selectTwoStepLoginMethod": {
- "message": "Select two-step login method"
+ "message": "Odaberi način prijave dvostrukom autentifikacijom"
},
"recoveryCodeDesc": {
"message": "Izgubljen je pristup uređaju za dvostruku autentifikaciju? Koristi svoj kôd za oporavak za onemogućavanje svih pružatelja usluga dvostruke autentifikacije na tvojem računu."
@@ -1668,7 +1668,7 @@
"message": "Povuci za sortiranje"
},
"dragToReorder": {
- "message": "Drag to reorder"
+ "message": "Povuci za premještanje"
},
"cfTypeText": {
"message": "Tekst"
@@ -2164,7 +2164,7 @@
"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": "Prilagodba trezora"
},
"vaultTimeoutAction": {
"message": "Nakon isteka trezora"
@@ -2173,13 +2173,13 @@
"message": "Radnja nakon isteka "
},
"newCustomizationOptionsCalloutTitle": {
- "message": "New customization options"
+ "message": "Nove mogućnosti prilagodbe"
},
"newCustomizationOptionsCalloutContent": {
- "message": "Customize your vault experience with quick copy actions, compact mode, and more!"
+ "message": "Prilagodi svoje iskustvo trezora brzim kopiranjem, kompaktnim načinom rada i više!"
},
"newCustomizationOptionsCalloutLink": {
- "message": "View all Appearance settings"
+ "message": "Pogledaj sve postavke izgleda"
},
"lock": {
"message": "Zaključaj",
@@ -2476,7 +2476,7 @@
"message": "Rizične lozinke"
},
"atRiskPasswordDescSingleOrg": {
- "message": "$ORGANIZATION$ is requesting you change one password because it is at-risk.",
+ "message": "$ORGANIZATION$ traži da promijeniš jednu rizičnu lozinku.",
"placeholders": {
"organization": {
"content": "$1",
@@ -2485,7 +2485,7 @@
}
},
"atRiskPasswordsDescSingleOrgPlural": {
- "message": "$ORGANIZATION$ is requesting you change the $COUNT$ passwords because they are at-risk.",
+ "message": "Broj rizičnih lozinki koje $ORGANIZATION$ traži da promijeniš: $COUNT$.",
"placeholders": {
"organization": {
"content": "$1",
@@ -2498,7 +2498,7 @@
}
},
"atRiskPasswordsDescMultiOrgPlural": {
- "message": "Your organizations are requesting you change the $COUNT$ passwords because they are at-risk.",
+ "message": "Broj rizičnih lozinki koje tvoja orgnaizacija traži da promijeniš: $COUNT$.",
"placeholders": {
"count": {
"content": "$1",
@@ -2525,34 +2525,34 @@
"message": "Ažuriraj svoje postavke kako za brzu auto-ispunu svojih lozinki i generiranje novih"
},
"reviewAtRiskLogins": {
- "message": "Review at-risk logins"
+ "message": "Pregledaj rizične prijave"
},
"reviewAtRiskPasswords": {
- "message": "Review at-risk passwords"
+ "message": "Pregdledaj rizične lozinke"
},
"reviewAtRiskLoginsSlideDesc": {
- "message": "Your organization passwords are at-risk because they are weak, reused, and/or exposed.",
+ "message": "Lozinke tvoje organizacije su rizične jer su slabe, nanovo korištene i/ili iscurile.",
"description": "Description of the review at-risk login slide on the at-risk password page carousel"
},
"reviewAtRiskLoginSlideImgAlt": {
- "message": "Illustration of a list of logins that are at-risk"
+ "message": "Ilustracija liste rizičnih prijava"
},
"generatePasswordSlideDesc": {
- "message": "Quickly generate a strong, unique password with the Bitwarden autofill menu on the at-risk site.",
+ "message": "Brzo generiraj jake, jedinstvene lozinke koristeći Bitwarden dijalog auto-ispune direktno na stranici.",
"description": "Description of the generate password slide on the at-risk password page carousel"
},
"generatePasswordSlideImgAlt": {
- "message": "Illustration of the Bitwarden autofill menu displaying a generated password"
+ "message": "Ilustracija Bitwarden dijalog auto-ispune s prikazom generirane lozinke"
},
"updateInBitwarden": {
- "message": "Update in Bitwarden"
+ "message": "Ažuriraj u Bitwardenu"
},
"updateInBitwardenSlideDesc": {
- "message": "Bitwarden will then prompt you to update the password in the password manager.",
+ "message": "Bitwarden će te pitati treba li ažurirati lozinku u upravitelju lozinki.",
"description": "Description of the update in Bitwarden slide on the at-risk password page carousel"
},
"updateInBitwardenSlideImgAlt": {
- "message": "Illustration of a Bitwarden’s notification prompting the user to update the login"
+ "message": "Ilustracija Bitwarden upita za ažuriranje prijave"
},
"turnOnAutofill": {
"message": "Uključi auto-ispunu"
@@ -3168,7 +3168,7 @@
}
},
"forwaderInvalidOperation": {
- "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.",
+ "message": "$SERVICENAME$ je odbio tvoj zahtjev. Obrati se svom pružatelju usluga za pomoć.",
"description": "Displayed when the user is forbidden from using the API by the forwarding service.",
"placeholders": {
"servicename": {
@@ -3178,7 +3178,7 @@
}
},
"forwaderInvalidOperationWithMessage": {
- "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$",
+ "message": "$SERVICENAME$ je odbio tvoj zahtjev: $ERRORMESSAGE$",
"description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.",
"placeholders": {
"servicename": {
@@ -4080,7 +4080,7 @@
"message": "Aktivni račun"
},
"bitwardenAccount": {
- "message": "Bitwarden account"
+ "message": "Bitwarden račun"
},
"availableAccounts": {
"message": "Dostupni računi"
@@ -4281,7 +4281,7 @@
}
},
"copyFieldValue": {
- "message": "Copy $FIELD$, $VALUE$",
+ "message": "Kopiraj $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
"placeholders": {
"field": {
@@ -4671,7 +4671,7 @@
}
},
"reorderWebsiteUriButton": {
- "message": "Reorder website URI. Use arrow key to move item up or down."
+ "message": "Ponovno poredaj URI. Koristi tipke sa strelicom za pomicanje stavke gore ili dolje."
},
"reorderFieldUp": {
"message": "$LABEL$ pomaknut gore, pozicija $INDEX$ od $LENGTH$",
@@ -5096,31 +5096,31 @@
"message": "Ekstra široko"
},
"sshKeyWrongPassword": {
- "message": "The password you entered is incorrect."
+ "message": "Unesena lozinka nije ispravna."
},
"importSshKey": {
- "message": "Import"
+ "message": "Uvoz"
},
"confirmSshKeyPassword": {
- "message": "Confirm password"
+ "message": "Potvrdi lozinku"
},
"enterSshKeyPasswordDesc": {
- "message": "Enter the password for the SSH key."
+ "message": "Unesi lozinku za SSH ključ."
},
"enterSshKeyPassword": {
- "message": "Enter password"
+ "message": "Unesi lozinku"
},
"invalidSshKey": {
- "message": "The SSH key is invalid"
+ "message": "SSH ključ nije valjan"
},
"sshKeyTypeUnsupported": {
- "message": "The SSH key type is not supported"
+ "message": "Tip SSH ključa nije podržan"
},
"importSshKeyFromClipboard": {
- "message": "Import key from clipboard"
+ "message": "Uvezi ključ iz međuspremnika"
},
"sshKeyImported": {
- "message": "SSH key imported successfully"
+ "message": "SSH ključ uspješno uvezen"
},
"cannotRemoveViewOnlyCollections": {
"message": "S dopuštenjima samo za prikaz ne možeš ukloniti zbirke: $COLLECTIONS$",
@@ -5138,6 +5138,6 @@
"message": "Za korištenje biometrijskog otključavanja ažuriraj desktop aplikaciju ili nemogući otključavanje otiskom prsta u desktop aplikaciji."
},
"changeAtRiskPassword": {
- "message": "Change at-risk password"
+ "message": "Promijeni rizičnu lozinku"
}
}
From 841e5980d728fd49cc9a1a43f524c8bc3d757e6c Mon Sep 17 00:00:00 2001
From: Patrick-Pimentel-Bitwarden
Date: Fri, 21 Mar 2025 10:22:37 -0400
Subject: [PATCH 032/113] fix(device-approval-login): [PM-19379] Approve Device
Login - Fix the call so that it works when not in a TDE scenario. (#13934)
---
.../login-via-auth-request.component.ts | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/libs/auth/src/angular/login-via-auth-request/login-via-auth-request.component.ts b/libs/auth/src/angular/login-via-auth-request/login-via-auth-request.component.ts
index bb2822d67e9..ea753a3f0c5 100644
--- a/libs/auth/src/angular/login-via-auth-request/login-via-auth-request.component.ts
+++ b/libs/auth/src/angular/login-via-auth-request/login-via-auth-request.component.ts
@@ -187,9 +187,7 @@ export class LoginViaAuthRequestComponent implements OnInit, OnDestroy {
private async initStandardAuthRequestFlow(): Promise {
this.flow = Flow.StandardAuthRequest;
- this.email = await firstValueFrom(
- this.accountService.activeAccount$.pipe(map((a) => a?.email)),
- );
+ this.email = (await firstValueFrom(this.loginEmailService.loginEmail$)) || undefined;
if (!this.email) {
await this.handleMissingEmail();
From 1c3084eef465676c1b1fd733cefad72edb376d56 Mon Sep 17 00:00:00 2001
From: "bw-ghapp[bot]" <178206702+bw-ghapp[bot]@users.noreply.github.com>
Date: Mon, 24 Mar 2025 10:27:12 +0100
Subject: [PATCH 033/113] Autosync the updated translations (#13960)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
---
apps/desktop/src/locales/pl/messages.json | 18 +++++++++---------
apps/desktop/src/locales/zh_CN/messages.json | 2 +-
2 files changed, 10 insertions(+), 10 deletions(-)
diff --git a/apps/desktop/src/locales/pl/messages.json b/apps/desktop/src/locales/pl/messages.json
index f78313c7fd6..ef8cdc6eb9b 100644
--- a/apps/desktop/src/locales/pl/messages.json
+++ b/apps/desktop/src/locales/pl/messages.json
@@ -907,7 +907,7 @@
"description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated."
},
"verifyYourIdentity": {
- "message": "Verify your Identity"
+ "message": "Potwierdź swoją tożsamość"
},
"weDontRecognizeThisDevice": {
"message": "Nie rozpoznajemy tego urządzenia. Wpisz kod wysłany na Twój e-mail, aby zweryfikować tożsamość."
@@ -998,7 +998,7 @@
"message": "Nie"
},
"location": {
- "message": "Location"
+ "message": "Lokalizacja"
},
"overwritePassword": {
"message": "Zastąp hasło"
@@ -3488,22 +3488,22 @@
"message": "Warning: Agent Forwarding"
},
"agentForwardingWarningText": {
- "message": "This request comes from a remote device that you are logged into"
+ "message": "To żądanie pochodzi ze zdalnego urządzenia, do którego jesteś zalogowany"
},
"sshkeyApprovalMessageInfix": {
"message": "wnioskuje o dostęp do"
},
"sshkeyApprovalMessageSuffix": {
- "message": "in order to"
+ "message": "w celu"
},
"sshActionLogin": {
- "message": "authenticate to a server"
+ "message": "autoryzacji na serwerze"
},
"sshActionSign": {
- "message": "sign a message"
+ "message": "podpisania wiadomości"
},
"sshActionGitSign": {
- "message": "sign a git commit"
+ "message": "podpisania commita w giciem"
},
"unknownApplication": {
"message": "Aplikacja"
@@ -3518,7 +3518,7 @@
"message": "Importuj klucz ze schowka"
},
"sshKeyImported": {
- "message": "SSH key imported successfully"
+ "message": "Klucz SSH zaimportowano pomyślnie"
},
"fileSavedToDevice": {
"message": "Plik zapisany na urządzeniu. Zarządzaj plikiem na swoim urządzeniu."
@@ -3578,6 +3578,6 @@
"message": "Rozszerzenie przeglądarki, którego używasz, jest nieaktualne. Zaktualizuj je lub wyłącz weryfikację odcisku palca integracji przeglądarki w ustawieniach aplikacji desktopowej."
},
"changeAtRiskPassword": {
- "message": "Change at-risk password"
+ "message": "Zmień zagrożone hasło"
}
}
diff --git a/apps/desktop/src/locales/zh_CN/messages.json b/apps/desktop/src/locales/zh_CN/messages.json
index 8bf4d16c474..6a12138b48f 100644
--- a/apps/desktop/src/locales/zh_CN/messages.json
+++ b/apps/desktop/src/locales/zh_CN/messages.json
@@ -2470,7 +2470,7 @@
"message": "切换账户"
},
"alreadyHaveAccount": {
- "message": "已经拥有账户了吗?"
+ "message": "已经有账户了吗?"
},
"options": {
"message": "选项"
From 714a3f33e40563ebc5734a10f9af0a3ba5d1cdb1 Mon Sep 17 00:00:00 2001
From: "bw-ghapp[bot]" <178206702+bw-ghapp[bot]@users.noreply.github.com>
Date: Mon, 24 Mar 2025 10:28:16 +0100
Subject: [PATCH 034/113] Autosync the updated translations (#13961)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
---
apps/browser/src/_locales/id/messages.json | 64 +++++++++----------
apps/browser/src/_locales/nl/messages.json | 36 +++++------
apps/browser/src/_locales/zh_CN/messages.json | 6 +-
3 files changed, 53 insertions(+), 53 deletions(-)
diff --git a/apps/browser/src/_locales/id/messages.json b/apps/browser/src/_locales/id/messages.json
index 33f44c60579..0146fb2a000 100644
--- a/apps/browser/src/_locales/id/messages.json
+++ b/apps/browser/src/_locales/id/messages.json
@@ -186,7 +186,7 @@
"message": "Salin catatan"
},
"copy": {
- "message": "Copy",
+ "message": "Salin",
"description": "Copy to clipboard"
},
"fill": {
@@ -380,7 +380,7 @@
"message": "Sunting Folder"
},
"editFolderWithName": {
- "message": "Edit folder: $FOLDERNAME$",
+ "message": "Sunting folder: $FOLDERNAME$",
"placeholders": {
"foldername": {
"content": "$1",
@@ -462,16 +462,16 @@
"message": "Buat frasa sandi"
},
"passwordGenerated": {
- "message": "Password generated"
+ "message": "Kata sandi dibuat"
},
"passphraseGenerated": {
- "message": "Passphrase generated"
+ "message": "Frasa sandi dibuat"
},
"usernameGenerated": {
- "message": "Username generated"
+ "message": "Nama pengguna dibuat"
},
"emailGenerated": {
- "message": "Email generated"
+ "message": "Surel dibuat"
},
"regeneratePassword": {
"message": "Buat Ulang Kata Sandi"
@@ -653,7 +653,7 @@
"message": "Peramban Anda tidak mendukung menyalin clipboard dengan mudah. Salin secara manual."
},
"verifyYourIdentity": {
- "message": "Verify your identity"
+ "message": "Verifikasikan identitas Anda"
},
"weDontRecognizeThisDevice": {
"message": "We don't recognize this device. Enter the code sent to your email to verify your identity."
@@ -905,7 +905,7 @@
"message": "Tidak"
},
"location": {
- "message": "Location"
+ "message": "Lokasi"
},
"unexpectedError": {
"message": "Terjadi kesalahan yang tak diduga."
@@ -2461,7 +2461,7 @@
"message": "Change this in settings"
},
"change": {
- "message": "Change"
+ "message": "Ubah"
},
"changeButtonTitle": {
"message": "Change password - $ITEMNAME$",
@@ -4784,7 +4784,7 @@
"message": "Text Sends"
},
"accountActions": {
- "message": "Account actions"
+ "message": "Tindakan akun"
},
"showNumberOfAutofillSuggestions": {
"message": "Show number of login autofill suggestions on extension icon"
@@ -4793,22 +4793,22 @@
"message": "Show quick copy actions on Vault"
},
"systemDefault": {
- "message": "System default"
+ "message": "Baku sistem"
},
"enterprisePolicyRequirementsApplied": {
- "message": "Enterprise policy requirements have been applied to this setting"
+ "message": "Persyaratan kebijakan perusahaan telah diterapkan ke pengaturan ini"
},
"sshPrivateKey": {
- "message": "Private key"
+ "message": "Kunci privat"
},
"sshPublicKey": {
- "message": "Public key"
+ "message": "Kunci publik"
},
"sshFingerprint": {
- "message": "Fingerprint"
+ "message": "Sidik jari"
},
"sshKeyAlgorithm": {
- "message": "Key type"
+ "message": "Tipe kunci"
},
"sshKeyAlgorithmED25519": {
"message": "ED25519"
@@ -4823,7 +4823,7 @@
"message": "RSA 4096-Bit"
},
"retry": {
- "message": "Retry"
+ "message": "Coba lagi"
},
"vaultCustomTimeoutMinimum": {
"message": "Minimum custom timeout is 1 minute."
@@ -5075,16 +5075,16 @@
}
},
"newDeviceVerificationNoticePageOneEmailAccessNo": {
- "message": "No, I do not"
+ "message": "Tidak"
},
"newDeviceVerificationNoticePageOneEmailAccessYes": {
- "message": "Yes, I can reliably access my email"
+ "message": "Ya, saya dapat mengakses surel saya secara handla"
},
"turnOnTwoStepLogin": {
"message": "Turn on two-step login"
},
"changeAcctEmail": {
- "message": "Change account email"
+ "message": "Ubah surel akun"
},
"extensionWidth": {
"message": "Lebar ekstensi"
@@ -5096,31 +5096,31 @@
"message": "Ekstra lebar"
},
"sshKeyWrongPassword": {
- "message": "The password you entered is incorrect."
+ "message": "Kata sandi yang Anda masukkan tidak benar."
},
"importSshKey": {
- "message": "Import"
+ "message": "Impor"
},
"confirmSshKeyPassword": {
- "message": "Confirm password"
+ "message": "Konfirmasi kata sandi"
},
"enterSshKeyPasswordDesc": {
- "message": "Enter the password for the SSH key."
+ "message": "Masukkan kata sandi untuk kunci SSH."
},
"enterSshKeyPassword": {
- "message": "Enter password"
+ "message": "Masukkan kata sandi"
},
"invalidSshKey": {
- "message": "The SSH key is invalid"
+ "message": "Kunci SSH tidak valid"
},
"sshKeyTypeUnsupported": {
- "message": "The SSH key type is not supported"
+ "message": "Tipe kunci SSH tidak didukung"
},
"importSshKeyFromClipboard": {
- "message": "Import key from clipboard"
+ "message": "Impor kunci dari papan klip"
},
"sshKeyImported": {
- "message": "SSH key imported successfully"
+ "message": "Kunci SSH sukses diimpor"
},
"cannotRemoveViewOnlyCollections": {
"message": "Anda tidak dapat menghapus koleksi dengan izin hanya lihat: $COLLECTIONS$",
@@ -5132,12 +5132,12 @@
}
},
"updateDesktopAppOrDisableFingerprintDialogTitle": {
- "message": "Please update your desktop application"
+ "message": "Harap perbarui aplikasi desktop Anda"
},
"updateDesktopAppOrDisableFingerprintDialogMessage": {
- "message": "To use biometric unlock, please update your desktop application, or disable fingerprint unlock in the desktop settings."
+ "message": "Untuk memakai pembuka kunci biometrik, harap perbarui aplikasi desktop Anda, atau matikan buka kunci sidik jari dalam pengaturan desktop."
},
"changeAtRiskPassword": {
- "message": "Change at-risk password"
+ "message": "Ubah kata sandi yang berrisiko"
}
}
diff --git a/apps/browser/src/_locales/nl/messages.json b/apps/browser/src/_locales/nl/messages.json
index 65d8d6f740f..1c2b09ca3e3 100644
--- a/apps/browser/src/_locales/nl/messages.json
+++ b/apps/browser/src/_locales/nl/messages.json
@@ -81,7 +81,7 @@
"message": "Hoofdwachtwoordhint (optioneel)"
},
"passwordStrengthScore": {
- "message": "Score wachtwoordsterkte $SCORE$",
+ "message": "Wachtwoordsterkte score $SCORE$",
"placeholders": {
"score": {
"content": "$1",
@@ -878,10 +878,10 @@
"message": "Druk op je YubiKey om te verifiëren"
},
"duoTwoFactorRequiredPageSubtitle": {
- "message": "Jouw account vereist Duo-tweestapsaanmelding. Volg de onderstaande stappen om het inloggen te voltooien."
+ "message": "Jouw account vereist Duo tweestapslogin. Volg de onderstaande stappen om het inloggen te voltooien."
},
"followTheStepsBelowToFinishLoggingIn": {
- "message": "Volg de onderstaande stappen om in te loggen."
+ "message": "Volg de onderstaande stappen om het inloggen af te ronden."
},
"restartRegistration": {
"message": "Registratie herstarten"
@@ -1040,7 +1040,7 @@
"message": "Klik op items om automatisch in te vullen op de kluisweergave"
},
"clickToAutofill": {
- "message": "Klik op gesuggereerde items om deze automatisch invullen"
+ "message": "Klik items in de automatisch invullen suggestie om in te vullen"
},
"clearClipboard": {
"message": "Klembord wissen",
@@ -1075,7 +1075,7 @@
"description": "Shown to user after login is updated."
},
"saveAsNewLoginAction": {
- "message": "Als nieuwe login opslaan",
+ "message": "Opslaan als nieuwe login",
"description": "Button text for saving login details as a new entry."
},
"updateLoginAction": {
@@ -1422,7 +1422,7 @@
"message": "Mijn gegevens onthouden"
},
"dontAskAgainOnThisDeviceFor30Days": {
- "message": "30 dagen niet meer vragen op dit apparaat"
+ "message": "30 dagen niet opnieuw vragen op dit apparaat"
},
"sendVerificationCodeEmailAgain": {
"message": "E-mail met verificatiecode opnieuw versturen"
@@ -1459,7 +1459,7 @@
"message": "Beveiligingssleutel lezen"
},
"awaitingSecurityKeyInteraction": {
- "message": "Wacht op interactie met beveiligingssleutel..."
+ "message": "Wacht op interactie met beveiligingssleutel…"
},
"loginUnavailable": {
"message": "Login niet beschikbaar"
@@ -1474,7 +1474,7 @@
"message": "Opties voor tweestapsaanmelding"
},
"selectTwoStepLoginMethod": {
- "message": "Kies methode voor tweestapsaanmelding"
+ "message": "Kies methode voor tweestapslogin"
},
"recoveryCodeDesc": {
"message": "Ben je de toegang tot al je tweestapsaanbieders verloren? Gebruik dan je herstelcode om alle tweestapsaanbieders op je account uit te schakelen."
@@ -2164,7 +2164,7 @@
"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": "Kluis-aanpassingen"
+ "message": "Kluis aanpassingen"
},
"vaultTimeoutAction": {
"message": "Actie bij time-out"
@@ -2176,10 +2176,10 @@
"message": "Nieuwe aanpassingsopties"
},
"newCustomizationOptionsCalloutContent": {
- "message": "Personaliseer je kluiservaring met snelle kopieeracties, compacte modus en meer!"
+ "message": "Pas je kluis ervaring aan met snelle kopieeracties, compacte modus en meer!"
},
"newCustomizationOptionsCalloutLink": {
- "message": "Alle personalisatie-instellingen bekijken"
+ "message": "Alle weergave-instellingen bekijken"
},
"lock": {
"message": "Vergrendelen",
@@ -2498,7 +2498,7 @@
}
},
"atRiskPasswordsDescMultiOrgPlural": {
- "message": "Je organisatie(s) vragen je de $COUNT$ wachtwoorden te wijzigen omdat ze een risico vormen.",
+ "message": "Je organisaties vragen je de $COUNT$ wachtwoorden te wijzigen omdat ze een risico vormen.",
"placeholders": {
"count": {
"content": "$1",
@@ -2531,14 +2531,14 @@
"message": "Risicovolle wachtwoorden bekijken"
},
"reviewAtRiskLoginsSlideDesc": {
- "message": "De wachtwoorden van je organisatie zijn in gevaar omdat ze zwak, hergebruikt en/of blootgelegd zijn.",
+ "message": "De wachtwoorden van je organisatie zijn in gevaar omdat ze zwak, hergebruikt en/of gelekt zijn.",
"description": "Description of the review at-risk login slide on the at-risk password page carousel"
},
"reviewAtRiskLoginSlideImgAlt": {
"message": "Voorbeeld van een lijst van risicovolle logins"
},
"generatePasswordSlideDesc": {
- "message": "Genereer snel een sterk, uniek wachtwoord met het automatisch invulmenu van Bitwaren op de risicovolle website.",
+ "message": "Genereer snel een sterk, uniek wachtwoord met het automatisch invulmenu van Bitwarden op de risicovolle website.",
"description": "Description of the generate password slide on the at-risk password page carousel"
},
"generatePasswordSlideImgAlt": {
@@ -2552,7 +2552,7 @@
"description": "Description of the update in Bitwarden slide on the at-risk password page carousel"
},
"updateInBitwardenSlideImgAlt": {
- "message": "Voorbeeld van een Bitwarden-melding die de gebruiker aanspoort tot het bijwerken van de login"
+ "message": "Voorbeeld van een Bitwarden melding die de gebruiker aanspoort tot het bijwerken van de login"
},
"turnOnAutofill": {
"message": "Automatisch invullen inschakelen"
@@ -4080,7 +4080,7 @@
"message": "Actief account"
},
"bitwardenAccount": {
- "message": "Bitwarden-account"
+ "message": "Bitwarden account"
},
"availableAccounts": {
"message": "Beschikbare accounts"
@@ -5096,7 +5096,7 @@
"message": "Extra breed"
},
"sshKeyWrongPassword": {
- "message": "Het door jou ingevoerde wachtwoord is onjuist."
+ "message": "Het wachtwoord dat je hebt ingevoerd is onjuist."
},
"importSshKey": {
"message": "Importeren"
@@ -5117,7 +5117,7 @@
"message": "Het type SSH-sleutel is niet ondersteund"
},
"importSshKeyFromClipboard": {
- "message": "Sleutel van klembord importeren"
+ "message": "Sleutel importeren van klembord"
},
"sshKeyImported": {
"message": "SSH-sleutel succesvol geïmporteerd"
diff --git a/apps/browser/src/_locales/zh_CN/messages.json b/apps/browser/src/_locales/zh_CN/messages.json
index 4595b20ddbf..bdfaeb92531 100644
--- a/apps/browser/src/_locales/zh_CN/messages.json
+++ b/apps/browser/src/_locales/zh_CN/messages.json
@@ -2208,7 +2208,7 @@
"message": "项目已恢复"
},
"alreadyHaveAccount": {
- "message": "已经拥有账户了吗?"
+ "message": "已经有账户了吗?"
},
"vaultTimeoutLogOutConfirmation": {
"message": "超时后注销账户将解除对密码库的所有访问权限,并需要进行在线身份验证。确定使用此设置吗?"
@@ -3628,7 +3628,7 @@
"message": "正在获取选项..."
},
"multiSelectNotFound": {
- "message": "未找到任何条目"
+ "message": "未找到任何项目"
},
"multiSelectClearAll": {
"message": "清除全部"
@@ -4209,7 +4209,7 @@
"message": "建议的项目"
},
"autofillSuggestionsTip": {
- "message": "将此站点保存为登录项目以用于自动填充"
+ "message": "为这个站点保存一个登录项目以自动填充"
},
"yourVaultIsEmpty": {
"message": "您的密码库是空的"
From d4116c05d7ac03ae474ba93aaad052a5c2718a34 Mon Sep 17 00:00:00 2001
From: "bw-ghapp[bot]" <178206702+bw-ghapp[bot]@users.noreply.github.com>
Date: Mon, 24 Mar 2025 10:28:51 +0100
Subject: [PATCH 035/113] Autosync the updated translations (#13962)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
---
apps/web/src/locales/nl/messages.json | 10 +++++-----
apps/web/src/locales/zh_CN/messages.json | 10 +++++-----
2 files changed, 10 insertions(+), 10 deletions(-)
diff --git a/apps/web/src/locales/nl/messages.json b/apps/web/src/locales/nl/messages.json
index d193b2496f3..6b752b5a195 100644
--- a/apps/web/src/locales/nl/messages.json
+++ b/apps/web/src/locales/nl/messages.json
@@ -9847,13 +9847,13 @@
"message": "Lees meer over Bitwarden's API"
},
"fileSend": {
- "message": "Bestand verzenden"
+ "message": "Bestand Send"
},
"fileSends": {
"message": "Bestand-Sends"
},
"textSend": {
- "message": "Tekst-Sends"
+ "message": "Tekst Send"
},
"textSends": {
"message": "Tekst-Sends"
@@ -10382,7 +10382,7 @@
"message": "Bitwarden-browserextensie openen"
},
"somethingWentWrong": {
- "message": "Er is iets fout gegaan..."
+ "message": "Er is iets fout gegaan…"
},
"openingExtensionError": {
"message": "We konden de Bitwarden-browserextensie niet openen. Klik op de knop om deze nu te openen."
@@ -10407,7 +10407,7 @@
"description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'"
},
"openExtensionManuallyPart2": {
- "message": "vanaf de werkbank.",
+ "message": "vanaf de werkbalk.",
"description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'"
},
"resellerRenewalWarningMsg": {
@@ -10542,7 +10542,7 @@
"message": "Upgrade voor echte event log gegevens"
},
"upgradeEventLogMessage": {
- "message": "Deze events zijn voorbeelden en weerspiegelen geen echte evenementen binnen je Bitwarden-organisatie."
+ "message": "Deze evenementen zijn alleen voorbeelden en weerspiegelen geen echte evenementen binnen je Bitwarden organisatie."
},
"cannotCreateCollection": {
"message": "Gratis organisaties kunnen maximaal twee collecties hebben. Upgrade naar een betaald abonnement voor het toevoegen van meer collecties."
diff --git a/apps/web/src/locales/zh_CN/messages.json b/apps/web/src/locales/zh_CN/messages.json
index bce969ee722..00bf9bc5973 100644
--- a/apps/web/src/locales/zh_CN/messages.json
+++ b/apps/web/src/locales/zh_CN/messages.json
@@ -2500,7 +2500,7 @@
}
},
"noInactive2fa": {
- "message": "没有在您的密码库发现未配置两步登录的网站。"
+ "message": "在您的密码库中没有发现未配置两步登录的网站。"
},
"instructions": {
"message": "说明"
@@ -2596,7 +2596,7 @@
}
},
"noReusedPasswords": {
- "message": "您密码库中没有密码重复使用的项目。"
+ "message": "您密码库中没有密码重复使用的登录项目。"
},
"timesReused": {
"message": "重复使用次数"
@@ -7921,7 +7921,7 @@
}
},
"domainNotVerifiedEvent": {
- "message": "$DOMAIN$ 未验证",
+ "message": "$DOMAIN$ 无法验证",
"placeholders": {
"DOMAIN": {
"content": "$1",
@@ -8779,7 +8779,7 @@
"description": "Label for field requesting a self-hosted integration service URL"
},
"alreadyHaveAccount": {
- "message": "已经拥有账户了吗?"
+ "message": "已经有账户了吗?"
},
"toggleSideNavigation": {
"message": "切换侧边导航"
@@ -10294,7 +10294,7 @@
"message": "已声明"
},
"domainStatusUnderVerification": {
- "message": "正在验证"
+ "message": "验证中"
},
"claimedDomainsDesc": {
"message": "声明一个域名,以拥有电子邮箱地址与该域名匹配的所有成员账户。成员登录时将可以跳过 SSO 标识符。管理员也可以删除成员账户。"
From a6e785d63cf71de0342446d29631efc8398bca5a Mon Sep 17 00:00:00 2001
From: Github Actions
Date: Mon, 24 Mar 2025 10:49:32 +0000
Subject: [PATCH 036/113] 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 25890cf4b6e..2af524c92b7 100644
--- a/apps/web/package.json
+++ b/apps/web/package.json
@@ -1,6 +1,6 @@
{
"name": "@bitwarden/web-vault",
- "version": "2025.3.0",
+ "version": "2025.3.1",
"scripts": {
"build:oss": "cross-env NODE_OPTIONS=\"--max-old-space-size=8192\" webpack",
"build:bit": "cross-env NODE_OPTIONS=\"--max-old-space-size=8192\" webpack -c ../../bitwarden_license/bit-web/webpack.config.js",
diff --git a/package-lock.json b/package-lock.json
index cb51c157b09..7331fcdde44 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -243,7 +243,7 @@
},
"apps/web": {
"name": "@bitwarden/web-vault",
- "version": "2025.3.0"
+ "version": "2025.3.1"
},
"libs/admin-console": {
"name": "@bitwarden/admin-console",
From 8e455007c028db3f98cd2affed6a7fd63e5f6331 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Anders=20=C3=85berg?=
Date: Mon, 24 Mar 2025 12:50:11 +0100
Subject: [PATCH 037/113] PM-19095: Wire passkey autofill to UI (#13051)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* Passkey stuff
Co-authored-by: Anders Åberg
* Ugly hacks
* Work On Modal State Management
* Applying modalStyles
* modal
* Improved hide/show
* fixed promise
* File name
* fix prettier
* Protecting against null API's and undefined data
* Only show fake popup to devs
* cleanup mock code
* rename minmimal-app to modal-app
* Added comment
* Added comment
* removed old comment
* Avoided changing minimum size
* Add small comment
* Rename component
* adress feedback
* Fixed uppercase file
* Fixed build
* Added codeowners
* added void
* commentary
* feat: reset setting on app start
* Moved reset to be in main / process launch
* Add comment to create window
* Added a little bit of styling
* Use Messaging service to loadUrl
* Enable passkeysautofill
* Add logging
* halfbaked
* Integration working
* And now it works without extra delay
* Clean up
* add note about messaging
* lb
* removed console.logs
* Cleanup and adress review feedback
* This hides the swift UI
* pick credential, draft
* Remove logger
* a whole lot of wiring
* not working
* Improved wiring
* Cancel after 90s
* Introduced observable
* Launching bitwarden if its not running
* Passing position from native to electron
* Rename inModalMode to modalMode
* remove tap
* revert spaces
* added back isDev
* cleaned up a bit
* Cleanup swift file
* tweaked logging
* clean up
* Update apps/desktop/macos/autofill-extension/CredentialProviderViewController.swift
Co-authored-by: Andreas Coroiu
* Update apps/desktop/src/platform/main/autofill/native-autofill.main.ts
Co-authored-by: Andreas Coroiu
* Update apps/desktop/src/platform/services/desktop-settings.service.ts
Co-authored-by: Andreas Coroiu
* adress position feedback
* Update apps/desktop/macos/autofill-extension/CredentialProviderViewController.swift
Co-authored-by: Andreas Coroiu
* Removed extra logging
* Adjusted error logging
* Use .error to log errors
* remove dead code
* Update desktop-autofill.service.ts
* use parseCredentialId instead of guidToRawFormat
* Update apps/desktop/src/autofill/services/desktop-autofill.service.ts
Co-authored-by: Andreas Coroiu
* Change windowXy to a Record instead of [number,number]
* Update apps/desktop/src/autofill/services/desktop-fido2-user-interface.service.ts
Co-authored-by: Andreas Coroiu
* Remove unsued dep and comment
* changed timeout to be spec recommended maxium, 10 minutes, for now.
* Correctly assume UP
* Removed extra cancelRequest in deinint
* Add timeout and UV to confirmChoseCipher
UV is performed by UI, not the service
* Improved docs regarding undefined cipherId
* cleanup: UP is no longer undefined
* Run completeError if ipc messages conversion failed
* don't throw, instead return undefined
* Disabled passkey provider
* Throw error if no activeUserId was found
* removed comment
* Fixed lint
* removed unsued service
* reset entitlement formatting
* Update entitlements.mas.plist
---------
Co-authored-by: Justin Baur <19896123+justindbaur@users.noreply.github.com>
Co-authored-by: Colton Hurst
Co-authored-by: Andreas Coroiu
Co-authored-by: Andreas Coroiu
---
.../macos_provider/src/assertion.rs | 14 +-
.../desktop_native/macos_provider/src/lib.rs | 20 +-
.../macos_provider/src/registration.rs | 3 +-
apps/desktop/desktop_native/napi/index.d.ts | 15 +-
apps/desktop/desktop_native/napi/src/lib.rs | 48 ++++
.../CredentialProviderViewController.xib | 23 +-
.../CredentialProviderViewController.swift | 261 ++++++++++++------
apps/desktop/src/app/app-routing.module.ts | 4 +
.../components/fido2placeholder.component.ts | 90 +++++-
.../src/app/services/services.module.ts | 17 +-
apps/desktop/src/autofill/preload.ts | 38 +++
.../services/desktop-autofill.service.ts | 153 ++++++----
.../desktop-fido2-user-interface.service.ts | 191 ++++++++++++-
apps/desktop/src/main.ts | 12 +-
apps/desktop/src/main/messaging.main.ts | 4 +
apps/desktop/src/main/tray.main.ts | 36 +--
apps/desktop/src/main/window.main.ts | 42 ++-
.../main/autofill/native-autofill.main.ts | 17 +-
.../platform/models/domain/window-state.ts | 5 +
.../src/platform/popup-modal-styles.ts | 20 +-
.../services/desktop-settings.service.ts | 19 +-
...ido2-user-interface.service.abstraction.ts | 2 +-
22 files changed, 826 insertions(+), 208 deletions(-)
diff --git a/apps/desktop/desktop_native/macos_provider/src/assertion.rs b/apps/desktop/desktop_native/macos_provider/src/assertion.rs
index 762ceaaed48..c5b43bb87fa 100644
--- a/apps/desktop/desktop_native/macos_provider/src/assertion.rs
+++ b/apps/desktop/desktop_native/macos_provider/src/assertion.rs
@@ -2,11 +2,22 @@ use std::sync::Arc;
use serde::{Deserialize, Serialize};
-use crate::{BitwardenError, Callback, UserVerification};
+use crate::{BitwardenError, Callback, Position, UserVerification};
#[derive(uniffi::Record, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PasskeyAssertionRequest {
+ rp_id: String,
+ client_data_hash: Vec,
+ user_verification: UserVerification,
+ allowed_credentials: Vec>,
+ window_xy: Position,
+ //extension_input: Vec, TODO: Implement support for extensions
+}
+
+#[derive(uniffi::Record, Debug, Serialize, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct PasskeyAssertionWithoutUserInterfaceRequest {
rp_id: String,
credential_id: Vec,
user_name: String,
@@ -14,6 +25,7 @@ pub struct PasskeyAssertionRequest {
record_identifier: Option,
client_data_hash: Vec,
user_verification: UserVerification,
+ window_xy: Position,
}
#[derive(uniffi::Record, Serialize, Deserialize)]
diff --git a/apps/desktop/desktop_native/macos_provider/src/lib.rs b/apps/desktop/desktop_native/macos_provider/src/lib.rs
index 5623436d874..8f2499ae68d 100644
--- a/apps/desktop/desktop_native/macos_provider/src/lib.rs
+++ b/apps/desktop/desktop_native/macos_provider/src/lib.rs
@@ -15,7 +15,10 @@ uniffi::setup_scaffolding!();
mod assertion;
mod registration;
-use assertion::{PasskeyAssertionRequest, PreparePasskeyAssertionCallback};
+use assertion::{
+ PasskeyAssertionRequest, PasskeyAssertionWithoutUserInterfaceRequest,
+ PreparePasskeyAssertionCallback,
+};
use registration::{PasskeyRegistrationRequest, PreparePasskeyRegistrationCallback};
#[derive(uniffi::Enum, Debug, Serialize, Deserialize)]
@@ -26,6 +29,13 @@ pub enum UserVerification {
Discouraged,
}
+#[derive(uniffi::Record, Debug, Serialize, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct Position {
+ pub x: i32,
+ pub y: i32,
+}
+
#[derive(Debug, uniffi::Error, Serialize, Deserialize)]
pub enum BitwardenError {
Internal(String),
@@ -141,6 +151,14 @@ impl MacOSProviderClient {
) {
self.send_message(request, Box::new(callback));
}
+
+ pub fn prepare_passkey_assertion_without_user_interface(
+ &self,
+ request: PasskeyAssertionWithoutUserInterfaceRequest,
+ callback: Arc,
+ ) {
+ self.send_message(request, Box::new(callback));
+ }
}
#[derive(Serialize, Deserialize)]
diff --git a/apps/desktop/desktop_native/macos_provider/src/registration.rs b/apps/desktop/desktop_native/macos_provider/src/registration.rs
index d484af58b6c..9e697b75c16 100644
--- a/apps/desktop/desktop_native/macos_provider/src/registration.rs
+++ b/apps/desktop/desktop_native/macos_provider/src/registration.rs
@@ -2,7 +2,7 @@ use std::sync::Arc;
use serde::{Deserialize, Serialize};
-use crate::{BitwardenError, Callback, UserVerification};
+use crate::{BitwardenError, Callback, Position, UserVerification};
#[derive(uniffi::Record, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
@@ -13,6 +13,7 @@ pub struct PasskeyRegistrationRequest {
client_data_hash: Vec,
user_verification: UserVerification,
supported_algorithms: Vec,
+ window_xy: Position,
}
#[derive(uniffi::Record, Serialize, Deserialize)]
diff --git a/apps/desktop/desktop_native/napi/index.d.ts b/apps/desktop/desktop_native/napi/index.d.ts
index 92f31cf5f89..ca1fe29e254 100644
--- a/apps/desktop/desktop_native/napi/index.d.ts
+++ b/apps/desktop/desktop_native/napi/index.d.ts
@@ -118,6 +118,10 @@ export declare namespace autofill {
Required = 'required',
Discouraged = 'discouraged'
}
+ export interface Position {
+ x: number
+ y: number
+ }
export interface PasskeyRegistrationRequest {
rpId: string
userName: string
@@ -125,6 +129,7 @@ export declare namespace autofill {
clientDataHash: Array
userVerification: UserVerification
supportedAlgorithms: Array
+ windowXy: Position
}
export interface PasskeyRegistrationResponse {
rpId: string
@@ -133,6 +138,13 @@ export declare namespace autofill {
attestationObject: Array
}
export interface PasskeyAssertionRequest {
+ rpId: string
+ clientDataHash: Array
+ userVerification: UserVerification
+ allowedCredentials: Array>
+ windowXy: Position
+ }
+ export interface PasskeyAssertionWithoutUserInterfaceRequest {
rpId: string
credentialId: Array
userName: string
@@ -140,6 +152,7 @@ export declare namespace autofill {
recordIdentifier?: string
clientDataHash: Array
userVerification: UserVerification
+ windowXy: Position
}
export interface PasskeyAssertionResponse {
rpId: string
@@ -156,7 +169,7 @@ export declare namespace autofill {
* @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): 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): Promise
/** Return the path to the IPC server. */
getPath(): string
/** Stop the IPC server. */
diff --git a/apps/desktop/desktop_native/napi/src/lib.rs b/apps/desktop/desktop_native/napi/src/lib.rs
index d0c859d427c..f02be2b27b6 100644
--- a/apps/desktop/desktop_native/napi/src/lib.rs
+++ b/apps/desktop/desktop_native/napi/src/lib.rs
@@ -515,6 +515,14 @@ pub mod autofill {
pub value: Result,
}
+ #[napi(object)]
+ #[derive(Debug, Serialize, Deserialize)]
+ #[serde(rename_all = "camelCase")]
+ pub struct Position {
+ pub x: i32,
+ pub y: i32,
+ }
+
#[napi(object)]
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
@@ -525,6 +533,7 @@ pub mod autofill {
pub client_data_hash: Vec,
pub user_verification: UserVerification,
pub supported_algorithms: Vec,
+ pub window_xy: Position,
}
#[napi(object)]
@@ -541,6 +550,18 @@ pub mod autofill {
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PasskeyAssertionRequest {
+ pub rp_id: String,
+ pub client_data_hash: Vec,
+ pub user_verification: UserVerification,
+ pub allowed_credentials: Vec>,
+ pub window_xy: Position,
+ //extension_input: Vec, TODO: Implement support for extensions
+ }
+
+ #[napi(object)]
+ #[derive(Debug, Serialize, Deserialize)]
+ #[serde(rename_all = "camelCase")]
+ pub struct PasskeyAssertionWithoutUserInterfaceRequest {
pub rp_id: String,
pub credential_id: Vec,
pub user_name: String,
@@ -548,6 +569,7 @@ pub mod autofill {
pub record_identifier: Option,
pub client_data_hash: Vec,
pub user_verification: UserVerification,
+ pub window_xy: Position,
}
#[napi(object)]
@@ -592,6 +614,13 @@ pub mod autofill {
(u32, u32, PasskeyAssertionRequest),
ErrorStrategy::CalleeHandled,
>,
+ #[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,
+ >,
) -> napi::Result {
let (send, mut recv) = tokio::sync::mpsc::channel::(32);
tokio::spawn(async move {
@@ -628,6 +657,25 @@ pub mod autofill {
}
}
+ match serde_json::from_str::<
+ PasskeyMessage,
+ >(&message)
+ {
+ Ok(msg) => {
+ let value = msg
+ .value
+ .map(|value| (client_id, msg.sequence_number, value))
+ .map_err(|e| napi::Error::from_reason(format!("{e:?}")));
+
+ assertion_without_user_interface_callback
+ .call(value, ThreadsafeFunctionCallMode::NonBlocking);
+ continue;
+ }
+ Err(e) => {
+ println!("[ERROR] Error deserializing message1: {e}");
+ }
+ }
+
match serde_json::from_str::>(
&message,
) {
diff --git a/apps/desktop/macos/autofill-extension/Base.lproj/CredentialProviderViewController.xib b/apps/desktop/macos/autofill-extension/Base.lproj/CredentialProviderViewController.xib
index ace3497a58b..1e47cc54de2 100644
--- a/apps/desktop/macos/autofill-extension/Base.lproj/CredentialProviderViewController.xib
+++ b/apps/desktop/macos/autofill-extension/Base.lproj/CredentialProviderViewController.xib
@@ -1,22 +1,23 @@
-
+
-
+
+
-
+
-
+
-
+
@@ -28,10 +29,7 @@
-
-
-
-
+
@@ -39,13 +37,16 @@
Gw
+
+
+
-
-
+
+
diff --git a/apps/desktop/macos/autofill-extension/CredentialProviderViewController.swift b/apps/desktop/macos/autofill-extension/CredentialProviderViewController.swift
index dbaa8517086..5568b2e75db 100644
--- a/apps/desktop/macos/autofill-extension/CredentialProviderViewController.swift
+++ b/apps/desktop/macos/autofill-extension/CredentialProviderViewController.swift
@@ -17,17 +17,56 @@ class CredentialProviderViewController: ASCredentialProviderViewController {
//
// If instead I make this a static, the deinit gets called correctly after each request.
// I think we still might want a static regardless, to be able to reuse the connection if possible.
- static let client: MacOsProviderClient = {
- let instance = MacOsProviderClient.connect()
- // setup code
- return instance
- }()
+ let client: MacOsProviderClient = {
+ let logger = Logger(subsystem: "com.bitwarden.desktop.autofill-extension", category: "credential-provider")
+
+ // Check if the Electron app is running
+ let workspace = NSWorkspace.shared
+ let isRunning = workspace.runningApplications.contains { app in
+ app.bundleIdentifier == "com.bitwarden.desktop"
+ }
+
+ if !isRunning {
+ logger.log("[autofill-extension] Bitwarden Desktop not running, attempting to launch")
+
+ // Try to launch the app
+ if let appURL = workspace.urlForApplication(withBundleIdentifier: "com.bitwarden.desktop") {
+ let semaphore = DispatchSemaphore(value: 0)
+
+ workspace.openApplication(at: appURL,
+ configuration: NSWorkspace.OpenConfiguration()) { app, error in
+ if let error = error {
+ logger.error("[autofill-extension] Failed to launch Bitwarden Desktop: \(error.localizedDescription)")
+ } else if let app = app {
+ logger.log("[autofill-extension] Successfully launched Bitwarden Desktop")
+ } else {
+ logger.error("[autofill-extension] Failed to launch Bitwarden Desktop: unknown error")
+ }
+ semaphore.signal()
+ }
+
+ // Wait for launch completion with timeout
+ _ = semaphore.wait(timeout: .now() + 5.0)
+
+ // Add a small delay to allow for initialization
+ Thread.sleep(forTimeInterval: 1.0)
+ } else {
+ logger.error("[autofill-extension] Could not find Bitwarden Desktop app")
+ }
+ } else {
+ logger.log("[autofill-extension] Bitwarden Desktop is running")
+ }
+
+ logger.log("[autofill-extension] Connecting to Bitwarden over IPC")
+
+ return MacOsProviderClient.connect()
+ }()
init() {
logger = Logger(subsystem: "com.bitwarden.desktop.autofill-extension", category: "credential-provider")
logger.log("[autofill-extension] initializing extension")
-
+
super.init(nibName: nil, bundle: nil)
}
@@ -43,40 +82,35 @@ class CredentialProviderViewController: ASCredentialProviderViewController {
@IBAction func cancel(_ sender: AnyObject?) {
self.extensionContext.cancelRequest(withError: NSError(domain: ASExtensionErrorDomain, code: ASExtensionError.userCanceled.rawValue))
}
-
+
@IBAction func passwordSelected(_ sender: AnyObject?) {
let passwordCredential = ASPasswordCredential(user: "j_appleseed", password: "apple1234")
self.extensionContext.completeRequest(withSelectedCredential: passwordCredential, completionHandler: nil)
}
- /*
- Implement this method if your extension supports showing credentials in the QuickType bar.
- When the user selects a credential from your app, this method will be called with the
- ASPasswordCredentialIdentity your app has previously saved to the ASCredentialIdentityStore.
- Provide the password by completing the extension request with the associated ASPasswordCredential.
- If using the credential would require showing custom UI for authenticating the user, cancel
- the request with error code ASExtensionError.userInteractionRequired.
-
- */
-
- // Deprecated
- override func provideCredentialWithoutUserInteraction(for credentialIdentity: ASPasswordCredentialIdentity) {
- logger.log("[autofill-extension] provideCredentialWithoutUserInteraction called \(credentialIdentity)")
- logger.log("[autofill-extension] user \(credentialIdentity.user)")
- logger.log("[autofill-extension] id \(credentialIdentity.recordIdentifier ?? "")")
- logger.log("[autofill-extension] sid \(credentialIdentity.serviceIdentifier.identifier)")
- logger.log("[autofill-extension] sidt \(credentialIdentity.serviceIdentifier.type.rawValue)")
+ private func getWindowPosition() -> Position {
+ let frame = self.view.window?.frame ?? .zero
+ let screenHeight = NSScreen.main?.frame.height ?? 0
-// let databaseIsUnlocked = true
-// if (databaseIsUnlocked) {
- let passwordCredential = ASPasswordCredential(user: credentialIdentity.user, password: "example1234")
- self.extensionContext.completeRequest(withSelectedCredential: passwordCredential, completionHandler: nil)
-// } else {
-// self.extensionContext.cancelRequest(withError: NSError(domain: ASExtensionErrorDomain, code:ASExtensionError.userInteractionRequired.rawValue))
-// }
+ // frame.width and frame.height is always 0. Estimating works OK for now.
+ let estimatedWidth:CGFloat = 400;
+ let estimatedHeight:CGFloat = 200;
+ let centerX = Int32(round(frame.origin.x + estimatedWidth/2))
+ let centerY = Int32(round(screenHeight - (frame.origin.y + estimatedHeight/2)))
+
+ return Position(x: centerX, y:centerY)
}
+ override func loadView() {
+ let view = NSView()
+ // Hide the native window since we only need the IPC connection
+ view.isHidden = true
+ self.view = view
+ }
+
override func provideCredentialWithoutUserInteraction(for credentialRequest: any ASCredentialRequest) {
+ let timeoutTimer = createTimer()
+
if let request = credentialRequest as? ASPasskeyCredentialRequest {
if let passkeyIdentity = request.credentialIdentity as? ASPasskeyCredentialIdentity {
@@ -84,11 +118,16 @@ class CredentialProviderViewController: ASCredentialProviderViewController {
class CallbackImpl: PreparePasskeyAssertionCallback {
let ctx: ASCredentialProviderExtensionContext
- required init(_ ctx: ASCredentialProviderExtensionContext) {
+ let logger: Logger
+ let timeoutTimer: DispatchWorkItem
+ required init(_ ctx: ASCredentialProviderExtensionContext,_ logger: Logger, _ timeoutTimer: DispatchWorkItem) {
self.ctx = ctx
+ self.logger = logger
+ self.timeoutTimer = timeoutTimer
}
func onComplete(credential: PasskeyAssertionResponse) {
+ self.timeoutTimer.cancel()
ctx.completeAssertionRequest(using: ASPasskeyAssertionCredential(
userHandle: credential.userHandle,
relyingParty: credential.rpId,
@@ -100,6 +139,8 @@ class CredentialProviderViewController: ASCredentialProviderViewController {
}
func onError(error: BitwardenError) {
+ logger.error("[autofill-extension] OnError called, cancelling the request \(error)")
+ self.timeoutTimer.cancel()
ctx.cancelRequest(withError: error)
}
}
@@ -113,55 +154,74 @@ class CredentialProviderViewController: ASCredentialProviderViewController {
UserVerification.discouraged
}
- let req = PasskeyAssertionRequest(
+ let req = PasskeyAssertionWithoutUserInterfaceRequest(
rpId: passkeyIdentity.relyingPartyIdentifier,
credentialId: passkeyIdentity.credentialID,
userName: passkeyIdentity.userName,
userHandle: passkeyIdentity.userHandle,
recordIdentifier: passkeyIdentity.recordIdentifier,
clientDataHash: request.clientDataHash,
- userVerification: userVerification
+ userVerification: userVerification,
+ windowXy: self.getWindowPosition()
)
- CredentialProviderViewController.client.preparePasskeyAssertion(request: req, callback: CallbackImpl(self.extensionContext))
+ self.client.preparePasskeyAssertionWithoutUserInterface(request: req, callback: CallbackImpl(self.extensionContext, self.logger, timeoutTimer))
return
}
}
- if let request = credentialRequest as? ASPasswordCredentialRequest {
- logger.log("[autofill-extension] provideCredentialWithoutUserInteraction2(password) called \(request)")
- return;
- }
-
+ timeoutTimer.cancel()
+
logger.log("[autofill-extension] provideCredentialWithoutUserInteraction2 called wrong")
self.extensionContext.cancelRequest(withError: BitwardenError.Internal("Invalid authentication request"))
}
-
+
/*
Implement this method if provideCredentialWithoutUserInteraction(for:) can fail with
ASExtensionError.userInteractionRequired. In this case, the system may present your extension's
UI and call this method. Show appropriate UI for authenticating the user then provide the password
by completing the extension request with the associated ASPasswordCredential.
+
+ override func prepareInterfaceToProvideCredential(for credentialIdentity: ASPasswordCredentialIdentity) {
+ }
+ */
- override func prepareInterfaceToProvideCredential(for credentialIdentity: ASPasswordCredentialIdentity) {
- }
- */
+ private func createTimer() -> DispatchWorkItem {
+ // Create a timer for 600 second timeout
+ let timeoutTimer = DispatchWorkItem { [weak self] in
+ guard let self = self else { return }
+ logger.log("[autofill-extension] The operation timed out after 600 seconds")
+ self.extensionContext.cancelRequest(withError: BitwardenError.Internal("The operation timed out"))
+ }
+
+ // Schedule the timeout
+ DispatchQueue.main.asyncAfter(deadline: .now() + 600, execute: timeoutTimer)
-
- override func prepareInterfaceForExtensionConfiguration() {
- logger.log("[autofill-extension] prepareInterfaceForExtensionConfiguration called")
+ return timeoutTimer
}
override func prepareInterface(forPasskeyRegistration registrationRequest: ASCredentialRequest) {
+ logger.log("[autofill-extension] prepareInterface")
+ let timeoutTimer = createTimer()
+
+
if let request = registrationRequest as? ASPasskeyCredentialRequest {
if let passkeyIdentity = registrationRequest.credentialIdentity as? ASPasskeyCredentialIdentity {
+ logger.log("[autofill-extension] prepareInterface(passkey) called \(request)")
+
class CallbackImpl: PreparePasskeyRegistrationCallback {
let ctx: ASCredentialProviderExtensionContext
- required init(_ ctx: ASCredentialProviderExtensionContext) {
+ let timeoutTimer: DispatchWorkItem
+ let logger: Logger
+
+ required init(_ ctx: ASCredentialProviderExtensionContext, _ logger: Logger,_ timeoutTimer: DispatchWorkItem) {
self.ctx = ctx
+ self.logger = logger
+ self.timeoutTimer = timeoutTimer
}
-
+
func onComplete(credential: PasskeyRegistrationResponse) {
+ self.timeoutTimer.cancel()
ctx.completeRegistrationRequest(using: ASPasskeyRegistrationCredential(
relyingParty: credential.rpId,
clientDataHash: credential.clientDataHash,
@@ -169,58 +229,99 @@ class CredentialProviderViewController: ASCredentialProviderViewController {
attestationObject: credential.attestationObject
))
}
-
+
func onError(error: BitwardenError) {
+ logger.error("[autofill-extension] OnError called, cancelling the request \(error)")
+ self.timeoutTimer.cancel()
ctx.cancelRequest(withError: error)
}
}
let userVerification = switch request.userVerificationPreference {
- case .preferred:
- UserVerification.preferred
- case .required:
- UserVerification.required
- default:
- UserVerification.discouraged
+ case .preferred:
+ UserVerification.preferred
+ case .required:
+ UserVerification.required
+ default:
+ UserVerification.discouraged
}
-
+
let req = PasskeyRegistrationRequest(
rpId: passkeyIdentity.relyingPartyIdentifier,
userName: passkeyIdentity.userName,
userHandle: passkeyIdentity.userHandle,
clientDataHash: request.clientDataHash,
userVerification: userVerification,
- supportedAlgorithms: request.supportedAlgorithms.map{ Int32($0.rawValue) }
+ supportedAlgorithms: request.supportedAlgorithms.map{ Int32($0.rawValue) },
+ windowXy: self.getWindowPosition()
)
- CredentialProviderViewController.client.preparePasskeyRegistration(request: req, callback: CallbackImpl(self.extensionContext))
+ logger.log("[autofill-extension] prepareInterface(passkey) calling preparePasskeyRegistration")
+
+ self.client.preparePasskeyRegistration(request: req, callback: CallbackImpl(self.extensionContext, self.logger, timeoutTimer))
return
}
}
-
+
+ logger.log("[autofill-extension] We didn't get a passkey")
+
+ timeoutTimer.cancel()
// If we didn't get a passkey, return an error
self.extensionContext.cancelRequest(withError: BitwardenError.Internal("Invalid registration request"))
}
-
- /*
- Prepare your UI to list available credentials for the user to choose from. The items in
- 'serviceIdentifiers' describe the service the user is logging in to, so your extension can
- prioritize the most relevant credentials in the list.
- */
- override func prepareCredentialList(for serviceIdentifiers: [ASCredentialServiceIdentifier]) {
- logger.log("[autofill-extension] prepareCredentialList for serviceIdentifiers: \(serviceIdentifiers.count)")
-
- for serviceIdentifier in serviceIdentifiers {
- logger.log(" service: \(serviceIdentifier.identifier)")
- }
- }
-
+
override func prepareCredentialList(for serviceIdentifiers: [ASCredentialServiceIdentifier], requestParameters: ASPasskeyCredentialRequestParameters) {
logger.log("[autofill-extension] prepareCredentialList(passkey) for serviceIdentifiers: \(serviceIdentifiers.count)")
- logger.log("request parameters: \(requestParameters.relyingPartyIdentifier)")
-
- for serviceIdentifier in serviceIdentifiers {
- logger.log(" service: \(serviceIdentifier.identifier)")
+
+ class CallbackImpl: PreparePasskeyAssertionCallback {
+ let ctx: ASCredentialProviderExtensionContext
+ let timeoutTimer: DispatchWorkItem
+ let logger: Logger
+ required init(_ ctx: ASCredentialProviderExtensionContext,_ logger: Logger, _ timeoutTimer: DispatchWorkItem) {
+ self.ctx = ctx
+ self.logger = logger
+ self.timeoutTimer = timeoutTimer
+ }
+
+ func onComplete(credential: PasskeyAssertionResponse) {
+ self.timeoutTimer.cancel()
+ ctx.completeAssertionRequest(using: ASPasskeyAssertionCredential(
+ userHandle: credential.userHandle,
+ relyingParty: credential.rpId,
+ signature: credential.signature,
+ clientDataHash: credential.clientDataHash,
+ authenticatorData: credential.authenticatorData,
+ credentialID: credential.credentialId
+ ))
+ }
+
+ func onError(error: BitwardenError) {
+ logger.error("[autofill-extension] OnError called, cancelling the request \(error)")
+ self.timeoutTimer.cancel()
+ ctx.cancelRequest(withError: error)
+ }
}
- }
-
+
+ let userVerification = switch requestParameters.userVerificationPreference {
+ case .preferred:
+ UserVerification.preferred
+ case .required:
+ UserVerification.required
+ default:
+ UserVerification.discouraged
+ }
+
+ let req = PasskeyAssertionRequest(
+ rpId: requestParameters.relyingPartyIdentifier,
+ clientDataHash: requestParameters.clientDataHash,
+ userVerification: userVerification,
+ allowedCredentials: requestParameters.allowedCredentials,
+ windowXy: self.getWindowPosition()
+ //extensionInput: requestParameters.extensionInput,
+ )
+
+ let timeoutTimer = createTimer()
+
+ self.client.preparePasskeyAssertion(request: req, callback: CallbackImpl(self.extensionContext, self.logger, timeoutTimer))
+ return
+ }
}
diff --git a/apps/desktop/src/app/app-routing.module.ts b/apps/desktop/src/app/app-routing.module.ts
index 36e267c9355..3bb130d321d 100644
--- a/apps/desktop/src/app/app-routing.module.ts
+++ b/apps/desktop/src/app/app-routing.module.ts
@@ -182,6 +182,10 @@ const routes: Routes = [
path: "passkeys",
component: Fido2PlaceholderComponent,
},
+ {
+ path: "passkeys",
+ component: Fido2PlaceholderComponent,
+ },
{
path: "",
component: AnonLayoutWrapperComponent,
diff --git a/apps/desktop/src/app/components/fido2placeholder.component.ts b/apps/desktop/src/app/components/fido2placeholder.component.ts
index b3302d63241..b95dcc6d890 100644
--- a/apps/desktop/src/app/components/fido2placeholder.component.ts
+++ b/apps/desktop/src/app/components/fido2placeholder.component.ts
@@ -1,16 +1,45 @@
-import { Component } from "@angular/core";
+import { CommonModule } from "@angular/common";
+import { Component, OnDestroy, OnInit } from "@angular/core";
import { Router } from "@angular/router";
+import { BehaviorSubject, Observable } from "rxjs";
+import {
+ DesktopFido2UserInterfaceService,
+ DesktopFido2UserInterfaceSession,
+} from "../../autofill/services/desktop-fido2-user-interface.service";
import { DesktopSettingsService } from "../../platform/services/desktop-settings.service";
@Component({
standalone: true,
+ imports: [CommonModule],
template: `
Select your passkey
+
+
+
+ {{ item }}
+
+
+
+
+ Confirm passkey
+
`,
})
-export class Fido2PlaceholderComponent {
+export class Fido2PlaceholderComponent implements OnInit, OnDestroy {
+ session?: DesktopFido2UserInterfaceSession = null;
+ private cipherIdsSubject = new BehaviorSubject([]);
+ cipherIds$: Observable;
+
constructor(
private readonly desktopSettingsService: DesktopSettingsService,
+ private readonly fido2UserInterfaceService: DesktopFido2UserInterfaceService,
private readonly router: Router,
) {}
+ ngOnInit() {
+ this.session = this.fido2UserInterfaceService.getCurrentSession();
+ this.cipherIds$ = this.session?.availableCipherIds$;
+ }
+
+ async chooseCipher(cipherId: string) {
+ // For now: Set UV to true
+ this.session?.confirmChosenCipher(cipherId, true);
+
+ await this.router.navigate(["/"]);
+ await this.desktopSettingsService.setModalMode(false);
+ }
+
+ ngOnDestroy() {
+ this.cipherIdsSubject.complete(); // Clean up the BehaviorSubject
+ }
+
+ async confirmPasskey() {
+ try {
+ // Retrieve the current UI session to control the flow
+ if (!this.session) {
+ // todo: handle error
+ throw new Error("No session found");
+ }
+
+ // If we want to we could submit information to the session in order to create the credential
+ // const cipher = await session.createCredential({
+ // userHandle: "userHandle2",
+ // userName: "username2",
+ // credentialName: "zxsd2",
+ // rpId: "webauthn.io",
+ // userVerification: true,
+ // });
+
+ this.session.notifyConfirmNewCredential(true);
+
+ // Not sure this clean up should happen here or in session.
+ // The session currently toggles modal on and send us here
+ // But if this route is somehow opened outside of session we want to make sure we clean up?
+ await this.router.navigate(["/"]);
+ await this.desktopSettingsService.setModalMode(false);
+ } catch {
+ // TODO: Handle error appropriately
+ }
+ }
+
async closeModal() {
await this.router.navigate(["/"]);
- await this.desktopSettingsService.setInModalMode(false);
+ await this.desktopSettingsService.setModalMode(false);
+
+ this.session.notifyConfirmNewCredential(false);
+ // little bit hacky:
+ this.session.confirmChosenCipher(null);
}
}
diff --git a/apps/desktop/src/app/services/services.module.ts b/apps/desktop/src/app/services/services.module.ts
index 66e3175fee7..db28f01f27a 100644
--- a/apps/desktop/src/app/services/services.module.ts
+++ b/apps/desktop/src/app/services/services.module.ts
@@ -1,6 +1,7 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { APP_INITIALIZER, NgModule } from "@angular/core";
+import { Router } from "@angular/router";
import { Subject, merge } from "rxjs";
import { OrganizationUserApiService } from "@bitwarden/admin-console/common";
@@ -334,9 +335,21 @@ const safeProviders: SafeProvider[] = [
],
}),
safeProvider({
- provide: Fido2UserInterfaceServiceAbstraction,
+ provide: DesktopFido2UserInterfaceService,
useClass: DesktopFido2UserInterfaceService,
- deps: [AuthServiceAbstraction, CipherServiceAbstraction, AccountService, LogService],
+ deps: [
+ AuthServiceAbstraction,
+ CipherServiceAbstraction,
+ AccountService,
+ LogService,
+ MessagingServiceAbstraction,
+ Router,
+ DesktopSettingsService,
+ ],
+ }),
+ safeProvider({
+ provide: Fido2UserInterfaceServiceAbstraction, // We utilize desktop specific methods when wiring OS API's
+ useExisting: DesktopFido2UserInterfaceService,
}),
safeProvider({
provide: Fido2AuthenticatorServiceAbstraction,
diff --git a/apps/desktop/src/autofill/preload.ts b/apps/desktop/src/autofill/preload.ts
index 494544f5858..2c006b5c928 100644
--- a/apps/desktop/src/autofill/preload.ts
+++ b/apps/desktop/src/autofill/preload.ts
@@ -80,6 +80,44 @@ export default {
return;
}
+ ipcRenderer.send("autofill.completePasskeyAssertion", {
+ clientId,
+ sequenceNumber,
+ response,
+ });
+ });
+ },
+ );
+ },
+ listenPasskeyAssertionWithoutUserInterface: (
+ fn: (
+ clientId: number,
+ sequenceNumber: number,
+ request: autofill.PasskeyAssertionWithoutUserInterfaceRequest,
+ completeCallback: (error: Error | null, response: autofill.PasskeyAssertionResponse) => void,
+ ) => void,
+ ) => {
+ ipcRenderer.on(
+ "autofill.passkeyAssertionWithoutUserInterface",
+ (
+ event,
+ data: {
+ clientId: number;
+ sequenceNumber: number;
+ request: autofill.PasskeyAssertionWithoutUserInterfaceRequest;
+ },
+ ) => {
+ const { clientId, sequenceNumber, request } = data;
+ fn(clientId, sequenceNumber, request, (error, response) => {
+ if (error) {
+ ipcRenderer.send("autofill.completeError", {
+ clientId,
+ sequenceNumber,
+ error: error.message,
+ });
+ return;
+ }
+
ipcRenderer.send("autofill.completePasskeyAssertion", {
clientId,
sequenceNumber,
diff --git a/apps/desktop/src/autofill/services/desktop-autofill.service.ts b/apps/desktop/src/autofill/services/desktop-autofill.service.ts
index 2ed5a649997..e88e16c2ffc 100644
--- a/apps/desktop/src/autofill/services/desktop-autofill.service.ts
+++ b/apps/desktop/src/autofill/services/desktop-autofill.service.ts
@@ -1,7 +1,6 @@
import { Injectable, OnDestroy } from "@angular/core";
import { autofill } from "desktop_native/napi";
import {
- EMPTY,
Subject,
distinctUntilChanged,
filter,
@@ -10,6 +9,7 @@ import {
mergeMap,
switchMap,
takeUntil,
+ EMPTY,
} from "rxjs";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
@@ -26,9 +26,9 @@ import {
} from "@bitwarden/common/platform/abstractions/fido2/fido2-authenticator.service.abstraction";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
+import { parseCredentialId } from "@bitwarden/common/platform/services/fido2/credential-id-utils";
import { getCredentialsForAutofill } from "@bitwarden/common/platform/services/fido2/fido2-autofill-utils";
import { Fido2Utils } from "@bitwarden/common/platform/services/fido2/fido2-utils";
-import { guidToRawFormat } from "@bitwarden/common/platform/services/fido2/guid-utils";
import { UserId } from "@bitwarden/common/types/guid";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { CipherType } from "@bitwarden/common/vault/enums";
@@ -41,6 +41,8 @@ import {
NativeAutofillSyncCommand,
} from "../../platform/main/autofill/sync.command";
+import type { NativeWindowObject } from "./desktop-fido2-user-interface.service";
+
@Injectable()
export class DesktopAutofillService implements OnDestroy {
private destroy$ = new Subject();
@@ -49,7 +51,7 @@ export class DesktopAutofillService implements OnDestroy {
private logService: LogService,
private cipherService: CipherService,
private configService: ConfigService,
- private fido2AuthenticatorService: Fido2AuthenticatorServiceAbstraction,
+ private fido2AuthenticatorService: Fido2AuthenticatorServiceAbstraction,
private accountService: AccountService,
) {}
@@ -155,7 +157,11 @@ export class DesktopAutofillService implements OnDestroy {
const controller = new AbortController();
void this.fido2AuthenticatorService
- .makeCredential(this.convertRegistrationRequest(request), null, controller)
+ .makeCredential(
+ this.convertRegistrationRequest(request),
+ { windowXy: request.windowXy },
+ controller,
+ )
.then((response) => {
callback(null, this.convertRegistrationResponse(request, response));
})
@@ -165,47 +171,77 @@ export class DesktopAutofillService implements OnDestroy {
});
});
+ ipc.autofill.listenPasskeyAssertionWithoutUserInterface(
+ async (clientId, sequenceNumber, request, callback) => {
+ this.logService.warning(
+ "listenPasskeyAssertion without user interface",
+ clientId,
+ sequenceNumber,
+ request,
+ );
+
+ // For some reason the credentialId is passed as an empty array in the request, so we need to
+ // get it from the cipher. For that we use the recordIdentifier, which is the cipherId.
+ if (request.recordIdentifier && request.credentialId.length === 0) {
+ const activeUserId = await firstValueFrom(
+ this.accountService.activeAccount$.pipe(getOptionalUserId),
+ );
+ if (!activeUserId) {
+ this.logService.error("listenPasskeyAssertion error", "Active user not found");
+ callback(new Error("Active user not found"), null);
+ return;
+ }
+
+ const cipher = await this.cipherService.get(request.recordIdentifier, activeUserId);
+ if (!cipher) {
+ this.logService.error("listenPasskeyAssertion error", "Cipher not found");
+ callback(new Error("Cipher not found"), null);
+ return;
+ }
+
+ const decrypted = await cipher.decrypt(
+ await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId),
+ );
+
+ const fido2Credential = decrypted.login.fido2Credentials?.[0];
+ if (!fido2Credential) {
+ this.logService.error("listenPasskeyAssertion error", "Fido2Credential not found");
+ callback(new Error("Fido2Credential not found"), null);
+ return;
+ }
+
+ request.credentialId = Array.from(
+ parseCredentialId(decrypted.login.fido2Credentials?.[0].credentialId),
+ );
+ }
+
+ const controller = new AbortController();
+ void this.fido2AuthenticatorService
+ .getAssertion(
+ this.convertAssertionRequest(request),
+ { windowXy: request.windowXy },
+ controller,
+ )
+ .then((response) => {
+ callback(null, this.convertAssertionResponse(request, response));
+ })
+ .catch((error) => {
+ this.logService.error("listenPasskeyAssertion error", error);
+ callback(error, null);
+ });
+ },
+ );
+
ipc.autofill.listenPasskeyAssertion(async (clientId, sequenceNumber, request, callback) => {
this.logService.warning("listenPasskeyAssertion", clientId, sequenceNumber, request);
- // TODO: For some reason the credentialId is passed as an empty array in the request, so we need to
- // get it from the cipher. For that we use the recordIdentifier, which is the cipherId.
- if (request.recordIdentifier && request.credentialId.length === 0) {
- const activeUserId = await firstValueFrom(
- this.accountService.activeAccount$.pipe(getOptionalUserId),
- );
- if (!activeUserId) {
- this.logService.error("listenPasskeyAssertion error", "Active user not found");
- callback(new Error("Active user not found"), null);
- return;
- }
-
- const cipher = await this.cipherService.get(request.recordIdentifier, activeUserId);
- if (!cipher) {
- this.logService.error("listenPasskeyAssertion error", "Cipher not found");
- callback(new Error("Cipher not found"), null);
- return;
- }
-
- const decrypted = await cipher.decrypt(
- await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId),
- );
-
- const fido2Credential = decrypted.login.fido2Credentials?.[0];
- if (!fido2Credential) {
- this.logService.error("listenPasskeyAssertion error", "Fido2Credential not found");
- callback(new Error("Fido2Credential not found"), null);
- return;
- }
-
- request.credentialId = Array.from(
- guidToRawFormat(decrypted.login.fido2Credentials?.[0].credentialId),
- );
- }
-
const controller = new AbortController();
void this.fido2AuthenticatorService
- .getAssertion(this.convertAssertionRequest(request), null, controller)
+ .getAssertion(
+ this.convertAssertionRequest(request),
+ { windowXy: request.windowXy },
+ controller,
+ )
.then((response) => {
callback(null, this.convertAssertionResponse(request, response));
})
@@ -257,27 +293,48 @@ export class DesktopAutofillService implements OnDestroy {
};
}
+ /**
+ *
+ * @param request
+ * @param assumeUserPresence For WithoutUserInterface requests, we assume the user is present
+ * @returns
+ */
private convertAssertionRequest(
- request: autofill.PasskeyAssertionRequest,
+ request:
+ | autofill.PasskeyAssertionRequest
+ | autofill.PasskeyAssertionWithoutUserInterfaceRequest,
): Fido2AuthenticatorGetAssertionParams {
+ let allowedCredentials;
+ if ("credentialId" in request) {
+ allowedCredentials = [
+ {
+ id: new Uint8Array(request.credentialId),
+ type: "public-key" as const,
+ },
+ ];
+ } else {
+ allowedCredentials = request.allowedCredentials.map((credentialId) => ({
+ id: new Uint8Array(credentialId),
+ type: "public-key" as const,
+ }));
+ }
+
return {
rpId: request.rpId,
hash: new Uint8Array(request.clientDataHash),
- allowCredentialDescriptorList: [
- {
- id: new Uint8Array(request.credentialId),
- type: "public-key",
- },
- ],
+ allowCredentialDescriptorList: allowedCredentials,
extensions: {},
requireUserVerification:
request.userVerification === "required" || request.userVerification === "preferred",
fallbackSupported: false,
+ assumeUserPresence: true, // For desktop assertions, it's safe to assume UP has been checked by OS dialogues
};
}
private convertAssertionResponse(
- request: autofill.PasskeyAssertionRequest,
+ request:
+ | autofill.PasskeyAssertionRequest
+ | autofill.PasskeyAssertionWithoutUserInterfaceRequest,
response: Fido2AuthenticatorGetAssertionResult,
): autofill.PasskeyAssertionResponse {
return {
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 684b19e7394..3caf13fa5b7 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
@@ -1,4 +1,14 @@
-import { firstValueFrom, map } from "rxjs";
+import { Router } from "@angular/router";
+import {
+ lastValueFrom,
+ firstValueFrom,
+ map,
+ Subject,
+ filter,
+ take,
+ BehaviorSubject,
+ timeout,
+} from "rxjs";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
@@ -10,8 +20,10 @@ import {
PickCredentialParams,
} from "@bitwarden/common/platform/abstractions/fido2/fido2-user-interface.service.abstraction";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
+import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { CipherRepromptType, CipherType, SecureNoteType } from "@bitwarden/common/vault/enums";
+import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
import { CardView } from "@bitwarden/common/vault/models/view/card.view";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { IdentityView } from "@bitwarden/common/vault/models/view/identity.view";
@@ -19,28 +31,54 @@ import { LoginUriView } from "@bitwarden/common/vault/models/view/login-uri.view
import { LoginView } from "@bitwarden/common/vault/models/view/login.view";
import { SecureNoteView } from "@bitwarden/common/vault/models/view/secure-note.view";
+import { DesktopSettingsService } from "../../platform/services/desktop-settings.service";
+
+/**
+ * This type is used to pass the window position from the native UI
+ */
+export type NativeWindowObject = {
+ /**
+ * The position of the window, first entry is the x position, second is the y position
+ */
+ windowXy?: { x: number; y: number };
+};
+
export class DesktopFido2UserInterfaceService
- implements Fido2UserInterfaceServiceAbstraction
+ implements Fido2UserInterfaceServiceAbstraction
{
constructor(
private authService: AuthService,
private cipherService: CipherService,
private accountService: AccountService,
private logService: LogService,
+ private messagingService: MessagingService,
+ private router: Router,
+ private desktopSettingsService: DesktopSettingsService,
) {}
+ private currentSession: any;
+
+ getCurrentSession(): DesktopFido2UserInterfaceSession | undefined {
+ return this.currentSession;
+ }
async newSession(
fallbackSupported: boolean,
- _tab: void,
+ nativeWindowObject: NativeWindowObject,
abortController?: AbortController,
): Promise {
- this.logService.warning("newSession", fallbackSupported, abortController);
- return new DesktopFido2UserInterfaceSession(
+ this.logService.warning("newSession", fallbackSupported, abortController, nativeWindowObject);
+ const session = new DesktopFido2UserInterfaceSession(
this.authService,
this.cipherService,
this.accountService,
this.logService,
+ this.router,
+ this.desktopSettingsService,
+ nativeWindowObject,
);
+
+ this.currentSession = session;
+ return session;
}
}
@@ -50,17 +88,110 @@ export class DesktopFido2UserInterfaceSession implements Fido2UserInterfaceSessi
private cipherService: CipherService,
private accountService: AccountService,
private logService: LogService,
+ private router: Router,
+ private desktopSettingsService: DesktopSettingsService,
+ private windowObject: NativeWindowObject,
) {}
+ private confirmCredentialSubject = new Subject();
+ private createdCipher: Cipher;
+
+ private availableCipherIdsSubject = new BehaviorSubject(null);
+ /**
+ * Observable that emits available cipher IDs once they're confirmed by the UI
+ */
+ availableCipherIds$ = this.availableCipherIdsSubject.pipe(
+ filter((ids) => ids != null),
+ take(1),
+ );
+
+ private chosenCipherSubject = new Subject<{ cipherId: string; userVerified: boolean }>();
+
+ // Method implementation
async pickCredential({
cipherIds,
userVerification,
+ assumeUserPresence,
+ masterPasswordRepromptRequired,
}: PickCredentialParams): Promise<{ cipherId: string; userVerified: boolean }> {
- this.logService.warning("pickCredential", cipherIds, userVerification);
+ this.logService.warning("pickCredential desktop function", {
+ cipherIds,
+ userVerification,
+ assumeUserPresence,
+ masterPasswordRepromptRequired,
+ });
- return { cipherId: cipherIds[0], userVerified: userVerification };
+ try {
+ // Check if we can return the credential without user interaction
+ if (assumeUserPresence && cipherIds.length === 1 && !masterPasswordRepromptRequired) {
+ this.logService.debug(
+ "shortcut - Assuming user presence and returning cipherId",
+ cipherIds[0],
+ );
+ return { cipherId: cipherIds[0], userVerified: userVerification };
+ }
+
+ this.logService.debug("Could not shortcut, showing UI");
+
+ // make the cipherIds available to the UI.
+ this.availableCipherIdsSubject.next(cipherIds);
+
+ await this.showUi("/passkeys", this.windowObject.windowXy);
+
+ const chosenCipherResponse = await this.waitForUiChosenCipher();
+
+ this.logService.debug("Received chosen cipher", chosenCipherResponse);
+
+ return {
+ cipherId: chosenCipherResponse.cipherId,
+ userVerified: chosenCipherResponse.userVerified,
+ };
+ } finally {
+ // Make sure to clean up so the app is never stuck in modal mode?
+ await this.desktopSettingsService.setModalMode(false);
+ }
}
+ confirmChosenCipher(cipherId: string, userVerified: boolean = false): void {
+ this.chosenCipherSubject.next({ cipherId, userVerified });
+ this.chosenCipherSubject.complete();
+ }
+
+ private async waitForUiChosenCipher(
+ timeoutMs: number = 60000,
+ ): Promise<{ cipherId: string; userVerified: boolean } | undefined> {
+ try {
+ return await lastValueFrom(this.chosenCipherSubject.pipe(timeout(timeoutMs)));
+ } catch {
+ // If we hit a timeout, return undefined instead of throwing
+ this.logService.warning("Timeout: User did not select a cipher within the allowed time", {
+ timeoutMs,
+ });
+ return { cipherId: undefined, userVerified: false };
+ }
+ }
+
+ /**
+ * Notifies the Fido2UserInterfaceSession that the UI operations has completed and it can return to the OS.
+ */
+ notifyConfirmNewCredential(confirmed: boolean): void {
+ this.confirmCredentialSubject.next(confirmed);
+ this.confirmCredentialSubject.complete();
+ }
+
+ /**
+ * Returns once the UI has confirmed and completed the operation
+ * @returns
+ */
+ private async waitForUiNewCredentialConfirmation(): Promise {
+ return lastValueFrom(this.confirmCredentialSubject);
+ }
+
+ /**
+ * This is called by the OS. It loads the UI and waits for the user to confirm the new credential. Once the UI has confirmed, it returns to the the OS.
+ * @param param0
+ * @returns
+ */
async confirmNewCredential({
credentialName,
userName,
@@ -75,6 +206,48 @@ export class DesktopFido2UserInterfaceSession implements Fido2UserInterfaceSessi
rpId,
);
+ try {
+ await this.showUi("/passkeys", this.windowObject.windowXy);
+
+ // Wait for the UI to wrap up
+ const confirmation = await this.waitForUiNewCredentialConfirmation();
+ if (!confirmation) {
+ return { cipherId: undefined, userVerified: false };
+ }
+ // Create the credential
+ await this.createCredential({
+ credentialName,
+ userName,
+ rpId,
+ userHandle: "",
+ userVerification,
+ });
+
+ // wait for 10ms to help RXJS catch up(?)
+ // We sometimes get a race condition from this.createCredential not updating cipherService in time
+ //console.log("waiting 10ms..");
+ //await new Promise((resolve) => setTimeout(resolve, 10));
+ //console.log("Just waited 10ms");
+
+ // Return the new cipher (this.createdCipher)
+ return { cipherId: this.createdCipher.id, userVerified: userVerification };
+ } finally {
+ // Make sure to clean up so the app is never stuck in modal mode?
+ await this.desktopSettingsService.setModalMode(false);
+ }
+ }
+
+ private async showUi(route: string, position?: { x: number; y: number }): Promise {
+ // Load the UI:
+ await this.desktopSettingsService.setModalMode(true, position);
+ await this.router.navigate(["/passkeys"]);
+ }
+
+ /**
+ * Can be called by the UI to create a new credential with user input etc.
+ * @param param0
+ */
+ async createCredential({ credentialName, userName, rpId }: NewCredentialParams): Promise {
// Store the passkey on a new cipher to avoid replacing something important
const cipher = new CipherView();
cipher.name = credentialName;
@@ -97,7 +270,9 @@ export class DesktopFido2UserInterfaceSession implements Fido2UserInterfaceSessi
const encCipher = await this.cipherService.encrypt(cipher, activeUserId);
const createdCipher = await this.cipherService.createWithServer(encCipher);
- return { cipherId: createdCipher.id, userVerified: userVerification };
+ this.createdCipher = createdCipher;
+
+ return createdCipher;
}
async informExcludedCredential(existingCipherIds: string[]): Promise {
diff --git a/apps/desktop/src/main.ts b/apps/desktop/src/main.ts
index e4894b159fe..20c632ec4ac 100644
--- a/apps/desktop/src/main.ts
+++ b/apps/desktop/src/main.ts
@@ -209,6 +209,14 @@ export class Main {
new ElectronMainMessagingService(this.windowMain),
);
+ this.trayMain = new TrayMain(
+ this.windowMain,
+ this.i18nService,
+ this.desktopSettingsService,
+ this.messagingService,
+ this.biometricsService,
+ );
+
messageSubject.asObservable().subscribe((message) => {
void this.messagingMain.onMessage(message).catch((err) => {
this.logService.error(
@@ -236,7 +244,7 @@ export class Main {
this.windowMain,
this.i18nService,
this.desktopSettingsService,
- biometricStateService,
+ this.messagingService,
this.biometricsService,
);
@@ -285,7 +293,7 @@ export class Main {
async () => {
await this.toggleHardwareAcceleration();
// Reset modal mode to make sure main window is displayed correctly
- await this.desktopSettingsService.resetInModalMode();
+ await this.desktopSettingsService.resetModalMode();
await this.windowMain.init();
await this.i18nService.init();
await this.messagingMain.init();
diff --git a/apps/desktop/src/main/messaging.main.ts b/apps/desktop/src/main/messaging.main.ts
index 0d86a7234e6..bb4063d64fd 100644
--- a/apps/desktop/src/main/messaging.main.ts
+++ b/apps/desktop/src/main/messaging.main.ts
@@ -37,6 +37,10 @@ export class MessagingMain {
async onMessage(message: any) {
switch (message.command) {
+ case "loadurl":
+ // TODO: Remove this once fakepopup is removed from tray (just used for dev)
+ await this.main.windowMain.loadUrl(message.url, message.modal);
+ break;
case "scheduleNextSync":
this.scheduleNextSync();
break;
diff --git a/apps/desktop/src/main/tray.main.ts b/apps/desktop/src/main/tray.main.ts
index e63e2a00c85..b7ddefe6e1b 100644
--- a/apps/desktop/src/main/tray.main.ts
+++ b/apps/desktop/src/main/tray.main.ts
@@ -1,16 +1,16 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import * as path from "path";
-import * as url from "url";
import { app, BrowserWindow, Menu, MenuItemConstructorOptions, nativeImage, Tray } from "electron";
import { firstValueFrom } from "rxjs";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
-import { BiometricStateService, BiometricsService } from "@bitwarden/key-management";
+import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
+import { BiometricsService } from "@bitwarden/key-management";
import { DesktopSettingsService } from "../platform/services/desktop-settings.service";
-import { cleanUserAgent, isDev } from "../utils";
+import { isDev } from "../utils";
import { WindowMain } from "./window.main";
@@ -26,7 +26,7 @@ export class TrayMain {
private windowMain: WindowMain,
private i18nService: I18nService,
private desktopSettingsService: DesktopSettingsService,
- private biometricsStateService: BiometricStateService,
+ private messagingService: MessagingService,
private biometricService: BiometricsService,
) {
if (process.platform === "win32") {
@@ -216,32 +216,6 @@ export class TrayMain {
* @returns
*/
private async fakePopup() {
- if (this.windowMain.win == null || this.windowMain.win.isDestroyed()) {
- await this.windowMain.createWindow("modal-app");
- return;
- }
-
- // Restyle existing
- const existingWin = this.windowMain.win;
-
- await this.desktopSettingsService.setInModalMode(true);
- await existingWin.loadURL(
- url.format({
- protocol: "file:",
- //pathname: `${__dirname}/index.html`,
- pathname: path.join(__dirname, "/index.html"),
- slashes: true,
- hash: "/passkeys",
- query: {
- redirectUrl: "/passkeys",
- },
- }),
- {
- userAgent: cleanUserAgent(existingWin.webContents.userAgent),
- },
- );
- existingWin.once("ready-to-show", () => {
- existingWin.show();
- });
+ await this.messagingService.send("loadurl", { url: "/passkeys", modal: true });
}
}
diff --git a/apps/desktop/src/main/window.main.ts b/apps/desktop/src/main/window.main.ts
index ca154400ff5..73231f1f730 100644
--- a/apps/desktop/src/main/window.main.ts
+++ b/apps/desktop/src/main/window.main.ts
@@ -78,18 +78,19 @@ export class WindowMain {
}
});
- this.desktopSettingsService.inModalMode$
+ this.desktopSettingsService.modalMode$
.pipe(
pairwise(),
concatMap(async ([lastValue, newValue]) => {
- if (lastValue && !newValue) {
+ if (lastValue.isModalModeActive && !newValue.isModalModeActive) {
// Reset the window state to the main window state
applyMainWindowStyles(this.win, this.windowStates[mainWindowSizeKey]);
// Because modal is used in front of another app, UX wise it makes sense to hide the main window when leaving modal mode.
this.win.hide();
- } else if (!lastValue && newValue) {
+ } else if (!lastValue.isModalModeActive && newValue.isModalModeActive) {
// Apply the popup modal styles
- applyPopupModalStyles(this.win);
+ this.logService.info("Applying popup modal styles", newValue.modalPosition);
+ applyPopupModalStyles(this.win, newValue.modalPosition);
this.win.show();
}
}),
@@ -209,6 +210,35 @@ export class WindowMain {
}
}
+ // TODO: REMOVE ONCE WE CAN STOP USING FAKE POP UP BTN FROM TRAY
+ // Only used for development
+ async loadUrl(targetPath: string, modal: boolean = false) {
+ if (this.win == null || this.win.isDestroyed()) {
+ await this.createWindow("modal-app");
+ return;
+ }
+
+ await this.desktopSettingsService.setModalMode(modal);
+ await this.win.loadURL(
+ url.format({
+ protocol: "file:",
+ //pathname: `${__dirname}/index.html`,
+ pathname: path.join(__dirname, "/index.html"),
+ slashes: true,
+ hash: targetPath,
+ query: {
+ redirectUrl: targetPath,
+ },
+ }),
+ {
+ userAgent: cleanUserAgent(this.win.webContents.userAgent),
+ },
+ );
+ this.win.once("ready-to-show", () => {
+ this.win.show();
+ });
+ }
+
/**
* Creates the main window. The template argument is used to determine the styling of the window and what url will be loaded.
* When the template is "modal-app", the window will be styled as a modal and the passkeys page will be loaded.
@@ -394,9 +424,9 @@ export class WindowMain {
return;
}
- const inModalMode = await firstValueFrom(this.desktopSettingsService.inModalMode$);
+ const modalMode = await firstValueFrom(this.desktopSettingsService.modalMode$);
- if (inModalMode) {
+ if (modalMode.isModalModeActive) {
return;
}
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 1465831340f..f66eea180cf 100644
--- a/apps/desktop/src/platform/main/autofill/native-autofill.main.ts
+++ b/apps/desktop/src/platform/main/autofill/native-autofill.main.ts
@@ -40,6 +40,7 @@ export class NativeAutofillMain {
(error, clientId, sequenceNumber, request) => {
if (error) {
this.logService.error("autofill.IpcServer.registration", error);
+ this.ipcServer.completeError(clientId, sequenceNumber, String(error));
return;
}
this.windowMain.win.webContents.send("autofill.passkeyRegistration", {
@@ -52,6 +53,7 @@ export class NativeAutofillMain {
(error, clientId, sequenceNumber, request) => {
if (error) {
this.logService.error("autofill.IpcServer.assertion", error);
+ this.ipcServer.completeError(clientId, sequenceNumber, String(error));
return;
}
this.windowMain.win.webContents.send("autofill.passkeyAssertion", {
@@ -60,6 +62,19 @@ export class NativeAutofillMain {
request,
});
},
+ // AssertionWithoutUserInterfaceCallback
+ (error, clientId, sequenceNumber, request) => {
+ if (error) {
+ this.logService.error("autofill.IpcServer.assertion", error);
+ this.ipcServer.completeError(clientId, sequenceNumber, String(error));
+ return;
+ }
+ this.windowMain.win.webContents.send("autofill.passkeyAssertionWithoutUserInterface", {
+ clientId,
+ sequenceNumber,
+ request,
+ });
+ },
);
ipcMain.on("autofill.completePasskeyRegistration", (event, data) => {
@@ -77,7 +92,7 @@ export class NativeAutofillMain {
ipcMain.on("autofill.completeError", (event, data) => {
this.logService.warning("autofill.completeError", data);
const { clientId, sequenceNumber, error } = data;
- this.ipcServer.completeAssertion(clientId, sequenceNumber, error);
+ this.ipcServer.completeError(clientId, sequenceNumber, String(error));
});
}
diff --git a/apps/desktop/src/platform/models/domain/window-state.ts b/apps/desktop/src/platform/models/domain/window-state.ts
index 00230319972..0efc9a1efab 100644
--- a/apps/desktop/src/platform/models/domain/window-state.ts
+++ b/apps/desktop/src/platform/models/domain/window-state.ts
@@ -11,3 +11,8 @@ export class WindowState {
y?: number;
zoomFactor?: number;
}
+
+export class ModalModeState {
+ isModalModeActive: boolean;
+ modalPosition?: { x: number; y: number }; // Modal position is often passed from the native UI
+}
diff --git a/apps/desktop/src/platform/popup-modal-styles.ts b/apps/desktop/src/platform/popup-modal-styles.ts
index 9c3f06b34bf..ae46ebb5c76 100644
--- a/apps/desktop/src/platform/popup-modal-styles.ts
+++ b/apps/desktop/src/platform/popup-modal-styles.ts
@@ -6,10 +6,11 @@ import { WindowState } from "./models/domain/window-state";
const popupWidth = 680;
const popupHeight = 500;
-export function applyPopupModalStyles(window: BrowserWindow) {
+type Position = { x: number; y: number };
+
+export function applyPopupModalStyles(window: BrowserWindow, position?: Position) {
window.unmaximize();
window.setSize(popupWidth, popupHeight);
- window.center();
window.setWindowButtonVisibility?.(false);
window.setMenuBarVisibility?.(false);
window.setResizable(false);
@@ -20,8 +21,21 @@ export function applyPopupModalStyles(window: BrowserWindow) {
window.setFullScreen(false);
window.once("leave-full-screen", () => {
window.setSize(popupWidth, popupHeight);
- window.center();
+ positionWindow(window, position);
});
+ } else {
+ // If not in full screen
+ positionWindow(window, position);
+ }
+}
+
+function positionWindow(window: BrowserWindow, position?: Position) {
+ if (position) {
+ const centeredX = position.x - popupWidth / 2;
+ const centeredY = position.y - popupHeight / 2;
+ window.setPosition(centeredX, centeredY);
+ } else {
+ window.center();
}
}
diff --git a/apps/desktop/src/platform/services/desktop-settings.service.ts b/apps/desktop/src/platform/services/desktop-settings.service.ts
index efac0cda252..f5789d6f40c 100644
--- a/apps/desktop/src/platform/services/desktop-settings.service.ts
+++ b/apps/desktop/src/platform/services/desktop-settings.service.ts
@@ -8,7 +8,7 @@ import {
} from "@bitwarden/common/platform/state";
import { UserId } from "@bitwarden/common/types/guid";
-import { WindowState } from "../models/domain/window-state";
+import { ModalModeState, WindowState } from "../models/domain/window-state";
export const HARDWARE_ACCELERATION = new KeyDefinition(
DESKTOP_SETTINGS_DISK,
@@ -75,7 +75,7 @@ const MINIMIZE_ON_COPY = new UserKeyDefinition(DESKTOP_SETTINGS_DISK, "
clearOn: [], // User setting, no need to clear
});
-const IN_MODAL_MODE = new KeyDefinition(DESKTOP_SETTINGS_DISK, "inModalMode", {
+const MODAL_MODE = new KeyDefinition(DESKTOP_SETTINGS_DISK, "modalMode", {
deserializer: (b) => b,
});
@@ -174,9 +174,9 @@ export class DesktopSettingsService {
*/
minimizeOnCopy$ = this.minimizeOnCopyState.state$.pipe(map(Boolean));
- private readonly inModalModeState = this.stateProvider.getGlobal(IN_MODAL_MODE);
+ private readonly modalModeState = this.stateProvider.getGlobal(MODAL_MODE);
- inModalMode$ = this.inModalModeState.state$.pipe(map(Boolean));
+ modalMode$ = this.modalModeState.state$;
constructor(private stateProvider: StateProvider) {
this.window$ = this.windowState.state$.pipe(
@@ -190,8 +190,8 @@ export class DesktopSettingsService {
* This is used to clear the setting on application start to make sure we don't end up
* stuck in modal mode if the application is force-closed in modal mode.
*/
- async resetInModalMode() {
- await this.inModalModeState.update(() => false);
+ async resetModalMode() {
+ await this.modalModeState.update(() => ({ isModalModeActive: false }));
}
async setHardwareAcceleration(enabled: boolean) {
@@ -306,8 +306,11 @@ export class DesktopSettingsService {
* Sets the modal mode of the application. Setting this changes the windows-size and other properties.
* @param value `true` if the application is in modal mode, `false` if it is not.
*/
- async setInModalMode(value: boolean) {
- await this.inModalModeState.update(() => value);
+ async setModalMode(value: boolean, modalPosition?: { x: number; y: number }) {
+ await this.modalModeState.update(() => ({
+ isModalModeActive: value,
+ modalPosition,
+ }));
}
/**
diff --git a/libs/common/src/platform/abstractions/fido2/fido2-user-interface.service.abstraction.ts b/libs/common/src/platform/abstractions/fido2/fido2-user-interface.service.abstraction.ts
index 7beefc3b4cc..1f871f6c70f 100644
--- a/libs/common/src/platform/abstractions/fido2/fido2-user-interface.service.abstraction.ts
+++ b/libs/common/src/platform/abstractions/fido2/fido2-user-interface.service.abstraction.ts
@@ -82,7 +82,7 @@ export abstract class Fido2UserInterfaceSession {
*
* @param params The parameters to use when asking the user to pick a credential.
* @param abortController An abort controller that can be used to cancel/close the session.
- * @returns The ID of the cipher that contains the credentials the user picked.
+ * @returns The ID of the cipher that contains the credentials the user picked. If not cipher was picked, return cipherId = undefined to to let the authenticator throw the error.
*/
pickCredential: (
params: PickCredentialParams,
From 8ed8c9af6a878abdcbf414169932697c6def8dd1 Mon Sep 17 00:00:00 2001
From: Vijay Oommen
Date: Mon, 24 Mar 2025 08:33:17 -0500
Subject: [PATCH 038/113] [PM-18685] Disable MyVault when the Person Ownership
policy is true (#13930)
---
.../src/components/export.component.html | 18 ++++--
.../src/components/export.component.ts | 57 +++++++++++++++----
2 files changed, 58 insertions(+), 17 deletions(-)
diff --git a/libs/tools/export/vault-export/vault-export-ui/src/components/export.component.html b/libs/tools/export/vault-export/vault-export-ui/src/components/export.component.html
index 7555b206976..5b196f51799 100644
--- a/libs/tools/export/vault-export/vault-export-ui/src/components/export.component.html
+++ b/libs/tools/export/vault-export/vault-export-ui/src/components/export.component.html
@@ -1,17 +1,23 @@
-
+
{{ "personalVaultExportPolicyInEffect" | i18n }}
-
+
-
+
{{ "changeMasterPassword" | i18n }}
diff --git a/apps/web/src/app/auth/settings/change-password.component.ts b/apps/web/src/app/auth/settings/change-password.component.ts
index 5f061dd1e2a..d8e371fd36b 100644
--- a/apps/web/src/app/auth/settings/change-password.component.ts
+++ b/apps/web/src/app/auth/settings/change-password.component.ts
@@ -12,7 +12,9 @@ import { MasterPasswordApiService } from "@bitwarden/common/auth/abstractions/ma
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
import { PasswordRequest } from "@bitwarden/common/auth/models/request/password.request";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
+import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
+import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
@@ -35,11 +37,13 @@ export class ChangePasswordComponent
extends BaseChangePasswordComponent
implements OnInit, OnDestroy
{
+ loading = false;
rotateUserKey = false;
currentMasterPassword: string;
masterPasswordHint: string;
checkForBreaches = true;
characterMinimumMessage = "";
+ userkeyRotationV2 = false;
constructor(
i18nService: I18nService,
@@ -56,9 +60,10 @@ export class ChangePasswordComponent
private userVerificationService: UserVerificationService,
private keyRotationService: UserKeyRotationService,
kdfConfigService: KdfConfigService,
- masterPasswordService: InternalMasterPasswordServiceAbstraction,
+ protected masterPasswordService: InternalMasterPasswordServiceAbstraction,
accountService: AccountService,
toastService: ToastService,
+ private configService: ConfigService,
) {
super(
i18nService,
@@ -75,6 +80,8 @@ export class ChangePasswordComponent
}
async ngOnInit() {
+ this.userkeyRotationV2 = await this.configService.getFeatureFlag(FeatureFlag.UserKeyRotationV2);
+
if (!(await this.userVerificationService.hasMasterPassword())) {
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
@@ -137,6 +144,130 @@ export class ChangePasswordComponent
}
async submit() {
+ if (this.userkeyRotationV2) {
+ this.loading = true;
+ await this.submitNew();
+ this.loading = false;
+ } else {
+ await this.submitOld();
+ }
+ }
+
+ async submitNew() {
+ if (this.currentMasterPassword == null || this.currentMasterPassword === "") {
+ this.toastService.showToast({
+ variant: "error",
+ title: this.i18nService.t("errorOccurred"),
+ message: this.i18nService.t("masterPasswordRequired"),
+ });
+ return;
+ }
+
+ if (
+ this.masterPasswordHint != null &&
+ this.masterPasswordHint.toLowerCase() === this.masterPassword.toLowerCase()
+ ) {
+ this.toastService.showToast({
+ variant: "error",
+ title: this.i18nService.t("errorOccurred"),
+ message: this.i18nService.t("hintEqualsPassword"),
+ });
+ return;
+ }
+
+ this.leakedPassword = false;
+ if (this.checkForBreaches) {
+ this.leakedPassword = (await this.auditService.passwordLeaked(this.masterPassword)) > 0;
+ }
+
+ if (!(await this.strongPassword())) {
+ return;
+ }
+
+ try {
+ if (this.rotateUserKey) {
+ await this.syncService.fullSync(true);
+ const user = await firstValueFrom(this.accountService.activeAccount$);
+ await this.keyRotationService.rotateUserKeyMasterPasswordAndEncryptedData(
+ this.currentMasterPassword,
+ this.masterPassword,
+ user,
+ this.masterPasswordHint,
+ );
+ } else {
+ await this.updatePassword(this.masterPassword);
+ }
+ } catch (e) {
+ this.toastService.showToast({
+ variant: "error",
+ title: this.i18nService.t("errorOccurred"),
+ message: e.message,
+ });
+ }
+ }
+
+ // todo: move this to a service
+ // https://bitwarden.atlassian.net/browse/PM-17108
+ private async updatePassword(newMasterPassword: string) {
+ const currentMasterPassword = this.currentMasterPassword;
+ const { userId, email } = await firstValueFrom(
+ this.accountService.activeAccount$.pipe(map((a) => ({ userId: a?.id, email: a?.email }))),
+ );
+ const kdfConfig = await firstValueFrom(this.kdfConfigService.getKdfConfig$(userId));
+
+ const currentMasterKey = await this.keyService.makeMasterKey(
+ currentMasterPassword,
+ email,
+ kdfConfig,
+ );
+ const decryptedUserKey = await this.masterPasswordService.decryptUserKeyWithMasterKey(
+ currentMasterKey,
+ userId,
+ );
+ if (decryptedUserKey == null) {
+ this.toastService.showToast({
+ variant: "error",
+ title: null,
+ message: this.i18nService.t("invalidMasterPassword"),
+ });
+ return;
+ }
+
+ const newMasterKey = await this.keyService.makeMasterKey(newMasterPassword, email, kdfConfig);
+ const newMasterKeyEncryptedUserKey = await this.keyService.encryptUserKeyWithMasterKey(
+ newMasterKey,
+ decryptedUserKey,
+ );
+
+ const request = new PasswordRequest();
+ request.masterPasswordHash = await this.keyService.hashMasterKey(
+ this.currentMasterPassword,
+ currentMasterKey,
+ );
+ request.masterPasswordHint = this.masterPasswordHint;
+ request.newMasterPasswordHash = await this.keyService.hashMasterKey(
+ newMasterPassword,
+ newMasterKey,
+ );
+ request.key = newMasterKeyEncryptedUserKey[1].encryptedString;
+ try {
+ await this.masterPasswordApiService.postPassword(request);
+ this.toastService.showToast({
+ variant: "success",
+ title: this.i18nService.t("masterPasswordChanged"),
+ message: this.i18nService.t("masterPasswordChangedDesc"),
+ });
+ this.messagingService.send("logout");
+ } catch {
+ this.toastService.showToast({
+ variant: "error",
+ title: null,
+ message: this.i18nService.t("errorOccurred"),
+ });
+ }
+ }
+
+ async submitOld() {
if (
this.masterPasswordHint != null &&
this.masterPasswordHint.toLowerCase() === this.masterPassword.toLowerCase()
@@ -242,6 +373,6 @@ export class ChangePasswordComponent
private async updateKey() {
const user = await firstValueFrom(this.accountService.activeAccount$);
- await this.keyRotationService.rotateUserKeyAndEncryptedData(this.masterPassword, user);
+ await this.keyRotationService.rotateUserKeyAndEncryptedDataLegacy(this.masterPassword, user);
}
}
diff --git a/apps/web/src/app/key-management/key-rotation/request/account-keys.request.ts b/apps/web/src/app/key-management/key-rotation/request/account-keys.request.ts
new file mode 100644
index 00000000000..1c9b6c9ceca
--- /dev/null
+++ b/apps/web/src/app/key-management/key-rotation/request/account-keys.request.ts
@@ -0,0 +1,10 @@
+export class AccountKeysRequest {
+ // Other keys encrypted by the userkey
+ userKeyEncryptedAccountPrivateKey: string;
+ accountPublicKey: string;
+
+ constructor(userKeyEncryptedAccountPrivateKey: string, accountPublicKey: string) {
+ this.userKeyEncryptedAccountPrivateKey = userKeyEncryptedAccountPrivateKey;
+ this.accountPublicKey = accountPublicKey;
+ }
+}
diff --git a/apps/web/src/app/key-management/key-rotation/request/master-password-unlock-data.request.ts b/apps/web/src/app/key-management/key-rotation/request/master-password-unlock-data.request.ts
new file mode 100644
index 00000000000..95e54e16464
--- /dev/null
+++ b/apps/web/src/app/key-management/key-rotation/request/master-password-unlock-data.request.ts
@@ -0,0 +1,35 @@
+import { Argon2KdfConfig, KdfConfig, KdfType } from "@bitwarden/key-management";
+
+export class MasterPasswordUnlockDataRequest {
+ kdfType: KdfType = KdfType.PBKDF2_SHA256;
+ kdfIterations: number = 0;
+ kdfMemory?: number;
+ kdfParallelism?: number;
+
+ email: string;
+ masterKeyAuthenticationHash: string;
+
+ masterKeyEncryptedUserKey: string;
+
+ masterPasswordHint?: string;
+
+ constructor(
+ kdfConfig: KdfConfig,
+ email: string,
+ masterKeyAuthenticationHash: string,
+ masterKeyEncryptedUserKey: string,
+ masterPasswordHash?: string,
+ ) {
+ this.kdfType = kdfConfig.kdfType;
+ this.kdfIterations = kdfConfig.iterations;
+ if (kdfConfig.kdfType === KdfType.Argon2id) {
+ this.kdfMemory = (kdfConfig as Argon2KdfConfig).memory;
+ this.kdfParallelism = (kdfConfig as Argon2KdfConfig).parallelism;
+ }
+
+ this.email = email;
+ this.masterKeyAuthenticationHash = masterKeyAuthenticationHash;
+ this.masterKeyEncryptedUserKey = masterKeyEncryptedUserKey;
+ this.masterPasswordHint = masterPasswordHash;
+ }
+}
diff --git a/apps/web/src/app/key-management/key-rotation/request/rotate-user-account-keys.request.ts b/apps/web/src/app/key-management/key-rotation/request/rotate-user-account-keys.request.ts
new file mode 100644
index 00000000000..f335bb66bbb
--- /dev/null
+++ b/apps/web/src/app/key-management/key-rotation/request/rotate-user-account-keys.request.ts
@@ -0,0 +1,29 @@
+import { AccountKeysRequest } from "./account-keys.request";
+import { UnlockDataRequest } from "./unlock-data.request";
+import { UserDataRequest as AccountDataRequest } from "./userdata.request";
+
+export class RotateUserAccountKeysRequest {
+ constructor(
+ accountUnlockData: UnlockDataRequest,
+ accountKeys: AccountKeysRequest,
+ accountData: AccountDataRequest,
+ oldMasterKeyAuthenticationHash: string,
+ ) {
+ this.accountUnlockData = accountUnlockData;
+ this.accountKeys = accountKeys;
+ this.accountData = accountData;
+ this.oldMasterKeyAuthenticationHash = oldMasterKeyAuthenticationHash;
+ }
+
+ // Authentication for the request
+ oldMasterKeyAuthenticationHash: string;
+
+ // All methods to get to the userkey
+ accountUnlockData: UnlockDataRequest;
+
+ // Other keys encrypted by the userkey
+ accountKeys: AccountKeysRequest;
+
+ // User vault data encrypted by the userkey
+ accountData: AccountDataRequest;
+}
diff --git a/apps/web/src/app/key-management/key-rotation/request/unlock-data.request.ts b/apps/web/src/app/key-management/key-rotation/request/unlock-data.request.ts
new file mode 100644
index 00000000000..5cdb56a3e23
--- /dev/null
+++ b/apps/web/src/app/key-management/key-rotation/request/unlock-data.request.ts
@@ -0,0 +1,26 @@
+import { OrganizationUserResetPasswordWithIdRequest } from "@bitwarden/admin-console/common";
+import { WebauthnRotateCredentialRequest } from "@bitwarden/common/auth/models/request/webauthn-rotate-credential.request";
+
+import { EmergencyAccessWithIdRequest } from "../../../auth/emergency-access/request/emergency-access-update.request";
+
+import { MasterPasswordUnlockDataRequest } from "./master-password-unlock-data.request";
+
+export class UnlockDataRequest {
+ // All methods to get to the userkey
+ masterPasswordUnlockData: MasterPasswordUnlockDataRequest;
+ emergencyAccessUnlockData: EmergencyAccessWithIdRequest[];
+ organizationAccountRecoveryUnlockData: OrganizationUserResetPasswordWithIdRequest[];
+ passkeyUnlockData: WebauthnRotateCredentialRequest[];
+
+ constructor(
+ masterPasswordUnlockData: MasterPasswordUnlockDataRequest,
+ emergencyAccessUnlockData: EmergencyAccessWithIdRequest[],
+ organizationAccountRecoveryUnlockData: OrganizationUserResetPasswordWithIdRequest[],
+ passkeyUnlockData: WebauthnRotateCredentialRequest[],
+ ) {
+ this.masterPasswordUnlockData = masterPasswordUnlockData;
+ this.emergencyAccessUnlockData = emergencyAccessUnlockData;
+ this.organizationAccountRecoveryUnlockData = organizationAccountRecoveryUnlockData;
+ this.passkeyUnlockData = passkeyUnlockData;
+ }
+}
diff --git a/apps/web/src/app/key-management/key-rotation/request/userdata.request.ts b/apps/web/src/app/key-management/key-rotation/request/userdata.request.ts
new file mode 100644
index 00000000000..02204428686
--- /dev/null
+++ b/apps/web/src/app/key-management/key-rotation/request/userdata.request.ts
@@ -0,0 +1,19 @@
+import { SendWithIdRequest } from "@bitwarden/common/tools/send/models/request/send-with-id.request";
+import { CipherWithIdRequest } from "@bitwarden/common/vault/models/request/cipher-with-id.request";
+import { FolderWithIdRequest } from "@bitwarden/common/vault/models/request/folder-with-id.request";
+
+export class UserDataRequest {
+ ciphers: CipherWithIdRequest[];
+ folders: FolderWithIdRequest[];
+ sends: SendWithIdRequest[];
+
+ constructor(
+ ciphers: CipherWithIdRequest[],
+ folders: FolderWithIdRequest[],
+ sends: SendWithIdRequest[],
+ ) {
+ this.ciphers = ciphers;
+ this.folders = folders;
+ this.sends = sends;
+ }
+}
diff --git a/apps/web/src/app/key-management/key-rotation/user-key-rotation-api.service.ts b/apps/web/src/app/key-management/key-rotation/user-key-rotation-api.service.ts
index 3c8adc886df..2a947359bcb 100644
--- a/apps/web/src/app/key-management/key-rotation/user-key-rotation-api.service.ts
+++ b/apps/web/src/app/key-management/key-rotation/user-key-rotation-api.service.ts
@@ -2,6 +2,7 @@ import { inject, Injectable } from "@angular/core";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
+import { RotateUserAccountKeysRequest } from "./request/rotate-user-account-keys.request";
import { UpdateKeyRequest } from "./request/update-key.request";
@Injectable()
@@ -11,4 +12,14 @@ export class UserKeyRotationApiService {
postUserKeyUpdate(request: UpdateKeyRequest): Promise {
return this.apiService.send("POST", "/accounts/key", request, true, false);
}
+
+ postUserKeyUpdateV2(request: RotateUserAccountKeysRequest): Promise {
+ return this.apiService.send(
+ "POST",
+ "/accounts/key-management/rotate-user-account-keys",
+ request,
+ true,
+ false,
+ );
+ }
}
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 09d35d34da8..4aa0a753f63 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
@@ -6,21 +6,25 @@ import { UserVerificationService } from "@bitwarden/common/auth/abstractions/use
import { WebauthnRotateCredentialRequest } from "@bitwarden/common/auth/models/request/webauthn-rotate-credential.request";
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 { VaultTimeoutService } from "@bitwarden/common/key-management/vault-timeout";
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 { Utils } from "@bitwarden/common/platform/misc/utils";
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import { 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";
-import { UserKey, UserPrivateKey } from "@bitwarden/common/types/key";
+import { UserKey, UserPrivateKey, UserPublicKey } from "@bitwarden/common/types/key";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
import { CipherType } from "@bitwarden/common/vault/enums";
import { CipherWithIdRequest } from "@bitwarden/common/vault/models/request/cipher-with-id.request";
import { FolderWithIdRequest } from "@bitwarden/common/vault/models/request/folder-with-id.request";
-import { KeyService } from "@bitwarden/key-management";
+import { ToastService } from "@bitwarden/components";
+import { DEFAULT_KDF_CONFIG, KeyService } from "@bitwarden/key-management";
import { OrganizationUserResetPasswordService } from "../../admin-console/organizations/members/services/organization-user-reset-password/organization-user-reset-password.service";
import { WebauthnLoginAdminService } from "../../auth/core";
@@ -47,6 +51,9 @@ describe("KeyRotationService", () => {
let mockSyncService: MockProxy;
let mockWebauthnLoginAdminService: MockProxy;
let mockLogService: MockProxy;
+ let mockVaultTimeoutService: MockProxy;
+ let mockToastService: MockProxy;
+ let mockI18nService: MockProxy;
const mockUser = {
id: "mockUserId" as UserId,
@@ -70,6 +77,9 @@ describe("KeyRotationService", () => {
mockSyncService = mock();
mockWebauthnLoginAdminService = mock();
mockLogService = mock();
+ mockVaultTimeoutService = mock();
+ mockToastService = mock();
+ mockI18nService = mock();
keyRotationService = new UserKeyRotationService(
mockUserVerificationService,
@@ -85,6 +95,9 @@ describe("KeyRotationService", () => {
mockSyncService,
mockWebauthnLoginAdminService,
mockLogService,
+ mockVaultTimeoutService,
+ mockToastService,
+ mockI18nService,
);
});
@@ -94,6 +107,7 @@ describe("KeyRotationService", () => {
describe("rotateUserKeyAndEncryptedData", () => {
let privateKey: BehaviorSubject;
+ let keyPair: BehaviorSubject<{ privateKey: UserPrivateKey; publicKey: UserPublicKey }>;
beforeEach(() => {
mockKeyService.makeUserKey.mockResolvedValue([
@@ -112,6 +126,8 @@ describe("KeyRotationService", () => {
// Mock user verification
mockUserVerificationService.verifyUserByMasterPassword.mockResolvedValue({
masterKey: "mockMasterKey" as any,
+ kdfConfig: DEFAULT_KDF_CONFIG,
+ email: "mockEmail",
policyOptions: null,
});
@@ -122,6 +138,12 @@ describe("KeyRotationService", () => {
privateKey = new BehaviorSubject("mockPrivateKey" as any);
mockKeyService.userPrivateKeyWithLegacySupport$.mockReturnValue(privateKey);
+ keyPair = new BehaviorSubject({
+ privateKey: "mockPrivateKey",
+ publicKey: "mockPublicKey",
+ } as any);
+ mockKeyService.userEncryptionKeyPair$.mockReturnValue(keyPair);
+
// Mock ciphers
const mockCiphers = [createMockCipher("1", "Cipher 1"), createMockCipher("2", "Cipher 2")];
mockCipherService.getRotatedData.mockResolvedValue(mockCiphers);
@@ -147,8 +169,8 @@ describe("KeyRotationService", () => {
mockWebauthnLoginAdminService.getRotatedData.mockResolvedValue(webauthn);
});
- it("rotates the user key and encrypted data", async () => {
- await keyRotationService.rotateUserKeyAndEncryptedData("mockMasterPassword", mockUser);
+ it("rotates the user key and encrypted data legacy", async () => {
+ await keyRotationService.rotateUserKeyAndEncryptedDataLegacy("mockMasterPassword", mockUser);
expect(mockApiService.postUserKeyUpdate).toHaveBeenCalled();
const arg = mockApiService.postUserKeyUpdate.mock.calls[0][0];
@@ -162,9 +184,47 @@ describe("KeyRotationService", () => {
expect(arg.webauthnKeys.length).toBe(2);
});
+ it("rotates the user key and encrypted data", async () => {
+ await keyRotationService.rotateUserKeyMasterPasswordAndEncryptedData(
+ "mockMasterPassword",
+ "mockNewMasterPassword",
+ mockUser,
+ );
+
+ expect(mockApiService.postUserKeyUpdateV2).toHaveBeenCalled();
+ const arg = mockApiService.postUserKeyUpdateV2.mock.calls[0][0];
+ expect(arg.oldMasterKeyAuthenticationHash).toBe("mockMasterPasswordHash");
+ expect(arg.accountUnlockData.masterPasswordUnlockData.email).toBe("mockEmail");
+ expect(arg.accountUnlockData.masterPasswordUnlockData.kdfType).toBe(
+ DEFAULT_KDF_CONFIG.kdfType,
+ );
+ expect(arg.accountUnlockData.masterPasswordUnlockData.kdfIterations).toBe(
+ DEFAULT_KDF_CONFIG.iterations,
+ );
+ expect(arg.accountKeys.accountPublicKey).toBe(Utils.fromUtf8ToB64("mockPublicKey"));
+ expect(arg.accountKeys.userKeyEncryptedAccountPrivateKey).toBe("mockEncryptedData");
+ expect(arg.accountData.ciphers.length).toBe(2);
+ expect(arg.accountData.folders.length).toBe(2);
+ expect(arg.accountData.sends.length).toBe(2);
+ });
+
+ it("legacy throws if master password provided is falsey", async () => {
+ await expect(
+ keyRotationService.rotateUserKeyAndEncryptedDataLegacy("", mockUser),
+ ).rejects.toThrow();
+ });
+
it("throws if master password provided is falsey", async () => {
await expect(
- keyRotationService.rotateUserKeyAndEncryptedData("", mockUser),
+ keyRotationService.rotateUserKeyMasterPasswordAndEncryptedData("", "", mockUser),
+ ).rejects.toThrow();
+ });
+
+ it("legacy throws if user key creation fails", async () => {
+ mockKeyService.makeUserKey.mockResolvedValueOnce([null, null]);
+
+ await expect(
+ keyRotationService.rotateUserKeyAndEncryptedDataLegacy("mockMasterPassword", mockUser),
).rejects.toThrow();
});
@@ -175,15 +235,41 @@ describe("KeyRotationService", () => {
]);
await expect(
- keyRotationService.rotateUserKeyAndEncryptedData("mockMasterPassword", mockUser),
+ keyRotationService.rotateUserKeyMasterPasswordAndEncryptedData(
+ "mockMasterPassword",
+ "mockMasterPassword1",
+ mockUser,
+ ),
+ ).rejects.toThrow();
+ });
+
+ it("legacy throws if no private key is found", async () => {
+ privateKey.next(null);
+
+ await expect(
+ keyRotationService.rotateUserKeyAndEncryptedDataLegacy("mockMasterPassword", mockUser),
).rejects.toThrow();
});
it("throws if no private key is found", async () => {
- privateKey.next(null);
+ keyPair.next(null);
await expect(
- keyRotationService.rotateUserKeyAndEncryptedData("mockMasterPassword", mockUser),
+ keyRotationService.rotateUserKeyMasterPasswordAndEncryptedData(
+ "mockMasterPassword",
+ "mockMasterPassword1",
+ mockUser,
+ ),
+ ).rejects.toThrow();
+ });
+
+ it("legacy throws if master password is incorrect", async () => {
+ mockUserVerificationService.verifyUserByMasterPassword.mockRejectedValueOnce(
+ new Error("Invalid master password"),
+ );
+
+ await expect(
+ keyRotationService.rotateUserKeyAndEncryptedDataLegacy("mockMasterPassword", mockUser),
).rejects.toThrow();
});
@@ -193,15 +279,31 @@ describe("KeyRotationService", () => {
);
await expect(
- keyRotationService.rotateUserKeyAndEncryptedData("mockMasterPassword", mockUser),
+ keyRotationService.rotateUserKeyMasterPasswordAndEncryptedData(
+ "mockMasterPassword",
+ "mockMasterPassword1",
+ mockUser,
+ ),
+ ).rejects.toThrow();
+ });
+
+ it("legacy throws if server rotation fails", async () => {
+ mockApiService.postUserKeyUpdate.mockRejectedValueOnce(new Error("mockError"));
+
+ await expect(
+ keyRotationService.rotateUserKeyAndEncryptedDataLegacy("mockMasterPassword", mockUser),
).rejects.toThrow();
});
it("throws if server rotation fails", async () => {
- mockApiService.postUserKeyUpdate.mockRejectedValueOnce(new Error("mockError"));
+ mockApiService.postUserKeyUpdateV2.mockRejectedValueOnce(new Error("mockError"));
await expect(
- keyRotationService.rotateUserKeyAndEncryptedData("mockMasterPassword", mockUser),
+ keyRotationService.rotateUserKeyMasterPasswordAndEncryptedData(
+ "mockMasterPassword",
+ "mockMasterPassword1",
+ mockUser,
+ ),
).rejects.toThrow();
});
});
diff --git a/apps/web/src/app/key-management/key-rotation/user-key-rotation.service.ts b/apps/web/src/app/key-management/key-rotation/user-key-rotation.service.ts
index d200fff256c..c1b7a04d62b 100644
--- a/apps/web/src/app/key-management/key-rotation/user-key-rotation.service.ts
+++ b/apps/web/src/app/key-management/key-rotation/user-key-rotation.service.ts
@@ -7,7 +7,11 @@ import { VerificationType } from "@bitwarden/common/auth/enums/verification-type
import { MasterPasswordVerification } from "@bitwarden/common/auth/types/verification";
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 { VaultTimeoutService } from "@bitwarden/common/key-management/vault-timeout";
+import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
+import { HashPurpose } from "@bitwarden/common/platform/enums";
+import { Utils } from "@bitwarden/common/platform/misc/utils";
import { EncryptedString } from "@bitwarden/common/platform/models/domain/enc-string";
import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction";
import { UserId } from "@bitwarden/common/types/guid";
@@ -15,13 +19,19 @@ import { UserKey } from "@bitwarden/common/types/key";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
+import { ToastService } from "@bitwarden/components";
import { KeyService } from "@bitwarden/key-management";
import { OrganizationUserResetPasswordService } from "../../admin-console/organizations/members/services/organization-user-reset-password/organization-user-reset-password.service";
import { WebauthnLoginAdminService } from "../../auth/core";
import { EmergencyAccessService } from "../../auth/emergency-access";
+import { AccountKeysRequest } from "./request/account-keys.request";
+import { MasterPasswordUnlockDataRequest } from "./request/master-password-unlock-data.request";
+import { RotateUserAccountKeysRequest } from "./request/rotate-user-account-keys.request";
+import { UnlockDataRequest } from "./request/unlock-data.request";
import { UpdateKeyRequest } from "./request/update-key.request";
+import { UserDataRequest } from "./request/userdata.request";
import { UserKeyRotationApiService } from "./user-key-rotation-api.service";
@Injectable()
@@ -40,14 +50,180 @@ export class UserKeyRotationService {
private syncService: SyncService,
private webauthnLoginAdminService: WebauthnLoginAdminService,
private logService: LogService,
+ private vaultTimeoutService: VaultTimeoutService,
+ private toastService: ToastService,
+ private i18nService: I18nService,
) {}
/**
* Creates a new user key and re-encrypts all required data with the it.
- * @param masterPassword current master password (used for validation)
+ * @param oldMasterPassword: The current master password
+ * @param newMasterPassword: The new master password
+ * @param user: The user account
+ * @param newMasterPasswordHint: The hint for the new master password
*/
- async rotateUserKeyAndEncryptedData(masterPassword: string, user: Account): Promise {
+ async rotateUserKeyMasterPasswordAndEncryptedData(
+ oldMasterPassword: string,
+ newMasterPassword: string,
+ user: Account,
+ newMasterPasswordHint?: string,
+ ): Promise {
this.logService.info("[Userkey rotation] Starting user key rotation...");
+ if (!newMasterPassword) {
+ this.logService.info("[Userkey rotation] Invalid master password provided. Aborting!");
+ throw new Error("Invalid master password");
+ }
+
+ if ((await this.syncService.getLastSync()) === null) {
+ this.logService.info("[Userkey rotation] Client was never synced. Aborting!");
+ throw new Error(
+ "The local vault is de-synced and the keys cannot be rotated. Please log out and log back in to resolve this issue.",
+ );
+ }
+
+ const {
+ masterKey: oldMasterKey,
+ email,
+ kdfConfig,
+ } = await this.userVerificationService.verifyUserByMasterPassword(
+ {
+ type: VerificationType.MasterPassword,
+ secret: oldMasterPassword,
+ },
+ user.id,
+ user.email,
+ );
+
+ const newMasterKey = await this.keyService.makeMasterKey(newMasterPassword, email, kdfConfig);
+
+ const [newUnencryptedUserKey, newMasterKeyEncryptedUserKey] =
+ await this.keyService.makeUserKey(newMasterKey);
+
+ if (!newUnencryptedUserKey || !newMasterKeyEncryptedUserKey) {
+ this.logService.info("[Userkey rotation] User key could not be created. Aborting!");
+ throw new Error("User key could not be created");
+ }
+
+ const newMasterKeyAuthenticationHash = await this.keyService.hashMasterKey(
+ newMasterPassword,
+ newMasterKey,
+ HashPurpose.ServerAuthorization,
+ );
+ const masterPasswordUnlockData = new MasterPasswordUnlockDataRequest(
+ kdfConfig,
+ email,
+ newMasterKeyAuthenticationHash,
+ newMasterKeyEncryptedUserKey.encryptedString!,
+ newMasterPasswordHint,
+ );
+
+ const keyPair = await firstValueFrom(this.keyService.userEncryptionKeyPair$(user.id));
+ if (keyPair == null) {
+ this.logService.info("[Userkey rotation] Key pair is null. Aborting!");
+ throw new Error("Key pair is null");
+ }
+ const { privateKey, publicKey } = keyPair;
+
+ const accountKeysRequest = new AccountKeysRequest(
+ (await this.encryptService.encrypt(privateKey, newUnencryptedUserKey)).encryptedString!,
+ Utils.fromBufferToB64(publicKey),
+ );
+
+ const originalUserKey = await firstValueFrom(this.keyService.userKey$(user.id));
+ if (originalUserKey == null) {
+ this.logService.info("[Userkey rotation] Userkey is null. Aborting!");
+ throw new Error("Userkey key is null");
+ }
+
+ const rotatedCiphers = await this.cipherService.getRotatedData(
+ originalUserKey,
+ newUnencryptedUserKey,
+ user.id,
+ );
+ const rotatedFolders = await this.folderService.getRotatedData(
+ originalUserKey,
+ newUnencryptedUserKey,
+ user.id,
+ );
+ const rotatedSends = await this.sendService.getRotatedData(
+ originalUserKey,
+ newUnencryptedUserKey,
+ user.id,
+ );
+ if (rotatedCiphers == null || rotatedFolders == null || rotatedSends == null) {
+ this.logService.info("[Userkey rotation] ciphers, folders, or sends are null. Aborting!");
+ throw new Error("ciphers, folders, or sends are null");
+ }
+ const accountDataRequest = new UserDataRequest(rotatedCiphers, rotatedFolders, rotatedSends);
+
+ const emergencyAccessUnlockData = await this.emergencyAccessService.getRotatedData(
+ originalUserKey,
+ newUnencryptedUserKey,
+ user.id,
+ );
+ // Note: Reset password keys request model has user verification
+ // properties, but the rotation endpoint uses its own MP hash.
+ const organizationAccountRecoveryUnlockData = await this.resetPasswordService.getRotatedData(
+ originalUserKey,
+ newUnencryptedUserKey,
+ user.id,
+ );
+ if (organizationAccountRecoveryUnlockData == null) {
+ this.logService.info(
+ "[Userkey rotation] Organization account recovery data is null. Aborting!",
+ );
+ throw new Error("Organization account recovery data is null");
+ }
+
+ const passkeyUnlockData = await this.webauthnLoginAdminService.getRotatedData(
+ originalUserKey,
+ newUnencryptedUserKey,
+ user.id,
+ );
+ const unlockDataRequest = new UnlockDataRequest(
+ masterPasswordUnlockData,
+ emergencyAccessUnlockData,
+ organizationAccountRecoveryUnlockData,
+ passkeyUnlockData,
+ );
+
+ const request = new RotateUserAccountKeysRequest(
+ unlockDataRequest,
+ accountKeysRequest,
+ accountDataRequest,
+ await this.keyService.hashMasterKey(oldMasterPassword, oldMasterKey),
+ );
+
+ this.logService.info("[Userkey rotation] Posting user key rotation request to server");
+ await this.apiService.postUserKeyUpdateV2(request);
+ this.logService.info("[Userkey rotation] Userkey rotation request posted to server");
+
+ // TODO PM-2199: Add device trust rotation support to the user key rotation endpoint
+ this.logService.info("[Userkey rotation] Rotating device trust...");
+ await this.deviceTrustService.rotateDevicesTrust(
+ user.id,
+ newUnencryptedUserKey,
+ newMasterKeyAuthenticationHash,
+ );
+ this.logService.info("[Userkey rotation] Device trust rotation completed");
+ this.toastService.showToast({
+ variant: "success",
+ title: this.i18nService.t("rotationCompletedTitle"),
+ message: this.i18nService.t("rotationCompletedDesc"),
+ timeout: 15000,
+ });
+
+ // temporary until userkey can be better verified
+ await this.vaultTimeoutService.logOut();
+ }
+
+ /**
+ * Creates a new user key and re-encrypts all required data with the it.
+ * @param masterPassword current master password (used for validation)
+ * @deprecated
+ */
+ async rotateUserKeyAndEncryptedDataLegacy(masterPassword: string, user: Account): Promise {
+ this.logService.info("[Userkey rotation] Starting legacy user key rotation...");
if (!masterPassword) {
this.logService.info("[Userkey rotation] Invalid master password provided. Aborting!");
throw new Error("Invalid master password");
@@ -168,6 +344,7 @@ export class UserKeyRotationService {
this.logService.info("[Userkey rotation] Rotating device trust...");
await this.deviceTrustService.rotateDevicesTrust(user.id, newUserKey, masterPasswordHash);
this.logService.info("[Userkey rotation] Device trust rotation completed");
+ await this.vaultTimeoutService.logOut();
}
private async encryptPrivateKey(
diff --git a/apps/web/src/app/key-management/migrate-encryption/migrate-legacy-encryption.component.ts b/apps/web/src/app/key-management/migrate-encryption/migrate-legacy-encryption.component.ts
index 72ceb2afe7d..62456d96401 100644
--- a/apps/web/src/app/key-management/migrate-encryption/migrate-legacy-encryption.component.ts
+++ b/apps/web/src/app/key-management/migrate-encryption/migrate-legacy-encryption.component.ts
@@ -64,7 +64,7 @@ export class MigrateFromLegacyEncryptionComponent {
try {
await this.syncService.fullSync(false, true);
- await this.keyRotationService.rotateUserKeyAndEncryptedData(masterPassword, activeUser);
+ await this.keyRotationService.rotateUserKeyAndEncryptedDataLegacy(masterPassword, activeUser);
this.toastService.showToast({
variant: "success",
diff --git a/libs/common/src/auth/services/user-verification/user-verification.service.spec.ts b/libs/common/src/auth/services/user-verification/user-verification.service.spec.ts
index 1b6199b771e..d56dd6dda3b 100644
--- a/libs/common/src/auth/services/user-verification/user-verification.service.spec.ts
+++ b/libs/common/src/auth/services/user-verification/user-verification.service.spec.ts
@@ -224,6 +224,8 @@ describe("UserVerificationService", () => {
expect(result).toEqual({
policyOptions: null,
masterKey: "masterKey",
+ kdfConfig: "kdfConfig",
+ email: "email",
});
});
@@ -282,6 +284,8 @@ describe("UserVerificationService", () => {
expect(result).toEqual({
policyOptions: "MasterPasswordPolicyOptions",
masterKey: "masterKey",
+ kdfConfig: "kdfConfig",
+ email: "email",
});
});
diff --git a/libs/common/src/auth/services/user-verification/user-verification.service.ts b/libs/common/src/auth/services/user-verification/user-verification.service.ts
index 8f171c6064e..1ff629114ab 100644
--- a/libs/common/src/auth/services/user-verification/user-verification.service.ts
+++ b/libs/common/src/auth/services/user-verification/user-verification.service.ts
@@ -237,7 +237,7 @@ export class UserVerificationService implements UserVerificationServiceAbstracti
);
await this.masterPasswordService.setMasterKeyHash(localKeyHash, userId);
await this.masterPasswordService.setMasterKey(masterKey, userId);
- return { policyOptions, masterKey };
+ return { policyOptions, masterKey, kdfConfig, email };
}
private async verifyUserByPIN(verification: PinVerification, userId: UserId): Promise {
diff --git a/libs/common/src/auth/types/verification.ts b/libs/common/src/auth/types/verification.ts
index f3df2c3ad1e..307a584fb36 100644
--- a/libs/common/src/auth/types/verification.ts
+++ b/libs/common/src/auth/types/verification.ts
@@ -1,3 +1,5 @@
+import { KdfConfig } from "@bitwarden/key-management";
+
import { MasterKey } from "../../types/key";
import { VerificationType } from "../enums/verification-type";
import { MasterPasswordPolicyResponse } from "../models/response/master-password-policy.response";
@@ -22,5 +24,7 @@ export type ServerSideVerification = OtpVerification | MasterPasswordVerificatio
export type MasterPasswordVerificationResponse = {
masterKey: MasterKey;
+ kdfConfig: KdfConfig;
+ email: string;
policyOptions: MasterPasswordPolicyResponse | null;
};
diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts
index d35a1854653..5e52eb31840 100644
--- a/libs/common/src/enums/feature-flag.enum.ts
+++ b/libs/common/src/enums/feature-flag.enum.ts
@@ -39,6 +39,7 @@ export enum FeatureFlag {
/* Auth */
PM9112_DeviceApprovalPersistence = "pm-9112-device-approval-persistence",
+ UserKeyRotationV2 = "userkey-rotation-v2",
PM4154_BulkEncryptionService = "PM-4154-bulk-encryption-service",
UnauthenticatedExtensionUIRefresh = "unauth-ui-refresh",
CipherKeyEncryption = "cipher-key-encryption",
@@ -99,6 +100,7 @@ export const DefaultFeatureFlagValue = {
/* Auth */
[FeatureFlag.PM9112_DeviceApprovalPersistence]: FALSE,
+ [FeatureFlag.UserKeyRotationV2]: FALSE,
[FeatureFlag.PM4154_BulkEncryptionService]: FALSE,
[FeatureFlag.UnauthenticatedExtensionUIRefresh]: FALSE,
[FeatureFlag.CipherKeyEncryption]: FALSE,
diff --git a/libs/key-management/src/abstractions/key.service.ts b/libs/key-management/src/abstractions/key.service.ts
index 8cc80e38435..659dd1bbb29 100644
--- a/libs/key-management/src/abstractions/key.service.ts
+++ b/libs/key-management/src/abstractions/key.service.ts
@@ -326,6 +326,17 @@ export abstract class KeyService {
*/
abstract userPrivateKeyWithLegacySupport$(userId: UserId): Observable;
+ /**
+ * Gets an observable stream of the given users decrypted private key and public key, guaranteed to be consistent.
+ * Will emit null if the user doesn't have a userkey to decrypt the encrypted private key, or null if the user doesn't have a private key
+ * at all.
+ *
+ * @param userId The user id of the user to get the data for.
+ */
+ abstract userEncryptionKeyPair$(
+ userId: UserId,
+ ): Observable<{ privateKey: UserPrivateKey; publicKey: UserPublicKey } | null>;
+
/**
* Generates a fingerprint phrase for the user based on their public key
*
diff --git a/libs/key-management/src/key.service.spec.ts b/libs/key-management/src/key.service.spec.ts
index 8c201152997..2eab9de7487 100644
--- a/libs/key-management/src/key.service.spec.ts
+++ b/libs/key-management/src/key.service.spec.ts
@@ -1,5 +1,5 @@
import { mock } from "jest-mock-extended";
-import { bufferCount, firstValueFrom, lastValueFrom, of, take, tap } from "rxjs";
+import { BehaviorSubject, bufferCount, firstValueFrom, lastValueFrom, of, take, tap } from "rxjs";
import { PinServiceAbstraction } from "@bitwarden/auth/common";
import { EncryptedOrganizationKeyData } from "@bitwarden/common/admin-console/models/data/encrypted-organization-key.data";
@@ -802,4 +802,51 @@ describe("keyService", () => {
},
);
});
+
+ describe("userPrivateKey$", () => {
+ type SetupKeysParams = {
+ makeMasterKey: boolean;
+ makeUserKey: boolean;
+ };
+
+ function setupKeys({ makeMasterKey, makeUserKey }: SetupKeysParams): [UserKey, MasterKey] {
+ const userKeyState = stateProvider.singleUser.getFake(mockUserId, USER_KEY);
+ const fakeMasterKey = makeMasterKey ? makeSymmetricCryptoKey(64) : null;
+ masterPasswordService.masterKeySubject.next(fakeMasterKey);
+ userKeyState.nextState(null);
+ const fakeUserKey = makeUserKey ? makeSymmetricCryptoKey(64) : null;
+ userKeyState.nextState(fakeUserKey);
+ return [fakeUserKey, fakeMasterKey];
+ }
+
+ it("returns null when private key is null", async () => {
+ setupKeys({ makeMasterKey: false, makeUserKey: false });
+
+ keyService.userPrivateKey$ = jest.fn().mockReturnValue(new BehaviorSubject(null));
+ const key = await firstValueFrom(keyService.userEncryptionKeyPair$(mockUserId));
+ expect(key).toEqual(null);
+ });
+
+ it("returns null when private key is undefined", async () => {
+ setupKeys({ makeUserKey: true, makeMasterKey: false });
+
+ keyService.userPrivateKey$ = jest.fn().mockReturnValue(new BehaviorSubject(undefined));
+ const key = await firstValueFrom(keyService.userEncryptionKeyPair$(mockUserId));
+ expect(key).toEqual(null);
+ });
+
+ it("returns keys when private key is defined", async () => {
+ setupKeys({ makeUserKey: false, makeMasterKey: true });
+
+ keyService.userPrivateKey$ = jest.fn().mockReturnValue(new BehaviorSubject("private key"));
+ cryptoFunctionService.rsaExtractPublicKey.mockResolvedValue(
+ Utils.fromUtf8ToArray("public key"),
+ );
+ const key = await firstValueFrom(keyService.userEncryptionKeyPair$(mockUserId));
+ expect(key).toEqual({
+ privateKey: "private key",
+ publicKey: Utils.fromUtf8ToArray("public key"),
+ });
+ });
+ });
});
diff --git a/libs/key-management/src/key.service.ts b/libs/key-management/src/key.service.ts
index 0de6d4859d2..b18405a4200 100644
--- a/libs/key-management/src/key.service.ts
+++ b/libs/key-management/src/key.service.ts
@@ -895,6 +895,21 @@ export class DefaultKeyService implements KeyServiceAbstraction {
);
}
+ userEncryptionKeyPair$(
+ userId: UserId,
+ ): Observable<{ privateKey: UserPrivateKey; publicKey: UserPublicKey } | null> {
+ return this.userPrivateKey$(userId).pipe(
+ switchMap(async (privateKey) => {
+ if (privateKey == null) {
+ return null;
+ }
+
+ const publicKey = (await this.derivePublicKey(privateKey))!;
+ return { privateKey, publicKey };
+ }),
+ );
+ }
+
userEncryptedPrivateKey$(userId: UserId): Observable {
return this.stateProvider.getUser(userId, USER_ENCRYPTED_PRIVATE_KEY).state$;
}
From c8069baa2403a2fa8d60d530cff9a52c2be9a8cd Mon Sep 17 00:00:00 2001
From: Jonathan Prusik
Date: Mon, 24 Mar 2025 16:26:40 -0400
Subject: [PATCH 041/113] fix typo (#13948)
---
.../src/app/billing/individual/premium/premium.component.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/apps/web/src/app/billing/individual/premium/premium.component.html b/apps/web/src/app/billing/individual/premium/premium.component.html
index 6a212ef9d7c..3f0f97541df 100644
--- a/apps/web/src/app/billing/individual/premium/premium.component.html
+++ b/apps/web/src/app/billing/individual/premium/premium.component.html
@@ -55,7 +55,7 @@
Date: Tue, 25 Mar 2025 07:41:35 -0400
Subject: [PATCH 042/113] Migrated Stripe payment fields from Bootstrap to
Tailwind (#13633)
---
.../app/billing/services/stripe.service.ts | 2 +-
.../shared/payment/payment.component.html | 6 +--
apps/web/src/scss/forms.scss | 52 -------------------
apps/web/src/scss/tailwind.css | 23 ++++++++
4 files changed, 27 insertions(+), 56 deletions(-)
diff --git a/apps/web/src/app/billing/services/stripe.service.ts b/apps/web/src/app/billing/services/stripe.service.ts
index aac86107e26..5c35923c1f4 100644
--- a/apps/web/src/app/billing/services/stripe.service.ts
+++ b/apps/web/src/app/billing/services/stripe.service.ts
@@ -161,6 +161,7 @@ export class StripeService {
},
},
classes: {
+ base: "tw-stripe-form-control",
focus: "is-focused",
empty: "is-empty",
invalid: "is-invalid",
@@ -168,7 +169,6 @@ export class StripeService {
};
options.style.base.fontWeight = "500";
- options.classes.base = "v2";
// Remove the placeholder for number and CVC fields
if (["cardNumber", "cardCvc"].includes(element)) {
diff --git a/apps/web/src/app/billing/shared/payment/payment.component.html b/apps/web/src/app/billing/shared/payment/payment.component.html
index af261155171..34b2c54d8b5 100644
--- a/apps/web/src/app/billing/shared/payment/payment.component.html
+++ b/apps/web/src/app/billing/shared/payment/payment.component.html
@@ -46,7 +46,7 @@
{{ "number" | i18n }}
-
+
{{ "expiration" | i18n }}
-
+
diff --git a/apps/web/src/scss/forms.scss b/apps/web/src/scss/forms.scss
index 330bc5f6547..fd40d977911 100644
--- a/apps/web/src/scss/forms.scss
+++ b/apps/web/src/scss/forms.scss
@@ -98,58 +98,6 @@ input[type="checkbox"] {
cursor: pointer;
}
-.form-control.stripe-form-control:not(.v2) {
- padding-top: 0.55rem;
-
- &.is-focused {
- outline: 0;
- @include themify($themes) {
- background-color: themed("inputBackgroundColor");
- border-color: themed("inputBorderColor");
- box-shadow: 0 0 0 $input-focus-width
- rgba(mix(color-yiq(themed("primary")), themed("primary"), 15%), 0.5);
- color: themed("inputTextColor");
- }
-
- &.is-invalid {
- opacity: 0.75;
- @include themify($themes) {
- box-shadow: 0 0 0 $input-focus-width themed("danger");
- }
- }
- }
-
- &.is-invalid {
- @include themify($themes) {
- border-color: themed("danger");
- }
- }
-}
-
-.form-control.stripe-form-control.v2 {
- padding: 0.6875rem 0.875rem;
- border-radius: 0.5rem;
- border-color: rgb(var(--color-text-muted));
- height: unset;
- font-weight: 500;
- color: rgb(var(--color-text-main));
- background-color: rgb(var(--color-background));
-
- &:hover {
- border-color: rgb(var(--color-primary-600));
- }
-
- &.is-focused {
- outline: 0;
- border-color: rgb(var(--color-primary-600));
- }
-
- &.is-invalid {
- color: rgb(var(--color-text-main));
- border-color: rgb(var(--color-danger-600));
- }
-}
-
.dropdown-menu,
.dropdown-item {
@include themify($themes) {
diff --git a/apps/web/src/scss/tailwind.css b/apps/web/src/scss/tailwind.css
index 0ae3c291b56..d0878ac080b 100644
--- a/apps/web/src/scss/tailwind.css
+++ b/apps/web/src/scss/tailwind.css
@@ -29,6 +29,29 @@
@apply tw-text-muted !important;
}
+ /**
+ * Stripe form control styling
+ */
+ .tw-stripe-form-control {
+ @apply tw-block tw-w-full tw-h-11 tw-rounded-lg tw-bg-background tw-border tw-border-solid tw-border-secondary-500 tw-px-3 tw-py-2 tw-text-main;
+
+ @apply hover:tw-border-primary-600;
+
+ @apply focus:tw-outline-none focus:tw-border-primary-600 focus:tw-border-2 focus:tw-ring-1 focus:tw-ring-inset focus:tw-ring-primary-600;
+
+ &.is-invalid {
+ @apply tw-border-danger-600 hover:tw-border-danger-700;
+ }
+
+ &.is-focused {
+ @apply tw-outline-none tw-border-primary-600 focus:tw-border-primary-600;
+
+ &.is-invalid {
+ @apply tw-border-danger-600;
+ }
+ }
+ }
+
/**
* Loading page
*/
From 034112f42ee141a84c93033fadced3893021cd27 Mon Sep 17 00:00:00 2001
From: Alex Morask <144709477+amorask-bitwarden@users.noreply.github.com>
Date: Tue, 25 Mar 2025 08:11:32 -0400
Subject: [PATCH 043/113] Hide bank account for provider when initial tax info
is non-US (#13968)
---
.../adjust-payment-dialog.component.html | 2 +-
.../adjust-payment-dialog.component.ts | 27 ++++++++++++++++---
.../manage-tax-information.component.ts | 24 ++++++++---------
3 files changed, 36 insertions(+), 17 deletions(-)
diff --git a/apps/web/src/app/billing/shared/adjust-payment-dialog/adjust-payment-dialog.component.html b/apps/web/src/app/billing/shared/adjust-payment-dialog/adjust-payment-dialog.component.html
index 4409ab56d60..9c70908af8e 100644
--- a/apps/web/src/app/billing/shared/adjust-payment-dialog/adjust-payment-dialog.component.html
+++ b/apps/web/src/app/billing/shared/adjust-payment-dialog/adjust-payment-dialog.component.html
@@ -1,4 +1,4 @@
-
+
{
this.taxInformation = TaxInformation.from(response);
+ this.toggleBankAccount();
})
.catch(() => {
this.taxInformation = new TaxInformation();
+ })
+ .finally(() => {
+ this.loading = false;
});
} else if (this.providerId) {
this.billingApiService
.getProviderTaxInformation(this.providerId)
- .then((response) => (this.taxInformation = TaxInformation.from(response)))
+ .then((response) => {
+ this.taxInformation = TaxInformation.from(response);
+ this.toggleBankAccount();
+ })
.catch(() => {
this.taxInformation = new TaxInformation();
+ })
+ .finally(() => {
+ this.loading = false;
});
} else {
this.apiService
@@ -91,21 +103,28 @@ export class AdjustPaymentDialogComponent implements OnInit {
})
.catch(() => {
this.taxInformation = new TaxInformation();
+ })
+ .finally(() => {
+ this.loading = false;
});
}
}
taxInformationChanged(event: TaxInformation) {
this.taxInformation = event;
- if (event.country === "US") {
- this.paymentComponent.showBankAccount = !!this.organizationId;
+ this.toggleBankAccount();
+ }
+
+ toggleBankAccount = () => {
+ if (this.taxInformation.country === "US") {
+ this.paymentComponent.showBankAccount = !!this.organizationId || !!this.providerId;
} else {
this.paymentComponent.showBankAccount = false;
if (this.paymentComponent.selected === PaymentMethodType.BankAccount) {
this.paymentComponent.select(PaymentMethodType.Card);
}
}
- }
+ };
submit = async (): Promise => {
if (!this.taxInfoComponent.validate()) {
diff --git a/libs/angular/src/billing/components/manage-tax-information/manage-tax-information.component.ts b/libs/angular/src/billing/components/manage-tax-information/manage-tax-information.component.ts
index 885afb1ae67..e966b4e0a75 100644
--- a/libs/angular/src/billing/components/manage-tax-information/manage-tax-information.component.ts
+++ b/libs/angular/src/billing/components/manage-tax-information/manage-tax-information.component.ts
@@ -77,6 +77,18 @@ export class ManageTaxInformationComponent implements OnInit, OnDestroy {
}
async ngOnInit() {
+ this.formGroup.valueChanges.pipe(takeUntil(this.destroy$)).subscribe((values) => {
+ this.taxInformation = {
+ country: values.country,
+ postalCode: values.postalCode,
+ taxId: values.taxId,
+ line1: values.line1,
+ line2: values.line2,
+ city: values.city,
+ state: values.state,
+ };
+ });
+
if (this.startWith) {
this.formGroup.controls.country.setValue(this.startWith.country);
this.formGroup.controls.postalCode.setValue(this.startWith.postalCode);
@@ -95,18 +107,6 @@ export class ManageTaxInformationComponent implements OnInit, OnDestroy {
}
}
- this.formGroup.valueChanges.pipe(takeUntil(this.destroy$)).subscribe((values) => {
- this.taxInformation = {
- country: values.country,
- postalCode: values.postalCode,
- taxId: values.taxId,
- line1: values.line1,
- line2: values.line2,
- city: values.city,
- state: values.state,
- };
- });
-
this.formGroup.controls.country.valueChanges
.pipe(debounceTime(1000), takeUntil(this.destroy$))
.subscribe((country: string) => {
From 27baa92fcfee619eaca65b148ef1f5b97c9b844c Mon Sep 17 00:00:00 2001
From: Bernd Schoolmann
Date: Tue, 25 Mar 2025 13:30:54 +0100
Subject: [PATCH 044/113] [PM-10749] [BEEEP] New export format: Zip with
attachments (#10465)
* Add new export format: zip
* Restrict zip export to just individual vaults
* Add tests
* Remove unused import
* Fix build error
* Fix tests
* Fix test
* Fix retrieval of ciphers by passing in activeUserId
* Guard feature behind `export-attachments`-feature-flag
* Extend cipher filter to also filter out any ciphers that are assigned to an organization
* Added apiService to retrieve AttachmentData (metaData) and then download the attachment
- Added ApiService as a depdency within DI for VaultExportService/IndividualVaultExportService
- Added unit tests for filtering ciphers
- Added unit test for downloading attachment metadata and attachments
* Moved attachment decryption into a separate method and added unit tests
* Added null check for creating the base attachment folder
* Move format check for zip within Org export into an early return/throw
* Add feature flag guard on the CLI
* Extend ExportScopeCallout to display an individual export will contain attachment when zip-format is selected
* Fix adding/removing the zip-export option based on selected vault and state of `export-attachments` feature-flag
* Separate AAA visually using whitespace within tests
* Remove unused error var
* Write test that verifies different http request failures when retrieving attachment data
* Remove uneeded ignore lint rule
* Rewrite test to actually check that ciphers assigned to an org are filtered out
* Introduce ExportedVault return type (#13842)
* Define ExportedVault type unioned by 2 new types that describe a plain-text export vs a blob-based zip-export
* Extend static getFileName to handle formats and add unit-tests
* Introduce new export return type throughout the vault export module
- Update abstractions
- Update return types within implementations
- Update callers/consumers to handle the new return value
- Fix all unit tests
* Add support for new export return type and fix download of blobs via CLI
* Add documentation to public methods
---------
Co-authored-by: Daniel James Smith
---------
Co-authored-by: Daniel James Smith <2670567+djsmith85@users.noreply.github.com>
Co-authored-by: Daniel James Smith
---
apps/browser/src/_locales/en/messages.json | 9 +
.../browser/src/background/main.background.ts | 1 +
.../service-container/service-container.ts | 1 +
apps/cli/src/tools/export.command.ts | 48 +++--
apps/cli/src/vault.program.ts | 1 +
apps/desktop/src/locales/en/messages.json | 9 +
apps/web/src/locales/en/messages.json | 9 +
.../src/services/jslib-services.module.ts | 1 +
.../src/services/export-helper.spec.ts | 30 +++
.../src/services/export-helper.ts | 15 +-
...vidual-vault-export.service.abstraction.ts | 8 +-
.../individual-vault-export.service.spec.ts | 194 ++++++++++++++++--
.../individual-vault-export.service.ts | 133 +++++++++++-
.../org-vault-export.service.abstraction.ts | 12 +-
.../src/services/org-vault-export.service.ts | 56 ++++-
.../vault-export.service.abstraction.ts | 13 +-
.../src/services/vault-export.service.spec.ts | 59 ++++--
.../src/services/vault-export.service.ts | 31 ++-
.../src/types/exported-vault-type.ts | 13 ++
.../vault-export-core/src/types/index.ts | 1 +
.../export-scope-callout.component.ts | 43 ++--
.../src/components/export.component.html | 5 +-
.../src/components/export.component.ts | 56 ++---
23 files changed, 592 insertions(+), 156 deletions(-)
create mode 100644 libs/tools/export/vault-export/vault-export-core/src/services/export-helper.spec.ts
create mode 100644 libs/tools/export/vault-export/vault-export-core/src/types/exported-vault-type.ts
diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json
index 16f462b7f17..39541aa2f8c 100644
--- a/apps/browser/src/_locales/en/messages.json
+++ b/apps/browser/src/_locales/en/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts
index 74fa6acdf79..f37f0a3c440 100644
--- a/apps/browser/src/background/main.background.ts
+++ b/apps/browser/src/background/main.background.ts
@@ -1026,6 +1026,7 @@ export default class MainBackground {
this.cryptoFunctionService,
this.kdfConfigService,
this.accountService,
+ this.apiService,
);
this.organizationVaultExportService = new OrganizationVaultExportService(
diff --git a/apps/cli/src/service-container/service-container.ts b/apps/cli/src/service-container/service-container.ts
index b7f423e8ff7..c82a744b4a1 100644
--- a/apps/cli/src/service-container/service-container.ts
+++ b/apps/cli/src/service-container/service-container.ts
@@ -795,6 +795,7 @@ export class ServiceContainer {
this.cryptoFunctionService,
this.kdfConfigService,
this.accountService,
+ this.apiService,
);
this.organizationExportService = new OrganizationVaultExportService(
diff --git a/apps/cli/src/tools/export.command.ts b/apps/cli/src/tools/export.command.ts
index b2d9d0151a9..df6e7bae1cb 100644
--- a/apps/cli/src/tools/export.command.ts
+++ b/apps/cli/src/tools/export.command.ts
@@ -7,11 +7,15 @@ import { EventCollectionService } from "@bitwarden/common/abstractions/event/eve
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { PolicyType } from "@bitwarden/common/admin-console/enums";
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 { Utils } from "@bitwarden/common/platform/misc/utils";
import {
ExportFormat,
EXPORT_FORMATS,
VaultExportServiceAbstraction,
+ ExportedVault,
+ ExportedVaultAsBlob,
} from "@bitwarden/vault-export-core";
import { Response } from "../models/response";
@@ -22,6 +26,7 @@ export class ExportCommand {
private exportService: VaultExportServiceAbstraction,
private policyService: PolicyService,
private eventCollectionService: EventCollectionService,
+ private configService: ConfigService,
) {}
async run(options: OptionValues): Promise {
@@ -42,6 +47,13 @@ export class ExportCommand {
const format =
password && options.format == "json" ? "encrypted_json" : (options.format ?? "csv");
+ if (
+ format == "zip" &&
+ !(await this.configService.getFeatureFlag(FeatureFlag.ExportAttachments))
+ ) {
+ return Response.badRequest("Exporting attachments is not supported in this environment.");
+ }
+
if (!this.isSupportedExportFormat(format)) {
return Response.badRequest(
`'${format}' is not a supported export format. Supported formats: ${EXPORT_FORMATS.join(
@@ -54,7 +66,7 @@ export class ExportCommand {
return Response.error("`" + options.organizationid + "` is not a GUID.");
}
- let exportContent: string = null;
+ let exportContent: ExportedVault = null;
try {
if (format === "encrypted_json") {
password = await this.promptPassword(password);
@@ -78,34 +90,28 @@ export class ExportCommand {
} catch (e) {
return Response.error(e);
}
- return await this.saveFile(exportContent, options, format);
+ return await this.saveFile(exportContent, options);
}
- private async saveFile(
- exportContent: string,
- options: OptionValues,
- format: ExportFormat,
- ): Promise {
+ private async saveFile(exportContent: ExportedVault, options: OptionValues): Promise {
try {
- const fileName = this.getFileName(format, options.organizationid != null ? "org" : null);
- return await CliUtils.saveResultToFile(exportContent, options.output, fileName);
+ if (exportContent.type === "application/zip") {
+ exportContent = exportContent as ExportedVaultAsBlob;
+ const arrayBuffer = await exportContent.data.arrayBuffer();
+ const buffer = Buffer.from(arrayBuffer);
+ return await CliUtils.saveResultToFile(buffer, options.output, exportContent.fileName);
+ }
+
+ return await CliUtils.saveResultToFile(
+ exportContent.data,
+ options.output,
+ exportContent.fileName,
+ );
} catch (e) {
return Response.error(e.toString());
}
}
- private getFileName(format: ExportFormat, prefix?: string) {
- if (format === "encrypted_json") {
- if (prefix == null) {
- prefix = "encrypted";
- } else {
- prefix = "encrypted_" + prefix;
- }
- format = "json";
- }
- return this.exportService.getFileName(prefix, format);
- }
-
private async promptPassword(password: string | boolean) {
// boolean => flag set with no value, we need to prompt for password
// string => flag set with value, use this value for password
diff --git a/apps/cli/src/vault.program.ts b/apps/cli/src/vault.program.ts
index d6ef27b1428..8b9d441f0ff 100644
--- a/apps/cli/src/vault.program.ts
+++ b/apps/cli/src/vault.program.ts
@@ -501,6 +501,7 @@ export class VaultProgram extends BaseProgram {
this.serviceContainer.exportService,
this.serviceContainer.policyService,
this.serviceContainer.eventCollectionService,
+ this.serviceContainer.configService,
);
const response = await command.run(options);
this.processResponse(response);
diff --git a/apps/desktop/src/locales/en/messages.json b/apps/desktop/src/locales/en/messages.json
index f93db44aa69..07404b37fcd 100644
--- a/apps/desktop/src/locales/en/messages.json
+++ b/apps/desktop/src/locales/en/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json
index 7f9c89484c2..b9ead1df44d 100644
--- a/apps/web/src/locales/en/messages.json
+++ b/apps/web/src/locales/en/messages.json
@@ -6762,6 +6762,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.",
"placeholders": {
diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts
index 9cb39a35856..4672788eb81 100644
--- a/libs/angular/src/services/jslib-services.module.ts
+++ b/libs/angular/src/services/jslib-services.module.ts
@@ -846,6 +846,7 @@ const safeProviders: SafeProvider[] = [
CryptoFunctionServiceAbstraction,
KdfConfigService,
AccountServiceAbstraction,
+ ApiServiceAbstraction,
],
}),
safeProvider({
diff --git a/libs/tools/export/vault-export/vault-export-core/src/services/export-helper.spec.ts b/libs/tools/export/vault-export/vault-export-core/src/services/export-helper.spec.ts
new file mode 100644
index 00000000000..913f6cdf56e
--- /dev/null
+++ b/libs/tools/export/vault-export/vault-export-core/src/services/export-helper.spec.ts
@@ -0,0 +1,30 @@
+import { ExportHelper } from "./export-helper";
+
+describe("ExportHelper", () => {
+ describe("getFileName", () => {
+ it("should generate a filename with default prefix and format", () => {
+ const fileName = ExportHelper.getFileName();
+ expect(fileName).toMatch(/^bitwarden_export_\d{8}\d{6}\.csv$/);
+ });
+
+ it("should generate a filename with given prefix and default format", () => {
+ const fileName = ExportHelper.getFileName("test");
+ expect(fileName).toMatch(/^bitwarden_test_export_\d{8}\d{6}\.csv$/);
+ });
+
+ it("should generate a filename with given prefix and given format", () => {
+ const fileName = ExportHelper.getFileName("org", "json");
+ expect(fileName).toMatch(/^bitwarden_org_export_\d{8}\d{6}\.json$/);
+ });
+
+ it("should generate a filename with encrypted_json format and modify prefix", () => {
+ const fileName = ExportHelper.getFileName("org", "encrypted_json");
+ expect(fileName).toMatch(/^bitwarden_encrypted_org_export_\d{8}\d{6}\.json$/);
+ });
+
+ it("should generate a filename with encrypted_json format and default prefix", () => {
+ const fileName = ExportHelper.getFileName("", "encrypted_json");
+ expect(fileName).toMatch(/^bitwarden_encrypted_export_\d{8}\d{6}\.json$/);
+ });
+ });
+});
diff --git a/libs/tools/export/vault-export/vault-export-core/src/services/export-helper.ts b/libs/tools/export/vault-export/vault-export-core/src/services/export-helper.ts
index bb38197a4ac..fa5ff2ca0ff 100644
--- a/libs/tools/export/vault-export/vault-export-core/src/services/export-helper.ts
+++ b/libs/tools/export/vault-export/vault-export-core/src/services/export-helper.ts
@@ -1,7 +1,14 @@
-// FIXME: Update this file to be type safe and remove this and next line
-// @ts-strict-ignore
export class ExportHelper {
- static getFileName(prefix: string = null, extension = "csv"): string {
+ static getFileName(prefix: string = "", format = "csv"): string {
+ if (format === "encrypted_json") {
+ if (prefix == "") {
+ prefix = "encrypted";
+ } else {
+ prefix = "encrypted_" + prefix;
+ }
+ format = "json";
+ }
+
const now = new Date();
const dateString =
now.getFullYear() +
@@ -14,7 +21,7 @@ export class ExportHelper {
this.padNumber(now.getMinutes(), 2) +
this.padNumber(now.getSeconds(), 2);
- return "bitwarden" + (prefix ? "_" + prefix : "") + "_export_" + dateString + "." + extension;
+ return "bitwarden" + (prefix ? "_" + prefix : "") + "_export_" + dateString + "." + format;
}
private static padNumber(num: number, width: number, padCharacter = "0"): string {
diff --git a/libs/tools/export/vault-export/vault-export-core/src/services/individual-vault-export.service.abstraction.ts b/libs/tools/export/vault-export/vault-export-core/src/services/individual-vault-export.service.abstraction.ts
index b075f605fd8..a2adae8aa91 100644
--- a/libs/tools/export/vault-export/vault-export-core/src/services/individual-vault-export.service.abstraction.ts
+++ b/libs/tools/export/vault-export/vault-export-core/src/services/individual-vault-export.service.abstraction.ts
@@ -1,8 +1,8 @@
-// FIXME: Update this file to be type safe and remove this and next line
-// @ts-strict-ignore
+import { ExportedVault } from "../types";
+
import { ExportFormat } from "./vault-export.service.abstraction";
export abstract class IndividualVaultExportServiceAbstraction {
- getExport: (format: ExportFormat) => Promise;
- getPasswordProtectedExport: (password: string) => Promise;
+ abstract getExport: (format: ExportFormat) => Promise;
+ abstract getPasswordProtectedExport: (password: string) => Promise;
}
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 32f441235b9..04d32b843bf 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
@@ -1,7 +1,9 @@
import { mock, MockProxy } from "jest-mock-extended";
+import * as JSZip from "jszip";
import { BehaviorSubject, of } from "rxjs";
import { PinServiceAbstraction } from "@bitwarden/auth/common";
+import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { AccountInfo, AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
import { CipherWithIdExport } from "@bitwarden/common/models/export/cipher-with-ids.export";
@@ -12,9 +14,14 @@ import { UserId } from "@bitwarden/common/types/guid";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
import { CipherType } from "@bitwarden/common/vault/enums";
+import { AttachmentData } from "@bitwarden/common/vault/models/data/attachment.data";
+import { CipherData } from "@bitwarden/common/vault/models/data/cipher.data";
+import { Attachment } from "@bitwarden/common/vault/models/domain/attachment";
import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
import { Folder } from "@bitwarden/common/vault/models/domain/folder";
import { Login } from "@bitwarden/common/vault/models/domain/login";
+import { AttachmentResponse } from "@bitwarden/common/vault/models/response/attachment.response";
+import { AttachmentView } from "@bitwarden/common/vault/models/view/attachment.view";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { FolderView } from "@bitwarden/common/vault/models/view/folder.view";
import { LoginView } from "@bitwarden/common/vault/models/view/login.view";
@@ -27,6 +34,12 @@ import {
} from "@bitwarden/key-management";
import { BuildTestObject, GetUniqueString } from "../../../../../../common/spec";
+import {
+ BitwardenJsonExport,
+ ExportedVault,
+ ExportedVaultAsBlob,
+ ExportedVaultAsString,
+} from "../types";
import { IndividualVaultExportService } from "./individual-vault-export.service";
@@ -156,6 +169,8 @@ describe("VaultExportService", () => {
let encryptService: MockProxy;
let kdfConfigService: MockProxy;
let accountService: MockProxy;
+ let apiService: MockProxy;
+ let fetchMock: jest.Mock;
beforeEach(() => {
cryptoFunctionService = mock();
@@ -166,6 +181,7 @@ describe("VaultExportService", () => {
encryptService = mock();
kdfConfigService = mock();
accountService = mock();
+ apiService = mock();
keyService.userKey$.mockReturnValue(new BehaviorSubject("mockOriginalUserKey" as any));
@@ -178,10 +194,23 @@ describe("VaultExportService", () => {
const activeAccount = { id: userId, ...accountInfo };
accountService.activeAccount$ = new BehaviorSubject(activeAccount);
+ fetchMock = jest.fn().mockResolvedValue({});
+ global.fetch = fetchMock;
+
+ const attachmentResponse = {
+ id: GetUniqueString("id"),
+ url: "https://someurl.com",
+ fileName: "fileName",
+ key: GetUniqueString("key"),
+ size: "size",
+ sizeName: "sizeName",
+ } as AttachmentResponse;
+
folderService.folderViews$.mockReturnValue(of(UserFolderViews));
folderService.folders$.mockReturnValue(of(UserFolders));
kdfConfigService.getKdfConfig.mockResolvedValue(DEFAULT_KDF_CONFIG);
encryptService.encrypt.mockResolvedValue(new EncString("encrypted"));
+ apiService.getAttachmentData.mockResolvedValue(attachmentResponse);
exportService = new IndividualVaultExportService(
folderService,
@@ -192,6 +221,7 @@ describe("VaultExportService", () => {
cryptoFunctionService,
kdfConfigService,
accountService,
+ apiService,
);
});
@@ -199,35 +229,160 @@ describe("VaultExportService", () => {
cipherService.getAllDecrypted.mockResolvedValue(UserCipherViews.slice(0, 1));
const actual = await exportService.getExport("json");
-
- expectEqualCiphers(UserCipherViews.slice(0, 1), actual);
+ expect(typeof actual.data).toBe("string");
+ const exportedData = actual as ExportedVaultAsString;
+ expectEqualCiphers(UserCipherViews.slice(0, 1), exportedData.data);
});
it("exports encrypted json user ciphers", async () => {
cipherService.getAll.mockResolvedValue(UserCipherDomains.slice(0, 1));
const actual = await exportService.getExport("encrypted_json");
-
- expectEqualCiphers(UserCipherDomains.slice(0, 1), actual);
+ expect(typeof actual.data).toBe("string");
+ const exportedData = actual as ExportedVaultAsString;
+ expectEqualCiphers(UserCipherDomains.slice(0, 1), exportedData.data);
});
it("does not unencrypted export trashed user items", async () => {
cipherService.getAllDecrypted.mockResolvedValue(UserCipherViews);
const actual = await exportService.getExport("json");
-
- expectEqualCiphers(UserCipherViews.slice(0, 2), actual);
+ expect(typeof actual.data).toBe("string");
+ const exportedData = actual as ExportedVaultAsString;
+ expectEqualCiphers(UserCipherViews.slice(0, 2), exportedData.data);
});
it("does not encrypted export trashed user items", async () => {
cipherService.getAll.mockResolvedValue(UserCipherDomains);
const actual = await exportService.getExport("encrypted_json");
+ expect(typeof actual.data).toBe("string");
+ const exportedData = actual as ExportedVaultAsString;
+ expectEqualCiphers(UserCipherDomains.slice(0, 2), exportedData.data);
+ });
- expectEqualCiphers(UserCipherDomains.slice(0, 2), actual);
+ describe("zip export", () => {
+ it("contains data.json", async () => {
+ cipherService.getAllDecrypted.mockResolvedValue([]);
+ folderService.getAllDecryptedFromState.mockResolvedValue([]);
+
+ const exportedVault = await exportService.getExport("zip");
+
+ expect(exportedVault.type).toBe("application/zip");
+ const exportZip = exportedVault as ExportedVaultAsBlob;
+ const zip = await JSZip.loadAsync(exportZip.data);
+ const data = await zip.file("data.json")?.async("string");
+ expect(data).toBeDefined();
+ });
+
+ it("filters out ciphers that are assigned to an org", async () => {
+ // Create a cipher that is not assigned to an org
+ const cipherData = new CipherData();
+ cipherData.id = "mock-id";
+ const cipherView = new CipherView(new Cipher(cipherData));
+
+ // Create a cipher that is assigned to an org
+ const orgCipher = new CipherData();
+ orgCipher.id = "mock-from-org-id";
+ orgCipher.organizationId = "mock-org-id";
+ const orgCipherView = new CipherView(new Cipher(orgCipher));
+
+ // Mock the cipher service to return both ciphers
+ cipherService.getAllDecrypted.mockResolvedValue([cipherView, orgCipherView]);
+ folderService.getAllDecryptedFromState.mockResolvedValue([]);
+
+ const exportedVault = await exportService.getExport("zip");
+
+ const zip = await JSZip.loadAsync(exportedVault.data);
+ const data = await zip.file("data.json")?.async("string");
+ const exportData: BitwardenJsonExport = JSON.parse(data);
+ expect(exportData.items.length).toBe(1);
+ expect(exportData.items[0].id).toBe("mock-id");
+ expect(exportData.items[0].organizationId).toBe(null);
+ });
+
+ it.each([[400], [401], [404], [500]])(
+ "throws error if the http request fails (status === %n)",
+ async (status) => {
+ const cipherData = new CipherData();
+ cipherData.id = "mock-id";
+ const cipherView = new CipherView(new Cipher(cipherData));
+ const attachmentView = new AttachmentView(new Attachment(new AttachmentData()));
+ attachmentView.fileName = "mock-file-name";
+ cipherView.attachments = [attachmentView];
+
+ cipherService.getAllDecrypted.mockResolvedValue([cipherView]);
+ folderService.getAllDecryptedFromState.mockResolvedValue([]);
+ encryptService.decryptToBytes.mockResolvedValue(new Uint8Array(255));
+
+ global.fetch = jest.fn(() =>
+ Promise.resolve({
+ status,
+ }),
+ ) as any;
+ global.Request = jest.fn(() => {}) as any;
+
+ await expect(async () => {
+ await exportService.getExport("zip");
+ }).rejects.toThrow("Error downloading attachment");
+ },
+ );
+
+ it("throws error if decrypting attachment fails", async () => {
+ const cipherData = new CipherData();
+ cipherData.id = "mock-id";
+ const cipherView = new CipherView(new Cipher(cipherData));
+ const attachmentView = new AttachmentView(new Attachment(new AttachmentData()));
+ attachmentView.fileName = "mock-file-name";
+ cipherView.attachments = [attachmentView];
+
+ cipherService.getAllDecrypted.mockResolvedValue([cipherView]);
+ folderService.getAllDecryptedFromState.mockResolvedValue([]);
+ encryptService.decryptToBytes.mockResolvedValue(new Uint8Array(255));
+
+ global.fetch = jest.fn(() =>
+ Promise.resolve({
+ status: 200,
+ arrayBuffer: () => Promise.resolve(null),
+ }),
+ ) as any;
+ global.Request = jest.fn(() => {}) as any;
+
+ await expect(async () => {
+ await exportService.getExport("zip");
+ }).rejects.toThrow("Error decrypting attachment");
+ });
+
+ it("contains attachments with folders", async () => {
+ const cipherData = new CipherData();
+ cipherData.id = "mock-id";
+ const cipherView = new CipherView(new Cipher(cipherData));
+ const attachmentView = new AttachmentView(new Attachment(new AttachmentData()));
+ attachmentView.fileName = "mock-file-name";
+ cipherView.attachments = [attachmentView];
+ cipherService.getAllDecrypted.mockResolvedValue([cipherView]);
+ folderService.getAllDecryptedFromState.mockResolvedValue([]);
+ encryptService.decryptToBytes.mockResolvedValue(new Uint8Array(255));
+ global.fetch = jest.fn(() =>
+ Promise.resolve({
+ status: 200,
+ arrayBuffer: () => Promise.resolve(new ArrayBuffer(255)),
+ }),
+ ) as any;
+ global.Request = jest.fn(() => {}) as any;
+
+ const exportedVault = await exportService.getExport("zip");
+
+ expect(exportedVault.type).toBe("application/zip");
+ const exportZip = exportedVault as ExportedVaultAsBlob;
+ const zip = await JSZip.loadAsync(exportZip.data);
+ const attachment = await zip.file("attachments/mock-id/mock-file-name")?.async("blob");
+ expect(attachment).toBeDefined();
+ });
});
describe("password protected export", () => {
+ let exportedVault: ExportedVault;
let exportString: string;
let exportObject: any;
let mac: MockProxy;
@@ -246,7 +401,8 @@ describe("VaultExportService", () => {
jest.spyOn(Utils, "fromBufferToB64").mockReturnValue(salt);
cipherService.getAllDecrypted.mockResolvedValue(UserCipherViews.slice(0, 1));
- exportString = await exportService.getPasswordProtectedExport(password);
+ exportedVault = await exportService.getPasswordProtectedExport(password);
+ exportString = exportedVault.data;
exportObject = JSON.parse(exportString);
});
@@ -272,7 +428,8 @@ describe("VaultExportService", () => {
it("has a mac property", async () => {
encryptService.encrypt.mockResolvedValue(mac);
- exportString = await exportService.getPasswordProtectedExport(password);
+ exportedVault = await exportService.getPasswordProtectedExport(password);
+ exportString = exportedVault.data;
exportObject = JSON.parse(exportString);
expect(exportObject.encKeyValidation_DO_NOT_EDIT).toEqual(mac.encryptedString);
@@ -280,15 +437,18 @@ describe("VaultExportService", () => {
it("has data property", async () => {
encryptService.encrypt.mockResolvedValue(data);
- exportString = await exportService.getPasswordProtectedExport(password);
+ exportedVault = await exportService.getPasswordProtectedExport(password);
+ exportString = exportedVault.data;
exportObject = JSON.parse(exportString);
expect(exportObject.data).toEqual(data.encryptedString);
});
it("encrypts the data property", async () => {
- const unencrypted = await exportService.getExport();
- expect(exportObject.data).not.toEqual(unencrypted);
+ const unEncryptedExportVault = await exportService.getExport();
+
+ const unEncryptedExportString = unEncryptedExportVault.data;
+ expect(exportObject.data).not.toEqual(unEncryptedExportString);
});
});
});
@@ -296,17 +456,23 @@ describe("VaultExportService", () => {
it("exported unencrypted object contains folders", async () => {
cipherService.getAllDecrypted.mockResolvedValue(UserCipherViews.slice(0, 1));
folderService.folderViews$.mockReturnValue(of(UserFolderViews));
+
const actual = await exportService.getExport("json");
- expectEqualFolderViews(UserFolderViews, actual);
+ expect(typeof actual.data).toBe("string");
+ const exportedData = actual as ExportedVaultAsString;
+ expectEqualFolderViews(UserFolderViews, exportedData.data);
});
it("exported encrypted json contains folders", async () => {
cipherService.getAll.mockResolvedValue(UserCipherDomains.slice(0, 1));
folderService.folders$.mockReturnValue(of(UserFolders));
+
const actual = await exportService.getExport("encrypted_json");
- expectEqualFolders(UserFolders, actual);
+ expect(typeof actual.data).toBe("string");
+ const exportedData = actual as ExportedVaultAsString;
+ expectEqualFolders(UserFolders, exportedData.data);
});
});
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 1718bd54234..0fc1f336b90 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
@@ -1,20 +1,24 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
+import * as JSZip from "jszip";
import * as papa from "papaparse";
import { firstValueFrom } from "rxjs";
import { PinServiceAbstraction } from "@bitwarden/auth/common";
+import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
import { CipherWithIdExport, FolderWithIdExport } from "@bitwarden/common/models/export";
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
+import { EncArrayBuffer } from "@bitwarden/common/platform/models/domain/enc-array-buffer";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
import { CipherType } from "@bitwarden/common/vault/enums";
import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
import { Folder } from "@bitwarden/common/vault/models/domain/folder";
+import { AttachmentView } from "@bitwarden/common/vault/models/view/attachment.view";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { FolderView } from "@bitwarden/common/vault/models/view/folder.view";
import { KdfConfigService, KeyService } from "@bitwarden/key-management";
@@ -23,9 +27,13 @@ import {
BitwardenCsvIndividualExportType,
BitwardenEncryptedIndividualJsonExport,
BitwardenUnEncryptedIndividualJsonExport,
+ ExportedVault,
+ ExportedVaultAsBlob,
+ ExportedVaultAsString,
} from "../types";
import { BaseVaultExportService } from "./base-vault-export.service";
+import { ExportHelper } from "./export-helper";
import { IndividualVaultExportServiceAbstraction } from "./individual-vault-export.service.abstraction";
import { ExportFormat } from "./vault-export.service.abstraction";
@@ -42,23 +50,118 @@ export class IndividualVaultExportService
cryptoFunctionService: CryptoFunctionService,
kdfConfigService: KdfConfigService,
private accountService: AccountService,
+ private apiService: ApiService,
) {
super(pinService, encryptService, cryptoFunctionService, kdfConfigService);
}
- async getExport(format: ExportFormat = "csv"): Promise {
+ /** 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
+ * @param format The format of the export
+ */
+ async getExport(format: ExportFormat = "csv"): Promise {
if (format === "encrypted_json") {
return this.getEncryptedExport();
+ } else if (format === "zip") {
+ return this.getDecryptedExportZip();
}
return this.getDecryptedExport(format);
}
- async getPasswordProtectedExport(password: string): Promise {
- const clearText = await this.getExport("json");
- return this.buildPasswordExport(clearText, password);
+ /** Creates a password protected export of an individiual vault (My Vault) as a JSON file
+ * @param password The password to encrypt the export with
+ * @returns A password-protected encrypted individual vault export
+ */
+ async getPasswordProtectedExport(password: string): Promise {
+ const exportVault = await this.getExport("json");
+
+ if (exportVault.type !== "text/plain") {
+ throw new Error("Unexpected export type");
+ }
+
+ return {
+ type: "text/plain",
+ data: await this.buildPasswordExport(exportVault.data, password),
+ fileName: ExportHelper.getFileName("json"),
+ } as ExportedVaultAsString;
}
- private async getDecryptedExport(format: "json" | "csv"): Promise {
+ /** Creates a unencrypted export of an individual vault including attachments
+ * @returns A unencrypted export including attachments
+ */
+ async getDecryptedExportZip(): Promise {
+ const zip = new JSZip();
+
+ // ciphers
+ const exportedVault = await this.getDecryptedExport("json");
+ zip.file("data.json", exportedVault.data);
+
+ const attachmentsFolder = zip.folder("attachments");
+ if (attachmentsFolder == null) {
+ throw new Error("Error creating attachments folder");
+ }
+
+ const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
+
+ // attachments
+ for (const cipher of await this.cipherService.getAllDecrypted(activeUserId)) {
+ if (
+ !cipher.attachments ||
+ cipher.attachments.length === 0 ||
+ cipher.deletedDate != null ||
+ cipher.organizationId != null
+ ) {
+ continue;
+ }
+
+ const cipherFolder = attachmentsFolder.folder(cipher.id);
+ for (const attachment of cipher.attachments) {
+ const response = await this.downloadAttachment(cipher.id, attachment.id);
+ const decBuf = await this.decryptAttachment(cipher, attachment, response);
+ cipherFolder.file(attachment.fileName, decBuf);
+ }
+ }
+
+ const blobData = await zip.generateAsync({ type: "blob" });
+
+ return {
+ type: "application/zip",
+ data: blobData,
+ fileName: ExportHelper.getFileName("json"),
+ } as ExportedVaultAsBlob;
+ }
+
+ private async downloadAttachment(cipherId: string, attachmentId: string): Promise {
+ const attachmentDownloadResponse = await this.apiService.getAttachmentData(
+ cipherId,
+ attachmentId,
+ );
+ const url = attachmentDownloadResponse.url;
+
+ const response = await fetch(new Request(url, { cache: "no-store" }));
+ if (response.status !== 200) {
+ throw new Error("Error downloading attachment");
+ }
+ return response;
+ }
+
+ private async decryptAttachment(
+ cipher: CipherView,
+ attachment: AttachmentView,
+ response: Response,
+ ) {
+ try {
+ const encBuf = await EncArrayBuffer.fromResponse(response);
+ const key =
+ attachment.key != null
+ ? attachment.key
+ : await this.keyService.getOrgKey(cipher.organizationId);
+ return await this.encryptService.decryptToBytes(encBuf, key);
+ } catch {
+ throw new Error("Error decrypting attachment");
+ }
+ }
+
+ private async getDecryptedExport(format: "json" | "csv"): Promise {
let decFolders: FolderView[] = [];
let decCiphers: CipherView[] = [];
const promises = [];
@@ -79,13 +182,21 @@ export class IndividualVaultExportService
await Promise.all(promises);
if (format === "csv") {
- return this.buildCsvExport(decFolders, decCiphers);
+ return {
+ type: "text/plain",
+ data: this.buildCsvExport(decFolders, decCiphers),
+ fileName: ExportHelper.getFileName("csv"),
+ } as ExportedVaultAsString;
}
- return this.buildJsonExport(decFolders, decCiphers);
+ return {
+ type: "text/plain",
+ data: this.buildJsonExport(decFolders, decCiphers),
+ fileName: ExportHelper.getFileName("json"),
+ } as ExportedVaultAsString;
}
- private async getEncryptedExport(): Promise {
+ private async getEncryptedExport(): Promise {
let folders: Folder[] = [];
let ciphers: Cipher[] = [];
const promises = [];
@@ -136,7 +247,11 @@ export class IndividualVaultExportService
jsonDoc.items.push(cipher);
});
- return JSON.stringify(jsonDoc, null, " ");
+ return {
+ type: "text/plain",
+ data: JSON.stringify(jsonDoc, null, " "),
+ fileName: ExportHelper.getFileName("json"),
+ } as ExportedVaultAsString;
}
private buildCsvExport(decFolders: FolderView[], decCiphers: CipherView[]): string {
diff --git a/libs/tools/export/vault-export/vault-export-core/src/services/org-vault-export.service.abstraction.ts b/libs/tools/export/vault-export/vault-export-core/src/services/org-vault-export.service.abstraction.ts
index 5c5ad0c313e..002b1c2d5dc 100644
--- a/libs/tools/export/vault-export/vault-export-core/src/services/org-vault-export.service.abstraction.ts
+++ b/libs/tools/export/vault-export/vault-export-core/src/services/org-vault-export.service.abstraction.ts
@@ -1,16 +1,16 @@
-// FIXME: Update this file to be type safe and remove this and next line
-// @ts-strict-ignore
+import { ExportedVaultAsString } from "../types";
+
import { ExportFormat } from "./vault-export.service.abstraction";
export abstract class OrganizationVaultExportServiceAbstraction {
- getPasswordProtectedExport: (
+ abstract getPasswordProtectedExport: (
organizationId: string,
password: string,
onlyManagedCollections: boolean,
- ) => Promise;
- getOrganizationExport: (
+ ) => Promise;
+ abstract getOrganizationExport: (
organizationId: string,
format: ExportFormat,
onlyManagedCollections: boolean,
- ) => Promise;
+ ) => Promise;
}
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 0961347664d..f9ecd778c23 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
@@ -30,9 +30,11 @@ import {
BitwardenCsvOrgExportType,
BitwardenEncryptedOrgJsonExport,
BitwardenUnEncryptedOrgJsonExport,
+ ExportedVaultAsString,
} from "../types";
import { BaseVaultExportService } from "./base-vault-export.service";
+import { ExportHelper } from "./export-helper";
import { OrganizationVaultExportServiceAbstraction } from "./org-vault-export.service.abstraction";
import { ExportFormat } from "./vault-export.service.abstraction";
@@ -54,38 +56,70 @@ export class OrganizationVaultExportService
super(pinService, encryptService, cryptoFunctionService, kdfConfigService);
}
+ /** Creates a password protected export of an organizational vault.
+ * @param organizationId The organization id
+ * @param password The password to protect the export
+ * @param onlyManagedCollections If true only managed collections will be exported
+ * @returns The exported vault
+ */
async getPasswordProtectedExport(
organizationId: string,
password: string,
onlyManagedCollections: boolean,
- ): Promise {
- const clearText = await this.getOrganizationExport(
+ ): Promise {
+ const exportVault = await this.getOrganizationExport(
organizationId,
"json",
onlyManagedCollections,
);
- return this.buildPasswordExport(clearText, password);
+ return {
+ type: "text/plain",
+ data: await this.buildPasswordExport(exportVault.data, password),
+ fileName: ExportHelper.getFileName("org", "encrypted_json"),
+ } as ExportedVaultAsString;
}
+ /** Creates an export of an organizational vault. Based on the provided format it will either be unencrypted, encrypted
+ * @param organizationId The organization id
+ * @param format The format of the export
+ * @param onlyManagedCollections If true only managed collections will be exported
+ * @returns The exported vault
+ * @throws Error if the format is zip
+ * @throws Error if the organization id is not set
+ * @throws Error if the format is not supported
+ * @throws Error if the organization policies prevent the export
+ */
async getOrganizationExport(
organizationId: string,
format: ExportFormat = "csv",
onlyManagedCollections: boolean,
- ): Promise {
+ ): Promise {
if (Utils.isNullOrWhitespace(organizationId)) {
throw new Error("OrganizationId must be set");
}
- if (format === "encrypted_json") {
- return onlyManagedCollections
- ? this.getEncryptedManagedExport(organizationId)
- : this.getOrganizationEncryptedExport(organizationId);
+ if (format === "zip") {
+ throw new Error("Zip export not supported for organization");
}
- return onlyManagedCollections
- ? this.getDecryptedManagedExport(organizationId, format)
- : this.getOrganizationDecryptedExport(organizationId, format);
+ if (format === "encrypted_json") {
+ return {
+ type: "text/plain",
+ data: onlyManagedCollections
+ ? await this.getEncryptedManagedExport(organizationId)
+ : await this.getOrganizationEncryptedExport(organizationId),
+ fileName: ExportHelper.getFileName("org", "json"),
+ } as ExportedVaultAsString;
+ }
+
+ return {
+ type: "text/plain",
+ data: onlyManagedCollections
+ ? await this.getDecryptedManagedExport(organizationId, format)
+ : await this.getOrganizationDecryptedExport(organizationId, format),
+ fileName: ExportHelper.getFileName("org", format),
+ } as ExportedVaultAsString;
}
private async getOrganizationDecryptedExport(
diff --git a/libs/tools/export/vault-export/vault-export-core/src/services/vault-export.service.abstraction.ts b/libs/tools/export/vault-export/vault-export-core/src/services/vault-export.service.abstraction.ts
index df827fb3e60..d41f1aa0a10 100644
--- a/libs/tools/export/vault-export/vault-export-core/src/services/vault-export.service.abstraction.ts
+++ b/libs/tools/export/vault-export/vault-export-core/src/services/vault-export.service.abstraction.ts
@@ -1,15 +1,14 @@
-// FIXME: Update this file to be type safe and remove this and next line
-// @ts-strict-ignore
-export const EXPORT_FORMATS = ["csv", "json", "encrypted_json"] as const;
+import { ExportedVault } from "../types";
+
+export const EXPORT_FORMATS = ["csv", "json", "encrypted_json", "zip"] as const;
export type ExportFormat = (typeof EXPORT_FORMATS)[number];
export abstract class VaultExportServiceAbstraction {
- getExport: (format: ExportFormat, password: string) => Promise;
- getOrganizationExport: (
+ abstract getExport: (format: ExportFormat, password: string) => Promise;
+ abstract getOrganizationExport: (
organizationId: string,
format: ExportFormat,
password: string,
onlyManagedCollections?: boolean,
- ) => Promise;
- getFileName: (prefix?: string, extension?: string) => string;
+ ) => Promise;
}
diff --git a/libs/tools/export/vault-export/vault-export-core/src/services/vault-export.service.spec.ts b/libs/tools/export/vault-export/vault-export-core/src/services/vault-export.service.spec.ts
index fb44296f632..6adaa7de3ba 100644
--- a/libs/tools/export/vault-export/vault-export-core/src/services/vault-export.service.spec.ts
+++ b/libs/tools/export/vault-export/vault-export-core/src/services/vault-export.service.spec.ts
@@ -2,6 +2,7 @@ import { mock, MockProxy } from "jest-mock-extended";
import { BehaviorSubject, of } from "rxjs";
import { PinServiceAbstraction } from "@bitwarden/auth/common";
+import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { AccountInfo, AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
import { CipherWithIdExport } from "@bitwarden/common/models/export/cipher-with-ids.export";
@@ -27,6 +28,7 @@ import {
} from "@bitwarden/key-management";
import { BuildTestObject, GetUniqueString } from "../../../../../../common/spec";
+import { ExportedVault, ExportedVaultAsString } from "../types";
import { IndividualVaultExportService } from "./individual-vault-export.service";
@@ -156,6 +158,7 @@ describe("VaultExportService", () => {
let encryptService: MockProxy;
let accountService: MockProxy;
let kdfConfigService: MockProxy;
+ let apiService: MockProxy;
beforeEach(() => {
cryptoFunctionService = mock();
@@ -165,6 +168,7 @@ describe("VaultExportService", () => {
keyService = mock();
encryptService = mock();
accountService = mock();
+ apiService = mock();
kdfConfigService = mock();
@@ -191,6 +195,7 @@ describe("VaultExportService", () => {
cryptoFunctionService,
kdfConfigService,
accountService,
+ apiService,
);
});
@@ -198,35 +203,40 @@ describe("VaultExportService", () => {
cipherService.getAllDecrypted.mockResolvedValue(UserCipherViews.slice(0, 1));
const actual = await exportService.getExport("json");
-
- expectEqualCiphers(UserCipherViews.slice(0, 1), actual);
+ expect(typeof actual.data).toBe("string");
+ const exportedData = actual as ExportedVaultAsString;
+ expectEqualCiphers(UserCipherViews.slice(0, 1), exportedData.data);
});
it("exports encrypted json user ciphers", async () => {
cipherService.getAll.mockResolvedValue(UserCipherDomains.slice(0, 1));
const actual = await exportService.getExport("encrypted_json");
-
- expectEqualCiphers(UserCipherDomains.slice(0, 1), actual);
+ expect(typeof actual.data).toBe("string");
+ const exportedData = actual as ExportedVaultAsString;
+ expectEqualCiphers(UserCipherDomains.slice(0, 1), exportedData.data);
});
it("does not unencrypted export trashed user items", async () => {
cipherService.getAllDecrypted.mockResolvedValue(UserCipherViews);
const actual = await exportService.getExport("json");
-
- expectEqualCiphers(UserCipherViews.slice(0, 2), actual);
+ expect(typeof actual.data).toBe("string");
+ const exportedData = actual as ExportedVaultAsString;
+ expectEqualCiphers(UserCipherViews.slice(0, 2), exportedData.data);
});
it("does not encrypted export trashed user items", async () => {
cipherService.getAll.mockResolvedValue(UserCipherDomains);
const actual = await exportService.getExport("encrypted_json");
-
- expectEqualCiphers(UserCipherDomains.slice(0, 2), actual);
+ expect(typeof actual.data).toBe("string");
+ const exportedData = actual as ExportedVaultAsString;
+ expectEqualCiphers(UserCipherDomains.slice(0, 2), exportedData.data);
});
describe("password protected export", () => {
+ let exportedVault: ExportedVault;
let exportString: string;
let exportObject: any;
let mac: MockProxy;
@@ -245,7 +255,9 @@ describe("VaultExportService", () => {
jest.spyOn(Utils, "fromBufferToB64").mockReturnValue(salt);
cipherService.getAllDecrypted.mockResolvedValue(UserCipherViews.slice(0, 1));
- exportString = await exportService.getPasswordProtectedExport(password);
+ exportedVault = await exportService.getPasswordProtectedExport(password);
+ expect(typeof exportedVault.data).toBe("string");
+ exportString = (exportedVault as ExportedVaultAsString).data;
exportObject = JSON.parse(exportString);
});
@@ -271,23 +283,32 @@ describe("VaultExportService", () => {
it("has a mac property", async () => {
encryptService.encrypt.mockResolvedValue(mac);
- exportString = await exportService.getPasswordProtectedExport(password);
- exportObject = JSON.parse(exportString);
+ exportedVault = await exportService.getPasswordProtectedExport(password);
+
+ expect(typeof exportedVault.data).toBe("string");
+ exportString = (exportedVault as ExportedVaultAsString).data;
+ exportObject = JSON.parse(exportString);
expect(exportObject.encKeyValidation_DO_NOT_EDIT).toEqual(mac.encryptedString);
});
it("has data property", async () => {
encryptService.encrypt.mockResolvedValue(data);
- exportString = await exportService.getPasswordProtectedExport(password);
- exportObject = JSON.parse(exportString);
+ exportedVault = await exportService.getPasswordProtectedExport(password);
+
+ expect(typeof exportedVault.data).toBe("string");
+ exportString = (exportedVault as ExportedVaultAsString).data;
+ exportObject = JSON.parse(exportString);
expect(exportObject.data).toEqual(data.encryptedString);
});
it("encrypts the data property", async () => {
- const unencrypted = await exportService.getExport();
- expect(exportObject.data).not.toEqual(unencrypted);
+ const unEncryptedExportVault = await exportService.getExport();
+
+ expect(typeof unEncryptedExportVault.data).toBe("string");
+ const unEncryptedExportString = (unEncryptedExportVault as ExportedVaultAsString).data;
+ expect(exportObject.data).not.toEqual(unEncryptedExportString);
});
});
});
@@ -297,7 +318,9 @@ describe("VaultExportService", () => {
const actual = await exportService.getExport("json");
- expectEqualFolderViews(UserFolderViews, actual);
+ expect(typeof actual.data).toBe("string");
+ const exportedData = actual as ExportedVaultAsString;
+ expectEqualFolderViews(UserFolderViews, exportedData.data);
});
it("exported encrypted json contains folders", async () => {
@@ -305,7 +328,9 @@ describe("VaultExportService", () => {
const actual = await exportService.getExport("encrypted_json");
- expectEqualFolders(UserFolders, actual);
+ expect(typeof actual.data).toBe("string");
+ const exportedData = actual as ExportedVaultAsString;
+ expectEqualFolders(UserFolders, exportedData.data);
});
});
diff --git a/libs/tools/export/vault-export/vault-export-core/src/services/vault-export.service.ts b/libs/tools/export/vault-export/vault-export-core/src/services/vault-export.service.ts
index 110f34bc458..8af961e2beb 100644
--- a/libs/tools/export/vault-export/vault-export-core/src/services/vault-export.service.ts
+++ b/libs/tools/export/vault-export/vault-export-core/src/services/vault-export.service.ts
@@ -1,8 +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 { ExportHelper } from "./export-helper";
+import { ExportedVault } from "../types";
+
import { IndividualVaultExportServiceAbstraction } from "./individual-vault-export.service.abstraction";
import { OrganizationVaultExportServiceAbstraction } from "./org-vault-export.service.abstraction";
import { ExportFormat, VaultExportServiceAbstraction } from "./vault-export.service.abstraction";
@@ -13,7 +12,13 @@ export class VaultExportService implements VaultExportServiceAbstraction {
private organizationVaultExportService: OrganizationVaultExportServiceAbstraction,
) {}
- async getExport(format: ExportFormat = "csv", password: string): Promise {
+ /** Creates an export of an individual vault (My vault). Based on the provided format it will either be unencrypted, encrypted or password protected
+ * @param format The format of the export
+ * @param password An optional password if the export should be password-protected
+ * @returns The exported vault
+ * @throws Error if the format is csv and a password is provided
+ */
+ async getExport(format: ExportFormat = "csv", password: string = ""): Promise {
if (!Utils.isNullOrWhitespace(password)) {
if (format == "csv") {
throw new Error("CSV does not support password protected export");
@@ -24,12 +29,24 @@ export class VaultExportService implements VaultExportServiceAbstraction {
return this.individualVaultExportService.getExport(format);
}
+ /** Creates an export of an organizational vault. Based on the provided format it will either be unencrypted, encrypted or password protected
+ * @param organizationId The organization id
+ * @param format The format of the export
+ * @param password The password to protect the export
+ * @param onlyManagedCollections If true only managed collections will be exported
+ * @returns The exported vault
+ * @throws Error if the format is csv and a password is provided
+ * @throws Error if the format is zip and the environment does not support exporting attachments
+ * @throws Error if the format is not supported
+ * @throws Error if the organization id is not a valid guid
+ * @throws Error if the organization policies prevent the export
+ */
async getOrganizationExport(
organizationId: string,
format: ExportFormat,
password: string,
onlyManagedCollections = false,
- ): Promise {
+ ): Promise {
if (!Utils.isNullOrWhitespace(password)) {
if (format == "csv") {
throw new Error("CSV does not support password protected export");
@@ -48,8 +65,4 @@ export class VaultExportService implements VaultExportServiceAbstraction {
onlyManagedCollections,
);
}
-
- getFileName(prefix: string = null, extension = "csv"): string {
- return ExportHelper.getFileName(prefix, extension);
- }
}
diff --git a/libs/tools/export/vault-export/vault-export-core/src/types/exported-vault-type.ts b/libs/tools/export/vault-export/vault-export-core/src/types/exported-vault-type.ts
new file mode 100644
index 00000000000..b7e6d770f0d
--- /dev/null
+++ b/libs/tools/export/vault-export/vault-export-core/src/types/exported-vault-type.ts
@@ -0,0 +1,13 @@
+export type ExportedVaultAsBlob = {
+ type: "application/zip";
+ data: Blob;
+ fileName: string;
+};
+
+export type ExportedVaultAsString = {
+ type: "text/plain";
+ data: string;
+ fileName: string;
+};
+
+export type ExportedVault = ExportedVaultAsBlob | ExportedVaultAsString;
diff --git a/libs/tools/export/vault-export/vault-export-core/src/types/index.ts b/libs/tools/export/vault-export/vault-export-core/src/types/index.ts
index 172c88e95d4..9be70301934 100644
--- a/libs/tools/export/vault-export/vault-export-core/src/types/index.ts
+++ b/libs/tools/export/vault-export/vault-export-core/src/types/index.ts
@@ -1,2 +1,3 @@
export * from "./bitwarden-csv-export-type";
export * from "./bitwarden-json-export-types";
+export * from "./exported-vault-type";
diff --git a/libs/tools/export/vault-export/vault-export-ui/src/components/export-scope-callout.component.ts b/libs/tools/export/vault-export/vault-export-ui/src/components/export-scope-callout.component.ts
index 76b5a893841..cb16c759ba2 100644
--- a/libs/tools/export/vault-export/vault-export-ui/src/components/export-scope-callout.component.ts
+++ b/libs/tools/export/vault-export/vault-export-ui/src/components/export-scope-callout.component.ts
@@ -1,7 +1,7 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { CommonModule } from "@angular/common";
-import { Component, Input, OnInit } from "@angular/core";
+import { Component, effect, input } from "@angular/core";
import { firstValueFrom, map } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module";
@@ -19,7 +19,7 @@ import { CalloutModule } from "@bitwarden/components";
standalone: true,
imports: [CommonModule, JslibModule, CalloutModule],
})
-export class ExportScopeCalloutComponent implements OnInit {
+export class ExportScopeCalloutComponent {
show = false;
scopeConfig: {
title: string;
@@ -27,35 +27,23 @@ export class ExportScopeCalloutComponent implements OnInit {
scopeIdentifier: string;
};
- private _organizationId: string;
-
- get organizationId(): string {
- return this._organizationId;
- }
-
- @Input() set organizationId(value: string) {
- this._organizationId = value;
- // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
- this.getScopeMessage(this._organizationId);
- }
+ /* Optional OrganizationId, if not provided, it will display individual vault export message */
+ readonly organizationId = input();
+ /* Optional export format, determines which individual export description to display */
+ readonly exportFormat = input();
constructor(
protected organizationService: OrganizationService,
protected accountService: AccountService,
- ) {}
-
- async ngOnInit(): Promise {
- const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
- if (!(await firstValueFrom(this.organizationService.hasOrganizations(userId)))) {
- return;
- }
-
- await this.getScopeMessage(this.organizationId);
- this.show = true;
+ ) {
+ effect(async () => {
+ this.show = false;
+ await this.getScopeMessage(this.organizationId(), this.exportFormat());
+ this.show = true;
+ });
}
- private async getScopeMessage(organizationId: string) {
+ private async getScopeMessage(organizationId: string, exportFormat: string): Promise {
const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
this.scopeConfig =
organizationId != null
@@ -72,7 +60,10 @@ export class ExportScopeCalloutComponent implements OnInit {
}
: {
title: "exportingPersonalVaultTitle",
- description: "exportingIndividualVaultDescription",
+ description:
+ exportFormat == "zip"
+ ? "exportingIndividualVaultWithAttachmentsDescription"
+ : "exportingIndividualVaultDescription",
scopeIdentifier: await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => a?.email)),
),
diff --git a/libs/tools/export/vault-export/vault-export-ui/src/components/export.component.html b/libs/tools/export/vault-export/vault-export-ui/src/components/export.component.html
index 5b196f51799..d6b1bbe216a 100644
--- a/libs/tools/export/vault-export/vault-export-ui/src/components/export.component.html
+++ b/libs/tools/export/vault-export/vault-export-ui/src/components/export.component.html
@@ -5,7 +5,10 @@
>
{{ "personalVaultExportPolicyInEffect" | i18n }}
-
+
diff --git a/apps/browser/src/vault/popup/components/at-risk-carousel-dialog/at-risk-carousel-dialog.component.html b/apps/browser/src/vault/popup/components/at-risk-carousel-dialog/at-risk-carousel-dialog.component.html
index aee456a8f2b..c5e3ea8fb0d 100644
--- a/apps/browser/src/vault/popup/components/at-risk-carousel-dialog/at-risk-carousel-dialog.component.html
+++ b/apps/browser/src/vault/popup/components/at-risk-carousel-dialog/at-risk-carousel-dialog.component.html
@@ -39,7 +39,7 @@
-
+
{{ "reviewAtRiskPasswords" | i18n }}
-
+
diff --git a/apps/desktop/src/auth/components/set-pin.component.html b/apps/desktop/src/auth/components/set-pin.component.html
index cadd5340bb2..6fb5829b79a 100644
--- a/apps/desktop/src/auth/components/set-pin.component.html
+++ b/apps/desktop/src/auth/components/set-pin.component.html
@@ -25,13 +25,13 @@
{{ "lockWithMasterPassOnRestart1" | i18n }}
-
+
{{ "ok" | i18n }}
{{ "cancel" | i18n }}
-
+
diff --git a/apps/desktop/src/auth/delete-account.component.html b/apps/desktop/src/auth/delete-account.component.html
index 42c06b74891..0d5eec29bf4 100644
--- a/apps/desktop/src/auth/delete-account.component.html
+++ b/apps/desktop/src/auth/delete-account.component.html
@@ -18,13 +18,13 @@
{{ "confirmIdentity" | i18n }}
-
+
{{ "deleteAccount" | i18n }}
{{ "cancel" | i18n }}
-
+
diff --git a/apps/web/src/app/admin-console/organizations/settings/account.component.html b/apps/web/src/app/admin-console/organizations/settings/account.component.html
index ae1ecb6f3bb..8e3d63a337d 100644
--- a/apps/web/src/app/admin-console/organizations/settings/account.component.html
+++ b/apps/web/src/app/admin-console/organizations/settings/account.component.html
@@ -42,12 +42,14 @@
{{ "learnMoreAboutApi" | i18n }}
-
- {{ "viewApiKey" | i18n }}
-
-
- {{ "rotateApiKey" | i18n }}
-
+
+
+ {{ "viewApiKey" | i18n }}
+
+
+ {{ "rotateApiKey" | i18n }}
+
+
diff --git a/apps/web/src/app/auth/settings/account/change-email.component.html b/apps/web/src/app/auth/settings/account/change-email.component.html
index b097047e663..d4462c3b056 100644
--- a/apps/web/src/app/auth/settings/account/change-email.component.html
+++ b/apps/web/src/app/auth/settings/account/change-email.component.html
@@ -39,10 +39,12 @@
-
- {{ (tokenSent ? "changeEmail" : "continue") | i18n }}
-
-
- {{ "cancel" | i18n }}
-
+
+
+ {{ (tokenSent ? "changeEmail" : "continue") | i18n }}
+
+
+ {{ "cancel" | i18n }}
+
+
diff --git a/apps/web/src/app/auth/settings/emergency-access/confirm/emergency-access-confirm.component.html b/apps/web/src/app/auth/settings/emergency-access/confirm/emergency-access-confirm.component.html
index a8a4bd53d90..d3815ffcd07 100644
--- a/apps/web/src/app/auth/settings/emergency-access/confirm/emergency-access-confirm.component.html
+++ b/apps/web/src/app/auth/settings/emergency-access/confirm/emergency-access-confirm.component.html
@@ -25,13 +25,13 @@
{{ "dontAskFingerprintAgain" | i18n }}
-
+
{{ "confirm" | i18n }}
{{ "cancel" | i18n }}
-
+
diff --git a/apps/web/src/app/auth/settings/emergency-access/takeover/emergency-access-takeover.component.html b/apps/web/src/app/auth/settings/emergency-access/takeover/emergency-access-takeover.component.html
index 102f8499015..64b35344455 100644
--- a/apps/web/src/app/auth/settings/emergency-access/takeover/emergency-access-takeover.component.html
+++ b/apps/web/src/app/auth/settings/emergency-access/takeover/emergency-access-takeover.component.html
@@ -42,13 +42,13 @@
-
+
{{ "save" | i18n }}
{{ "cancel" | i18n }}
-
+
diff --git a/apps/web/src/app/auth/settings/security/api-key.component.html b/apps/web/src/app/auth/settings/security/api-key.component.html
index b9416305acc..a352f9b0dad 100644
--- a/apps/web/src/app/auth/settings/security/api-key.component.html
+++ b/apps/web/src/app/auth/settings/security/api-key.component.html
@@ -30,13 +30,13 @@
-
+
{{ (data.isRotation ? "rotateApiKey" : "viewApiKey") | i18n }}
{{ "close" | i18n }}
-
+
diff --git a/apps/web/src/app/auth/settings/security/security-keys.component.html b/apps/web/src/app/auth/settings/security/security-keys.component.html
index acfe4319c95..1f8d9dfeb00 100644
--- a/apps/web/src/app/auth/settings/security/security-keys.component.html
+++ b/apps/web/src/app/auth/settings/security/security-keys.component.html
@@ -8,11 +8,11 @@
{{ "userApiKeyDesc" | i18n }}
-
- {{ "viewApiKey" | i18n }}
-
-
- {{ "rotateApiKey" | i18n }}
-
-
-
+
+
+ {{ "viewApiKey" | i18n }}
+
+
+ {{ "rotateApiKey" | i18n }}
+
+
diff --git a/apps/web/src/app/auth/settings/security/security-keys.component.ts b/apps/web/src/app/auth/settings/security/security-keys.component.ts
index 78b48bea8b4..70e33b242b3 100644
--- a/apps/web/src/app/auth/settings/security/security-keys.component.ts
+++ b/apps/web/src/app/auth/settings/security/security-keys.component.ts
@@ -1,6 +1,6 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
-import { Component, OnInit, ViewChild, ViewContainerRef } from "@angular/core";
+import { Component, OnInit } from "@angular/core";
import { firstValueFrom, map } from "rxjs";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
@@ -15,11 +15,6 @@ import { ApiKeyComponent } from "./api-key.component";
templateUrl: "security-keys.component.html",
})
export class SecurityKeysComponent implements OnInit {
- @ViewChild("viewUserApiKeyTemplate", { read: ViewContainerRef, static: true })
- viewUserApiKeyModalRef: ViewContainerRef;
- @ViewChild("rotateUserApiKeyTemplate", { read: ViewContainerRef, static: true })
- rotateUserApiKeyModalRef: ViewContainerRef;
-
showChangeKdf = true;
constructor(
diff --git a/apps/web/src/app/tools/send/send.component.html b/apps/web/src/app/tools/send/send.component.html
index 6f690459bb0..e1d921af940 100644
--- a/apps/web/src/app/tools/send/send.component.html
+++ b/apps/web/src/app/tools/send/send.component.html
@@ -93,58 +93,60 @@
-
-
-
-
-
- {{ s.name }}
-
-
-
- {{ "disabled" | i18n }}
-
-
-
- {{ "password" | i18n }}
-
-
-
- {{ "maxAccessCountReached" | i18n }}
-
-
-
- {{ "expired" | i18n }}
-
-
-
- {{ "pendingDeletion" | i18n }}
-
+
+
+
+
+
+
+ {{ s.name }}
+
+
+
+ {{ "disabled" | i18n }}
+
+
+
+ {{ "password" | i18n }}
+
+
+
+ {{ "maxAccessCountReached" | i18n }}
+
+
+
+ {{ "expired" | i18n }}
+
+
+
+ {{ "pendingDeletion" | i18n }}
+
+
{{ s.deletionDate | date: "medium" }}
diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/shared/dialogs/bulk-confirmation-dialog.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/shared/dialogs/bulk-confirmation-dialog.component.html
index ff0c21e4525..ee4e0ec5233 100644
--- a/bitwarden_license/bit-web/src/app/secrets-manager/shared/dialogs/bulk-confirmation-dialog.component.html
+++ b/bitwarden_license/bit-web/src/app/secrets-manager/shared/dialogs/bulk-confirmation-dialog.component.html
@@ -21,7 +21,7 @@
-
+
{{ "cancel" | i18n }}
-
+
diff --git a/libs/importer/src/components/dialog/import-error-dialog.component.html b/libs/importer/src/components/dialog/import-error-dialog.component.html
index c1ad8b53932..da4cc1de421 100644
--- a/libs/importer/src/components/dialog/import-error-dialog.component.html
+++ b/libs/importer/src/components/dialog/import-error-dialog.component.html
@@ -21,9 +21,9 @@
-
+
{{ "ok" | i18n }}
-
+
diff --git a/libs/vault/src/cipher-form/components/custom-fields/add-edit-custom-field-dialog/add-edit-custom-field-dialog.component.html b/libs/vault/src/cipher-form/components/custom-fields/add-edit-custom-field-dialog/add-edit-custom-field-dialog.component.html
index 1ad42f95ec2..f379f466b4a 100644
--- a/libs/vault/src/cipher-form/components/custom-fields/add-edit-custom-field-dialog/add-edit-custom-field-dialog.component.html
+++ b/libs/vault/src/cipher-form/components/custom-fields/add-edit-custom-field-dialog/add-edit-custom-field-dialog.component.html
@@ -26,7 +26,7 @@
-
+
{{ (variant === "add" ? "add" : "save") | i18n }}
@@ -43,6 +43,6 @@
[appA11yTitle]="'deleteCustomField' | i18n: customFieldForm.value.label"
(click)="removeField()"
>
-
+
diff --git a/libs/vault/src/components/add-edit-folder-dialog/add-edit-folder-dialog.component.html b/libs/vault/src/components/add-edit-folder-dialog/add-edit-folder-dialog.component.html
index 4869714332c..cefd6305973 100644
--- a/libs/vault/src/components/add-edit-folder-dialog/add-edit-folder-dialog.component.html
+++ b/libs/vault/src/components/add-edit-folder-dialog/add-edit-folder-dialog.component.html
@@ -12,7 +12,7 @@
-
+
-
+
From abb314a0e78e326bc248c43182cf7fba503d5516 Mon Sep 17 00:00:00 2001
From: Justin Baur <19896123+justindbaur@users.noreply.github.com>
Date: Tue, 25 Mar 2025 13:32:11 -0400
Subject: [PATCH 048/113] [PM-19432] Fix Multiple WS Connections (#13985)
* Test facilitation changes
* Fix multiple connections to SignalR
---
.../internal/signalr-connection.service.ts | 46 +++++++++++++------
1 file changed, 33 insertions(+), 13 deletions(-)
diff --git a/libs/common/src/platform/notifications/internal/signalr-connection.service.ts b/libs/common/src/platform/notifications/internal/signalr-connection.service.ts
index e5d210266c0..8bea98cb506 100644
--- a/libs/common/src/platform/notifications/internal/signalr-connection.service.ts
+++ b/libs/common/src/platform/notifications/internal/signalr-connection.service.ts
@@ -23,6 +23,11 @@ export type ReceiveMessage = { type: "ReceiveMessage"; message: NotificationResp
export type SignalRNotification = Heartbeat | ReceiveMessage;
+export type TimeoutManager = {
+ setTimeout: (handler: TimerHandler, timeout: number) => number;
+ clearTimeout: (timeoutId: number) => void;
+};
+
class SignalRLogger implements ILogger {
constructor(private readonly logService: LogService) {}
@@ -51,11 +56,14 @@ export class SignalRConnectionService {
constructor(
private readonly apiService: ApiService,
private readonly logService: LogService,
+ private readonly hubConnectionBuilderFactory: () => HubConnectionBuilder = () =>
+ new HubConnectionBuilder(),
+ private readonly timeoutManager: TimeoutManager = globalThis,
) {}
connect$(userId: UserId, notificationsUrl: string) {
return new Observable((subsciber) => {
- const connection = new HubConnectionBuilder()
+ const connection = this.hubConnectionBuilderFactory()
.withUrl(notificationsUrl + "/hub", {
accessTokenFactory: () => this.apiService.getActiveBearerToken(),
skipNegotiation: true,
@@ -76,48 +84,60 @@ export class SignalRConnectionService {
let reconnectSubscription: Subscription | null = null;
// Create schedule reconnect function
- const scheduleReconnect = (): Subscription => {
+ const scheduleReconnect = () => {
if (
connection == null ||
connection.state !== HubConnectionState.Disconnected ||
(reconnectSubscription != null && !reconnectSubscription.closed)
) {
- return Subscription.EMPTY;
+ // Skip scheduling a new reconnect, either the connection isn't disconnected
+ // or an active reconnect is already scheduled.
+ return;
}
- const randomTime = this.random();
- const timeoutHandler = setTimeout(() => {
+ // If we've somehow gotten here while the subscriber is closed,
+ // we do not want to reconnect. So leave.
+ if (subsciber.closed) {
+ return;
+ }
+
+ const randomTime = this.randomReconnectTime();
+ const timeoutHandler = this.timeoutManager.setTimeout(() => {
connection
.start()
- .then(() => (reconnectSubscription = null))
+ .then(() => {
+ reconnectSubscription = null;
+ })
.catch(() => {
- reconnectSubscription = scheduleReconnect();
+ scheduleReconnect();
});
}, randomTime);
- return new Subscription(() => clearTimeout(timeoutHandler));
+ reconnectSubscription = new Subscription(() =>
+ this.timeoutManager.clearTimeout(timeoutHandler),
+ );
};
connection.onclose((error) => {
- reconnectSubscription = scheduleReconnect();
+ scheduleReconnect();
});
// Start connection
connection.start().catch(() => {
- reconnectSubscription = scheduleReconnect();
+ scheduleReconnect();
});
return () => {
+ // Cancel any possible scheduled reconnects
+ reconnectSubscription?.unsubscribe();
connection?.stop().catch((error) => {
this.logService.error("Error while stopping SignalR connection", error);
- // TODO: Does calling stop call `onclose`?
- reconnectSubscription?.unsubscribe();
});
};
});
}
- private random() {
+ private randomReconnectTime() {
return (
Math.floor(Math.random() * (MAX_RECONNECT_TIME - MIN_RECONNECT_TIME + 1)) + MIN_RECONNECT_TIME
);
From 15b2b46b85b16e3e477dc562ccac80e2a9f2bdab Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E2=9C=A8=20Audrey=20=E2=9C=A8?=
Date: Tue, 25 Mar 2025 17:08:30 -0400
Subject: [PATCH 049/113] [PM-18665] introduce metadata provider (#13744)
---
.../integration/integration-context.spec.ts | 15 +-
.../tools/integration/integration-metadata.ts | 4 +-
.../src/tools/log/disabled-semantic-logger.ts | 8 +-
.../tools/log/semantic-logger.abstraction.ts | 1 +
.../generator/core/src/data/integrations.ts | 8 +-
.../generator/core/src/integration/addy-io.ts | 3 +-
.../core/src/integration/duck-duck-go.ts | 3 +-
.../core/src/integration/fastmail.ts | 3 +-
.../core/src/integration/firefox-relay.ts | 3 +-
.../core/src/integration/forward-email.ts | 3 +-
.../core/src/integration/simple-login.ts | 3 +-
.../core/src/metadata/algorithm-metadata.ts | 16 +-
.../core/src/metadata/email/catchall.spec.ts | 6 +-
.../core/src/metadata/email/forwarder.ts | 79 +++-
.../src/metadata/email/plus-address.spec.ts | 6 +-
.../generator/core/src/metadata/index.ts | 18 +-
.../metadata/password/eff-word-list.spec.ts | 28 +-
.../metadata/password/random-password.spec.ts | 12 +-
.../metadata/username/eff-word-list.spec.ts | 6 +-
.../policies/available-algorithms-policy.ts | 32 +-
.../generator-metadata-provider.spec.ts | 438 ++++++++++++++++++
.../services/generator-metadata-provider.ts | 252 ++++++++++
.../credential-generator-configuration.ts | 4 +-
.../core/src/types/generator-type.ts | 8 +-
.../core/src/types/metadata-request.ts | 13 +
25 files changed, 910 insertions(+), 62 deletions(-)
create mode 100644 libs/tools/generator/core/src/services/generator-metadata-provider.spec.ts
create mode 100644 libs/tools/generator/core/src/services/generator-metadata-provider.ts
create mode 100644 libs/tools/generator/core/src/types/metadata-request.ts
diff --git a/libs/common/src/tools/integration/integration-context.spec.ts b/libs/common/src/tools/integration/integration-context.spec.ts
index 42581c08dee..67a40afb337 100644
--- a/libs/common/src/tools/integration/integration-context.spec.ts
+++ b/libs/common/src/tools/integration/integration-context.spec.ts
@@ -1,6 +1,7 @@
import { mock } from "jest-mock-extended";
import { I18nService } from "../../platform/abstractions/i18n.service";
+import { VendorId } from "../extension";
import { IntegrationContext } from "./integration-context";
import { IntegrationId } from "./integration-id";
@@ -8,7 +9,7 @@ import { IntegrationMetadata } from "./integration-metadata";
const EXAMPLE_META = Object.freeze({
// arbitrary
- id: "simplelogin" as IntegrationId,
+ id: "simplelogin" as IntegrationId & VendorId,
name: "Example",
// arbitrary
extends: ["forwarder"],
@@ -34,7 +35,7 @@ describe("IntegrationContext", () => {
it("throws when the baseurl isn't defined in metadata", () => {
const noBaseUrl: IntegrationMetadata = {
- id: "simplelogin" as IntegrationId, // arbitrary
+ id: "simplelogin" as IntegrationId & VendorId, // arbitrary
name: "Example",
extends: ["forwarder"], // arbitrary
selfHost: "maybe",
@@ -56,7 +57,7 @@ describe("IntegrationContext", () => {
it("ignores settings when selfhost is 'never'", () => {
const selfHostNever: IntegrationMetadata = {
- id: "simplelogin" as IntegrationId, // arbitrary
+ id: "simplelogin" as IntegrationId & VendorId, // arbitrary
name: "Example",
extends: ["forwarder"], // arbitrary
baseUrl: "example.com",
@@ -71,7 +72,7 @@ describe("IntegrationContext", () => {
it("always reads the settings when selfhost is 'always'", () => {
const selfHostAlways: IntegrationMetadata = {
- id: "simplelogin" as IntegrationId, // arbitrary
+ id: "simplelogin" as IntegrationId & VendorId, // arbitrary
name: "Example",
extends: ["forwarder"], // arbitrary
baseUrl: "example.com",
@@ -86,7 +87,7 @@ describe("IntegrationContext", () => {
it("fails when the settings are empty and selfhost is 'always'", () => {
const selfHostAlways: IntegrationMetadata = {
- id: "simplelogin" as IntegrationId, // arbitrary
+ id: "simplelogin" as IntegrationId & VendorId, // arbitrary
name: "Example",
extends: ["forwarder"], // arbitrary
baseUrl: "example.com",
@@ -101,7 +102,7 @@ describe("IntegrationContext", () => {
it("reads from the metadata by default when selfhost is 'maybe'", () => {
const selfHostMaybe: IntegrationMetadata = {
- id: "simplelogin" as IntegrationId, // arbitrary
+ id: "simplelogin" as IntegrationId & VendorId, // arbitrary
name: "Example",
extends: ["forwarder"], // arbitrary
baseUrl: "example.com",
@@ -117,7 +118,7 @@ describe("IntegrationContext", () => {
it("overrides the metadata when selfhost is 'maybe'", () => {
const selfHostMaybe: IntegrationMetadata = {
- id: "simplelogin" as IntegrationId, // arbitrary
+ id: "simplelogin" as IntegrationId & VendorId, // arbitrary
name: "Example",
extends: ["forwarder"], // arbitrary
baseUrl: "example.com",
diff --git a/libs/common/src/tools/integration/integration-metadata.ts b/libs/common/src/tools/integration/integration-metadata.ts
index e460aae828c..2073b16feb0 100644
--- a/libs/common/src/tools/integration/integration-metadata.ts
+++ b/libs/common/src/tools/integration/integration-metadata.ts
@@ -1,10 +1,12 @@
+import { VendorId } from "../extension";
+
import { ExtensionPointId } from "./extension-point-id";
import { IntegrationId } from "./integration-id";
/** The capabilities and descriptive content for an integration */
export type IntegrationMetadata = {
/** Uniquely identifies the integrator. */
- id: IntegrationId;
+ id: IntegrationId & VendorId;
/** Brand name of the integrator. */
name: string;
diff --git a/libs/common/src/tools/log/disabled-semantic-logger.ts b/libs/common/src/tools/log/disabled-semantic-logger.ts
index 054c3ed390b..21ea48bbe51 100644
--- a/libs/common/src/tools/log/disabled-semantic-logger.ts
+++ b/libs/common/src/tools/log/disabled-semantic-logger.ts
@@ -12,7 +12,11 @@ export class DisabledSemanticLogger implements SemanticLogger {
error(_content: Jsonify, _message?: string): void {}
- panic(_content: Jsonify, message?: string): never {
- throw new Error(message);
+ panic(content: Jsonify, message?: string): never {
+ if (typeof content === "string" && !message) {
+ throw new Error(content);
+ } else {
+ throw new Error(message);
+ }
}
}
diff --git a/libs/common/src/tools/log/semantic-logger.abstraction.ts b/libs/common/src/tools/log/semantic-logger.abstraction.ts
index 196d1f3f12c..51aaa917378 100644
--- a/libs/common/src/tools/log/semantic-logger.abstraction.ts
+++ b/libs/common/src/tools/log/semantic-logger.abstraction.ts
@@ -9,6 +9,7 @@ export interface SemanticLogger {
*/
debug(message: string): void;
+ // FIXME: replace Jsonify parameter with structural logging schema type
/** Logs the content at debug priority.
* Debug messages are used for diagnostics, and are typically disabled
* in production builds.
diff --git a/libs/tools/generator/core/src/data/integrations.ts b/libs/tools/generator/core/src/data/integrations.ts
index 21c883cae02..ffe4676fcd7 100644
--- a/libs/tools/generator/core/src/data/integrations.ts
+++ b/libs/tools/generator/core/src/data/integrations.ts
@@ -1,5 +1,6 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
+import { VendorId } from "@bitwarden/common/tools/extension";
import { IntegrationId } from "@bitwarden/common/tools/integration";
import { ApiSettings } from "@bitwarden/common/tools/integration/rpc";
@@ -29,8 +30,11 @@ export const Integrations = Object.freeze({
const integrations = new Map(Object.values(Integrations).map((i) => [i.id, i]));
-export function getForwarderConfiguration(id: IntegrationId): ForwarderConfiguration {
- const maybeForwarder = integrations.get(id);
+export function getForwarderConfiguration(
+ id: IntegrationId | VendorId,
+): ForwarderConfiguration {
+ // these casts are for compatibility; `IntegrationId` is the old form of `VendorId`
+ const maybeForwarder = integrations.get(id as string as IntegrationId & VendorId);
if (maybeForwarder && "forwarder" in maybeForwarder) {
return maybeForwarder as ForwarderConfiguration;
diff --git a/libs/tools/generator/core/src/integration/addy-io.ts b/libs/tools/generator/core/src/integration/addy-io.ts
index d9f2b9f121d..631c5fdb510 100644
--- a/libs/tools/generator/core/src/integration/addy-io.ts
+++ b/libs/tools/generator/core/src/integration/addy-io.ts
@@ -3,6 +3,7 @@ import {
GENERATOR_MEMORY,
UserKeyDefinition,
} from "@bitwarden/common/platform/state";
+import { VendorId } from "@bitwarden/common/tools/extension";
import { IntegrationContext, IntegrationId } from "@bitwarden/common/tools/integration";
import {
ApiSettings,
@@ -100,7 +101,7 @@ const forwarder = Object.freeze({
export const AddyIo = Object.freeze({
// integration
- id: "anonaddy" as IntegrationId,
+ id: "anonaddy" as IntegrationId & VendorId,
name: "Addy.io",
extends: ["forwarder"],
diff --git a/libs/tools/generator/core/src/integration/duck-duck-go.ts b/libs/tools/generator/core/src/integration/duck-duck-go.ts
index 0bcdd560503..d2bd6173a14 100644
--- a/libs/tools/generator/core/src/integration/duck-duck-go.ts
+++ b/libs/tools/generator/core/src/integration/duck-duck-go.ts
@@ -3,6 +3,7 @@ import {
GENERATOR_MEMORY,
UserKeyDefinition,
} from "@bitwarden/common/platform/state";
+import { VendorId } from "@bitwarden/common/tools/extension";
import { IntegrationContext, IntegrationId } from "@bitwarden/common/tools/integration";
import { ApiSettings, IntegrationRequest } from "@bitwarden/common/tools/integration/rpc";
import { PrivateClassifier } from "@bitwarden/common/tools/private-classifier";
@@ -89,7 +90,7 @@ const forwarder = Object.freeze({
// integration-wide configuration
export const DuckDuckGo = Object.freeze({
- id: "duckduckgo" as IntegrationId,
+ id: "duckduckgo" as IntegrationId & VendorId,
name: "DuckDuckGo",
baseUrl: "https://quack.duckduckgo.com/api",
selfHost: "never",
diff --git a/libs/tools/generator/core/src/integration/fastmail.ts b/libs/tools/generator/core/src/integration/fastmail.ts
index 69b908badc9..bfde1aa70f5 100644
--- a/libs/tools/generator/core/src/integration/fastmail.ts
+++ b/libs/tools/generator/core/src/integration/fastmail.ts
@@ -5,6 +5,7 @@ import {
GENERATOR_MEMORY,
UserKeyDefinition,
} from "@bitwarden/common/platform/state";
+import { VendorId } from "@bitwarden/common/tools/extension";
import { IntegrationContext, IntegrationId } from "@bitwarden/common/tools/integration";
import { ApiSettings, IntegrationRequest } from "@bitwarden/common/tools/integration/rpc";
import { PrivateClassifier } from "@bitwarden/common/tools/private-classifier";
@@ -159,7 +160,7 @@ const forwarder = Object.freeze({
// integration-wide configuration
export const Fastmail = Object.freeze({
- id: "fastmail" as IntegrationId,
+ id: "fastmail" as IntegrationId & VendorId,
name: "Fastmail",
baseUrl: "https://api.fastmail.com",
selfHost: "maybe",
diff --git a/libs/tools/generator/core/src/integration/firefox-relay.ts b/libs/tools/generator/core/src/integration/firefox-relay.ts
index ae65611905f..9f40a3631ff 100644
--- a/libs/tools/generator/core/src/integration/firefox-relay.ts
+++ b/libs/tools/generator/core/src/integration/firefox-relay.ts
@@ -3,6 +3,7 @@ import {
GENERATOR_MEMORY,
UserKeyDefinition,
} from "@bitwarden/common/platform/state";
+import { VendorId } from "@bitwarden/common/tools/extension";
import { IntegrationContext, IntegrationId } from "@bitwarden/common/tools/integration";
import { ApiSettings, IntegrationRequest } from "@bitwarden/common/tools/integration/rpc";
import { PrivateClassifier } from "@bitwarden/common/tools/private-classifier";
@@ -97,7 +98,7 @@ const forwarder = Object.freeze({
// integration-wide configuration
export const FirefoxRelay = Object.freeze({
- id: "firefoxrelay" as IntegrationId,
+ id: "firefoxrelay" as IntegrationId & VendorId,
name: "Firefox Relay",
baseUrl: "https://relay.firefox.com/api",
selfHost: "never",
diff --git a/libs/tools/generator/core/src/integration/forward-email.ts b/libs/tools/generator/core/src/integration/forward-email.ts
index d67b8d588bf..34b4602b94b 100644
--- a/libs/tools/generator/core/src/integration/forward-email.ts
+++ b/libs/tools/generator/core/src/integration/forward-email.ts
@@ -3,6 +3,7 @@ import {
GENERATOR_MEMORY,
UserKeyDefinition,
} from "@bitwarden/common/platform/state";
+import { VendorId } from "@bitwarden/common/tools/extension";
import { IntegrationContext, IntegrationId } from "@bitwarden/common/tools/integration";
import { ApiSettings, IntegrationRequest } from "@bitwarden/common/tools/integration/rpc";
import { PrivateClassifier } from "@bitwarden/common/tools/private-classifier";
@@ -101,7 +102,7 @@ const forwarder = Object.freeze({
export const ForwardEmail = Object.freeze({
// integration metadata
- id: "forwardemail" as IntegrationId,
+ id: "forwardemail" as IntegrationId & VendorId,
name: "Forward Email",
extends: ["forwarder"],
diff --git a/libs/tools/generator/core/src/integration/simple-login.ts b/libs/tools/generator/core/src/integration/simple-login.ts
index 1581f3861f5..efbac69cec2 100644
--- a/libs/tools/generator/core/src/integration/simple-login.ts
+++ b/libs/tools/generator/core/src/integration/simple-login.ts
@@ -3,6 +3,7 @@ import {
GENERATOR_MEMORY,
UserKeyDefinition,
} from "@bitwarden/common/platform/state";
+import { VendorId } from "@bitwarden/common/tools/extension";
import { IntegrationContext, IntegrationId } from "@bitwarden/common/tools/integration";
import {
ApiSettings,
@@ -103,7 +104,7 @@ const forwarder = Object.freeze({
// integration-wide configuration
export const SimpleLogin = Object.freeze({
- id: "simplelogin" as IntegrationId,
+ id: "simplelogin" as IntegrationId & VendorId,
name: "SimpleLogin",
selfHost: "maybe",
extends: ["forwarder"],
diff --git a/libs/tools/generator/core/src/metadata/algorithm-metadata.ts b/libs/tools/generator/core/src/metadata/algorithm-metadata.ts
index f776dd76e54..c07deef5535 100644
--- a/libs/tools/generator/core/src/metadata/algorithm-metadata.ts
+++ b/libs/tools/generator/core/src/metadata/algorithm-metadata.ts
@@ -1,5 +1,7 @@
import { CredentialAlgorithm, CredentialType } from "./type";
+type I18nKeyOrLiteral = string | { literal: string };
+
/** Credential generator metadata common across credential generators */
export type AlgorithmMetadata = {
/** Uniquely identifies the credential configuration
@@ -23,25 +25,25 @@ export type AlgorithmMetadata = {
/** Localization keys */
i18nKeys: {
/** descriptive name of the algorithm */
- name: string;
+ name: I18nKeyOrLiteral;
/** explanatory text for the algorithm */
- description?: string;
+ description?: I18nKeyOrLiteral;
/** labels the generate action */
- generateCredential: string;
+ generateCredential: I18nKeyOrLiteral;
/** message informing users when the generator produces a new credential */
- credentialGenerated: string;
+ credentialGenerated: I18nKeyOrLiteral;
/* labels the action that assigns a generated value to a domain object */
- useCredential: string;
+ useCredential: I18nKeyOrLiteral;
/** labels the generated output */
- credentialType: string;
+ credentialType: I18nKeyOrLiteral;
/** labels the copy output action */
- copyCredential: string;
+ copyCredential: I18nKeyOrLiteral;
};
/** fine-tunings for generator user experiences */
diff --git a/libs/tools/generator/core/src/metadata/email/catchall.spec.ts b/libs/tools/generator/core/src/metadata/email/catchall.spec.ts
index f63f141842c..d6cc1795e0b 100644
--- a/libs/tools/generator/core/src/metadata/email/catchall.spec.ts
+++ b/libs/tools/generator/core/src/metadata/email/catchall.spec.ts
@@ -19,11 +19,13 @@ describe("email - catchall generator metadata", () => {
});
describe("profiles[account]", () => {
- let accountProfile: CoreProfileMetadata = null;
+ let accountProfile: CoreProfileMetadata = null!;
beforeEach(() => {
const profile = catchall.profiles[Profile.account];
- if (isCoreProfile(profile)) {
+ if (isCoreProfile(profile!)) {
accountProfile = profile;
+ } else {
+ throw new Error("this branch should never run");
}
});
diff --git a/libs/tools/generator/core/src/metadata/email/forwarder.ts b/libs/tools/generator/core/src/metadata/email/forwarder.ts
index 1dfc219d466..f4f150f33fa 100644
--- a/libs/tools/generator/core/src/metadata/email/forwarder.ts
+++ b/libs/tools/generator/core/src/metadata/email/forwarder.ts
@@ -1,4 +1,75 @@
-// Forwarders are pending integration with the extension API
-//
-// They use the 300-block of weights and derive their metadata
-// using logic similar to `toCredentialGeneratorConfiguration`
+import { ExtensionMetadata, ExtensionStorageKey } from "@bitwarden/common/tools/extension/type";
+import { SelfHostedApiSettings } from "@bitwarden/common/tools/integration/rpc";
+import { IdentityConstraint } from "@bitwarden/common/tools/state/identity-state-constraint";
+
+import { getForwarderConfiguration } from "../../data";
+import { EmailDomainSettings, EmailPrefixSettings } from "../../engine";
+import { Forwarder } from "../../engine/forwarder";
+import { GeneratorDependencyProvider } from "../../types";
+import { Profile, Type } from "../data";
+import { GeneratorMetadata } from "../generator-metadata";
+import { ForwarderProfileMetadata } from "../profile-metadata";
+
+// These options are used by all forwarders; each forwarder uses a different set,
+// as defined by `GeneratorMetadata.capabilities.fields`.
+type ForwarderOptions = Partial;
+
+// update the extension metadata
+export function toForwarderMetadata(
+ extension: ExtensionMetadata,
+): GeneratorMetadata {
+ if (extension.site.id !== "forwarder") {
+ throw new Error(
+ `expected forwarder extension; received ${extension.site.id} (${extension.product.vendor.id})`,
+ );
+ }
+
+ const name = { literal: extension.product.name ?? extension.product.vendor.name };
+
+ const generator: GeneratorMetadata = {
+ id: { forwarder: extension.product.vendor.id },
+ category: Type.email,
+ weight: 300,
+ i18nKeys: {
+ name,
+ description: "forwardedEmailDesc",
+ generateCredential: "generateEmail",
+ credentialGenerated: "emailGenerated",
+ useCredential: "useThisEmail",
+ credentialType: "email",
+ copyCredential: "copyEmail",
+ },
+ capabilities: {
+ autogenerate: false,
+ fields: [...extension.requestedFields],
+ },
+ engine: {
+ create(dependencies: GeneratorDependencyProvider) {
+ const config = getForwarderConfiguration(extension.product.vendor.id);
+ return new Forwarder(config, dependencies.client, dependencies.i18nService);
+ },
+ },
+ profiles: {
+ [Profile.account]: {
+ type: "extension",
+ site: "forwarder",
+ storage: {
+ key: "forwarder",
+ frame: 512,
+ options: {
+ deserializer: (value) => value,
+ clearOn: ["logout"],
+ },
+ } satisfies ExtensionStorageKey,
+ constraints: {
+ default: {},
+ create() {
+ return new IdentityConstraint();
+ },
+ },
+ } satisfies ForwarderProfileMetadata,
+ },
+ };
+
+ return generator;
+}
diff --git a/libs/tools/generator/core/src/metadata/email/plus-address.spec.ts b/libs/tools/generator/core/src/metadata/email/plus-address.spec.ts
index 2ac7645ed30..063cb71c23a 100644
--- a/libs/tools/generator/core/src/metadata/email/plus-address.spec.ts
+++ b/libs/tools/generator/core/src/metadata/email/plus-address.spec.ts
@@ -19,11 +19,13 @@ describe("email - plus address generator metadata", () => {
});
describe("profiles[account]", () => {
- let accountProfile: CoreProfileMetadata = null;
+ let accountProfile: CoreProfileMetadata = null!;
beforeEach(() => {
const profile = plusAddress.profiles[Profile.account];
- if (isCoreProfile(profile)) {
+ if (isCoreProfile(profile!)) {
accountProfile = profile;
+ } else {
+ throw new Error("this branch should never run");
}
});
diff --git a/libs/tools/generator/core/src/metadata/index.ts b/libs/tools/generator/core/src/metadata/index.ts
index 79806fd1bcc..d9437822270 100644
--- a/libs/tools/generator/core/src/metadata/index.ts
+++ b/libs/tools/generator/core/src/metadata/index.ts
@@ -1,12 +1,24 @@
-import { AlgorithmsByType as ABT } from "./data";
+import {
+ Algorithm as AlgorithmData,
+ AlgorithmsByType as AlgorithmsByTypeData,
+ Type as TypeData,
+} from "./data";
import { CredentialType, CredentialAlgorithm } from "./type";
// `CredentialAlgorithm` is defined in terms of `ABT`; supplying
// type information in the barrel file breaks a circular dependency.
/** Credential generation algorithms grouped by purpose. */
-export const AlgorithmsByType: Record> = ABT;
+export const AlgorithmsByType: Record<
+ CredentialType,
+ ReadonlyArray
+> = AlgorithmsByTypeData;
+export const Algorithms: ReadonlyArray = Object.freeze(
+ Object.values(AlgorithmData),
+);
+export const Types: ReadonlyArray = Object.freeze(Object.values(TypeData));
-export { Profile, Type } from "./data";
+export { Profile, Type, Algorithm } from "./data";
+export { toForwarderMetadata } from "./email/forwarder";
export { GeneratorMetadata } from "./generator-metadata";
export { ProfileContext, CoreProfileMetadata, ProfileMetadata } from "./profile-metadata";
export { GeneratorProfile, CredentialAlgorithm, CredentialType } from "./type";
diff --git a/libs/tools/generator/core/src/metadata/password/eff-word-list.spec.ts b/libs/tools/generator/core/src/metadata/password/eff-word-list.spec.ts
index 57961a60033..e02d63d3d59 100644
--- a/libs/tools/generator/core/src/metadata/password/eff-word-list.spec.ts
+++ b/libs/tools/generator/core/src/metadata/password/eff-word-list.spec.ts
@@ -22,19 +22,21 @@ describe("password - eff words generator metadata", () => {
});
describe("profiles[account]", () => {
- let accountProfile: CoreProfileMetadata = null;
+ let accountProfile: CoreProfileMetadata | null = null;
beforeEach(() => {
const profile = effPassphrase.profiles[Profile.account];
- if (isCoreProfile(profile)) {
+ if (isCoreProfile(profile!)) {
accountProfile = profile;
+ } else {
+ accountProfile = null;
}
});
describe("storage.options.deserializer", () => {
it("returns its input", () => {
- const value: PassphraseGenerationOptions = { ...accountProfile.storage.initial };
+ const value: PassphraseGenerationOptions = { ...accountProfile!.storage.initial };
- const result = accountProfile.storage.options.deserializer(value);
+ const result = accountProfile!.storage.options.deserializer(value);
expect(result).toBe(value);
});
@@ -46,15 +48,15 @@ describe("password - eff words generator metadata", () => {
// enclosed behaviors change.
it("creates a passphrase policy constraints", () => {
- const context = { defaultConstraints: accountProfile.constraints.default };
+ const context = { defaultConstraints: accountProfile!.constraints.default };
- const constraints = accountProfile.constraints.create([], context);
+ const constraints = accountProfile!.constraints.create([], context);
expect(constraints).toBeInstanceOf(PassphrasePolicyConstraints);
});
it("forwards the policy to the constraints", () => {
- const context = { defaultConstraints: accountProfile.constraints.default };
+ const context = { defaultConstraints: accountProfile!.constraints.default };
const policies = [
{
type: PolicyType.PasswordGenerator,
@@ -66,13 +68,13 @@ describe("password - eff words generator metadata", () => {
},
] as Policy[];
- const constraints = accountProfile.constraints.create(policies, context);
+ const constraints = accountProfile!.constraints.create(policies, context);
- expect(constraints.constraints.numWords.min).toEqual(6);
+ expect(constraints.constraints.numWords?.min).toEqual(6);
});
it("combines multiple policies in the constraints", () => {
- const context = { defaultConstraints: accountProfile.constraints.default };
+ const context = { defaultConstraints: accountProfile!.constraints.default };
const policies = [
{
type: PolicyType.PasswordGenerator,
@@ -92,10 +94,10 @@ describe("password - eff words generator metadata", () => {
},
] as Policy[];
- const constraints = accountProfile.constraints.create(policies, context);
+ const constraints = accountProfile!.constraints.create(policies, context);
- expect(constraints.constraints.numWords.min).toEqual(6);
- expect(constraints.constraints.capitalize.requiredValue).toEqual(true);
+ expect(constraints.constraints.numWords?.min).toEqual(6);
+ expect(constraints.constraints.capitalize?.requiredValue).toEqual(true);
});
});
});
diff --git a/libs/tools/generator/core/src/metadata/password/random-password.spec.ts b/libs/tools/generator/core/src/metadata/password/random-password.spec.ts
index d91ceaac248..9e38c50ee2a 100644
--- a/libs/tools/generator/core/src/metadata/password/random-password.spec.ts
+++ b/libs/tools/generator/core/src/metadata/password/random-password.spec.ts
@@ -22,11 +22,13 @@ describe("password - characters generator metadata", () => {
});
describe("profiles[account]", () => {
- let accountProfile: CoreProfileMetadata = null;
+ let accountProfile: CoreProfileMetadata = null!;
beforeEach(() => {
const profile = password.profiles[Profile.account];
- if (isCoreProfile(profile)) {
+ if (isCoreProfile(profile!)) {
accountProfile = profile;
+ } else {
+ throw new Error("this branch should never run");
}
});
@@ -69,7 +71,7 @@ describe("password - characters generator metadata", () => {
const constraints = accountProfile.constraints.create(policies, context);
- expect(constraints.constraints.length.min).toEqual(10);
+ expect(constraints.constraints.length?.min).toEqual(10);
});
it("combines multiple policies in the constraints", () => {
@@ -97,8 +99,8 @@ describe("password - characters generator metadata", () => {
const constraints = accountProfile.constraints.create(policies, context);
- expect(constraints.constraints.length.min).toEqual(14);
- expect(constraints.constraints.special.requiredValue).toEqual(true);
+ expect(constraints.constraints.length?.min).toEqual(14);
+ expect(constraints.constraints.special?.requiredValue).toEqual(true);
});
});
});
diff --git a/libs/tools/generator/core/src/metadata/username/eff-word-list.spec.ts b/libs/tools/generator/core/src/metadata/username/eff-word-list.spec.ts
index aba9680a448..d47d5ec9fcb 100644
--- a/libs/tools/generator/core/src/metadata/username/eff-word-list.spec.ts
+++ b/libs/tools/generator/core/src/metadata/username/eff-word-list.spec.ts
@@ -20,11 +20,13 @@ describe("username - eff words generator metadata", () => {
});
describe("profiles[account]", () => {
- let accountProfile: CoreProfileMetadata = null;
+ let accountProfile: CoreProfileMetadata = null!;
beforeEach(() => {
const profile = effWordList.profiles[Profile.account];
- if (isCoreProfile(profile)) {
+ if (isCoreProfile(profile!)) {
accountProfile = profile;
+ } else {
+ throw new Error("this branch should never run");
}
});
diff --git a/libs/tools/generator/core/src/policies/available-algorithms-policy.ts b/libs/tools/generator/core/src/policies/available-algorithms-policy.ts
index f37a8b21a3f..0c44a1a0408 100644
--- a/libs/tools/generator/core/src/policies/available-algorithms-policy.ts
+++ b/libs/tools/generator/core/src/policies/available-algorithms-policy.ts
@@ -5,13 +5,41 @@ import { PolicyType } from "@bitwarden/common/admin-console/enums";
// implement ADR-0002
import { Policy } from "@bitwarden/common/admin-console/models/domain/policy";
-import { CredentialAlgorithm, EmailAlgorithms, PasswordAlgorithms, UsernameAlgorithms } from "..";
+import {
+ CredentialAlgorithm as LegacyAlgorithm,
+ EmailAlgorithms,
+ PasswordAlgorithms,
+ UsernameAlgorithms,
+} from "..";
+import { CredentialAlgorithm } from "../metadata";
/** Reduces policies to a set of available algorithms
* @param policies the policies to reduce
* @returns the resulting `AlgorithmAvailabilityPolicy`
*/
-export function availableAlgorithms(policies: Policy[]): CredentialAlgorithm[] {
+export function availableAlgorithms(policies: Policy[]): LegacyAlgorithm[] {
+ const overridePassword = policies
+ .filter((policy) => policy.type === PolicyType.PasswordGenerator && policy.enabled)
+ .reduce(
+ (type, policy) => (type === "password" ? type : (policy.data.overridePasswordType ?? type)),
+ null as LegacyAlgorithm,
+ );
+
+ const policy: LegacyAlgorithm[] = [...EmailAlgorithms, ...UsernameAlgorithms];
+ if (overridePassword) {
+ policy.push(overridePassword);
+ } else {
+ policy.push(...PasswordAlgorithms);
+ }
+
+ return policy;
+}
+
+/** Reduces policies to a set of available algorithms
+ * @param policies the policies to reduce
+ * @returns the resulting `AlgorithmAvailabilityPolicy`
+ */
+export function availableAlgorithms_vNext(policies: Policy[]): CredentialAlgorithm[] {
const overridePassword = policies
.filter((policy) => policy.type === PolicyType.PasswordGenerator && policy.enabled)
.reduce(
diff --git a/libs/tools/generator/core/src/services/generator-metadata-provider.spec.ts b/libs/tools/generator/core/src/services/generator-metadata-provider.spec.ts
new file mode 100644
index 00000000000..958e5608449
--- /dev/null
+++ b/libs/tools/generator/core/src/services/generator-metadata-provider.spec.ts
@@ -0,0 +1,438 @@
+import { mock } from "jest-mock-extended";
+import { BehaviorSubject, ReplaySubject, firstValueFrom } from "rxjs";
+
+import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
+import { PolicyType } from "@bitwarden/common/admin-console/enums";
+import { Policy } from "@bitwarden/common/admin-console/models/domain/policy";
+import { Account } from "@bitwarden/common/auth/abstractions/account.service";
+import { LegacyEncryptorProvider } from "@bitwarden/common/tools/cryptography/legacy-encryptor-provider";
+import { UserEncryptor } from "@bitwarden/common/tools/cryptography/user-encryptor.abstraction";
+import {
+ ExtensionMetadata,
+ ExtensionSite,
+ Site,
+ SiteId,
+ SiteMetadata,
+} from "@bitwarden/common/tools/extension";
+import { ExtensionService } from "@bitwarden/common/tools/extension/extension.service";
+import { Bitwarden } from "@bitwarden/common/tools/extension/vendor/bitwarden";
+import { disabledSemanticLoggerProvider } from "@bitwarden/common/tools/log";
+import { SystemServiceProvider } from "@bitwarden/common/tools/providers";
+import { UserStateSubject } from "@bitwarden/common/tools/state/user-state-subject";
+import { UserStateSubjectDependencyProvider } from "@bitwarden/common/tools/state/user-state-subject-dependency-provider";
+import { deepFreeze } from "@bitwarden/common/tools/util";
+import { UserId } from "@bitwarden/common/types/guid";
+
+import { FakeAccountService, FakeStateProvider } from "../../../../../common/spec";
+import { Algorithm, AlgorithmsByType, CredentialAlgorithm, Type, Types } from "../metadata";
+import catchall from "../metadata/email/catchall";
+import plusAddress from "../metadata/email/plus-address";
+import passphrase from "../metadata/password/eff-word-list";
+import password from "../metadata/password/random-password";
+import effWordList from "../metadata/username/eff-word-list";
+import { CredentialPreference } from "../types";
+
+import { PREFERENCES } from "./credential-preferences";
+import { GeneratorMetadataProvider } from "./generator-metadata-provider";
+
+const SomeUser = "some user" as UserId;
+const SomeAccount = {
+ id: SomeUser,
+ email: "someone@example.com",
+ emailVerified: true,
+ name: "Someone",
+};
+const SomeAccount$ = new BehaviorSubject(SomeAccount);
+
+const SomeEncryptor: UserEncryptor = {
+ userId: SomeUser,
+
+ encrypt(secret) {
+ const tmp: any = secret;
+ return Promise.resolve({ foo: `encrypt(${tmp.foo})` } as any);
+ },
+
+ decrypt(secret) {
+ const tmp: any = JSON.parse(secret.encryptedString!);
+ return Promise.resolve({ foo: `decrypt(${tmp.foo})` } as any);
+ },
+};
+
+const SomeAccountService = new FakeAccountService({
+ [SomeUser]: SomeAccount,
+});
+
+const SomeStateProvider = new FakeStateProvider(SomeAccountService);
+
+const SystemProvider = {
+ encryptor: {
+ userEncryptor$: () => {
+ return new BehaviorSubject({ encryptor: SomeEncryptor, userId: SomeUser }).asObservable();
+ },
+ organizationEncryptor$() {
+ throw new Error("`organizationEncryptor$` should never be invoked.");
+ },
+ } as LegacyEncryptorProvider,
+ state: SomeStateProvider,
+ log: disabledSemanticLoggerProvider,
+} as UserStateSubjectDependencyProvider;
+
+const SomeSiteId: SiteId = Site.forwarder;
+
+const SomeSite: SiteMetadata = Object.freeze({
+ id: SomeSiteId,
+ availableFields: [],
+});
+
+const SomePolicyService = mock();
+
+const SomeExtensionService = mock();
+
+const ApplicationProvider = {
+ /** Policy configured by the administrative console */
+ policy: SomePolicyService,
+
+ /** Client extension metadata and profile access */
+ extension: SomeExtensionService,
+
+ /** Event monitoring and diagnostic interfaces */
+ log: disabledSemanticLoggerProvider,
+} as SystemServiceProvider;
+
+describe("GeneratorMetadataProvider", () => {
+ beforeEach(() => {
+ jest.resetAllMocks();
+ SomeExtensionService.site.mockImplementation(() => new ExtensionSite(SomeSite, new Map()));
+ });
+
+ describe("constructor", () => {
+ it("throws when the forwarder site isn't defined by the extension service", () => {
+ SomeExtensionService.site.mockReturnValue(undefined);
+ expect(() => new GeneratorMetadataProvider(SystemProvider, ApplicationProvider, [])).toThrow(
+ "forwarder extension site not found",
+ );
+ });
+ });
+
+ describe("metadata", () => {
+ it("returns algorithm metadata", async () => {
+ const provider = new GeneratorMetadataProvider(SystemProvider, ApplicationProvider, [
+ password,
+ ]);
+
+ const metadata = provider.metadata(password.id);
+
+ expect(metadata).toEqual(password);
+ });
+
+ it("returns forwarder metadata", async () => {
+ const extensionMetadata: ExtensionMetadata = {
+ site: SomeSite,
+ product: { vendor: Bitwarden },
+ host: { authentication: true, selfHost: "maybe", baseUrl: "https://www.example.com" },
+ requestedFields: [],
+ };
+ const application = {
+ ...ApplicationProvider,
+ extension: mock({
+ site: () => new ExtensionSite(SomeSite, new Map([[Bitwarden.id, extensionMetadata]])),
+ }),
+ };
+ const provider = new GeneratorMetadataProvider(SystemProvider, application, []);
+
+ const metadata = provider.metadata({ forwarder: Bitwarden.id });
+
+ expect(metadata.id).toEqual({ forwarder: Bitwarden.id });
+ });
+
+ it("panics when metadata not found", async () => {
+ const provider = new GeneratorMetadataProvider(SystemProvider, ApplicationProvider, []);
+
+ expect(() => provider.metadata("not found" as any)).toThrow("metadata not found");
+ });
+
+ it("panics when an extension not found", async () => {
+ const provider = new GeneratorMetadataProvider(SystemProvider, ApplicationProvider, []);
+
+ expect(() => provider.metadata({ forwarder: "not found" as any })).toThrow(
+ "extension not found",
+ );
+ });
+ });
+
+ describe("types", () => {
+ it("returns the credential types", async () => {
+ const provider = new GeneratorMetadataProvider(SystemProvider, ApplicationProvider, []);
+
+ const result = provider.types();
+
+ expect(result).toEqual(expect.arrayContaining(Types));
+ });
+ });
+
+ describe("algorithms", () => {
+ it("returns the password category's algorithms", () => {
+ const provider = new GeneratorMetadataProvider(SystemProvider, ApplicationProvider, []);
+
+ const result = provider.algorithms({ type: Type.password });
+
+ expect(result).toEqual(expect.arrayContaining(AlgorithmsByType[Type.password]));
+ });
+
+ it("returns the username category's algorithms", () => {
+ const provider = new GeneratorMetadataProvider(SystemProvider, ApplicationProvider, []);
+
+ const result = provider.algorithms({ type: Type.username });
+
+ expect(result).toEqual(expect.arrayContaining(AlgorithmsByType[Type.username]));
+ });
+
+ it("returns the email category's algorithms", () => {
+ const provider = new GeneratorMetadataProvider(SystemProvider, ApplicationProvider, []);
+
+ const result = provider.algorithms({ type: Type.email });
+
+ expect(result).toEqual(expect.arrayContaining(AlgorithmsByType[Type.email]));
+ });
+
+ it("includes forwarder vendors in the email category's algorithms", () => {
+ const extensionMetadata: ExtensionMetadata = {
+ site: SomeSite,
+ product: { vendor: Bitwarden },
+ host: { authentication: true, selfHost: "maybe", baseUrl: "https://www.example.com" },
+ requestedFields: [],
+ };
+ const application = {
+ ...ApplicationProvider,
+ extension: mock({
+ site: () => new ExtensionSite(SomeSite, new Map([[Bitwarden.id, extensionMetadata]])),
+ }),
+ };
+ const provider = new GeneratorMetadataProvider(SystemProvider, application, []);
+
+ const result = provider.algorithms({ type: Type.email });
+
+ expect(result).toEqual(expect.arrayContaining([{ forwarder: Bitwarden.id }]));
+ });
+
+ it.each([
+ [Algorithm.catchall],
+ [Algorithm.passphrase],
+ [Algorithm.password],
+ [Algorithm.plusAddress],
+ [Algorithm.username],
+ ])("returns explicit algorithms (=%p)", (algorithm) => {
+ const provider = new GeneratorMetadataProvider(SystemProvider, ApplicationProvider, []);
+
+ const result = provider.algorithms({ algorithm });
+
+ expect(result).toEqual([algorithm]);
+ });
+
+ it("returns explicit forwarders", () => {
+ const extensionMetadata: ExtensionMetadata = {
+ site: SomeSite,
+ product: { vendor: Bitwarden },
+ host: { authentication: true, selfHost: "maybe", baseUrl: "https://www.example.com" },
+ requestedFields: [],
+ };
+ const application = {
+ ...ApplicationProvider,
+ extension: mock({
+ site: () => new ExtensionSite(SomeSite, new Map([[Bitwarden.id, extensionMetadata]])),
+ }),
+ };
+ const provider = new GeneratorMetadataProvider(SystemProvider, application, []);
+
+ const result = provider.algorithms({ algorithm: { forwarder: Bitwarden.id } });
+
+ expect(result).toEqual(expect.arrayContaining([{ forwarder: Bitwarden.id }]));
+ });
+
+ it("returns an empty array when the algorithm is invalid", () => {
+ const provider = new GeneratorMetadataProvider(SystemProvider, ApplicationProvider, []);
+
+ // `any` cast required because this test subverts the type system
+ const result = provider.algorithms({ algorithm: "an invalid algorithm" as any });
+
+ expect(result).toEqual([]);
+ });
+
+ it("returns an empty array when the forwarder is invalid", () => {
+ const extensionMetadata: ExtensionMetadata = {
+ site: SomeSite,
+ product: { vendor: Bitwarden },
+ host: { authentication: true, selfHost: "maybe", baseUrl: "https://www.example.com" },
+ requestedFields: [],
+ };
+ const application = {
+ ...ApplicationProvider,
+ extension: mock({
+ site: () => new ExtensionSite(SomeSite, new Map([[Bitwarden.id, extensionMetadata]])),
+ }),
+ };
+ const provider = new GeneratorMetadataProvider(SystemProvider, application, []);
+
+ // `any` cast required because this test subverts the type system
+ const result = provider.algorithms({
+ algorithm: { forwarder: "an invalid forwarder" as any },
+ });
+
+ expect(result).toEqual([]);
+ });
+
+ it("panics when neither an algorithm nor a category is specified", () => {
+ const provider = new GeneratorMetadataProvider(SystemProvider, ApplicationProvider, []);
+
+ // `any` cast required because this test subverts the type system
+ expect(() => provider.algorithms({} as any)).toThrow("algorithm or type required");
+ });
+ });
+
+ describe("algorithms$", () => {
+ it.each([
+ [Algorithm.catchall, catchall],
+ [Algorithm.username, effWordList],
+ [Algorithm.password, password],
+ ])("gets a specific algorithm", async (algorithm, metadata) => {
+ SomePolicyService.getAll$.mockReturnValue(new BehaviorSubject([]));
+ const provider = new GeneratorMetadataProvider(SystemProvider, ApplicationProvider, [
+ metadata,
+ ]);
+ const result = new ReplaySubject(1);
+
+ provider.algorithms$({ algorithm }, { account$: SomeAccount$ }).subscribe(result);
+
+ await expect(firstValueFrom(result)).resolves.toEqual([algorithm]);
+ });
+
+ it.each([
+ [Type.email, [catchall, plusAddress]],
+ [Type.username, [effWordList]],
+ [Type.password, [password, passphrase]],
+ ])("gets a category of algorithms", async (category, metadata) => {
+ SomePolicyService.getAll$.mockReturnValue(new BehaviorSubject([]));
+ const provider = new GeneratorMetadataProvider(SystemProvider, ApplicationProvider, metadata);
+ const result = new ReplaySubject(1);
+
+ provider.algorithms$({ type: category }, { account$: SomeAccount$ }).subscribe(result);
+
+ const expectedAlgorithms = expect.arrayContaining(metadata.map((m) => m.id));
+ await expect(firstValueFrom(result)).resolves.toEqual(expectedAlgorithms);
+ });
+
+ it("omits algorithms blocked by policy", async () => {
+ const policy = new Policy({
+ type: PolicyType.PasswordGenerator,
+ enabled: true,
+ data: {
+ overridePasswordType: Algorithm.password,
+ },
+ } as any);
+ SomePolicyService.getAll$.mockReturnValue(new BehaviorSubject([policy]));
+ const metadata = [password, passphrase];
+ const provider = new GeneratorMetadataProvider(SystemProvider, ApplicationProvider, metadata);
+ const algorithmResult = new ReplaySubject(1);
+ const categoryResult = new ReplaySubject(1);
+
+ provider
+ .algorithms$({ algorithm: Algorithm.passphrase }, { account$: SomeAccount$ })
+ .subscribe(algorithmResult);
+ provider
+ .algorithms$({ type: Type.password }, { account$: SomeAccount$ })
+ .subscribe(categoryResult);
+
+ await expect(firstValueFrom(algorithmResult)).resolves.toEqual([]);
+ await expect(firstValueFrom(categoryResult)).resolves.toEqual([password.id]);
+ });
+
+ it("omits algorithms whose metadata is unavailable", async () => {
+ SomePolicyService.getAll$.mockReturnValue(new BehaviorSubject([]));
+ const provider = new GeneratorMetadataProvider(SystemProvider, ApplicationProvider, [
+ password,
+ ]);
+ const algorithmResult = new ReplaySubject(1);
+ const categoryResult = new ReplaySubject(1);
+
+ provider
+ .algorithms$({ algorithm: Algorithm.passphrase }, { account$: SomeAccount$ })
+ .subscribe(algorithmResult);
+ provider
+ .algorithms$({ type: Type.password }, { account$: SomeAccount$ })
+ .subscribe(categoryResult);
+
+ await expect(firstValueFrom(algorithmResult)).resolves.toEqual([]);
+ await expect(firstValueFrom(categoryResult)).resolves.toEqual([password.id]);
+ });
+
+ it("panics when neither algorithm nor category are specified", () => {
+ const provider = new GeneratorMetadataProvider(SystemProvider, ApplicationProvider, []);
+
+ expect(() => provider.algorithms$({} as any, { account$: SomeAccount$ })).toThrow(
+ "algorithm or type required",
+ );
+ });
+ });
+
+ describe("preference$", () => {
+ const preferences: CredentialPreference = deepFreeze({
+ [Type.email]: { algorithm: Algorithm.catchall, updated: new Date() },
+ [Type.username]: { algorithm: Algorithm.username, updated: new Date() },
+ [Type.password]: { algorithm: Algorithm.password, updated: new Date() },
+ });
+ beforeEach(async () => {
+ await SomeStateProvider.setUserState(PREFERENCES, preferences, SomeAccount.id);
+ });
+
+ it.each([
+ [Type.email, catchall],
+ [Type.username, effWordList],
+ [Type.password, password],
+ ])("emits the user's %s preference", async (type, metadata) => {
+ SomePolicyService.getAll$.mockReturnValue(new BehaviorSubject([]));
+ const provider = new GeneratorMetadataProvider(SystemProvider, ApplicationProvider, [
+ metadata,
+ ]);
+ const result = new ReplaySubject(1);
+
+ provider.preference$(type, { account$: SomeAccount$ }).subscribe(result);
+
+ await expect(firstValueFrom(result)).resolves.toEqual(preferences[type].algorithm);
+ });
+
+ it("emits a default when the user's preference is unavailable", async () => {
+ SomePolicyService.getAll$.mockReturnValue(new BehaviorSubject([]));
+ const provider = new GeneratorMetadataProvider(SystemProvider, ApplicationProvider, [
+ plusAddress,
+ ]);
+ const result = new ReplaySubject(1);
+
+ // precondition: the preferred email is excluded from the provided metadata
+ expect(preferences.email.algorithm).not.toEqual(plusAddress.id);
+
+ provider.preference$(Type.email, { account$: SomeAccount$ }).subscribe(result);
+
+ await expect(firstValueFrom(result)).resolves.toEqual(plusAddress.id);
+ });
+
+ it("emits undefined when the user's preference is unavailable and there is no metadata", async () => {
+ SomePolicyService.getAll$.mockReturnValue(new BehaviorSubject([]));
+ const provider = new GeneratorMetadataProvider(SystemProvider, ApplicationProvider, []);
+ const result = new ReplaySubject(1);
+
+ provider.preference$(Type.email, { account$: SomeAccount$ }).subscribe(result);
+
+ await expect(firstValueFrom(result)).resolves.toBeUndefined();
+ });
+ });
+
+ describe("preferences", () => {
+ it("returns a user state subject", () => {
+ const provider = new GeneratorMetadataProvider(SystemProvider, ApplicationProvider, []);
+
+ const subject = provider.preferences({ account$: SomeAccount$ });
+
+ expect(subject).toBeInstanceOf(UserStateSubject);
+ });
+ });
+});
diff --git a/libs/tools/generator/core/src/services/generator-metadata-provider.ts b/libs/tools/generator/core/src/services/generator-metadata-provider.ts
new file mode 100644
index 00000000000..f8c07283f5a
--- /dev/null
+++ b/libs/tools/generator/core/src/services/generator-metadata-provider.ts
@@ -0,0 +1,252 @@
+import {
+ Observable,
+ combineLatestWith,
+ distinctUntilChanged,
+ map,
+ shareReplay,
+ switchMap,
+ takeUntil,
+} from "rxjs";
+
+import { PolicyType } from "@bitwarden/common/admin-console/enums";
+import { Account } from "@bitwarden/common/auth/abstractions/account.service";
+import { BoundDependency } from "@bitwarden/common/tools/dependencies";
+import { ExtensionSite } from "@bitwarden/common/tools/extension";
+import { SemanticLogger } from "@bitwarden/common/tools/log";
+import { SystemServiceProvider } from "@bitwarden/common/tools/providers";
+import { anyComplete, pin } from "@bitwarden/common/tools/rx";
+import { UserStateSubject } from "@bitwarden/common/tools/state/user-state-subject";
+import { UserStateSubjectDependencyProvider } from "@bitwarden/common/tools/state/user-state-subject-dependency-provider";
+
+import {
+ GeneratorMetadata,
+ AlgorithmsByType,
+ CredentialAlgorithm,
+ CredentialType,
+ isForwarderExtensionId,
+ toForwarderMetadata,
+ Type,
+ Algorithms,
+ Types,
+} from "../metadata";
+import { availableAlgorithms_vNext } from "../policies/available-algorithms-policy";
+import { CredentialPreference } from "../types";
+import {
+ AlgorithmRequest,
+ TypeRequest,
+ MetadataRequest,
+ isAlgorithmRequest,
+ isTypeRequest,
+} from "../types/metadata-request";
+
+import { PREFERENCES } from "./credential-preferences";
+
+/** Surfaces contextual information to credential generators */
+export class GeneratorMetadataProvider {
+ /** Instantiates the context provider
+ * @param system dependency providers for user state subjects
+ * @param application dependency providers for system services
+ */
+ constructor(
+ private readonly system: UserStateSubjectDependencyProvider,
+ private readonly application: SystemServiceProvider,
+ algorithms: ReadonlyArray>,
+ ) {
+ this.log = system.log({ type: "GeneratorMetadataProvider" });
+
+ const site = application.extension.site("forwarder");
+ if (!site) {
+ this.log.panic("forwarder extension site not found");
+ }
+ this.site = site;
+
+ this._metadata = new Map(algorithms.map((a) => [a.id, a] as const));
+ }
+
+ private readonly site: ExtensionSite;
+ private readonly log: SemanticLogger;
+
+ private _metadata: Map>;
+
+ /** Retrieve an algorithm's generator metadata
+ * @param algorithm identifies the algorithm
+ * @returns the algorithm's generator metadata
+ * @throws when the algorithm doesn't identify a known metadata entry
+ */
+ metadata(algorithm: CredentialAlgorithm) {
+ let result = null;
+ if (isForwarderExtensionId(algorithm)) {
+ const extension = this.site.extensions.get(algorithm.forwarder);
+ if (!extension) {
+ this.log.panic(algorithm, "extension not found");
+ }
+
+ result = toForwarderMetadata(extension);
+ } else {
+ result = this._metadata.get(algorithm);
+ }
+
+ if (!result) {
+ this.log.panic({ algorithm }, "metadata not found");
+ }
+
+ return result;
+ }
+
+ /** retrieve credential types */
+ types(): ReadonlyArray {
+ return Types;
+ }
+
+ /** Retrieve the credential algorithm ids that match the request.
+ * @param requested when this has a `type` property, the method
+ * returns all algorithms with the same credential type. When this has an `algorithm`
+ * property, the method returns 0 or 1 matching algorithms.
+ * @returns the matching algorithms. This method always returns an array;
+ * the array is empty when no algorithms match the input criteria.
+ * @throws when neither `requested.algorithm` nor `requested.type` contains
+ * a value.
+ * @remarks this method enforces technical requirements only.
+ * If you want these algorithms with policy controls applied, use `algorithms$`.
+ */
+ algorithms(requested: AlgorithmRequest): CredentialAlgorithm[];
+ algorithms(requested: TypeRequest): CredentialAlgorithm[];
+ algorithms(requested: MetadataRequest): CredentialAlgorithm[] {
+ let algorithms: CredentialAlgorithm[];
+ if (isTypeRequest(requested)) {
+ let forwarders: CredentialAlgorithm[] = [];
+ if (requested.type === Type.email) {
+ forwarders = Array.from(this.site.extensions.keys()).map((forwarder) => ({ forwarder }));
+ }
+
+ algorithms = AlgorithmsByType[requested.type].concat(forwarders);
+ } else if (isAlgorithmRequest(requested) && isForwarderExtensionId(requested.algorithm)) {
+ algorithms = this.site.extensions.has(requested.algorithm.forwarder)
+ ? [requested.algorithm]
+ : [];
+ } else if (isAlgorithmRequest(requested)) {
+ algorithms = Algorithms.includes(requested.algorithm) ? [requested.algorithm] : [];
+ } else {
+ this.log.panic(requested, "algorithm or type required");
+ }
+
+ return algorithms;
+ }
+
+ // emits a function that returns `true` when the input algorithm is available
+ private isAvailable$(
+ dependencies: BoundDependency<"account", Account>,
+ ): Observable<(a: CredentialAlgorithm) => boolean> {
+ const id$ = dependencies.account$.pipe(
+ map((account) => account.id),
+ pin(),
+ shareReplay({ bufferSize: 1, refCount: true }),
+ );
+
+ const available$ = id$.pipe(
+ switchMap((id) => {
+ const policies$ = this.application.policy.getAll$(PolicyType.PasswordGenerator, id).pipe(
+ map((p) => availableAlgorithms_vNext(p).filter((a) => this._metadata.has(a))),
+ map((p) => new Set(p)),
+ // complete policy emissions otherwise `switchMap` holds `available$` open indefinitely
+ takeUntil(anyComplete(id$)),
+ );
+ return policies$;
+ }),
+ map(
+ (available) =>
+ function (a: CredentialAlgorithm) {
+ return isForwarderExtensionId(a) || available.has(a);
+ },
+ ),
+ );
+
+ return available$;
+ }
+
+ /** Retrieve credential algorithms filtered by the user's active policy.
+ * @param requested when this has a `type` property, the method
+ * returns all algorithms with a matching credential type. When this has an `algorithm`
+ * property, the method returns 0 or 1 matching algorithms.
+ * @param dependencies.account the account requesting algorithm access;
+ * this parameter controls which policy, if any, is applied.
+ * @returns an observable that emits matching algorithms. When no algorithms
+ * match the request, an empty array is emitted.
+ * @throws when neither `requested.algorithm` nor `requested.type` contains
+ * a value.
+ * @remarks this method applies policy controls. In particular, it excludes
+ * algorithms prohibited by a policy control. If you want lists of algorithms
+ * supported by the client, use `algorithms`.
+ */
+ algorithms$(
+ requested: AlgorithmRequest,
+ dependencies: BoundDependency<"account", Account>,
+ ): Observable;
+ algorithms$(
+ requested: TypeRequest,
+ dependencies: BoundDependency<"account", Account>,
+ ): Observable;
+ algorithms$(
+ requested: MetadataRequest,
+ dependencies: BoundDependency<"account", Account>,
+ ): Observable {
+ if (isTypeRequest(requested)) {
+ const { type } = requested;
+ return this.isAvailable$(dependencies).pipe(
+ map((isAvailable) => this.algorithms({ type }).filter(isAvailable)),
+ );
+ } else if (isAlgorithmRequest(requested)) {
+ const { algorithm } = requested;
+ return this.isAvailable$(dependencies).pipe(
+ map((isAvailable) => (isAvailable(algorithm) ? [algorithm] : [])),
+ );
+ } else {
+ this.log.panic(requested, "algorithm or type required");
+ }
+ }
+
+ preference$(type: CredentialType, dependencies: BoundDependency<"account", Account>) {
+ const account$ = dependencies.account$.pipe(shareReplay({ bufferSize: 1, refCount: true }));
+
+ const algorithm$ = this.preferences({ account$ }).pipe(
+ combineLatestWith(this.isAvailable$({ account$ })),
+ map(([preferences, isAvailable]) => {
+ const algorithm: CredentialAlgorithm = preferences[type].algorithm;
+ if (isAvailable(algorithm)) {
+ return algorithm;
+ }
+
+ const algorithms = type ? this.algorithms({ type: type }) : [];
+ // `?? null` because logging types must be `Jsonify`
+ const defaultAlgorithm = algorithms.find(isAvailable) ?? null;
+ this.log.debug(
+ { algorithm, defaultAlgorithm, credentialType: type },
+ "preference not available; defaulting the generator algorithm",
+ );
+
+ // `?? undefined` so that interface is ADR-14 compliant
+ return defaultAlgorithm ?? undefined;
+ }),
+ distinctUntilChanged(),
+ );
+
+ return algorithm$;
+ }
+
+ /** Get a subject bound to credential generator preferences.
+ * @param dependencies.account$ identifies the account to which the preferences are bound
+ * @returns a subject bound to the user's preferences
+ * @remarks Preferences determine which algorithms are used when generating a
+ * credential from a credential type (e.g. `PassX` or `Username`). Preferences
+ * should not be used to hold navigation history. Use @bitwarden/generator-navigation
+ * instead.
+ */
+ preferences(
+ dependencies: BoundDependency<"account", Account>,
+ ): UserStateSubject {
+ // FIXME: enforce policy
+ const subject = new UserStateSubject(PREFERENCES, this.system, dependencies);
+
+ return subject;
+ }
+}
diff --git a/libs/tools/generator/core/src/types/credential-generator-configuration.ts b/libs/tools/generator/core/src/types/credential-generator-configuration.ts
index 08aec48a9e7..36b0f3046a9 100644
--- a/libs/tools/generator/core/src/types/credential-generator-configuration.ts
+++ b/libs/tools/generator/core/src/types/credential-generator-configuration.ts
@@ -133,7 +133,9 @@ export type CredentialGeneratorConfiguration = CredentialGener
};
/** Defines the stored parameters for credential generation */
settings: {
- /** value used when an account's settings haven't been initialized */
+ /** value used when an account's settings haven't been initialized
+ * @deprecated use `ObjectKey.initial` for your desired storage property instead
+ */
initial: Readonly>;
/** Application-global constraints that apply to account settings */
diff --git a/libs/tools/generator/core/src/types/generator-type.ts b/libs/tools/generator/core/src/types/generator-type.ts
index 5b74d17fa4a..c75e4329610 100644
--- a/libs/tools/generator/core/src/types/generator-type.ts
+++ b/libs/tools/generator/core/src/types/generator-type.ts
@@ -1,6 +1,8 @@
+import { VendorId } from "@bitwarden/common/tools/extension";
import { IntegrationId } from "@bitwarden/common/tools/integration";
import { EmailAlgorithms, PasswordAlgorithms, UsernameAlgorithms } from "../data/generator-types";
+import { AlgorithmsByType, CredentialType } from "../metadata";
/** A type of password that may be generated by the credential generator. */
export type PasswordAlgorithm = (typeof PasswordAlgorithms)[number];
@@ -11,7 +13,7 @@ export type UsernameAlgorithm = (typeof UsernameAlgorithms)[number];
/** A type of email address that may be generated by the credential generator. */
export type EmailAlgorithm = (typeof EmailAlgorithms)[number];
-export type ForwarderIntegration = { forwarder: IntegrationId };
+export type ForwarderIntegration = { forwarder: IntegrationId & VendorId };
/** Returns true when the input algorithm is a forwarder integration. */
export function isForwarderIntegration(
@@ -74,8 +76,8 @@ export type CredentialCategory = keyof typeof CredentialCategories;
/** The kind of credential to generate using a compound configuration. */
// FIXME: extend the preferences to include a preferred forwarder
export type CredentialPreference = {
- [Key in CredentialCategory]: {
- algorithm: (typeof CredentialCategories)[Key][number];
+ [Key in CredentialType & CredentialCategory]: {
+ algorithm: CredentialAlgorithm & (typeof AlgorithmsByType)[Key][number];
updated: Date;
};
};
diff --git a/libs/tools/generator/core/src/types/metadata-request.ts b/libs/tools/generator/core/src/types/metadata-request.ts
new file mode 100644
index 00000000000..e9cae7060f0
--- /dev/null
+++ b/libs/tools/generator/core/src/types/metadata-request.ts
@@ -0,0 +1,13 @@
+import { CredentialAlgorithm, CredentialType } from "../metadata";
+
+export type AlgorithmRequest = { algorithm: CredentialAlgorithm };
+export type TypeRequest = { type: CredentialType };
+export type MetadataRequest = Partial;
+
+export function isAlgorithmRequest(request: MetadataRequest): request is AlgorithmRequest {
+ return !!request.algorithm;
+}
+
+export function isTypeRequest(request: MetadataRequest): request is TypeRequest {
+ return !!request.type;
+}
From f3a26497520e4fccc3365965811dcb72256156db Mon Sep 17 00:00:00 2001
From: Alec Rippberger <127791530+alec-livefront@users.noreply.github.com>
Date: Tue, 25 Mar 2025 16:34:43 -0500
Subject: [PATCH 050/113] refactor(auth): [PM-18148] replace app-link-sso
directive with LinkSsoService
Removes the app-link-sso directive and adds a LinkSsoService which is used to link an organization with SSO.
Resolves PM-18148
---
apps/web/src/app/auth/core/services/index.ts | 1 +
.../core/services/link-sso.service.spec.ts | 154 ++++++++++++++++++
.../auth/core/services/link-sso.service.ts | 91 +++++++++++
apps/web/src/app/core/core.module.ts | 13 ++
.../components/link-sso.directive.ts | 26 ---
.../organization-options.component.html | 4 +-
.../organization-options.component.ts | 33 +++-
.../vault-filter/vault-filter.module.ts | 3 +-
8 files changed, 287 insertions(+), 38 deletions(-)
create mode 100644 apps/web/src/app/auth/core/services/link-sso.service.spec.ts
create mode 100644 apps/web/src/app/auth/core/services/link-sso.service.ts
delete mode 100644 apps/web/src/app/vault/individual-vault/vault-filter/components/link-sso.directive.ts
diff --git a/apps/web/src/app/auth/core/services/index.ts b/apps/web/src/app/auth/core/services/index.ts
index 1e8eec759b1..11c8dd98872 100644
--- a/apps/web/src/app/auth/core/services/index.ts
+++ b/apps/web/src/app/auth/core/services/index.ts
@@ -4,3 +4,4 @@ export * from "./webauthn-login";
export * from "./set-password-jit";
export * from "./registration";
export * from "./two-factor-auth";
+export * from "./link-sso.service";
diff --git a/apps/web/src/app/auth/core/services/link-sso.service.spec.ts b/apps/web/src/app/auth/core/services/link-sso.service.spec.ts
new file mode 100644
index 00000000000..70b52999875
--- /dev/null
+++ b/apps/web/src/app/auth/core/services/link-sso.service.spec.ts
@@ -0,0 +1,154 @@
+import { mock, MockProxy } from "jest-mock-extended";
+import { BehaviorSubject } from "rxjs";
+
+import { ApiService } from "@bitwarden/common/abstractions/api.service";
+import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
+import { SsoPreValidateResponse } from "@bitwarden/common/auth/models/response/sso-pre-validate.response";
+import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
+import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
+import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
+import { Utils } from "@bitwarden/common/platform/misc/utils";
+import {
+ PasswordGenerationServiceAbstraction,
+ PasswordGeneratorOptions,
+} from "@bitwarden/generator-legacy";
+
+import { LinkSsoService } from "./link-sso.service";
+
+describe("LinkSsoService", () => {
+ let sut: LinkSsoService;
+
+ let mockSsoLoginService: MockProxy;
+ let mockApiService: MockProxy;
+ let mockCryptoFunctionService: MockProxy;
+ let mockEnvironmentService: MockProxy;
+ let mockPasswordGenerationService: MockProxy;
+ let mockPlatformUtilsService: MockProxy;
+
+ const mockEnvironment$ = new BehaviorSubject({
+ getIdentityUrl: jest.fn().mockReturnValue("https://identity.bitwarden.com"),
+ });
+
+ beforeEach(() => {
+ // Create mock implementations
+ mockSsoLoginService = mock();
+ mockApiService = mock();
+ mockCryptoFunctionService = mock();
+ mockEnvironmentService = mock();
+ mockPasswordGenerationService = mock();
+ mockPlatformUtilsService = mock();
+
+ // Set up environment service to return our mock environment
+ mockEnvironmentService.environment$ = mockEnvironment$;
+
+ // Set up API service mocks
+ const mockResponse = { Token: "mockSsoToken" };
+ mockApiService.preValidateSso.mockResolvedValue(new SsoPreValidateResponse(mockResponse));
+ mockApiService.getSsoUserIdentifier.mockResolvedValue("mockUserIdentifier");
+
+ // Set up password generation service mock
+ mockPasswordGenerationService.generatePassword.mockImplementation(
+ async (options: PasswordGeneratorOptions) => {
+ return "mockGeneratedPassword";
+ },
+ );
+
+ // Set up crypto function service mock
+ mockCryptoFunctionService.hash.mockResolvedValue(new Uint8Array([1, 2, 3, 4]));
+
+ // Create the service under test with mock dependencies
+ sut = new LinkSsoService(
+ mockSsoLoginService,
+ mockApiService,
+ mockCryptoFunctionService,
+ mockEnvironmentService,
+ mockPasswordGenerationService,
+ mockPlatformUtilsService,
+ );
+
+ // Mock Utils.fromBufferToUrlB64
+ jest.spyOn(Utils, "fromBufferToUrlB64").mockReturnValue("mockCodeChallenge");
+
+ // Mock window.location
+ Object.defineProperty(window, "location", {
+ value: {
+ origin: "https://bitwarden.com",
+ },
+ writable: true,
+ });
+ });
+
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
+
+ describe("linkSso", () => {
+ it("throws an error when identifier is null", async () => {
+ await expect(sut.linkSso(null as unknown as string)).rejects.toThrow(
+ "SSO identifier is required",
+ );
+ });
+
+ it("throws an error when identifier is empty", async () => {
+ await expect(sut.linkSso("")).rejects.toThrow("SSO identifier is required");
+ });
+
+ it("calls preValidateSso with the provided identifier", async () => {
+ await sut.linkSso("org123");
+
+ expect(mockApiService.preValidateSso).toHaveBeenCalledWith("org123");
+ });
+
+ it("generates a password for code verifier", async () => {
+ await sut.linkSso("org123");
+
+ expect(mockPasswordGenerationService.generatePassword).toHaveBeenCalledWith({
+ type: "password",
+ length: 64,
+ uppercase: true,
+ lowercase: true,
+ number: true,
+ special: false,
+ });
+ });
+
+ it("sets the code verifier in the ssoLoginService", async () => {
+ await sut.linkSso("org123");
+
+ expect(mockSsoLoginService.setCodeVerifier).toHaveBeenCalledWith("mockGeneratedPassword");
+ });
+
+ it("generates a state and sets it in the ssoLoginService", async () => {
+ await sut.linkSso("org123");
+
+ const expectedState =
+ "mockGeneratedPassword_returnUri='/settings/organizations'_identifier=org123";
+ expect(mockSsoLoginService.setSsoState).toHaveBeenCalledWith(expectedState);
+ });
+
+ it("gets the SSO user identifier from the API", async () => {
+ await sut.linkSso("org123");
+
+ expect(mockApiService.getSsoUserIdentifier).toHaveBeenCalled();
+ });
+
+ it("launches the authorize URL with the correct parameters", async () => {
+ await sut.linkSso("org123");
+
+ expect(mockPlatformUtilsService.launchUri).toHaveBeenCalledWith(
+ expect.stringContaining("https://identity.bitwarden.com/connect/authorize"),
+ { sameWindow: true },
+ );
+
+ const launchUriArg = mockPlatformUtilsService.launchUri.mock.calls[0][0];
+ expect(launchUriArg).toContain("client_id=web");
+ expect(launchUriArg).toContain(
+ "redirect_uri=https%3A%2F%2Fbitwarden.com%2Fsso-connector.html",
+ );
+ expect(launchUriArg).toContain("response_type=code");
+ expect(launchUriArg).toContain("code_challenge=mockCodeChallenge");
+ expect(launchUriArg).toContain("ssoToken=mockSsoToken");
+ expect(launchUriArg).toContain("user_identifier=mockUserIdentifier");
+ });
+ });
+});
diff --git a/apps/web/src/app/auth/core/services/link-sso.service.ts b/apps/web/src/app/auth/core/services/link-sso.service.ts
new file mode 100644
index 00000000000..3d51525add1
--- /dev/null
+++ b/apps/web/src/app/auth/core/services/link-sso.service.ts
@@ -0,0 +1,91 @@
+import { firstValueFrom } from "rxjs";
+
+import { ApiService } from "@bitwarden/common/abstractions/api.service";
+import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
+import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
+import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
+import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
+import { Utils } from "@bitwarden/common/platform/misc/utils";
+import {
+ PasswordGenerationServiceAbstraction,
+ PasswordGeneratorOptions,
+} from "@bitwarden/generator-legacy";
+
+/**
+ * Provides a service for linking SSO.
+ */
+export class LinkSsoService {
+ constructor(
+ private ssoLoginService: SsoLoginServiceAbstraction,
+ private apiService: ApiService,
+ private cryptoFunctionService: CryptoFunctionService,
+ private environmentService: EnvironmentService,
+ private passwordGenerationService: PasswordGenerationServiceAbstraction,
+ private platformUtilsService: PlatformUtilsService,
+ ) {}
+
+ /**
+ * Links SSO to an organization.
+ * Ported from the SsoComponent
+ * @param identifier The identifier of the organization to link to.
+ */
+ async linkSso(identifier: string) {
+ if (identifier == null || identifier === "") {
+ throw new Error("SSO identifier is required");
+ }
+
+ const redirectUri = window.location.origin + "/sso-connector.html";
+ const clientId = "web";
+ const returnUri = "/settings/organizations";
+
+ const response = await this.apiService.preValidateSso(identifier);
+
+ const passwordOptions: PasswordGeneratorOptions = {
+ type: "password",
+ length: 64,
+ uppercase: true,
+ lowercase: true,
+ number: true,
+ special: false,
+ };
+
+ const codeVerifier = await this.passwordGenerationService.generatePassword(passwordOptions);
+ const codeVerifierHash = await this.cryptoFunctionService.hash(codeVerifier, "sha256");
+ const codeChallenge = Utils.fromBufferToUrlB64(codeVerifierHash);
+ await this.ssoLoginService.setCodeVerifier(codeVerifier);
+
+ let state = await this.passwordGenerationService.generatePassword(passwordOptions);
+ state += `_returnUri='${returnUri}'`;
+ state += `_identifier=${identifier}`;
+
+ // Save state
+ await this.ssoLoginService.setSsoState(state);
+
+ const env = await firstValueFrom(this.environmentService.environment$);
+
+ let authorizeUrl =
+ env.getIdentityUrl() +
+ "/connect/authorize?" +
+ "client_id=" +
+ clientId +
+ "&redirect_uri=" +
+ encodeURIComponent(redirectUri) +
+ "&" +
+ "response_type=code&scope=api offline_access&" +
+ "state=" +
+ state +
+ "&code_challenge=" +
+ codeChallenge +
+ "&" +
+ "code_challenge_method=S256&response_mode=query&" +
+ "domain_hint=" +
+ encodeURIComponent(identifier) +
+ "&ssoToken=" +
+ encodeURIComponent(response.token);
+
+ const userIdentifier = await this.apiService.getSsoUserIdentifier();
+ authorizeUrl += `&user_identifier=${encodeURIComponent(userIdentifier)}`;
+
+ this.platformUtilsService.launchUri(authorizeUrl, { sameWindow: true });
+ }
+}
diff --git a/apps/web/src/app/core/core.module.ts b/apps/web/src/app/core/core.module.ts
index cc9024490d6..9e6f88d18d6 100644
--- a/apps/web/src/app/core/core.module.ts
+++ b/apps/web/src/app/core/core.module.ts
@@ -116,6 +116,7 @@ import {
WebLoginDecryptionOptionsService,
WebTwoFactorAuthComponentService,
WebTwoFactorAuthDuoComponentService,
+ LinkSsoService,
} from "../auth";
import { WebSsoComponentService } from "../auth/core/services/login/web-sso-component.service";
import { AcceptOrganizationInviteService } from "../auth/organization-invite/accept-organization.service";
@@ -345,6 +346,18 @@ const safeProviders: SafeProvider[] = [
useClass: WebSsoComponentService,
deps: [I18nServiceAbstraction],
}),
+ safeProvider({
+ provide: LinkSsoService,
+ useClass: LinkSsoService,
+ deps: [
+ SsoLoginServiceAbstraction,
+ ApiService,
+ CryptoFunctionService,
+ EnvironmentService,
+ PasswordGenerationServiceAbstraction,
+ PlatformUtilsService,
+ ],
+ }),
safeProvider({
provide: TwoFactorAuthDuoComponentService,
useClass: WebTwoFactorAuthDuoComponentService,
diff --git a/apps/web/src/app/vault/individual-vault/vault-filter/components/link-sso.directive.ts b/apps/web/src/app/vault/individual-vault/vault-filter/components/link-sso.directive.ts
deleted file mode 100644
index a1781889c49..00000000000
--- a/apps/web/src/app/vault/individual-vault/vault-filter/components/link-sso.directive.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-// FIXME: Update this file to be type safe and remove this and next line
-// @ts-strict-ignore
-import { AfterContentInit, Directive, HostListener, Input } from "@angular/core";
-
-import { SsoComponent } from "@bitwarden/angular/auth/components/sso.component";
-import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
-
-@Directive({
- selector: "[app-link-sso]",
-})
-export class LinkSsoDirective extends SsoComponent implements AfterContentInit {
- @Input() organization: Organization;
- returnUri = "/settings/organizations";
- redirectUri = window.location.origin + "/sso-connector.html";
- clientId = "web";
-
- @HostListener("click", ["$event"])
- async onClick($event: MouseEvent) {
- $event.preventDefault();
- await this.submit(this.returnUri, true);
- }
-
- async ngAfterContentInit() {
- this.identifier = this.organization.identifier;
- }
-}
diff --git a/apps/web/src/app/vault/individual-vault/vault-filter/components/organization-options.component.html b/apps/web/src/app/vault/individual-vault/vault-filter/components/organization-options.component.html
index 0b94b6e2be2..0fe243ed20a 100644
--- a/apps/web/src/app/vault/individual-vault/vault-filter/components/organization-options.component.html
+++ b/apps/web/src/app/vault/individual-vault/vault-filter/components/organization-options.component.html
@@ -50,10 +50,10 @@
{{ "unlinkSso" | i18n }}
-
+
{{ "linkSso" | i18n }}
-
+
diff --git a/apps/web/src/app/vault/individual-vault/vault-filter/components/organization-options.component.ts b/apps/web/src/app/vault/individual-vault/vault-filter/components/organization-options.component.ts
index 4b9791c61bf..4abf8a9ee9c 100644
--- a/apps/web/src/app/vault/individual-vault/vault-filter/components/organization-options.component.ts
+++ b/apps/web/src/app/vault/individual-vault/vault-filter/components/organization-options.component.ts
@@ -1,5 +1,3 @@
-// FIXME: Update this file to be type safe and remove this and next line
-// @ts-strict-ignore
import { Component, Inject, OnDestroy, OnInit } from "@angular/core";
import {
combineLatest,
@@ -37,6 +35,7 @@ import { DialogService, ToastService } from "@bitwarden/components";
import { OrganizationUserResetPasswordService } from "../../../../admin-console/organizations/members/services/organization-user-reset-password/organization-user-reset-password.service";
import { EnrollMasterPasswordReset } from "../../../../admin-console/organizations/users/enroll-master-password-reset.component";
+import { LinkSsoService } from "../../../../auth/core/services";
import { OptionsInput } from "../shared/components/vault-filter-section.component";
import { OrganizationFilter } from "../shared/models/vault-filter.type";
@@ -45,12 +44,12 @@ import { OrganizationFilter } from "../shared/models/vault-filter.type";
templateUrl: "organization-options.component.html",
})
export class OrganizationOptionsComponent implements OnInit, OnDestroy {
- protected actionPromise: Promise;
+ protected actionPromise?: Promise;
protected resetPasswordPolicy?: Policy | undefined;
protected loaded = false;
protected hideMenu = false;
protected showLeaveOrgOption = false;
- protected organization: OrganizationFilter;
+ protected organization!: OrganizationFilter;
private destroy$ = new Subject();
@@ -72,6 +71,7 @@ export class OrganizationOptionsComponent implements OnInit, OnDestroy {
private configService: ConfigService,
private organizationService: OrganizationService,
private accountService: AccountService,
+ private linkSsoService: LinkSsoService,
) {}
async ngOnInit() {
@@ -147,6 +147,23 @@ export class OrganizationOptionsComponent implements OnInit, OnDestroy {
return org?.useSso && org?.identifier;
}
+ /**
+ * Links SSO to an organization.
+ * @param organization The organization to link SSO to.
+ */
+ async handleLinkSso(organization: Organization) {
+ try {
+ await this.linkSsoService.linkSso(organization.identifier);
+ } catch (e) {
+ this.logService.error(e);
+ this.toastService.showToast({
+ variant: "error",
+ title: "",
+ message: this.i18nService.t("errorOccurred"),
+ });
+ }
+ }
+
async unlinkSso(org: Organization) {
const confirmed = await this.dialogService.openSimpleDialog({
title: org.name,
@@ -165,7 +182,7 @@ export class OrganizationOptionsComponent implements OnInit, OnDestroy {
await this.actionPromise;
this.toastService.showToast({
variant: "success",
- title: null,
+ title: "",
message: this.i18nService.t("unlinkedSso"),
});
} catch (e) {
@@ -189,7 +206,7 @@ export class OrganizationOptionsComponent implements OnInit, OnDestroy {
await this.actionPromise;
this.toastService.showToast({
variant: "success",
- title: null,
+ title: "",
message: this.i18nService.t("leftOrganization"),
});
} catch (e) {
@@ -215,7 +232,7 @@ export class OrganizationOptionsComponent implements OnInit, OnDestroy {
// Remove reset password
const request = new OrganizationUserResetPasswordEnrollmentRequest();
request.masterPasswordHash = "ignored";
- request.resetPasswordKey = null;
+ request.resetPasswordKey = "";
this.actionPromise =
this.organizationUserApiService.putOrganizationUserResetPasswordEnrollment(
this.organization.id,
@@ -226,7 +243,7 @@ export class OrganizationOptionsComponent implements OnInit, OnDestroy {
await this.actionPromise;
this.toastService.showToast({
variant: "success",
- title: null,
+ title: "",
message: this.i18nService.t("withdrawPasswordResetSuccess"),
});
await this.syncService.fullSync(true);
diff --git a/apps/web/src/app/vault/individual-vault/vault-filter/vault-filter.module.ts b/apps/web/src/app/vault/individual-vault/vault-filter/vault-filter.module.ts
index 641e4352696..1f2d5652735 100644
--- a/apps/web/src/app/vault/individual-vault/vault-filter/vault-filter.module.ts
+++ b/apps/web/src/app/vault/individual-vault/vault-filter/vault-filter.module.ts
@@ -4,7 +4,6 @@ import { SearchModule } from "@bitwarden/components";
import { VaultFilterSharedModule } from "../../individual-vault/vault-filter/shared/vault-filter-shared.module";
-import { LinkSsoDirective } from "./components/link-sso.directive";
import { OrganizationOptionsComponent } from "./components/organization-options.component";
import { VaultFilterComponent } from "./components/vault-filter.component";
import { VaultFilterService as VaultFilterServiceAbstraction } from "./services/abstractions/vault-filter.service";
@@ -12,7 +11,7 @@ import { VaultFilterService } from "./services/vault-filter.service";
@NgModule({
imports: [VaultFilterSharedModule, SearchModule],
- declarations: [VaultFilterComponent, OrganizationOptionsComponent, LinkSsoDirective],
+ declarations: [VaultFilterComponent, OrganizationOptionsComponent],
exports: [VaultFilterComponent],
providers: [
{
From 2b9ef7fe195152adc0e7efae2e0623fa1e755f6c Mon Sep 17 00:00:00 2001
From: Addison Beck
Date: Tue, 25 Mar 2025 19:11:26 -0400
Subject: [PATCH 051/113] fix: use newly-minted policy methods in metadata
provider (#13993)
Recently the policy service was refactored and some method signatures were
changed. One of these changes involved renaming the `getAll` observable to
`policiesByType`.
This was not merged into the metadata provider work before it was merged, so
those changes were committed using removed method signatures.
This commit updates these references.
---
.../services/generator-metadata-provider.spec.ts | 14 +++++++-------
.../src/services/generator-metadata-provider.ts | 14 ++++++++------
2 files changed, 15 insertions(+), 13 deletions(-)
diff --git a/libs/tools/generator/core/src/services/generator-metadata-provider.spec.ts b/libs/tools/generator/core/src/services/generator-metadata-provider.spec.ts
index 958e5608449..37a987f88bc 100644
--- a/libs/tools/generator/core/src/services/generator-metadata-provider.spec.ts
+++ b/libs/tools/generator/core/src/services/generator-metadata-provider.spec.ts
@@ -295,7 +295,7 @@ describe("GeneratorMetadataProvider", () => {
[Algorithm.username, effWordList],
[Algorithm.password, password],
])("gets a specific algorithm", async (algorithm, metadata) => {
- SomePolicyService.getAll$.mockReturnValue(new BehaviorSubject([]));
+ SomePolicyService.policiesByType$.mockReturnValue(new BehaviorSubject([]));
const provider = new GeneratorMetadataProvider(SystemProvider, ApplicationProvider, [
metadata,
]);
@@ -311,7 +311,7 @@ describe("GeneratorMetadataProvider", () => {
[Type.username, [effWordList]],
[Type.password, [password, passphrase]],
])("gets a category of algorithms", async (category, metadata) => {
- SomePolicyService.getAll$.mockReturnValue(new BehaviorSubject([]));
+ SomePolicyService.policiesByType$.mockReturnValue(new BehaviorSubject([]));
const provider = new GeneratorMetadataProvider(SystemProvider, ApplicationProvider, metadata);
const result = new ReplaySubject(1);
@@ -329,7 +329,7 @@ describe("GeneratorMetadataProvider", () => {
overridePasswordType: Algorithm.password,
},
} as any);
- SomePolicyService.getAll$.mockReturnValue(new BehaviorSubject([policy]));
+ SomePolicyService.policiesByType$.mockReturnValue(new BehaviorSubject([policy]));
const metadata = [password, passphrase];
const provider = new GeneratorMetadataProvider(SystemProvider, ApplicationProvider, metadata);
const algorithmResult = new ReplaySubject(1);
@@ -347,7 +347,7 @@ describe("GeneratorMetadataProvider", () => {
});
it("omits algorithms whose metadata is unavailable", async () => {
- SomePolicyService.getAll$.mockReturnValue(new BehaviorSubject([]));
+ SomePolicyService.policiesByType$.mockReturnValue(new BehaviorSubject([]));
const provider = new GeneratorMetadataProvider(SystemProvider, ApplicationProvider, [
password,
]);
@@ -389,7 +389,7 @@ describe("GeneratorMetadataProvider", () => {
[Type.username, effWordList],
[Type.password, password],
])("emits the user's %s preference", async (type, metadata) => {
- SomePolicyService.getAll$.mockReturnValue(new BehaviorSubject([]));
+ SomePolicyService.policiesByType$.mockReturnValue(new BehaviorSubject([]));
const provider = new GeneratorMetadataProvider(SystemProvider, ApplicationProvider, [
metadata,
]);
@@ -401,7 +401,7 @@ describe("GeneratorMetadataProvider", () => {
});
it("emits a default when the user's preference is unavailable", async () => {
- SomePolicyService.getAll$.mockReturnValue(new BehaviorSubject([]));
+ SomePolicyService.policiesByType$.mockReturnValue(new BehaviorSubject([]));
const provider = new GeneratorMetadataProvider(SystemProvider, ApplicationProvider, [
plusAddress,
]);
@@ -416,7 +416,7 @@ describe("GeneratorMetadataProvider", () => {
});
it("emits undefined when the user's preference is unavailable and there is no metadata", async () => {
- SomePolicyService.getAll$.mockReturnValue(new BehaviorSubject([]));
+ SomePolicyService.policiesByType$.mockReturnValue(new BehaviorSubject([]));
const provider = new GeneratorMetadataProvider(SystemProvider, ApplicationProvider, []);
const result = new ReplaySubject(1);
diff --git a/libs/tools/generator/core/src/services/generator-metadata-provider.ts b/libs/tools/generator/core/src/services/generator-metadata-provider.ts
index f8c07283f5a..161f7192c39 100644
--- a/libs/tools/generator/core/src/services/generator-metadata-provider.ts
+++ b/libs/tools/generator/core/src/services/generator-metadata-provider.ts
@@ -145,12 +145,14 @@ export class GeneratorMetadataProvider {
const available$ = id$.pipe(
switchMap((id) => {
- const policies$ = this.application.policy.getAll$(PolicyType.PasswordGenerator, id).pipe(
- map((p) => availableAlgorithms_vNext(p).filter((a) => this._metadata.has(a))),
- map((p) => new Set(p)),
- // complete policy emissions otherwise `switchMap` holds `available$` open indefinitely
- takeUntil(anyComplete(id$)),
- );
+ const policies$ = this.application.policy
+ .policiesByType$(PolicyType.PasswordGenerator, id)
+ .pipe(
+ map((p) => availableAlgorithms_vNext(p).filter((a) => this._metadata.has(a))),
+ map((p) => new Set(p)),
+ // complete policy emissions otherwise `switchMap` holds `available$` open indefinitely
+ takeUntil(anyComplete(id$)),
+ );
return policies$;
}),
map(
From 279b1a90dddad8637bfd11923a44e9f2050fccda Mon Sep 17 00:00:00 2001
From: cyprain-okeke <108260115+cyprain-okeke@users.noreply.github.com>
Date: Wed, 26 Mar 2025 09:44:52 +0100
Subject: [PATCH 052/113] resolve the hover effect issue (#13981)
---
.../admin-console/organizations/manage/events.component.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/apps/web/src/app/admin-console/organizations/manage/events.component.html b/apps/web/src/app/admin-console/organizations/manage/events.component.html
index 4a48ae1ead7..adadec5075a 100644
--- a/apps/web/src/app/admin-console/organizations/manage/events.component.html
+++ b/apps/web/src/app/admin-console/organizations/manage/events.component.html
@@ -111,7 +111,7 @@
Date: Wed, 26 Mar 2025 12:49:53 +0000
Subject: [PATCH 053/113] [PM-19046] Update README mobile references (#13990)
* Update mobile references in README.md
* Matching ios / android repo titles
---
README.md | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/README.md b/README.md
index 22c8d329f1c..cdeaa4c8cf7 100644
--- a/README.md
+++ b/README.md
@@ -13,15 +13,15 @@
# Bitwarden Client Applications
-This repository houses all Bitwarden client applications except the [Mobile application](https://github.com/bitwarden/mobile).
+This repository houses all Bitwarden client applications except the mobile applications ([iOS](https://github.com/bitwarden/ios) | [android](https://github.com/bitwarden/android)).
Please refer to the [Clients section](https://contributing.bitwarden.com/getting-started/clients/) of the [Contributing Documentation](https://contributing.bitwarden.com/) for build instructions, recommended tooling, code style tips, and lots of other great information to get you started.
## Related projects:
- [bitwarden/server](https://github.com/bitwarden/server): The core infrastructure backend (API, database, Docker, etc).
-- [bitwarden/ios](https://github.com/bitwarden/ios): Bitwarden mobile app for iOS.
-- [bitwarden/android](https://github.com/bitwarden/android): Bitwarden mobile app for Android.
+- [bitwarden/ios](https://github.com/bitwarden/ios): Bitwarden iOS Password Manager & Authenticator apps.
+- [bitwarden/android](https://github.com/bitwarden/android): Bitwarden Android Password Manager & Authenticator apps.
- [bitwarden/directory-connector](https://github.com/bitwarden/directory-connector): A tool for syncing a directory (AD, LDAP, Azure, G Suite, Okta) to an organization.
# We're Hiring!
From be8c5f28b5807c9682abd652294a4b3746cf5892 Mon Sep 17 00:00:00 2001
From: Jonas Hendrickx
Date: Wed, 26 Mar 2025 17:59:27 +0100
Subject: [PATCH 054/113] [PM-18170] Remove
'PM-15814-alert-owners-of-reseller-managed-orgs' feature flag (#13757)
---
.../organizations/collections/vault.component.ts | 8 +-------
.../src/app/billing/services/trial-flow.service.ts | 13 ++-----------
libs/common/src/enums/feature-flag.enum.ts | 2 --
3 files changed, 3 insertions(+), 20 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 ec92597dc7b..8dfebea5229 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
@@ -45,7 +45,6 @@ import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { OrganizationBillingServiceAbstraction } from "@bitwarden/common/billing/abstractions";
import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billing-api.service.abstraction";
import { EventType } from "@bitwarden/common/enums";
-import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
@@ -196,7 +195,6 @@ export class VaultComponent implements OnInit, OnDestroy {
private refresh$ = new BehaviorSubject(null);
private destroy$ = new Subject();
protected addAccessStatus$ = new BehaviorSubject(0);
- private resellerManagedOrgAlert: boolean;
private vaultItemDialogRef?: DialogRef | undefined;
private readonly unpaidSubscriptionDialog$ = this.accountService.activeAccount$.pipe(
@@ -264,10 +262,6 @@ export class VaultComponent implements OnInit, OnDestroy {
async ngOnInit() {
this.userId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
- this.resellerManagedOrgAlert = await this.configService.getFeatureFlag(
- FeatureFlag.ResellerManagedOrgAlert,
- );
-
this.trashCleanupWarning = this.i18nService.t(
this.platformUtilsService.isSelfHost()
? "trashCleanupWarningSelfHosted"
@@ -654,7 +648,7 @@ export class VaultComponent implements OnInit, OnDestroy {
);
this.resellerWarning$ = organization$.pipe(
- filter((org) => org.isOwner && this.resellerManagedOrgAlert),
+ filter((org) => org.isOwner),
switchMap((org) =>
from(this.billingApiService.getOrganizationBillingMetadata(org.id)).pipe(
map((metadata) => ({ org, metadata })),
diff --git a/apps/web/src/app/billing/services/trial-flow.service.ts b/apps/web/src/app/billing/services/trial-flow.service.ts
index eb08e5bd7ad..979fc29aed7 100644
--- a/apps/web/src/app/billing/services/trial-flow.service.ts
+++ b/apps/web/src/app/billing/services/trial-flow.service.ts
@@ -11,8 +11,6 @@ import { BillingSourceResponse } from "@bitwarden/common/billing/models/response
import { OrganizationBillingMetadataResponse } from "@bitwarden/common/billing/models/response/organization-billing-metadata.response";
import { OrganizationSubscriptionResponse } from "@bitwarden/common/billing/models/response/organization-subscription.response";
import { PaymentSourceResponse } from "@bitwarden/common/billing/models/response/payment-source.response";
-import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
-import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { DialogService } from "@bitwarden/components";
@@ -24,15 +22,12 @@ import { FreeTrial } from "../types/free-trial";
@Injectable({ providedIn: "root" })
export class TrialFlowService {
- private resellerManagedOrgAlert: boolean;
-
constructor(
private i18nService: I18nService,
protected dialogService: DialogService,
private router: Router,
protected billingApiService: BillingApiServiceAbstraction,
private organizationApiService: OrganizationApiServiceAbstraction,
- private configService: ConfigService,
) {}
checkForOrgsWithUpcomingPaymentIssues(
organization: Organization,
@@ -98,10 +93,6 @@ export class TrialFlowService {
isCanceled: boolean,
isUnpaid: boolean,
): Promise {
- this.resellerManagedOrgAlert = await this.configService.getFeatureFlag(
- FeatureFlag.ResellerManagedOrgAlert,
- );
-
if (!org?.isOwner && !org.providerId) {
await this.dialogService.openSimpleDialog({
title: this.i18nService.t("suspendedOrganizationTitle", org?.name),
@@ -113,7 +104,7 @@ export class TrialFlowService {
return false;
}
- if (org.providerId && this.resellerManagedOrgAlert) {
+ if (org.providerId) {
await this.dialogService.openSimpleDialog({
title: this.i18nService.t("suspendedOrganizationTitle", org.name),
content: { key: "suspendedManagedOrgMessage", placeholders: [org.providerName] },
@@ -134,7 +125,7 @@ export class TrialFlowService {
});
}
- if (org.isOwner && isCanceled && this.resellerManagedOrgAlert) {
+ if (org.isOwner && isCanceled) {
await this.changePlan(org);
}
}
diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts
index 5e52eb31840..e3c15a1d40b 100644
--- a/libs/common/src/enums/feature-flag.enum.ts
+++ b/libs/common/src/enums/feature-flag.enum.ts
@@ -46,7 +46,6 @@ export enum FeatureFlag {
TrialPaymentOptional = "PM-8163-trial-payment",
MacOsNativeCredentialSync = "macos-native-credential-sync",
PrivateKeyRegeneration = "pm-12241-private-key-regeneration",
- ResellerManagedOrgAlert = "PM-15814-alert-owners-of-reseller-managed-orgs",
AccountDeprovisioningBanner = "pm-17120-account-deprovisioning-admin-console-banner",
PM15179_AddExistingOrgsFromProviderPortal = "pm-15179-add-existing-orgs-from-provider-portal",
PM12276_BreadcrumbEventLogs = "pm-12276-breadcrumbing-for-business-features",
@@ -107,7 +106,6 @@ export const DefaultFeatureFlagValue = {
[FeatureFlag.TrialPaymentOptional]: FALSE,
[FeatureFlag.MacOsNativeCredentialSync]: FALSE,
[FeatureFlag.PrivateKeyRegeneration]: FALSE,
- [FeatureFlag.ResellerManagedOrgAlert]: FALSE,
[FeatureFlag.AccountDeprovisioningBanner]: FALSE,
[FeatureFlag.PM15179_AddExistingOrgsFromProviderPortal]: FALSE,
[FeatureFlag.PM12276_BreadcrumbEventLogs]: FALSE,
From a3e01ad672575532c8eac4187562d66663db2ad1 Mon Sep 17 00:00:00 2001
From: Jason Ng
Date: Wed, 26 Mar 2025 13:16:40 -0400
Subject: [PATCH 055/113] [PM-10610] push notification to end user notification
service (#13876)
* use NotificationsService.notifictions$ for tracking inside default end user notification
---
.../src/services/jslib-services.module.ts | 2 +-
.../src/enums/notification-type.enum.ts | 2 ++
.../internal/noop-notifications.service.ts | 7 ++++-
.../notifications/notifications.service.ts | 6 +++-
.../end-user-notification.service.ts | 7 -----
...ault-end-user-notification.service.spec.ts | 9 +++++-
.../default-end-user-notification.service.ts | 28 +++++++++++++++----
7 files changed, 45 insertions(+), 16 deletions(-)
diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts
index 3a28f28caaf..37220b5195d 100644
--- a/libs/angular/src/services/jslib-services.module.ts
+++ b/libs/angular/src/services/jslib-services.module.ts
@@ -1480,7 +1480,7 @@ const safeProviders: SafeProvider[] = [
safeProvider({
provide: EndUserNotificationService,
useClass: DefaultEndUserNotificationService,
- deps: [StateProvider, ApiServiceAbstraction],
+ deps: [StateProvider, ApiServiceAbstraction, NotificationsService],
}),
safeProvider({
provide: DeviceTrustToastServiceAbstraction,
diff --git a/libs/common/src/enums/notification-type.enum.ts b/libs/common/src/enums/notification-type.enum.ts
index db59fcafa69..c366af1eb61 100644
--- a/libs/common/src/enums/notification-type.enum.ts
+++ b/libs/common/src/enums/notification-type.enum.ts
@@ -24,4 +24,6 @@ export enum NotificationType {
SyncOrganizations = 17,
SyncOrganizationStatusChanged = 18,
SyncOrganizationCollectionSettingChanged = 19,
+ Notification = 20,
+ NotificationStatus = 21,
}
diff --git a/libs/common/src/platform/notifications/internal/noop-notifications.service.ts b/libs/common/src/platform/notifications/internal/noop-notifications.service.ts
index f79cabfca8a..9c2435fb1a7 100644
--- a/libs/common/src/platform/notifications/internal/noop-notifications.service.ts
+++ b/libs/common/src/platform/notifications/internal/noop-notifications.service.ts
@@ -1,9 +1,14 @@
-import { Subscription } from "rxjs";
+import { Observable, Subject, Subscription } from "rxjs";
+
+import { NotificationResponse } from "@bitwarden/common/models/response/notification.response";
+import { UserId } from "@bitwarden/common/types/guid";
import { LogService } from "../../abstractions/log.service";
import { NotificationsService } from "../notifications.service";
export class NoopNotificationsService implements NotificationsService {
+ notifications$: Observable = new Subject();
+
constructor(private logService: LogService) {}
startListening(): Subscription {
diff --git a/libs/common/src/platform/notifications/notifications.service.ts b/libs/common/src/platform/notifications/notifications.service.ts
index aa4ff2a57a6..2adc66e361f 100644
--- a/libs/common/src/platform/notifications/notifications.service.ts
+++ b/libs/common/src/platform/notifications/notifications.service.ts
@@ -1,9 +1,13 @@
-import { Subscription } from "rxjs";
+import { Observable, Subscription } from "rxjs";
+
+import { NotificationResponse } from "@bitwarden/common/models/response/notification.response";
+import { UserId } from "@bitwarden/common/types/guid";
/**
* A service offering abilities to interact with push notifications from the server.
*/
export abstract class NotificationsService {
+ abstract notifications$: Observable;
/**
* Starts automatic listening and processing of notifications, should only be called once per application,
* or you will risk notifications being processed multiple times.
diff --git a/libs/vault/src/notifications/abstractions/end-user-notification.service.ts b/libs/vault/src/notifications/abstractions/end-user-notification.service.ts
index 2ed7e1de631..fe2852994f7 100644
--- a/libs/vault/src/notifications/abstractions/end-user-notification.service.ts
+++ b/libs/vault/src/notifications/abstractions/end-user-notification.service.ts
@@ -34,13 +34,6 @@ export abstract class EndUserNotificationService {
*/
abstract markAsDeleted(notificationId: any, userId: UserId): Promise;
- /**
- * Create/update a notification in the state for the user specified within the notification.
- * @remarks This method should only be called when a notification payload is received from the web socket.
- * @param notification
- */
- abstract upsert(notification: Notification): Promise;
-
/**
* Clear all notifications from state for the given user.
* @param userId
diff --git a/libs/vault/src/notifications/services/default-end-user-notification.service.spec.ts b/libs/vault/src/notifications/services/default-end-user-notification.service.spec.ts
index ac4304998bc..1d7b2e5aa19 100644
--- a/libs/vault/src/notifications/services/default-end-user-notification.service.spec.ts
+++ b/libs/vault/src/notifications/services/default-end-user-notification.service.spec.ts
@@ -1,7 +1,8 @@
import { TestBed } from "@angular/core/testing";
-import { firstValueFrom } from "rxjs";
+import { firstValueFrom, of } from "rxjs";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
+import { NotificationsService } from "@bitwarden/common/platform/notifications";
import { StateProvider } from "@bitwarden/common/platform/state";
import { NotificationId, UserId } from "@bitwarden/common/types/guid";
import { DefaultEndUserNotificationService } from "@bitwarden/vault";
@@ -36,6 +37,12 @@ describe("End User Notification Center Service", () => {
send: mockApiSend,
},
},
+ {
+ provide: NotificationsService,
+ useValue: {
+ notifications$: of(null),
+ },
+ },
],
});
});
diff --git a/libs/vault/src/notifications/services/default-end-user-notification.service.ts b/libs/vault/src/notifications/services/default-end-user-notification.service.ts
index 517a968f8af..404cb7c75c7 100644
--- a/libs/vault/src/notifications/services/default-end-user-notification.service.ts
+++ b/libs/vault/src/notifications/services/default-end-user-notification.service.ts
@@ -1,8 +1,10 @@
import { Injectable } from "@angular/core";
-import { map, Observable, switchMap } from "rxjs";
+import { concatMap, filter, map, Observable, switchMap } from "rxjs";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
+import { NotificationType } from "@bitwarden/common/enums";
import { ListResponse } from "@bitwarden/common/models/response/list.response";
+import { NotificationsService } from "@bitwarden/common/platform/notifications";
import { StateProvider } from "@bitwarden/common/platform/state";
import { UserId } from "@bitwarden/common/types/guid";
@@ -14,12 +16,30 @@ import { NOTIFICATIONS } from "../state/end-user-notification.state";
/**
* A service for retrieving and managing notifications for end users.
*/
-@Injectable()
+@Injectable({
+ providedIn: "root",
+})
export class DefaultEndUserNotificationService implements EndUserNotificationService {
constructor(
private stateProvider: StateProvider,
private apiService: ApiService,
- ) {}
+ private defaultNotifications: NotificationsService,
+ ) {
+ this.defaultNotifications.notifications$
+ .pipe(
+ filter(
+ ([notification]) =>
+ notification.type === NotificationType.Notification ||
+ notification.type === NotificationType.NotificationStatus,
+ ),
+ concatMap(([notification, userId]) =>
+ this.updateNotificationState(userId, [
+ new NotificationViewData(notification.payload as NotificationViewResponse),
+ ]),
+ ),
+ )
+ .subscribe();
+ }
notifications$ = perUserCache$((userId: UserId): Observable => {
return this.notificationState(userId).state$.pipe(
@@ -58,8 +78,6 @@ export class DefaultEndUserNotificationService implements EndUserNotificationSer
await this.getNotifications(userId);
}
- upsert(notification: Notification): any {}
-
async clearState(userId: UserId): Promise {
await this.updateNotificationState(userId, []);
}
From 6d2231d1b2eb4d2f046eb94f0e288ba2da897e0f Mon Sep 17 00:00:00 2001
From: Github Actions
Date: Wed, 26 Mar 2025 21:20:59 +0000
Subject: [PATCH 056/113] Bumped client version(s)
---
apps/browser/package.json | 2 +-
apps/browser/src/manifest.json | 2 +-
apps/browser/src/manifest.v3.json | 2 +-
package-lock.json | 2 +-
4 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/apps/browser/package.json b/apps/browser/package.json
index 5a8ddd03b41..6ef35d88c10 100644
--- a/apps/browser/package.json
+++ b/apps/browser/package.json
@@ -1,6 +1,6 @@
{
"name": "@bitwarden/browser",
- "version": "2025.3.1",
+ "version": "2025.3.2",
"scripts": {
"build": "npm run build:chrome",
"build:chrome": "cross-env BROWSER=chrome MANIFEST_VERSION=3 NODE_OPTIONS=\"--max-old-space-size=8192\" webpack",
diff --git a/apps/browser/src/manifest.json b/apps/browser/src/manifest.json
index 4510c2f342d..af2bf72b7b5 100644
--- a/apps/browser/src/manifest.json
+++ b/apps/browser/src/manifest.json
@@ -2,7 +2,7 @@
"manifest_version": 2,
"name": "__MSG_extName__",
"short_name": "__MSG_appName__",
- "version": "2025.3.1",
+ "version": "2025.3.2",
"description": "__MSG_extDesc__",
"default_locale": "en",
"author": "Bitwarden Inc.",
diff --git a/apps/browser/src/manifest.v3.json b/apps/browser/src/manifest.v3.json
index fc897c1b1c3..131724e38f9 100644
--- a/apps/browser/src/manifest.v3.json
+++ b/apps/browser/src/manifest.v3.json
@@ -3,7 +3,7 @@
"minimum_chrome_version": "102.0",
"name": "__MSG_extName__",
"short_name": "__MSG_appName__",
- "version": "2025.3.1",
+ "version": "2025.3.2",
"description": "__MSG_extDesc__",
"default_locale": "en",
"author": "Bitwarden Inc.",
diff --git a/package-lock.json b/package-lock.json
index 7331fcdde44..19b1772e4bc 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -189,7 +189,7 @@
},
"apps/browser": {
"name": "@bitwarden/browser",
- "version": "2025.3.1"
+ "version": "2025.3.2"
},
"apps/cli": {
"name": "@bitwarden/cli",
From 4734cab9a6374e0b2647eadfa01aa9e83f5a1a7e Mon Sep 17 00:00:00 2001
From: Vicki League
Date: Wed, 26 Mar 2025 17:26:05 -0400
Subject: [PATCH 057/113] [CL-550] Fix popup layout padding and adjust views
with extra bottom space (#13562)
* [CL-550] Fix popup layout padding and adjust views with extra bottom space
* simplify css
* fix add/edit item page
* cr changes
* fix failing test
---------
Co-authored-by: Will Martin
---
.../popup/settings/account-security.component.html | 2 +-
.../autofill/popup/settings/autofill.component.html | 4 ++--
.../popup/settings/notifications.component.html | 2 +-
.../platform/popup/layout/popup-page.component.html | 2 +-
.../components/options/send-options.component.html | 2 +-
.../send-details/send-details.component.html | 2 +-
.../send-list-items-container.component.html | 2 +-
.../additional-options-section.component.html | 10 ++++++++--
.../additional-options-section.component.spec.ts | 6 ++++--
.../additional-options-section.component.ts | 4 +++-
.../cipher-form/components/cipher-form.component.html | 4 +++-
.../custom-fields/custom-fields.component.html | 2 +-
.../custom-fields/custom-fields.component.ts | 3 +++
.../item-history/item-history-v2.component.html | 2 +-
14 files changed, 31 insertions(+), 16 deletions(-)
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 3c5fd7a6af8..b8252aa6e13 100644
--- a/apps/browser/src/auth/popup/settings/account-security.component.html
+++ b/apps/browser/src/auth/popup/settings/account-security.component.html
@@ -95,7 +95,7 @@
-
+
{{ "otherOptions" | i18n }}
diff --git a/apps/browser/src/autofill/popup/settings/autofill.component.html b/apps/browser/src/autofill/popup/settings/autofill.component.html
index 340197f6bf3..c690eb3d2ca 100644
--- a/apps/browser/src/autofill/popup/settings/autofill.component.html
+++ b/apps/browser/src/autofill/popup/settings/autofill.component.html
@@ -209,7 +209,7 @@
-
+
{{ "additionalOptions" | i18n }}
@@ -270,7 +270,7 @@
-
+
{{ "blockedDomains" | i18n }}
diff --git a/apps/browser/src/autofill/popup/settings/notifications.component.html b/apps/browser/src/autofill/popup/settings/notifications.component.html
index c6446012d0c..385db8c059b 100644
--- a/apps/browser/src/autofill/popup/settings/notifications.component.html
+++ b/apps/browser/src/autofill/popup/settings/notifications.component.html
@@ -47,7 +47,7 @@
-
+
{{ "excludedDomains" | i18n }}
diff --git a/apps/browser/src/platform/popup/layout/popup-page.component.html b/apps/browser/src/platform/popup/layout/popup-page.component.html
index 94f0846a852..2313b942a38 100644
--- a/apps/browser/src/platform/popup/layout/popup-page.component.html
+++ b/apps/browser/src/platform/popup/layout/popup-page.component.html
@@ -19,7 +19,7 @@
[ngClass]="{ 'tw-invisible': loading }"
>
diff --git a/libs/tools/send/send-ui/src/send-form/components/options/send-options.component.html b/libs/tools/send/send-ui/src/send-form/components/options/send-options.component.html
index 322fea94e3a..b9edf8eebcc 100644
--- a/libs/tools/send/send-ui/src/send-form/components/options/send-options.component.html
+++ b/libs/tools/send/send-ui/src/send-form/components/options/send-options.component.html
@@ -1,4 +1,4 @@
-
+
{{ "additionalOptions" | i18n }}
diff --git a/libs/tools/send/send-ui/src/send-form/components/send-details/send-details.component.html b/libs/tools/send/send-ui/src/send-form/components/send-details/send-details.component.html
index 06b0f1a55df..214c978ad48 100644
--- a/libs/tools/send/send-ui/src/send-form/components/send-details/send-details.component.html
+++ b/libs/tools/send/send-ui/src/send-form/components/send-details/send-details.component.html
@@ -1,4 +1,4 @@
-
+
{{ "sendDetails" | i18n }}
diff --git a/libs/tools/send/send-ui/src/send-list-items-container/send-list-items-container.component.html b/libs/tools/send/send-ui/src/send-list-items-container/send-list-items-container.component.html
index d244be15087..4b0e5970606 100644
--- a/libs/tools/send/send-ui/src/send-list-items-container/send-list-items-container.component.html
+++ b/libs/tools/send/send-ui/src/send-list-items-container/send-list-items-container.component.html
@@ -1,4 +1,4 @@
- 0">
+ 0" disableMargin>
{{ headerText }}
diff --git a/libs/vault/src/cipher-form/components/additional-options/additional-options-section.component.html b/libs/vault/src/cipher-form/components/additional-options/additional-options-section.component.html
index c00f51c8eba..e1eedb36e85 100644
--- a/libs/vault/src/cipher-form/components/additional-options/additional-options-section.component.html
+++ b/libs/vault/src/cipher-form/components/additional-options/additional-options-section.component.html
@@ -1,4 +1,7 @@
-
+
{{ "additionalOptions" | i18n }}
@@ -29,4 +32,7 @@
-
+
diff --git a/libs/vault/src/cipher-form/components/additional-options/additional-options-section.component.spec.ts b/libs/vault/src/cipher-form/components/additional-options/additional-options-section.component.spec.ts
index 705c170933a..f1c8085ae15 100644
--- a/libs/vault/src/cipher-form/components/additional-options/additional-options-section.component.spec.ts
+++ b/libs/vault/src/cipher-form/components/additional-options/additional-options-section.component.spec.ts
@@ -1,4 +1,4 @@
-import { Component } from "@angular/core";
+import { Component, Input } from "@angular/core";
import { ComponentFixture, TestBed } from "@angular/core/testing";
import { mock, MockProxy } from "jest-mock-extended";
import { BehaviorSubject } from "rxjs";
@@ -18,7 +18,9 @@ import { AdditionalOptionsSectionComponent } from "./additional-options-section.
selector: "vault-custom-fields",
template: "",
})
-class MockCustomFieldsComponent {}
+class MockCustomFieldsComponent {
+ @Input() disableSectionMargin: boolean;
+}
describe("AdditionalOptionsSectionComponent", () => {
let component: AdditionalOptionsSectionComponent;
diff --git a/libs/vault/src/cipher-form/components/additional-options/additional-options-section.component.ts b/libs/vault/src/cipher-form/components/additional-options/additional-options-section.component.ts
index 9c619ca2f84..4d72a89e4e1 100644
--- a/libs/vault/src/cipher-form/components/additional-options/additional-options-section.component.ts
+++ b/libs/vault/src/cipher-form/components/additional-options/additional-options-section.component.ts
@@ -1,7 +1,7 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { CommonModule } from "@angular/common";
-import { ChangeDetectorRef, Component, OnInit, ViewChild } from "@angular/core";
+import { ChangeDetectorRef, Component, Input, OnInit, ViewChild } from "@angular/core";
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
import { FormBuilder, ReactiveFormsModule } from "@angular/forms";
import { shareReplay } from "rxjs";
@@ -59,6 +59,8 @@ export class AdditionalOptionsSectionComponent implements OnInit {
/** True when the form is in `partial-edit` mode */
isPartialEdit = false;
+ @Input() disableSectionMargin: boolean;
+
constructor(
private cipherFormContainer: CipherFormContainer,
private formBuilder: FormBuilder,
diff --git a/libs/vault/src/cipher-form/components/cipher-form.component.html b/libs/vault/src/cipher-form/components/cipher-form.component.html
index 2644741385b..6b327486c47 100644
--- a/libs/vault/src/cipher-form/components/cipher-form.component.html
+++ b/libs/vault/src/cipher-form/components/cipher-form.component.html
@@ -28,7 +28,9 @@
[originalCipherView]="originalCipherView"
>
-
+
diff --git a/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.html b/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.html
index c7c5f4a930e..3bce3c5f385 100644
--- a/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.html
+++ b/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.html
@@ -1,4 +1,4 @@
-
+
{{ "customFields" | i18n }}
diff --git a/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.ts b/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.ts
index bdb96f4327d..083615798b6 100644
--- a/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.ts
+++ b/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.ts
@@ -11,6 +11,7 @@ import {
ElementRef,
EventEmitter,
inject,
+ Input,
OnInit,
Output,
QueryList,
@@ -94,6 +95,8 @@ export class CustomFieldsComponent implements OnInit, AfterViewInit {
@ViewChildren("customFieldRow") customFieldRows: QueryList>;
+ @Input() disableSectionMargin: boolean;
+
customFieldsForm = this.formBuilder.group({
fields: new FormArray([]),
});
diff --git a/libs/vault/src/cipher-view/item-history/item-history-v2.component.html b/libs/vault/src/cipher-view/item-history/item-history-v2.component.html
index c069e36dde1..9395fb34fc7 100644
--- a/libs/vault/src/cipher-view/item-history/item-history-v2.component.html
+++ b/libs/vault/src/cipher-view/item-history/item-history-v2.component.html
@@ -1,4 +1,4 @@
-
+
{{ "itemHistory" | i18n }}
From e60ea3497ac3fa90264b2a2d8bf10463dd0bc8bd Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E2=9C=A8=20Audrey=20=E2=9C=A8?=
Date: Wed, 26 Mar 2025 18:25:32 -0400
Subject: [PATCH 058/113] [PM-19571] do not import ssh keys when rehydrating
exported data (#14012)
---
libs/common/src/models/export/ssh-key.export.ts | 7 +++----
1 file changed, 3 insertions(+), 4 deletions(-)
diff --git a/libs/common/src/models/export/ssh-key.export.ts b/libs/common/src/models/export/ssh-key.export.ts
index 5387daf7dd0..15cd3015160 100644
--- a/libs/common/src/models/export/ssh-key.export.ts
+++ b/libs/common/src/models/export/ssh-key.export.ts
@@ -26,10 +26,9 @@ export class SshKeyExport {
}
static toDomain(req: SshKeyExport, domain = new SshKeyDomain()) {
- const parsedKey = import_ssh_key(req.privateKey);
- domain.privateKey = new EncString(parsedKey.privateKey);
- domain.publicKey = new EncString(parsedKey.publicKey);
- domain.keyFingerprint = new EncString(parsedKey.fingerprint);
+ domain.privateKey = new EncString(req.privateKey);
+ domain.publicKey = new EncString(req.publicKey);
+ domain.keyFingerprint = new EncString(req.keyFingerprint);
return domain;
}
From 9eeeac24d4ac6b288dea2b43d2432080f244b34a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E2=9C=A8=20Audrey=20=E2=9C=A8?=
Date: Wed, 26 Mar 2025 18:37:24 -0400
Subject: [PATCH 059/113] =?UTF-8?q?Revert=20"[PM-19571]=20do=20not=20impor?=
=?UTF-8?q?t=20ssh=20keys=20when=20rehydrating=20exported=20data=20(#14?=
=?UTF-8?q?=E2=80=A6"=20(#14013)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
This reverts commit e60ea3497ac3fa90264b2a2d8bf10463dd0bc8bd.
---
libs/common/src/models/export/ssh-key.export.ts | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/libs/common/src/models/export/ssh-key.export.ts b/libs/common/src/models/export/ssh-key.export.ts
index 15cd3015160..5387daf7dd0 100644
--- a/libs/common/src/models/export/ssh-key.export.ts
+++ b/libs/common/src/models/export/ssh-key.export.ts
@@ -26,9 +26,10 @@ export class SshKeyExport {
}
static toDomain(req: SshKeyExport, domain = new SshKeyDomain()) {
- domain.privateKey = new EncString(req.privateKey);
- domain.publicKey = new EncString(req.publicKey);
- domain.keyFingerprint = new EncString(req.keyFingerprint);
+ const parsedKey = import_ssh_key(req.privateKey);
+ domain.privateKey = new EncString(parsedKey.privateKey);
+ domain.publicKey = new EncString(parsedKey.publicKey);
+ domain.keyFingerprint = new EncString(parsedKey.fingerprint);
return domain;
}
From 07245d790f1eee23b8ae97e477ba1877199f843c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E2=9C=A8=20Audrey=20=E2=9C=A8?=
Date: Thu, 27 Mar 2025 09:32:10 -0400
Subject: [PATCH 060/113] [PM-19571] do not import ssh keys when rehydrating
exported data (#14014)
* do not import ssh keys when rehydrating exported data
* Remove import_ssh_key sdk function from toView
---------
Co-authored-by: Daniel James Smith
---
libs/common/src/models/export/ssh-key.export.ts | 15 ++++++---------
1 file changed, 6 insertions(+), 9 deletions(-)
diff --git a/libs/common/src/models/export/ssh-key.export.ts b/libs/common/src/models/export/ssh-key.export.ts
index 5387daf7dd0..e82cb38b6d4 100644
--- a/libs/common/src/models/export/ssh-key.export.ts
+++ b/libs/common/src/models/export/ssh-key.export.ts
@@ -1,6 +1,5 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
-import { import_ssh_key } from "@bitwarden/sdk-internal";
import { EncString } from "../../platform/models/domain/enc-string";
import { SshKey as SshKeyDomain } from "../../vault/models/domain/ssh-key";
@@ -18,18 +17,16 @@ export class SshKeyExport {
}
static toView(req: SshKeyExport, view = new SshKeyView()) {
- const parsedKey = import_ssh_key(req.privateKey);
- view.privateKey = parsedKey.privateKey;
- view.publicKey = parsedKey.publicKey;
- view.keyFingerprint = parsedKey.fingerprint;
+ view.privateKey = req.privateKey;
+ view.publicKey = req.publicKey;
+ view.keyFingerprint = req.keyFingerprint;
return view;
}
static toDomain(req: SshKeyExport, domain = new SshKeyDomain()) {
- const parsedKey = import_ssh_key(req.privateKey);
- domain.privateKey = new EncString(parsedKey.privateKey);
- domain.publicKey = new EncString(parsedKey.publicKey);
- domain.keyFingerprint = new EncString(parsedKey.fingerprint);
+ domain.privateKey = new EncString(req.privateKey);
+ domain.publicKey = new EncString(req.publicKey);
+ domain.keyFingerprint = new EncString(req.keyFingerprint);
return domain;
}
From a80d289b1bfe582cbf84b98de90c37e813c83cba Mon Sep 17 00:00:00 2001
From: Bernd Schoolmann
Date: Thu, 27 Mar 2025 15:10:06 +0100
Subject: [PATCH 061/113] [PM-17809] Fix org-owned ssh showing up in ssh list
request (#13175)
* Fix org keys showing up in list request
* Change from tripleequal to double equal
---
apps/desktop/src/autofill/services/ssh-agent.service.ts | 7 +++++--
1 file changed, 5 insertions(+), 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 bf7167c0240..6522ef19500 100644
--- a/apps/desktop/src/autofill/services/ssh-agent.service.ts
+++ b/apps/desktop/src/autofill/services/ssh-agent.service.ts
@@ -143,7 +143,10 @@ export class SshAgentService implements OnDestroy {
if (isListRequest) {
const sshCiphers = ciphers.filter(
- (cipher) => cipher.type === CipherType.SshKey && !cipher.isDeleted,
+ (cipher) =>
+ cipher.type === CipherType.SshKey &&
+ !cipher.isDeleted &&
+ cipher.organizationId == null,
);
const keys = sshCiphers.map((cipher) => {
return {
@@ -247,7 +250,7 @@ export class SshAgentService implements OnDestroy {
(cipher) =>
cipher.type === CipherType.SshKey &&
!cipher.isDeleted &&
- cipher.organizationId === null,
+ cipher.organizationId == null,
);
const keys = sshCiphers.map((cipher) => {
return {
From 1887b75c773441b1c77ea1043f4388de4a2f71e9 Mon Sep 17 00:00:00 2001
From: Brandon Treston
Date: Thu, 27 Mar 2025 10:17:11 -0400
Subject: [PATCH 062/113] fix width (#14005)
---
.../organizations/collections/vault.component.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/apps/web/src/app/admin-console/organizations/collections/vault.component.html b/apps/web/src/app/admin-console/organizations/collections/vault.component.html
index 65cd26bafee..604d326bf37 100644
--- a/apps/web/src/app/admin-console/organizations/collections/vault.component.html
+++ b/apps/web/src/app/admin-console/organizations/collections/vault.component.html
@@ -54,7 +54,7 @@
(searchTextChanged)="filterSearchText($event)"
>
-
+
Date: Thu, 27 Mar 2025 10:33:16 -0400
Subject: [PATCH 063/113] [PM-12035] Vault filter updates to use
SingleUserState (#13641)
* vault filter use SingleUserState
* fixing tests
* Changes so that userId is passed to service, instead of access in service
* passing activeUserId from the components to service
* Sugggested changes
* updating functions to be abstract on vault-filter.service
* updating all functions to be abstract on vault filter service
---
.../vault-filter/vault-filter.component.ts | 4 +--
.../components/vault-filter.component.ts | 4 ++-
.../abstractions/vault-filter.service.ts | 20 ++++++++------
.../services/vault-filter.service.spec.ts | 18 ++++++-------
.../services/vault-filter.service.ts | 27 +++++++++++--------
.../vault-filter-section.component.ts | 9 +++++--
6 files changed, 49 insertions(+), 33 deletions(-)
diff --git a/apps/web/src/app/admin-console/organizations/collections/vault-filter/vault-filter.component.ts b/apps/web/src/app/admin-console/organizations/collections/vault-filter/vault-filter.component.ts
index 43d8f910d0f..384390d738e 100644
--- a/apps/web/src/app/admin-console/organizations/collections/vault-filter/vault-filter.component.ts
+++ b/apps/web/src/app/admin-console/organizations/collections/vault-filter/vault-filter.component.ts
@@ -89,8 +89,8 @@ export class VaultFilterComponent
const collapsedNodes = await firstValueFrom(this.vaultFilterService.collapsedFilterNodes$);
collapsedNodes.delete("AllCollections");
-
- await this.vaultFilterService.setCollapsedFilterNodes(collapsedNodes);
+ const userId = await firstValueFrom(this.activeUserId$);
+ await this.vaultFilterService.setCollapsedFilterNodes(collapsedNodes, userId);
}
protected async addCollectionFilter(): Promise {
diff --git a/apps/web/src/app/vault/individual-vault/vault-filter/components/vault-filter.component.ts b/apps/web/src/app/vault/individual-vault/vault-filter/components/vault-filter.component.ts
index 0a168157705..3f1e7755c8f 100644
--- a/apps/web/src/app/vault/individual-vault/vault-filter/components/vault-filter.component.ts
+++ b/apps/web/src/app/vault/individual-vault/vault-filter/components/vault-filter.component.ts
@@ -94,6 +94,7 @@ export class VaultFilterComponent implements OnInit, OnDestroy {
}
private trialFlowService = inject(TrialFlowService);
+ protected activeUserId$ = this.accountService.activeAccount$.pipe(getUserId);
constructor(
protected vaultFilterService: VaultFilterService,
@@ -162,7 +163,8 @@ export class VaultFilterComponent implements OnInit, OnDestroy {
filter.selectedOrganizationNode = orgNode;
}
this.vaultFilterService.setOrganizationFilter(orgNode.node);
- await this.vaultFilterService.expandOrgFilter();
+ const userId = await firstValueFrom(this.activeUserId$);
+ await this.vaultFilterService.expandOrgFilter(userId);
};
applyTypeFilter = async (filterNode: TreeNode): Promise => {
diff --git a/apps/web/src/app/vault/individual-vault/vault-filter/services/abstractions/vault-filter.service.ts b/apps/web/src/app/vault/individual-vault/vault-filter/services/abstractions/vault-filter.service.ts
index b8494c8aa54..0e3ee69a2c6 100644
--- a/apps/web/src/app/vault/individual-vault/vault-filter/services/abstractions/vault-filter.service.ts
+++ b/apps/web/src/app/vault/individual-vault/vault-filter/services/abstractions/vault-filter.service.ts
@@ -4,6 +4,7 @@ import { Observable } from "rxjs";
import { CollectionAdminView, CollectionView } from "@bitwarden/admin-console/common";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
+import { UserId } from "@bitwarden/common/types/guid";
import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node";
import { FolderView } from "@bitwarden/common/vault/models/view/folder.view";
@@ -22,16 +23,19 @@ export abstract class VaultFilterService {
folderTree$: Observable>;
collectionTree$: Observable>;
cipherTypeTree$: Observable>;
- getCollectionNodeFromTree: (id: string) => Promise>;
- setCollapsedFilterNodes: (collapsedFilterNodes: Set) => Promise;
- expandOrgFilter: () => Promise;
- getOrganizationFilter: () => Observable;
- setOrganizationFilter: (organization: Organization) => void;
- buildTypeTree: (
+ abstract getCollectionNodeFromTree: (id: string) => Promise>;
+ abstract setCollapsedFilterNodes: (
+ collapsedFilterNodes: Set,
+ userId: UserId,
+ ) => Promise;
+ abstract expandOrgFilter: (userId: UserId) => Promise;
+ abstract getOrganizationFilter: () => Observable;
+ abstract setOrganizationFilter: (organization: Organization) => void;
+ abstract buildTypeTree: (
head: CipherTypeFilter,
array: CipherTypeFilter[],
) => Observable>;
// TODO: Remove this from org vault when collection admin service adopts state management
- reloadCollections?: (collections: CollectionAdminView[]) => void;
- clearOrganizationFilter: () => void;
+ abstract reloadCollections?: (collections: CollectionAdminView[]) => void;
+ abstract clearOrganizationFilter: () => void;
}
diff --git a/apps/web/src/app/vault/individual-vault/vault-filter/services/vault-filter.service.spec.ts b/apps/web/src/app/vault/individual-vault/vault-filter/services/vault-filter.service.spec.ts
index f56931fa987..559d0cc60c5 100644
--- a/apps/web/src/app/vault/individual-vault/vault-filter/services/vault-filter.service.spec.ts
+++ b/apps/web/src/app/vault/individual-vault/vault-filter/services/vault-filter.service.spec.ts
@@ -2,7 +2,7 @@ import {
FakeAccountService,
mockAccountServiceWith,
} from "@bitwarden/common/../spec/fake-account-service";
-import { FakeActiveUserState } from "@bitwarden/common/../spec/fake-state";
+import { FakeSingleUserState } from "@bitwarden/common/../spec/fake-state";
import { FakeStateProvider } from "@bitwarden/common/../spec/fake-state-provider";
import { mock, MockProxy } from "jest-mock-extended";
import { firstValueFrom, ReplaySubject } from "rxjs";
@@ -42,7 +42,7 @@ describe("vault filter service", () => {
const mockUserId = Utils.newGuid() as UserId;
let accountService: FakeAccountService;
- let collapsedGroupingsState: FakeActiveUserState;
+ let collapsedGroupingsState: FakeSingleUserState;
beforeEach(() => {
organizationService = mock();
@@ -83,21 +83,21 @@ describe("vault filter service", () => {
collectionService,
accountService,
);
- collapsedGroupingsState = stateProvider.activeUser.getFake(COLLAPSED_GROUPINGS);
+ collapsedGroupingsState = stateProvider.singleUser.getFake(mockUserId, COLLAPSED_GROUPINGS);
});
describe("collapsed filter nodes", () => {
const nodes = new Set(["1", "2"]);
it("should update the collapsedFilterNodes$", async () => {
- await vaultFilterService.setCollapsedFilterNodes(nodes);
+ await vaultFilterService.setCollapsedFilterNodes(nodes, mockUserId);
- const collapsedGroupingsState = stateProvider.activeUser.getFake(COLLAPSED_GROUPINGS);
- expect(await firstValueFrom(collapsedGroupingsState.state$)).toEqual(Array.from(nodes));
- expect(collapsedGroupingsState.nextMock).toHaveBeenCalledWith([
+ const collapsedGroupingsState = stateProvider.singleUser.getFake(
mockUserId,
- Array.from(nodes),
- ]);
+ COLLAPSED_GROUPINGS,
+ );
+ expect(await firstValueFrom(collapsedGroupingsState.state$)).toEqual(Array.from(nodes));
+ expect(collapsedGroupingsState.nextMock).toHaveBeenCalledWith(Array.from(nodes));
});
it("loads from state on initialization", async () => {
diff --git a/apps/web/src/app/vault/individual-vault/vault-filter/services/vault-filter.service.ts b/apps/web/src/app/vault/individual-vault/vault-filter/services/vault-filter.service.ts
index f3e4441af9f..058c84517cb 100644
--- a/apps/web/src/app/vault/individual-vault/vault-filter/services/vault-filter.service.ts
+++ b/apps/web/src/app/vault/individual-vault/vault-filter/services/vault-filter.service.ts
@@ -23,8 +23,10 @@ import { PolicyService } from "@bitwarden/common/admin-console/abstractions/poli
import { PolicyType } from "@bitwarden/common/admin-console/enums";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
+import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
-import { ActiveUserState, StateProvider } from "@bitwarden/common/platform/state";
+import { SingleUserState, StateProvider } from "@bitwarden/common/platform/state";
+import { UserId } from "@bitwarden/common/types/guid";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
import { CipherType } from "@bitwarden/common/vault/enums";
@@ -47,12 +49,17 @@ const NestingDelimiter = "/";
@Injectable()
export class VaultFilterService implements VaultFilterServiceAbstraction {
- private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id));
+ protected activeUserId$ = this.accountService.activeAccount$.pipe(getUserId);
memberOrganizations$ = this.activeUserId$.pipe(
switchMap((id) => this.organizationService.memberOrganizations$(id)),
);
+ collapsedFilterNodes$ = this.activeUserId$.pipe(
+ switchMap((id) => this.collapsedGroupingsState(id).state$),
+ map((state) => new Set(state)),
+ );
+
organizationTree$: Observable> = combineLatest([
this.memberOrganizations$,
this.activeUserId$.pipe(
@@ -103,11 +110,9 @@ export class VaultFilterService implements VaultFilterServiceAbstraction {
cipherTypeTree$: Observable> = this.buildCipherTypeTree();
- private collapsedGroupingsState: ActiveUserState =
- this.stateProvider.getActive(COLLAPSED_GROUPINGS);
-
- readonly collapsedFilterNodes$: Observable> =
- this.collapsedGroupingsState.state$.pipe(map((c) => new Set(c)));
+ private collapsedGroupingsState(userId: UserId): SingleUserState {
+ return this.stateProvider.getUser(userId, COLLAPSED_GROUPINGS);
+ }
constructor(
protected organizationService: OrganizationService,
@@ -125,8 +130,8 @@ export class VaultFilterService implements VaultFilterServiceAbstraction {
return ServiceUtils.getTreeNodeObject(collections, id) as TreeNode;
}
- async setCollapsedFilterNodes(collapsedFilterNodes: Set): Promise {
- await this.collapsedGroupingsState.update(() => Array.from(collapsedFilterNodes));
+ async setCollapsedFilterNodes(collapsedFilterNodes: Set, userId: UserId): Promise {
+ await this.collapsedGroupingsState(userId).update(() => Array.from(collapsedFilterNodes));
}
protected async getCollapsedFilterNodes(): Promise> {
@@ -149,13 +154,13 @@ export class VaultFilterService implements VaultFilterServiceAbstraction {
}
}
- async expandOrgFilter() {
+ async expandOrgFilter(userId: UserId) {
const collapsedFilterNodes = await firstValueFrom(this.collapsedFilterNodes$);
if (!collapsedFilterNodes.has("AllVaults")) {
return;
}
collapsedFilterNodes.delete("AllVaults");
- await this.setCollapsedFilterNodes(collapsedFilterNodes);
+ await this.setCollapsedFilterNodes(collapsedFilterNodes, userId);
}
protected async buildOrganizationTree(
diff --git a/apps/web/src/app/vault/individual-vault/vault-filter/shared/components/vault-filter-section.component.ts b/apps/web/src/app/vault/individual-vault/vault-filter/shared/components/vault-filter-section.component.ts
index b231200a7cb..41329319805 100644
--- a/apps/web/src/app/vault/individual-vault/vault-filter/shared/components/vault-filter-section.component.ts
+++ b/apps/web/src/app/vault/individual-vault/vault-filter/shared/components/vault-filter-section.component.ts
@@ -1,10 +1,12 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { Component, InjectionToken, Injector, Input, OnDestroy, OnInit } from "@angular/core";
-import { Observable, Subject, takeUntil } from "rxjs";
+import { firstValueFrom, Observable, Subject, takeUntil } from "rxjs";
import { map } from "rxjs/operators";
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 { ITreeNodeObject, TreeNode } from "@bitwarden/common/vault/models/domain/tree-node";
import { VaultFilterService } from "../../services/abstractions/vault-filter.service";
@@ -17,6 +19,7 @@ import { VaultFilter } from "../models/vault-filter.model";
})
export class VaultFilterSectionComponent implements OnInit, OnDestroy {
private destroy$ = new Subject();
+ private activeUserId$ = getUserId(this.accountService.activeAccount$);
@Input() activeFilter: VaultFilter;
@Input() section: VaultFilterSection;
@@ -29,6 +32,7 @@ export class VaultFilterSectionComponent implements OnInit, OnDestroy {
constructor(
private vaultFilterService: VaultFilterService,
private injector: Injector,
+ private accountService: AccountService,
) {
this.vaultFilterService.collapsedFilterNodes$
.pipe(takeUntil(this.destroy$))
@@ -126,7 +130,8 @@ export class VaultFilterSectionComponent implements OnInit, OnDestroy {
} else {
this.collapsedFilterNodes.add(node.id);
}
- await this.vaultFilterService.setCollapsedFilterNodes(this.collapsedFilterNodes);
+ const userId = await firstValueFrom(this.activeUserId$);
+ await this.vaultFilterService.setCollapsedFilterNodes(this.collapsedFilterNodes, userId);
}
// an injector is necessary to pass data into a dynamic component
From b6e9596c84ee83f7b77863a091c8f8d677bded1c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Rui=20Tom=C3=A9?=
<108268980+r-tome@users.noreply.github.com>
Date: Thu, 27 Mar 2025 14:53:32 +0000
Subject: [PATCH 064/113] [PM-18523] Make the External ID in the Group dialog
conditional (#14006)
* Add SsoExternalIdVisibility feature flag
* Implement visibility logic for Group external ID based on SsoExternalIdVisibility feature flag
---
.../organizations/manage/group-add-edit.component.html | 2 +-
.../organizations/manage/group-add-edit.component.ts | 7 +++++++
libs/common/src/enums/feature-flag.enum.ts | 2 ++
3 files changed, 10 insertions(+), 1 deletion(-)
diff --git a/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.html b/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.html
index 9fb8b245f72..5c8c0c07f88 100644
--- a/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.html
+++ b/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.html
@@ -23,7 +23,7 @@
{{ "characterMaximum" | i18n: 100 }}
-
+
{{ "externalId" | i18n }}
{{ "externalIdDesc" | i18n }}
diff --git a/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.ts b/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.ts
index 330ffe86f0b..d1a9d4919e5 100644
--- a/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.ts
+++ b/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.ts
@@ -29,7 +29,9 @@ import {
} 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 { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
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";
@@ -215,6 +217,10 @@ export class GroupAddEditComponent implements OnInit, OnDestroy {
this.groupDetails$,
]).pipe(map(([allowAdminAccess, groupDetails]) => !allowAdminAccess && groupDetails != null));
+ protected isExternalIdVisible$ = this.configService
+ .getFeatureFlag$(FeatureFlag.SsoExternalIdVisibility)
+ .pipe(map((isEnabled) => !isEnabled || !!this.groupForm.get("externalId")?.value));
+
constructor(
@Inject(DIALOG_DATA) private params: GroupAddEditDialogParams,
private dialogRef: DialogRef,
@@ -231,6 +237,7 @@ export class GroupAddEditComponent implements OnInit, OnDestroy {
private accountService: AccountService,
private collectionAdminService: CollectionAdminService,
private toastService: ToastService,
+ private configService: ConfigService,
) {
this.tabIndex = params.initialTab ?? GroupAddEditTabType.Info;
}
diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts
index e3c15a1d40b..c5119cd5206 100644
--- a/libs/common/src/enums/feature-flag.enum.ts
+++ b/libs/common/src/enums/feature-flag.enum.ts
@@ -8,6 +8,7 @@ export enum FeatureFlag {
AccountDeprovisioning = "pm-10308-account-deprovisioning",
VerifiedSsoDomainEndpoint = "pm-12337-refactor-sso-details-endpoint",
LimitItemDeletion = "pm-15493-restrict-item-deletion-to-can-manage-permission",
+ SsoExternalIdVisibility = "pm-18630-sso-external-id-visibility",
/* Autofill */
BlockBrowserInjectionsByDomain = "block-browser-injections-by-domain",
@@ -68,6 +69,7 @@ export const DefaultFeatureFlagValue = {
[FeatureFlag.AccountDeprovisioning]: FALSE,
[FeatureFlag.VerifiedSsoDomainEndpoint]: FALSE,
[FeatureFlag.LimitItemDeletion]: FALSE,
+ [FeatureFlag.SsoExternalIdVisibility]: FALSE,
/* Autofill */
[FeatureFlag.BlockBrowserInjectionsByDomain]: FALSE,
From 96558074e2b3e01031ab32bba4fab20a08579568 Mon Sep 17 00:00:00 2001
From: Todd Martin <106564991+trmartin4@users.noreply.github.com>
Date: Thu, 27 Mar 2025 12:14:16 -0400
Subject: [PATCH 065/113] Updated short name to no longer be localized (#14022)
---
apps/browser/src/manifest.json | 2 +-
apps/browser/src/manifest.v3.json | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/apps/browser/src/manifest.json b/apps/browser/src/manifest.json
index af2bf72b7b5..07aa3d2e4a9 100644
--- a/apps/browser/src/manifest.json
+++ b/apps/browser/src/manifest.json
@@ -1,7 +1,7 @@
{
"manifest_version": 2,
"name": "__MSG_extName__",
- "short_name": "__MSG_appName__",
+ "short_name": "Bitwarden",
"version": "2025.3.2",
"description": "__MSG_extDesc__",
"default_locale": "en",
diff --git a/apps/browser/src/manifest.v3.json b/apps/browser/src/manifest.v3.json
index 131724e38f9..be1a3f17827 100644
--- a/apps/browser/src/manifest.v3.json
+++ b/apps/browser/src/manifest.v3.json
@@ -2,7 +2,7 @@
"manifest_version": 3,
"minimum_chrome_version": "102.0",
"name": "__MSG_extName__",
- "short_name": "__MSG_appName__",
+ "short_name": "Bitwarden",
"version": "2025.3.2",
"description": "__MSG_extDesc__",
"default_locale": "en",
From 7d3e43abd8f98ec1169f2d2b1ac148a6b7820da9 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Thu, 27 Mar 2025 12:21:27 -0400
Subject: [PATCH 066/113] [deps] Autofill: Update prettier-plugin-tailwindcss
to v0.6.11 (#13225)
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 19b1772e4bc..395cf52f05a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -158,7 +158,7 @@
"postcss": "8.5.1",
"postcss-loader": "8.1.1",
"prettier": "3.4.2",
- "prettier-plugin-tailwindcss": "0.6.10",
+ "prettier-plugin-tailwindcss": "0.6.11",
"process": "0.11.10",
"remark-gfm": "4.0.0",
"rimraf": "6.0.1",
@@ -30145,9 +30145,9 @@
}
},
"node_modules/prettier-plugin-tailwindcss": {
- "version": "0.6.10",
- "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.6.10.tgz",
- "integrity": "sha512-ndj2WLDaMzACnr1gAYZiZZLs5ZdOeBYgOsbBmHj3nvW/6q8h8PymsXiEnKvj/9qgCCAoHyvLOisoQdIcsDvIgw==",
+ "version": "0.6.11",
+ "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.6.11.tgz",
+ "integrity": "sha512-YxaYSIvZPAqhrrEpRtonnrXdghZg1irNg4qrjboCXrpybLWVs55cW2N3juhspVJiO0JBvYJT8SYsJpc8OQSnsA==",
"dev": true,
"license": "MIT",
"engines": {
diff --git a/package.json b/package.json
index 943fde5fcf3..83a0aa96fa3 100644
--- a/package.json
+++ b/package.json
@@ -120,7 +120,7 @@
"postcss": "8.5.1",
"postcss-loader": "8.1.1",
"prettier": "3.4.2",
- "prettier-plugin-tailwindcss": "0.6.10",
+ "prettier-plugin-tailwindcss": "0.6.11",
"process": "0.11.10",
"remark-gfm": "4.0.0",
"rimraf": "6.0.1",
From 93a289bfa81d1ef8cc955696f03e7272e31f39ce Mon Sep 17 00:00:00 2001
From: Jordan Aasen <166539328+jaasen-livefront@users.noreply.github.com>
Date: Thu, 27 Mar 2025 10:46:39 -0700
Subject: [PATCH 067/113] add tests (#13923)
---
.../src/vault/services/cipher.service.spec.ts | 69 ++++++++++++++-----
.../src/vault/services/cipher.service.ts | 6 +-
2 files changed, 57 insertions(+), 18 deletions(-)
diff --git a/libs/common/src/vault/services/cipher.service.spec.ts b/libs/common/src/vault/services/cipher.service.spec.ts
index def0c04dd16..dd7faea8e8a 100644
--- a/libs/common/src/vault/services/cipher.service.spec.ts
+++ b/libs/common/src/vault/services/cipher.service.spec.ts
@@ -305,51 +305,86 @@ describe("Cipher Service", () => {
});
describe("cipher.key", () => {
- it("is null when feature flag is false", async () => {
- configService.getFeatureFlag.mockResolvedValue(false);
-
+ beforeEach(() => {
keyService.getOrgKey.mockReturnValue(
Promise.resolve(new SymmetricCryptoKey(new Uint8Array(32)) as OrgKey),
);
+ });
+
+ it("is null when feature flag is false", async () => {
+ configService.getFeatureFlag.mockResolvedValue(false);
const cipher = await cipherService.encrypt(cipherView, userId);
expect(cipher.key).toBeNull();
});
- it("is defined when feature flag flag is true", async () => {
- configService.getFeatureFlag.mockResolvedValue(true);
+ describe("when feature flag is true", () => {
+ beforeEach(() => {
+ configService.getFeatureFlag.mockResolvedValue(true);
+ });
- const cipher = await cipherService.encrypt(cipherView, userId);
+ it("is null when the cipher is not viewPassword", async () => {
+ cipherView.viewPassword = false;
- expect(cipher.key).toBeDefined();
+ const cipher = await cipherService.encrypt(cipherView, userId);
+
+ expect(cipher.key).toBeNull();
+ });
+
+ it("is defined when the cipher is viewPassword", async () => {
+ cipherView.viewPassword = true;
+
+ const cipher = await cipherService.encrypt(cipherView, userId);
+
+ expect(cipher.key).toBeDefined();
+ });
});
});
describe("encryptWithCipherKey", () => {
beforeEach(() => {
jest.spyOn(cipherService, "encryptCipherWithCipherKey");
+ keyService.getOrgKey.mockReturnValue(
+ Promise.resolve(new SymmetricCryptoKey(new Uint8Array(32)) as OrgKey),
+ );
});
it("is not called when feature flag is false", async () => {
configService.getFeatureFlag.mockResolvedValue(false);
- keyService.getOrgKey.mockReturnValue(
- Promise.resolve(new SymmetricCryptoKey(new Uint8Array(32)) as OrgKey),
- );
await cipherService.encrypt(cipherView, userId);
expect(cipherService["encryptCipherWithCipherKey"]).not.toHaveBeenCalled();
});
- it("is called when feature flag is true", async () => {
- configService.getFeatureFlag.mockResolvedValue(true);
- keyService.getOrgKey.mockReturnValue(
- Promise.resolve(new SymmetricCryptoKey(new Uint8Array(32)) as OrgKey),
- );
+ describe("when feature flag is true", () => {
+ beforeEach(() => {
+ configService.getFeatureFlag.mockResolvedValue(true);
+ });
- await cipherService.encrypt(cipherView, userId);
+ it("is called when cipher viewPassword is true", async () => {
+ cipherView.viewPassword = true;
- expect(cipherService["encryptCipherWithCipherKey"]).toHaveBeenCalled();
+ await cipherService.encrypt(cipherView, userId);
+
+ expect(cipherService["encryptCipherWithCipherKey"]).toHaveBeenCalled();
+ });
+
+ it("is not called when cipher viewPassword is false and original cipher has no key", async () => {
+ cipherView.viewPassword = false;
+
+ await cipherService.encrypt(cipherView, userId, undefined, undefined, new Cipher());
+
+ expect(cipherService["encryptCipherWithCipherKey"]).not.toHaveBeenCalled();
+ });
+
+ it("is called when cipher viewPassword is false and original cipher has a key", async () => {
+ cipherView.viewPassword = false;
+
+ await cipherService.encrypt(cipherView, userId, undefined, undefined, cipherObj);
+
+ expect(cipherService["encryptCipherWithCipherKey"]).toHaveBeenCalled();
+ });
});
});
});
diff --git a/libs/common/src/vault/services/cipher.service.ts b/libs/common/src/vault/services/cipher.service.ts
index 4876a755ed8..bb5ae710cfb 100644
--- a/libs/common/src/vault/services/cipher.service.ts
+++ b/libs/common/src/vault/services/cipher.service.ts
@@ -222,7 +222,11 @@ export class CipherService implements CipherServiceAbstraction {
cipher.reprompt = model.reprompt;
cipher.edit = model.edit;
- if (await this.getCipherKeyEncryptionEnabled()) {
+ if (
+ // prevent unprivileged users from migrating to cipher key encryption
+ (model.viewPassword || originalCipher?.key) &&
+ (await this.getCipherKeyEncryptionEnabled())
+ ) {
cipher.key = originalCipher?.key ?? null;
const userOrOrgKey = await this.getKeyForCipherKeyDecryption(cipher, userId);
// The keyForEncryption is only used for encrypting the cipher key, not the cipher itself, since cipher key encryption is enabled.
From 10695fd9719b0bdeac5d73f3a11b1b3a7f5bd464 Mon Sep 17 00:00:00 2001
From: bnagawiecki <107435978+bnagawiecki@users.noreply.github.com>
Date: Thu, 27 Mar 2025 14:20:34 -0400
Subject: [PATCH 068/113] add data-testids to 3 ssh key fields (#14024)
---
.../sshkey-view.component.html | 25 ++++++++++++++++---
1 file changed, 22 insertions(+), 3 deletions(-)
diff --git a/libs/vault/src/cipher-view/sshkey-sections/sshkey-view.component.html b/libs/vault/src/cipher-view/sshkey-sections/sshkey-view.component.html
index ee5a94249c4..20390c0a285 100644
--- a/libs/vault/src/cipher-view/sshkey-sections/sshkey-view.component.html
+++ b/libs/vault/src/cipher-view/sshkey-sections/sshkey-view.component.html
@@ -5,7 +5,14 @@
{{ "sshPrivateKey" | i18n }}
-
+
{{ "sshPublicKey" | i18n }}
-
+
{{ "sshFingerprint" | i18n }}
-
+
Date: Thu, 27 Mar 2025 11:57:24 -0700
Subject: [PATCH 069/113] [PM-16100][A11y][Extension] Usernames not being read
by screen readers like they used to be (#13800)
* update autofill a11y
* fixes to cipher item title
---
apps/browser/src/_locales/en/messages.json | 28 +++++++++++++++++++
.../autofill-vault-list-items.component.html | 2 +-
.../autofill-vault-list-items.component.ts | 16 ++++-------
.../vault-list-items-container.component.html | 4 ++-
.../vault-list-items-container.component.ts | 13 +++++----
.../vault-settings/vault-settings.service.ts | 7 +++--
6 files changed, 51 insertions(+), 19 deletions(-)
diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json
index 39541aa2f8c..995b90ec661 100644
--- a/apps/browser/src/_locales/en/messages.json
+++ b/apps/browser/src/_locales/en/messages.json
@@ -4279,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Autofill - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4289,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Copy $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/vault/popup/components/vault-v2/autofill-vault-list-items/autofill-vault-list-items.component.html b/apps/browser/src/vault/popup/components/vault-v2/autofill-vault-list-items/autofill-vault-list-items.component.html
index 40f00ab4332..19f1668eba4 100644
--- a/apps/browser/src/vault/popup/components/vault-v2/autofill-vault-list-items/autofill-vault-list-items.component.html
+++ b/apps/browser/src/vault/popup/components/vault-v2/autofill-vault-list-items/autofill-vault-list-items.component.html
@@ -7,6 +7,6 @@
[description]="(showEmptyAutofillTip$ | async) ? ('autofillSuggestionsTip' | i18n) : null"
showAutofillButton
[disableDescriptionMargin]="showEmptyAutofillTip$ | async"
- [primaryActionAutofill]="clickItemsToAutofillVaultView"
+ [primaryActionAutofill]="clickItemsToAutofillVaultView$ | async"
[groupByType]="groupByType()"
>
diff --git a/apps/browser/src/vault/popup/components/vault-v2/autofill-vault-list-items/autofill-vault-list-items.component.ts b/apps/browser/src/vault/popup/components/vault-v2/autofill-vault-list-items/autofill-vault-list-items.component.ts
index 03d84120785..72d51776f7b 100644
--- a/apps/browser/src/vault/popup/components/vault-v2/autofill-vault-list-items/autofill-vault-list-items.component.ts
+++ b/apps/browser/src/vault/popup/components/vault-v2/autofill-vault-list-items/autofill-vault-list-items.component.ts
@@ -1,7 +1,7 @@
import { CommonModule } from "@angular/common";
-import { Component, OnInit } from "@angular/core";
+import { Component } from "@angular/core";
import { toSignal } from "@angular/core/rxjs-interop";
-import { combineLatest, firstValueFrom, map, Observable } from "rxjs";
+import { combineLatest, map, Observable } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service";
@@ -33,7 +33,7 @@ import { VaultListItemsContainerComponent } from "../vault-list-items-container/
selector: "app-autofill-vault-list-items",
templateUrl: "autofill-vault-list-items.component.html",
})
-export class AutofillVaultListItemsComponent implements OnInit {
+export class AutofillVaultListItemsComponent {
/**
* The list of ciphers that can be used to autofill the current page.
* @protected
@@ -47,7 +47,9 @@ export class AutofillVaultListItemsComponent implements OnInit {
*/
protected showRefresh: boolean = BrowserPopupUtils.inSidebar(window);
- clickItemsToAutofillVaultView = false;
+ /** Flag indicating whether the login item should automatically autofill when clicked */
+ protected clickItemsToAutofillVaultView$: Observable =
+ this.vaultSettingsService.clickItemsToAutofillVaultView$;
protected groupByType = toSignal(
this.vaultPopupItemsService.hasFilterApplied$.pipe(map((hasFilter) => !hasFilter)),
@@ -84,12 +86,6 @@ export class AutofillVaultListItemsComponent implements OnInit {
// TODO: Migrate logic to show Autofill policy toast PM-8144
}
- async ngOnInit() {
- this.clickItemsToAutofillVaultView = await firstValueFrom(
- this.vaultSettingsService.clickItemsToAutofillVaultView$,
- );
- }
-
/**
* Refreshes the current tab to re-populate the autofill ciphers.
* @protected
diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.html b/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.html
index cbce4bf2961..a55bba622e4 100644
--- a/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.html
+++ b/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.html
@@ -99,7 +99,9 @@
type="button"
(click)="primaryActionOnSelect(cipher)"
(dblclick)="launchCipher(cipher)"
- [appA11yTitle]="cipherItemTitleKey | async | i18n: cipher.name"
+ [appA11yTitle]="
+ cipherItemTitleKey(cipher) | async | i18n: cipher.name : cipher.login.username
+ "
class="{{ itemHeightClass }}"
>
diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.ts
index 9d70c0ba236..6df1bdf8ae5 100644
--- a/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.ts
+++ b/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.ts
@@ -206,11 +206,14 @@ export class VaultListItemsContainerComponent implements OnInit, AfterViewInit {
/**
* Resolved i18n key to use for suggested cipher items
*/
- cipherItemTitleKey = this.currentURIIsBlocked$.pipe(
- map((uriIsBlocked) =>
- this.primaryActionAutofill && !uriIsBlocked ? "autofillTitle" : "viewItemTitle",
- ),
- );
+ cipherItemTitleKey = (cipher: CipherView) =>
+ this.currentURIIsBlocked$.pipe(
+ map((uriIsBlocked) => {
+ const hasUsername = cipher.login?.username != null;
+ const key = this.primaryActionAutofill && !uriIsBlocked ? "autofillTitle" : "viewItemTitle";
+ return hasUsername ? `${key}WithField` : key;
+ }),
+ );
/**
* Option to show the autofill button for each item.
diff --git a/libs/common/src/vault/services/vault-settings/vault-settings.service.ts b/libs/common/src/vault/services/vault-settings/vault-settings.service.ts
index 85ab3914158..423acba8fff 100644
--- a/libs/common/src/vault/services/vault-settings/vault-settings.service.ts
+++ b/libs/common/src/vault/services/vault-settings/vault-settings.service.ts
@@ -1,4 +1,4 @@
-import { Observable, map } from "rxjs";
+import { Observable, map, shareReplay } from "rxjs";
import { ActiveUserState, GlobalState, StateProvider } from "../../../platform/state";
import { VaultSettingsService as VaultSettingsServiceAbstraction } from "../../abstractions/vault-settings/vault-settings.service";
@@ -46,7 +46,10 @@ export class VaultSettingsService implements VaultSettingsServiceAbstraction {
* {@link VaultSettingsServiceAbstraction.clickItemsToAutofillVaultView$$}
*/
readonly clickItemsToAutofillVaultView$: Observable
=
- this.clickItemsToAutofillVaultViewState.state$.pipe(map((x) => x ?? false));
+ this.clickItemsToAutofillVaultViewState.state$.pipe(
+ map((x) => x ?? false),
+ shareReplay({ bufferSize: 1, refCount: false }),
+ );
constructor(private stateProvider: StateProvider) {}
From 79d3d41a2f37a5744c5e03106e067d938013b31f Mon Sep 17 00:00:00 2001
From: Jonathan Prusik
Date: Thu, 27 Mar 2025 17:42:14 -0400
Subject: [PATCH 070/113] [PM-14052] Add lit signals dependency (#14032)
* add lit signals dependency
* add @lit-labs/signals to autofill team ownership
---
.github/renovate.json5 | 1 +
package-lock.json | 19 +++++++++++++++++++
package.json | 1 +
3 files changed, 21 insertions(+)
diff --git a/.github/renovate.json5 b/.github/renovate.json5
index 01361a97404..74c4ceed948 100644
--- a/.github/renovate.json5
+++ b/.github/renovate.json5
@@ -175,6 +175,7 @@
"del",
"ed25519",
"lit",
+ "@lit-labs/signals",
"patch-package",
"pkcs8",
"prettier",
diff --git a/package-lock.json b/package-lock.json
index 395cf52f05a..afe2997f0d2 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -83,6 +83,7 @@
"@compodoc/compodoc": "1.1.26",
"@electron/notarize": "2.5.0",
"@electron/rebuild": "3.7.1",
+ "@lit-labs/signals": "0.1.2",
"@ngtools/webpack": "18.2.12",
"@storybook/addon-a11y": "8.5.2",
"@storybook/addon-actions": "8.5.2",
@@ -7869,6 +7870,17 @@
"dev": true,
"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==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "lit": "^2.0.0 || ^3.0.0",
+ "signal-polyfill": "^0.2.0"
+ }
+ },
"node_modules/@lit-labs/ssr-dom-shim": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.3.0.tgz",
@@ -32315,6 +32327,13 @@
"url": "https://github.com/sponsors/isaacs"
}
},
+ "node_modules/signal-polyfill": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/signal-polyfill/-/signal-polyfill-0.2.2.tgz",
+ "integrity": "sha512-p63Y4Er5/eMQ9RHg0M0Y64NlsQKpiu6MDdhBXpyywRuWiPywhJTpKJ1iB5K2hJEbFZ0BnDS7ZkJ+0AfTuL37Rg==",
+ "dev": true,
+ "license": "Apache-2.0"
+ },
"node_modules/sigstore": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/sigstore/-/sigstore-2.3.1.tgz",
diff --git a/package.json b/package.json
index 83a0aa96fa3..051ff4f7251 100644
--- a/package.json
+++ b/package.json
@@ -45,6 +45,7 @@
"@compodoc/compodoc": "1.1.26",
"@electron/notarize": "2.5.0",
"@electron/rebuild": "3.7.1",
+ "@lit-labs/signals": "0.1.2",
"@ngtools/webpack": "18.2.12",
"@storybook/addon-a11y": "8.5.2",
"@storybook/addon-actions": "8.5.2",
From cfafeaac66820db6fdd8030f37b90bc87948120c Mon Sep 17 00:00:00 2001
From: Matt Bishop
Date: Thu, 27 Mar 2025 15:46:48 -0700
Subject: [PATCH 071/113] Remove references to Codecov token (#14033)
---
.github/workflows/test.yml | 2 --
1 file changed, 2 deletions(-)
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 0b039315b30..6411337f6e9 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -71,8 +71,6 @@ jobs:
- name: Upload results to codecov.io
uses: codecov/test-results-action@4e79e65778be1cecd5df25e14af1eafb6df80ea9 # v1.0.2
- env:
- CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
rust:
name: Run Rust tests on ${{ matrix.os }}
From 8dead9baf6f82c279394bf479d8599d652e299c4 Mon Sep 17 00:00:00 2001
From: Shane Melton
Date: Thu, 27 Mar 2025 17:01:58 -0700
Subject: [PATCH 072/113] [PM-14426] At-risk password carousel fixes (#13936)
* [PM-19004] Add period to image alt text
* [PM-19298] Add data-testid for carousel confirmation button
---
apps/browser/src/_locales/en/messages.json | 12 ++++++------
.../at-risk-carousel-dialog.component.html | 7 ++++---
2 files changed, 10 insertions(+), 9 deletions(-)
diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json
index 995b90ec661..2c940ccdf5a 100644
--- a/apps/browser/src/_locales/en/messages.json
+++ b/apps/browser/src/_locales/en/messages.json
@@ -2534,15 +2534,15 @@
"message": "Your organization passwords are at-risk because they are weak, reused, and/or exposed.",
"description": "Description of the review at-risk login slide on the at-risk password page carousel"
},
- "reviewAtRiskLoginSlideImgAlt": {
- "message": "Illustration of a list of logins that are at-risk"
+ "reviewAtRiskLoginSlideImgAltPeriod": {
+ "message": "Illustration of a list of logins that are at-risk."
},
"generatePasswordSlideDesc": {
"message": "Quickly generate a strong, unique password with the Bitwarden autofill menu on the at-risk site.",
"description": "Description of the generate password slide on the at-risk password page carousel"
},
- "generatePasswordSlideImgAlt": {
- "message": "Illustration of the Bitwarden autofill menu displaying a generated password"
+ "generatePasswordSlideImgAltPeriod": {
+ "message": "Illustration of the Bitwarden autofill menu displaying a generated password."
},
"updateInBitwarden": {
"message": "Update in Bitwarden"
@@ -2551,8 +2551,8 @@
"message": "Bitwarden will then prompt you to update the password in the password manager.",
"description": "Description of the update in Bitwarden slide on the at-risk password page carousel"
},
- "updateInBitwardenSlideImgAlt": {
- "message": "Illustration of a Bitwarden’s notification prompting the user to update the login"
+ "updateInBitwardenSlideImgAltPeriod": {
+ "message": "Illustration of a Bitwarden’s notification prompting the user to update the login."
},
"turnOnAutofill": {
"message": "Turn on autofill"
diff --git a/apps/browser/src/vault/popup/components/at-risk-carousel-dialog/at-risk-carousel-dialog.component.html b/apps/browser/src/vault/popup/components/at-risk-carousel-dialog/at-risk-carousel-dialog.component.html
index c5e3ea8fb0d..e63b06750c8 100644
--- a/apps/browser/src/vault/popup/components/at-risk-carousel-dialog/at-risk-carousel-dialog.component.html
+++ b/apps/browser/src/vault/popup/components/at-risk-carousel-dialog/at-risk-carousel-dialog.component.html
@@ -6,7 +6,7 @@
class="tw-max-w-full tw-max-h-40"
src="../../../../images/at-risk-password-carousel/review_at-risk_logins.light.png"
appDarkImgSrc="../../../../images/at-risk-password-carousel/review_at-risk_logins.dark.png"
- [alt]="'reviewAtRiskLoginSlideImgAlt' | i18n"
+ [alt]="'reviewAtRiskLoginSlideImgAltPeriod' | i18n"
/>
{{ "reviewAtRiskLogins" | i18n }}
@@ -18,7 +18,7 @@
class="tw-max-w-full tw-max-h-40"
src="../../../../images/at-risk-password-carousel/generate_password.light.png"
appDarkImgSrc="../../../../images/at-risk-password-carousel/generate_password.dark.png"
- [alt]="'generatePasswordSlideImgAlt' | i18n"
+ [alt]="'generatePasswordSlideImgAltPeriod' | i18n"
/>
{{ "generatePassword" | i18n }}
@@ -30,7 +30,7 @@
class="tw-max-w-full tw-max-h-40"
src="../../../../images/at-risk-password-carousel/update_login.light.png"
appDarkImgSrc="../../../../images/at-risk-password-carousel/update_login.dark.png"
- [alt]="'updateInBitwardenSlideImgAlt' | i18n"
+ [alt]="'updateInBitwardenSlideImgAltPeriod' | i18n"
/>
{{ "updateInBitwarden" | i18n }}
@@ -47,6 +47,7 @@
block
[disabled]="!dismissBtnEnabled()"
(click)="dismiss()"
+ data-testid="confirm-carousel-button"
>
{{ "reviewAtRiskPasswords" | i18n }}
From 6d4179052a318655f26de0408c82fe0563ef5295 Mon Sep 17 00:00:00 2001
From: "bw-ghapp[bot]" <178206702+bw-ghapp[bot]@users.noreply.github.com>
Date: Fri, 28 Mar 2025 12:22:38 +0100
Subject: [PATCH 073/113] Autosync the updated translations (#14038)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
---
apps/desktop/src/locales/af/messages.json | 9 +++++++++
apps/desktop/src/locales/ar/messages.json | 9 +++++++++
apps/desktop/src/locales/az/messages.json | 9 +++++++++
apps/desktop/src/locales/be/messages.json | 9 +++++++++
apps/desktop/src/locales/bg/messages.json | 9 +++++++++
apps/desktop/src/locales/bn/messages.json | 9 +++++++++
apps/desktop/src/locales/bs/messages.json | 9 +++++++++
apps/desktop/src/locales/ca/messages.json | 9 +++++++++
apps/desktop/src/locales/cs/messages.json | 9 +++++++++
apps/desktop/src/locales/cy/messages.json | 9 +++++++++
apps/desktop/src/locales/da/messages.json | 9 +++++++++
apps/desktop/src/locales/de/messages.json | 9 +++++++++
apps/desktop/src/locales/el/messages.json | 9 +++++++++
apps/desktop/src/locales/en_GB/messages.json | 9 +++++++++
apps/desktop/src/locales/en_IN/messages.json | 9 +++++++++
apps/desktop/src/locales/eo/messages.json | 9 +++++++++
apps/desktop/src/locales/es/messages.json | 9 +++++++++
apps/desktop/src/locales/et/messages.json | 9 +++++++++
apps/desktop/src/locales/eu/messages.json | 9 +++++++++
apps/desktop/src/locales/fa/messages.json | 9 +++++++++
apps/desktop/src/locales/fi/messages.json | 9 +++++++++
apps/desktop/src/locales/fil/messages.json | 9 +++++++++
apps/desktop/src/locales/fr/messages.json | 9 +++++++++
apps/desktop/src/locales/gl/messages.json | 9 +++++++++
apps/desktop/src/locales/he/messages.json | 9 +++++++++
apps/desktop/src/locales/hi/messages.json | 9 +++++++++
apps/desktop/src/locales/hr/messages.json | 9 +++++++++
apps/desktop/src/locales/hu/messages.json | 9 +++++++++
apps/desktop/src/locales/id/messages.json | 9 +++++++++
apps/desktop/src/locales/it/messages.json | 9 +++++++++
apps/desktop/src/locales/ja/messages.json | 11 ++++++++++-
apps/desktop/src/locales/ka/messages.json | 9 +++++++++
apps/desktop/src/locales/km/messages.json | 9 +++++++++
apps/desktop/src/locales/kn/messages.json | 9 +++++++++
apps/desktop/src/locales/ko/messages.json | 9 +++++++++
apps/desktop/src/locales/lt/messages.json | 9 +++++++++
apps/desktop/src/locales/lv/messages.json | 11 ++++++++++-
apps/desktop/src/locales/me/messages.json | 9 +++++++++
apps/desktop/src/locales/ml/messages.json | 9 +++++++++
apps/desktop/src/locales/mr/messages.json | 9 +++++++++
apps/desktop/src/locales/my/messages.json | 9 +++++++++
apps/desktop/src/locales/nb/messages.json | 9 +++++++++
apps/desktop/src/locales/ne/messages.json | 9 +++++++++
apps/desktop/src/locales/nl/messages.json | 9 +++++++++
apps/desktop/src/locales/nn/messages.json | 9 +++++++++
apps/desktop/src/locales/or/messages.json | 9 +++++++++
apps/desktop/src/locales/pl/messages.json | 9 +++++++++
apps/desktop/src/locales/pt_BR/messages.json | 9 +++++++++
apps/desktop/src/locales/pt_PT/messages.json | 9 +++++++++
apps/desktop/src/locales/ro/messages.json | 9 +++++++++
apps/desktop/src/locales/ru/messages.json | 9 +++++++++
apps/desktop/src/locales/si/messages.json | 9 +++++++++
apps/desktop/src/locales/sk/messages.json | 9 +++++++++
apps/desktop/src/locales/sl/messages.json | 9 +++++++++
apps/desktop/src/locales/sr/messages.json | 9 +++++++++
apps/desktop/src/locales/sv/messages.json | 9 +++++++++
apps/desktop/src/locales/te/messages.json | 9 +++++++++
apps/desktop/src/locales/th/messages.json | 9 +++++++++
apps/desktop/src/locales/tr/messages.json | 9 +++++++++
apps/desktop/src/locales/uk/messages.json | 9 +++++++++
apps/desktop/src/locales/vi/messages.json | 9 +++++++++
apps/desktop/src/locales/zh_CN/messages.json | 9 +++++++++
apps/desktop/src/locales/zh_TW/messages.json | 9 +++++++++
63 files changed, 569 insertions(+), 2 deletions(-)
diff --git a/apps/desktop/src/locales/af/messages.json b/apps/desktop/src/locales/af/messages.json
index ae324eda6bc..e288e9e70cc 100644
--- a/apps/desktop/src/locales/af/messages.json
+++ b/apps/desktop/src/locales/af/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
diff --git a/apps/desktop/src/locales/ar/messages.json b/apps/desktop/src/locales/ar/messages.json
index 9f5b1f3501c..7de8ecb50f5 100644
--- a/apps/desktop/src/locales/ar/messages.json
+++ b/apps/desktop/src/locales/ar/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "تصدير خزانة المؤسسة"
},
diff --git a/apps/desktop/src/locales/az/messages.json b/apps/desktop/src/locales/az/messages.json
index b7c7cb3cd67..738944f1791 100644
--- a/apps/desktop/src/locales/az/messages.json
+++ b/apps/desktop/src/locales/az/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Yalnız $EMAIL$ ilə əlaqələndirilmiş qoşmalar daxil olmaqla fərdi anbar elementləri xaricə köçürüləcək. Təşkilat anbar elementləri daxil edilməyəcək",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Təşkilat seyfini xaricə köçürmə"
},
diff --git a/apps/desktop/src/locales/be/messages.json b/apps/desktop/src/locales/be/messages.json
index 4c90222be42..ef73dcb3ed3 100644
--- a/apps/desktop/src/locales/be/messages.json
+++ b/apps/desktop/src/locales/be/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
diff --git a/apps/desktop/src/locales/bg/messages.json b/apps/desktop/src/locales/bg/messages.json
index 6a1c0983278..c9bae830eff 100644
--- a/apps/desktop/src/locales/bg/messages.json
+++ b/apps/desktop/src/locales/bg/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Ще бъдат изнесени само записите и прикачените файлове от личния трезор свързан с $EMAIL$. Записите в трезора на организацията няма да бъдат включени.",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Изнасяне на трезора на организацията"
},
diff --git a/apps/desktop/src/locales/bn/messages.json b/apps/desktop/src/locales/bn/messages.json
index 68848c9ef2a..440462cf5d3 100644
--- a/apps/desktop/src/locales/bn/messages.json
+++ b/apps/desktop/src/locales/bn/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
diff --git a/apps/desktop/src/locales/bs/messages.json b/apps/desktop/src/locales/bs/messages.json
index 885af404f95..4fb9697a2c9 100644
--- a/apps/desktop/src/locales/bs/messages.json
+++ b/apps/desktop/src/locales/bs/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
diff --git a/apps/desktop/src/locales/ca/messages.json b/apps/desktop/src/locales/ca/messages.json
index 5707fd0c0ff..dbba24a9050 100644
--- a/apps/desktop/src/locales/ca/messages.json
+++ b/apps/desktop/src/locales/ca/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "S'està exportant la caixa forta de l’organització"
},
diff --git a/apps/desktop/src/locales/cs/messages.json b/apps/desktop/src/locales/cs/messages.json
index 336ea2a795c..c4d61da936b 100644
--- a/apps/desktop/src/locales/cs/messages.json
+++ b/apps/desktop/src/locales/cs/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Budou exportovány jen osobní položky trezoru včetně příloh spojené s účtem $EMAIL$. Nebudou zahrnuty položky trezoru v organizaci.",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exportování trezoru organizace"
},
diff --git a/apps/desktop/src/locales/cy/messages.json b/apps/desktop/src/locales/cy/messages.json
index 1c6d824c22e..5f06fd762f4 100644
--- a/apps/desktop/src/locales/cy/messages.json
+++ b/apps/desktop/src/locales/cy/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
diff --git a/apps/desktop/src/locales/da/messages.json b/apps/desktop/src/locales/da/messages.json
index ef5b22e33f4..e11aef18e14 100644
--- a/apps/desktop/src/locales/da/messages.json
+++ b/apps/desktop/src/locales/da/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Eksport af organisationsboks"
},
diff --git a/apps/desktop/src/locales/de/messages.json b/apps/desktop/src/locales/de/messages.json
index 79f61475ab0..b28c2fced1d 100644
--- a/apps/desktop/src/locales/de/messages.json
+++ b/apps/desktop/src/locales/de/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Tresor der Organisation wird exportiert"
},
diff --git a/apps/desktop/src/locales/el/messages.json b/apps/desktop/src/locales/el/messages.json
index ecee8bd9894..9c59be2e0a7 100644
--- a/apps/desktop/src/locales/el/messages.json
+++ b/apps/desktop/src/locales/el/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Εξαγωγή θησαυ/κίου οργανισμού"
},
diff --git a/apps/desktop/src/locales/en_GB/messages.json b/apps/desktop/src/locales/en_GB/messages.json
index f70a70e975a..74e43b24076 100644
--- a/apps/desktop/src/locales/en_GB/messages.json
+++ b/apps/desktop/src/locales/en_GB/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organisation vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organisation vault"
},
diff --git a/apps/desktop/src/locales/en_IN/messages.json b/apps/desktop/src/locales/en_IN/messages.json
index f91a7b21876..3ec9e17769b 100644
--- a/apps/desktop/src/locales/en_IN/messages.json
+++ b/apps/desktop/src/locales/en_IN/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organisation vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organisation vault"
},
diff --git a/apps/desktop/src/locales/eo/messages.json b/apps/desktop/src/locales/eo/messages.json
index 200c3f4a1de..2dfdcdd9484 100644
--- a/apps/desktop/src/locales/eo/messages.json
+++ b/apps/desktop/src/locales/eo/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
diff --git a/apps/desktop/src/locales/es/messages.json b/apps/desktop/src/locales/es/messages.json
index 665721beb3a..6936753ebb2 100644
--- a/apps/desktop/src/locales/es/messages.json
+++ b/apps/desktop/src/locales/es/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exportando caja fuerte de la organización"
},
diff --git a/apps/desktop/src/locales/et/messages.json b/apps/desktop/src/locales/et/messages.json
index c4ca9bef886..20650145478 100644
--- a/apps/desktop/src/locales/et/messages.json
+++ b/apps/desktop/src/locales/et/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Ekspordin organisatsiooni hoidlat"
},
diff --git a/apps/desktop/src/locales/eu/messages.json b/apps/desktop/src/locales/eu/messages.json
index 1abc24612ca..57763cc62c1 100644
--- a/apps/desktop/src/locales/eu/messages.json
+++ b/apps/desktop/src/locales/eu/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
diff --git a/apps/desktop/src/locales/fa/messages.json b/apps/desktop/src/locales/fa/messages.json
index 2d13b86c4a8..eecbd6dcb85 100644
--- a/apps/desktop/src/locales/fa/messages.json
+++ b/apps/desktop/src/locales/fa/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
diff --git a/apps/desktop/src/locales/fi/messages.json b/apps/desktop/src/locales/fi/messages.json
index 2da2b7dcf7a..8964e7cf982 100644
--- a/apps/desktop/src/locales/fi/messages.json
+++ b/apps/desktop/src/locales/fi/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Organisaation holvin vienti"
},
diff --git a/apps/desktop/src/locales/fil/messages.json b/apps/desktop/src/locales/fil/messages.json
index d54c5a21a87..ec1a2d0e281 100644
--- a/apps/desktop/src/locales/fil/messages.json
+++ b/apps/desktop/src/locales/fil/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
diff --git a/apps/desktop/src/locales/fr/messages.json b/apps/desktop/src/locales/fr/messages.json
index 431a70ef373..750b91adf57 100644
--- a/apps/desktop/src/locales/fr/messages.json
+++ b/apps/desktop/src/locales/fr/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Export du coffre-fort de l'organisation"
},
diff --git a/apps/desktop/src/locales/gl/messages.json b/apps/desktop/src/locales/gl/messages.json
index f93db44aa69..07404b37fcd 100644
--- a/apps/desktop/src/locales/gl/messages.json
+++ b/apps/desktop/src/locales/gl/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
diff --git a/apps/desktop/src/locales/he/messages.json b/apps/desktop/src/locales/he/messages.json
index 7f98ab977ae..34db4dd53ba 100644
--- a/apps/desktop/src/locales/he/messages.json
+++ b/apps/desktop/src/locales/he/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "מייצא כספת ארגון"
},
diff --git a/apps/desktop/src/locales/hi/messages.json b/apps/desktop/src/locales/hi/messages.json
index 90fed49de76..7cfbb08a7d4 100644
--- a/apps/desktop/src/locales/hi/messages.json
+++ b/apps/desktop/src/locales/hi/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
diff --git a/apps/desktop/src/locales/hr/messages.json b/apps/desktop/src/locales/hr/messages.json
index cb66437bedd..989faae1759 100644
--- a/apps/desktop/src/locales/hr/messages.json
+++ b/apps/desktop/src/locales/hr/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Izvest će se samo stavke i privici osobnog trezora povezanog s $EMAIL$. Stavke organizacijskog trezora neće biti uključene",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Izvoz organizacijskog trezora"
},
diff --git a/apps/desktop/src/locales/hu/messages.json b/apps/desktop/src/locales/hu/messages.json
index b9573428136..775ce2dd80c 100644
--- a/apps/desktop/src/locales/hu/messages.json
+++ b/apps/desktop/src/locales/hu/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Csak $EMAIL$ email címmel társított személyes széf elemek kerülnek exportálásra. Ebbe nem kerülnek be a szervezeti széf elemek.",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Szervezeti széf exportálása"
},
diff --git a/apps/desktop/src/locales/id/messages.json b/apps/desktop/src/locales/id/messages.json
index df9b6484778..0c46ff5e5ea 100644
--- a/apps/desktop/src/locales/id/messages.json
+++ b/apps/desktop/src/locales/id/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
diff --git a/apps/desktop/src/locales/it/messages.json b/apps/desktop/src/locales/it/messages.json
index ef5fa5ce381..f97b3135c9f 100644
--- a/apps/desktop/src/locales/it/messages.json
+++ b/apps/desktop/src/locales/it/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Esportando cassaforte dell'organizzazione"
},
diff --git a/apps/desktop/src/locales/ja/messages.json b/apps/desktop/src/locales/ja/messages.json
index e8ba0e8509b..58c4d866e42 100644
--- a/apps/desktop/src/locales/ja/messages.json
+++ b/apps/desktop/src/locales/ja/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "$EMAIL$ に関連付けられた個人用保管庫のアイテムのみが、添付ファイルを含めてエクスポートされます。組織用保管庫のアイテムは含まれません。",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "組織保管庫のエクスポート"
},
@@ -3539,7 +3548,7 @@
"message": "後で再通知"
},
"newDeviceVerificationNoticePageOneFormContent": {
- "message": "新しいメールアドレス $EMAIL$ はあなたが管理しているものですか?",
+ "message": "メールアドレス $EMAIL$ は、確実にアクセスできるものですか?",
"placeholders": {
"email": {
"content": "$1",
diff --git a/apps/desktop/src/locales/ka/messages.json b/apps/desktop/src/locales/ka/messages.json
index 96962e14ff5..3eb666c7d44 100644
--- a/apps/desktop/src/locales/ka/messages.json
+++ b/apps/desktop/src/locales/ka/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
diff --git a/apps/desktop/src/locales/km/messages.json b/apps/desktop/src/locales/km/messages.json
index f93db44aa69..07404b37fcd 100644
--- a/apps/desktop/src/locales/km/messages.json
+++ b/apps/desktop/src/locales/km/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
diff --git a/apps/desktop/src/locales/kn/messages.json b/apps/desktop/src/locales/kn/messages.json
index f9bb154b66e..c530ab799f7 100644
--- a/apps/desktop/src/locales/kn/messages.json
+++ b/apps/desktop/src/locales/kn/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
diff --git a/apps/desktop/src/locales/ko/messages.json b/apps/desktop/src/locales/ko/messages.json
index 44bc681f205..e5df2555531 100644
--- a/apps/desktop/src/locales/ko/messages.json
+++ b/apps/desktop/src/locales/ko/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
diff --git a/apps/desktop/src/locales/lt/messages.json b/apps/desktop/src/locales/lt/messages.json
index 40436c7ccda..7cf1bc463b3 100644
--- a/apps/desktop/src/locales/lt/messages.json
+++ b/apps/desktop/src/locales/lt/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
diff --git a/apps/desktop/src/locales/lv/messages.json b/apps/desktop/src/locales/lv/messages.json
index c519987fc72..b3fa396d811 100644
--- a/apps/desktop/src/locales/lv/messages.json
+++ b/apps/desktop/src/locales/lv/messages.json
@@ -1706,7 +1706,7 @@
"message": "Apstiprināt glabātavas satura izgūšanu"
},
"exportWarningDesc": {
- "message": "Šī izguve satur glabātavas datus nešifrētā veidā. Izdoto datni nevajadzētu glabāt vai sūtīt nedrošos veidos (piemēram, e-pastā). Tā ir jāizdzēš uzreiz pēc izmantošanas."
+ "message": "Šī izguve satur glabātavas datus nešifrētā veidā. Izgūto datni nevajadzētu glabāt vai sūtīt nedrošos veidos (piemēram, e-pastā). Tā ir jāizdzēš uzreiz pēc izmantošanas."
},
"encExportKeyWarningDesc": {
"message": "Šī izguve šifrē datus ar konta šifrēšanas atslēgu. Ja tā jebkad tiks mainīta, izvadi vajadzētu veikt vēlreiz, jo vairs nebūs iespējams atšifrēt šo datni."
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Tiks izdoti tikai atsevišķi glabātavas vienumi, tajā skaitā pielikumi, kas ir saistīti ar $EMAIL$. Apvienības glabātavas vienumi netiks iekļauti",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Izgūst apvienības glabātavu"
},
diff --git a/apps/desktop/src/locales/me/messages.json b/apps/desktop/src/locales/me/messages.json
index ea8defdc07c..99944c6e10c 100644
--- a/apps/desktop/src/locales/me/messages.json
+++ b/apps/desktop/src/locales/me/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
diff --git a/apps/desktop/src/locales/ml/messages.json b/apps/desktop/src/locales/ml/messages.json
index a4af9251191..79d36b99526 100644
--- a/apps/desktop/src/locales/ml/messages.json
+++ b/apps/desktop/src/locales/ml/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
diff --git a/apps/desktop/src/locales/mr/messages.json b/apps/desktop/src/locales/mr/messages.json
index f93db44aa69..07404b37fcd 100644
--- a/apps/desktop/src/locales/mr/messages.json
+++ b/apps/desktop/src/locales/mr/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
diff --git a/apps/desktop/src/locales/my/messages.json b/apps/desktop/src/locales/my/messages.json
index 493d6fdb5b9..8aa0327203b 100644
--- a/apps/desktop/src/locales/my/messages.json
+++ b/apps/desktop/src/locales/my/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
diff --git a/apps/desktop/src/locales/nb/messages.json b/apps/desktop/src/locales/nb/messages.json
index caf4d3da64b..d96eb92efb2 100644
--- a/apps/desktop/src/locales/nb/messages.json
+++ b/apps/desktop/src/locales/nb/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
diff --git a/apps/desktop/src/locales/ne/messages.json b/apps/desktop/src/locales/ne/messages.json
index 297708953df..79aa89aed90 100644
--- a/apps/desktop/src/locales/ne/messages.json
+++ b/apps/desktop/src/locales/ne/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
diff --git a/apps/desktop/src/locales/nl/messages.json b/apps/desktop/src/locales/nl/messages.json
index 1cfd5ff5523..29dddbf0f08 100644
--- a/apps/desktop/src/locales/nl/messages.json
+++ b/apps/desktop/src/locales/nl/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Exporteert alleen de persoonlijke kluis-items, inclusief attachments, gerelateerd aan $EMAIL$. Geen kluis-items van de organisatie",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Organisatiekluis exporteren"
},
diff --git a/apps/desktop/src/locales/nn/messages.json b/apps/desktop/src/locales/nn/messages.json
index 9195311cc67..753b3ba5524 100644
--- a/apps/desktop/src/locales/nn/messages.json
+++ b/apps/desktop/src/locales/nn/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
diff --git a/apps/desktop/src/locales/or/messages.json b/apps/desktop/src/locales/or/messages.json
index 6f15ed21991..b5c3d4eab46 100644
--- a/apps/desktop/src/locales/or/messages.json
+++ b/apps/desktop/src/locales/or/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
diff --git a/apps/desktop/src/locales/pl/messages.json b/apps/desktop/src/locales/pl/messages.json
index ef8cdc6eb9b..a8b5205a9d9 100644
--- a/apps/desktop/src/locales/pl/messages.json
+++ b/apps/desktop/src/locales/pl/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Eksportowanie sejfu organizacji"
},
diff --git a/apps/desktop/src/locales/pt_BR/messages.json b/apps/desktop/src/locales/pt_BR/messages.json
index e09ee6e16d2..539d254344d 100644
--- a/apps/desktop/src/locales/pt_BR/messages.json
+++ b/apps/desktop/src/locales/pt_BR/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exportando cofre da organização"
},
diff --git a/apps/desktop/src/locales/pt_PT/messages.json b/apps/desktop/src/locales/pt_PT/messages.json
index 2261357ba08..28ac0c82486 100644
--- a/apps/desktop/src/locales/pt_PT/messages.json
+++ b/apps/desktop/src/locales/pt_PT/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Apenas os itens individuais do cofre, incluindo os anexos associados a $EMAIL$, serão exportados. Os itens do cofre da organização não serão incluídos",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "A exportar o cofre da organização"
},
diff --git a/apps/desktop/src/locales/ro/messages.json b/apps/desktop/src/locales/ro/messages.json
index d148d90a4fd..b160bfc14e3 100644
--- a/apps/desktop/src/locales/ro/messages.json
+++ b/apps/desktop/src/locales/ro/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
diff --git a/apps/desktop/src/locales/ru/messages.json b/apps/desktop/src/locales/ru/messages.json
index ea5b8c6d526..73f52200e04 100644
--- a/apps/desktop/src/locales/ru/messages.json
+++ b/apps/desktop/src/locales/ru/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Будут экспортированы только отдельные элементы хранилища, включая вложения, связанные с $EMAIL$. Элементы хранилища организации включены не будут",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Экспорт хранилища организации"
},
diff --git a/apps/desktop/src/locales/si/messages.json b/apps/desktop/src/locales/si/messages.json
index cb837ea4f76..e2f23c7bc56 100644
--- a/apps/desktop/src/locales/si/messages.json
+++ b/apps/desktop/src/locales/si/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
diff --git a/apps/desktop/src/locales/sk/messages.json b/apps/desktop/src/locales/sk/messages.json
index 7eaa1026c00..0f32c9945ce 100644
--- a/apps/desktop/src/locales/sk/messages.json
+++ b/apps/desktop/src/locales/sk/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Exportované budú iba položky osobného trezora spojené s $EMAIL$. Položky trezora organizácie nebudú zahrnuté v exporte",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exportovanie trezora organizácie"
},
diff --git a/apps/desktop/src/locales/sl/messages.json b/apps/desktop/src/locales/sl/messages.json
index 3987587828a..4d50b4d9def 100644
--- a/apps/desktop/src/locales/sl/messages.json
+++ b/apps/desktop/src/locales/sl/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
diff --git a/apps/desktop/src/locales/sr/messages.json b/apps/desktop/src/locales/sr/messages.json
index 69b45469dc8..561e3d35ec2 100644
--- a/apps/desktop/src/locales/sr/messages.json
+++ b/apps/desktop/src/locales/sr/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Извоз сефа организације"
},
diff --git a/apps/desktop/src/locales/sv/messages.json b/apps/desktop/src/locales/sv/messages.json
index 1303243a8cc..f33339ea80d 100644
--- a/apps/desktop/src/locales/sv/messages.json
+++ b/apps/desktop/src/locales/sv/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
diff --git a/apps/desktop/src/locales/te/messages.json b/apps/desktop/src/locales/te/messages.json
index f93db44aa69..07404b37fcd 100644
--- a/apps/desktop/src/locales/te/messages.json
+++ b/apps/desktop/src/locales/te/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
diff --git a/apps/desktop/src/locales/th/messages.json b/apps/desktop/src/locales/th/messages.json
index cb9df027ad7..2bec157ec15 100644
--- a/apps/desktop/src/locales/th/messages.json
+++ b/apps/desktop/src/locales/th/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
diff --git a/apps/desktop/src/locales/tr/messages.json b/apps/desktop/src/locales/tr/messages.json
index 55b3b6059b7..974d27f4735 100644
--- a/apps/desktop/src/locales/tr/messages.json
+++ b/apps/desktop/src/locales/tr/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Yalnızca $EMAIL$ ile ilişkili kişisel kasadaki kayıtlar ve dosyalar dışa aktarılacaktır. Kuruluş kasasındaki kayıtlar dahil edilmeyecektir",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Kuruluş kasasını dışa aktarma"
},
diff --git a/apps/desktop/src/locales/uk/messages.json b/apps/desktop/src/locales/uk/messages.json
index d5f908e011c..6fb5c564f3a 100644
--- a/apps/desktop/src/locales/uk/messages.json
+++ b/apps/desktop/src/locales/uk/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Експортування сховища організації"
},
diff --git a/apps/desktop/src/locales/vi/messages.json b/apps/desktop/src/locales/vi/messages.json
index f6495e4f915..8b5f3250b85 100644
--- a/apps/desktop/src/locales/vi/messages.json
+++ b/apps/desktop/src/locales/vi/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Đang xuất dữ liệu kho tổ chức"
},
diff --git a/apps/desktop/src/locales/zh_CN/messages.json b/apps/desktop/src/locales/zh_CN/messages.json
index 6a12138b48f..e85b2606535 100644
--- a/apps/desktop/src/locales/zh_CN/messages.json
+++ b/apps/desktop/src/locales/zh_CN/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "仅会导出与 $EMAIL$ 关联的个人密码库项目(包括附件)。不包括组织密码库项目。",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "正在导出组织密码库"
},
diff --git a/apps/desktop/src/locales/zh_TW/messages.json b/apps/desktop/src/locales/zh_TW/messages.json
index 5da6c4a0b6e..ab09790dcd0 100644
--- a/apps/desktop/src/locales/zh_TW/messages.json
+++ b/apps/desktop/src/locales/zh_TW/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "正匯出組織密碼庫"
},
From 251a08fd93c4c5526f1c2d6e398633d70fcca221 Mon Sep 17 00:00:00 2001
From: "bw-ghapp[bot]" <178206702+bw-ghapp[bot]@users.noreply.github.com>
Date: Fri, 28 Mar 2025 12:31:57 +0100
Subject: [PATCH 074/113] Autosync the updated translations (#14037)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
---
apps/browser/src/_locales/ar/messages.json | 37 ++++
apps/browser/src/_locales/az/messages.json | 37 ++++
apps/browser/src/_locales/be/messages.json | 53 ++++-
apps/browser/src/_locales/bg/messages.json | 37 ++++
apps/browser/src/_locales/bn/messages.json | 37 ++++
apps/browser/src/_locales/bs/messages.json | 37 ++++
apps/browser/src/_locales/ca/messages.json | 37 ++++
apps/browser/src/_locales/cs/messages.json | 37 ++++
apps/browser/src/_locales/cy/messages.json | 37 ++++
apps/browser/src/_locales/da/messages.json | 37 ++++
apps/browser/src/_locales/de/messages.json | 37 ++++
apps/browser/src/_locales/el/messages.json | 37 ++++
apps/browser/src/_locales/en_GB/messages.json | 37 ++++
apps/browser/src/_locales/en_IN/messages.json | 37 ++++
apps/browser/src/_locales/es/messages.json | 37 ++++
apps/browser/src/_locales/et/messages.json | 37 ++++
apps/browser/src/_locales/eu/messages.json | 37 ++++
apps/browser/src/_locales/fa/messages.json | 37 ++++
apps/browser/src/_locales/fi/messages.json | 37 ++++
apps/browser/src/_locales/fil/messages.json | 37 ++++
apps/browser/src/_locales/fr/messages.json | 37 ++++
apps/browser/src/_locales/gl/messages.json | 37 ++++
apps/browser/src/_locales/he/messages.json | 37 ++++
apps/browser/src/_locales/hi/messages.json | 37 ++++
apps/browser/src/_locales/hr/messages.json | 37 ++++
apps/browser/src/_locales/hu/messages.json | 37 ++++
apps/browser/src/_locales/id/messages.json | 193 +++++++++++-------
apps/browser/src/_locales/it/messages.json | 37 ++++
apps/browser/src/_locales/ja/messages.json | 39 +++-
apps/browser/src/_locales/ka/messages.json | 37 ++++
apps/browser/src/_locales/km/messages.json | 37 ++++
apps/browser/src/_locales/kn/messages.json | 37 ++++
apps/browser/src/_locales/ko/messages.json | 37 ++++
apps/browser/src/_locales/lt/messages.json | 37 ++++
apps/browser/src/_locales/lv/messages.json | 37 ++++
apps/browser/src/_locales/ml/messages.json | 37 ++++
apps/browser/src/_locales/mr/messages.json | 37 ++++
apps/browser/src/_locales/my/messages.json | 37 ++++
apps/browser/src/_locales/nb/messages.json | 37 ++++
apps/browser/src/_locales/ne/messages.json | 37 ++++
apps/browser/src/_locales/nl/messages.json | 37 ++++
apps/browser/src/_locales/nn/messages.json | 37 ++++
apps/browser/src/_locales/or/messages.json | 37 ++++
apps/browser/src/_locales/pl/messages.json | 37 ++++
apps/browser/src/_locales/pt_BR/messages.json | 37 ++++
apps/browser/src/_locales/pt_PT/messages.json | 37 ++++
apps/browser/src/_locales/ro/messages.json | 37 ++++
apps/browser/src/_locales/ru/messages.json | 37 ++++
apps/browser/src/_locales/si/messages.json | 37 ++++
apps/browser/src/_locales/sk/messages.json | 37 ++++
apps/browser/src/_locales/sl/messages.json | 37 ++++
apps/browser/src/_locales/sr/messages.json | 37 ++++
apps/browser/src/_locales/sv/messages.json | 37 ++++
apps/browser/src/_locales/te/messages.json | 37 ++++
apps/browser/src/_locales/th/messages.json | 37 ++++
apps/browser/src/_locales/tr/messages.json | 37 ++++
apps/browser/src/_locales/uk/messages.json | 37 ++++
apps/browser/src/_locales/vi/messages.json | 37 ++++
apps/browser/src/_locales/zh_CN/messages.json | 37 ++++
apps/browser/src/_locales/zh_TW/messages.json | 37 ++++
60 files changed, 2307 insertions(+), 87 deletions(-)
diff --git a/apps/browser/src/_locales/ar/messages.json b/apps/browser/src/_locales/ar/messages.json
index 4c051d455ac..bf22a38be08 100644
--- a/apps/browser/src/_locales/ar/messages.json
+++ b/apps/browser/src/_locales/ar/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Autofill - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Copy $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/az/messages.json b/apps/browser/src/_locales/az/messages.json
index 6f3ace453ae..1ca7642eb19 100644
--- a/apps/browser/src/_locales/az/messages.json
+++ b/apps/browser/src/_locales/az/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Yalnız $EMAIL$ ilə əlaqələndirilmiş qoşmalar daxil olmaqla fərdi anbar elementləri xaricə köçürüləcək. Təşkilat anbar elementləri daxil edilməyəcək",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Təşkilat seyfini xaricə köçürmə"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Avto-doldur - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Kopyala: $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/be/messages.json b/apps/browser/src/_locales/be/messages.json
index 905f6b6ac1d..a9a237aeb99 100644
--- a/apps/browser/src/_locales/be/messages.json
+++ b/apps/browser/src/_locales/be/messages.json
@@ -23,7 +23,7 @@
"message": "Упершыню ў Bitwarden?"
},
"logInWithPasskey": {
- "message": "Log in with passkey"
+ "message": "Увайсці з ключом доступу"
},
"useSingleSignOn": {
"message": "Выкарыстаць аднаразовы ўваход"
@@ -35,7 +35,7 @@
"message": "Прызначыць надзейны пароль"
},
"finishCreatingYourAccountBySettingAPassword": {
- "message": "Finish creating your account by setting a password"
+ "message": "Завяршыць стварэнне вашага ўліковага запісу нарадзіўшы пароль"
},
"enterpriseSingleSignOn": {
"message": "Адзіны ўваход прадпрыемства (SSO)"
@@ -62,7 +62,7 @@
"message": "Падказка да асноўнага пароля можа дапамагчы вам успомніць яго, калі вы яго забылі."
},
"masterPassHintText": {
- "message": "If you forget your password, the password hint can be sent to your email. $CURRENT$/$MAXIMUM$ character maximum.",
+ "message": "Падказка пароля можа быць адпраўлена на ваш адрас электроннай пошты, калі вы яго забудзеце. Максімум сімвалаў: $CURRENT$/$MAXIMUM$.",
"placeholders": {
"current": {
"content": "$1",
@@ -81,7 +81,7 @@
"message": "Падказка да асноўнага пароля (неабавязкова)"
},
"passwordStrengthScore": {
- "message": "Password strength score $SCORE$",
+ "message": "Ацэнка надзейнасці пароля $SCORE$",
"placeholders": {
"score": {
"content": "$1",
@@ -206,10 +206,10 @@
"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": {
@@ -261,7 +261,7 @@
"message": "Запытаць падказку да асноўнага пароля"
},
"enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": {
- "message": "Enter your account email address and your password hint will be sent to you"
+ "message": "Увядзіце адрас электроннай пошты ўліковага запісу і падказка пароля будзе адпраўлена вам"
},
"getMasterPasswordHint": {
"message": "Атрымаць падказку да асноўнага пароля"
@@ -291,7 +291,7 @@
"message": "Працягнуць у вэб-праграме?"
},
"continueToWebAppDesc": {
- "message": "Explore more features of your Bitwarden account on the web app."
+ "message": "Даследуйце больш функцый вашага уліковага запісу Bitwarden у вэб-праграме."
},
"continueToHelpCenter": {
"message": "Працягнуць працу ў Даведачным цэнтры?"
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Экспартаванне сховішча арганізацыі"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Autofill - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Copy $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/bg/messages.json b/apps/browser/src/_locales/bg/messages.json
index 226e63e32cb..c5dd0237a2c 100644
--- a/apps/browser/src/_locales/bg/messages.json
+++ b/apps/browser/src/_locales/bg/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Ще бъдат изнесени само записите и прикачените файлове от личния трезор свързан с $EMAIL$. Записите в трезора на организацията няма да бъдат включени.",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Изнасяне на трезора на организацията"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Авт. попълване – $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Копиране на $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/bn/messages.json b/apps/browser/src/_locales/bn/messages.json
index 983b9fadde4..21a455265e4 100644
--- a/apps/browser/src/_locales/bn/messages.json
+++ b/apps/browser/src/_locales/bn/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Autofill - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Copy $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/bs/messages.json b/apps/browser/src/_locales/bs/messages.json
index 08fedb6f10a..a2457c94080 100644
--- a/apps/browser/src/_locales/bs/messages.json
+++ b/apps/browser/src/_locales/bs/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Autofill - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Copy $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/ca/messages.json b/apps/browser/src/_locales/ca/messages.json
index 14e4a577440..d16a679824d 100644
--- a/apps/browser/src/_locales/ca/messages.json
+++ b/apps/browser/src/_locales/ca/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Només s'exportaran els elements personals incloent adjunts de la caixa forta associats a $EMAIL$. Els elements de la caixa forta de l'organització no s'inclouran",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "S'està exportant la caixa forta de l’organització"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Autofill - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Copy $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/cs/messages.json b/apps/browser/src/_locales/cs/messages.json
index e106d371d57..e6bf4a728e4 100644
--- a/apps/browser/src/_locales/cs/messages.json
+++ b/apps/browser/src/_locales/cs/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Budou exportovány jen osobní položky trezoru včetně příloh spojené s účtem $EMAIL$. Nebudou zahrnuty položky trezoru v organizaci.",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exportování trezoru organizace"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "Zobrazit položku - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Automatické vyplnění - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Automatické vyplnění - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Kopírovat $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/cy/messages.json b/apps/browser/src/_locales/cy/messages.json
index 83d09d13273..5ccff5a8332 100644
--- a/apps/browser/src/_locales/cy/messages.json
+++ b/apps/browser/src/_locales/cy/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Autofill - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Copy $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/da/messages.json b/apps/browser/src/_locales/da/messages.json
index 69c8b28d29a..f66f8f34495 100644
--- a/apps/browser/src/_locales/da/messages.json
+++ b/apps/browser/src/_locales/da/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Eksport af organisationsboks"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Autoudfyld - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Copy $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/de/messages.json b/apps/browser/src/_locales/de/messages.json
index a7439db6432..25e8b53cdb5 100644
--- a/apps/browser/src/_locales/de/messages.json
+++ b/apps/browser/src/_locales/de/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Tresor der Organisation wird exportiert"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Auto-Ausfüllen - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "$FIELD$, $VALUE$ kopieren",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/el/messages.json b/apps/browser/src/_locales/el/messages.json
index fc07f12a48f..47dca3701ec 100644
--- a/apps/browser/src/_locales/el/messages.json
+++ b/apps/browser/src/_locales/el/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Εξαγωγή θησαυ/κίου οργανισμού"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Αυτόματη συμπλήρωση - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Copy $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/en_GB/messages.json b/apps/browser/src/_locales/en_GB/messages.json
index 1381afbdc6e..9c6d212f8c9 100644
--- a/apps/browser/src/_locales/en_GB/messages.json
+++ b/apps/browser/src/_locales/en_GB/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organisation vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organisation vault"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Auto-fill - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Copy $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/en_IN/messages.json b/apps/browser/src/_locales/en_IN/messages.json
index 6b14a358148..aa7a234246f 100644
--- a/apps/browser/src/_locales/en_IN/messages.json
+++ b/apps/browser/src/_locales/en_IN/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organisation vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organisation vault"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Auto-fill - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Copy $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/es/messages.json b/apps/browser/src/_locales/es/messages.json
index 4d430d23337..d282011e628 100644
--- a/apps/browser/src/_locales/es/messages.json
+++ b/apps/browser/src/_locales/es/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exportando caja fuerte de la organización"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Autocompletar - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Copy $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/et/messages.json b/apps/browser/src/_locales/et/messages.json
index 2daf5f9d7f0..e2ad9a1e53a 100644
--- a/apps/browser/src/_locales/et/messages.json
+++ b/apps/browser/src/_locales/et/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Autofill - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Copy $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/eu/messages.json b/apps/browser/src/_locales/eu/messages.json
index b1e1ca3526b..95d843a3aa8 100644
--- a/apps/browser/src/_locales/eu/messages.json
+++ b/apps/browser/src/_locales/eu/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Autofill - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Copy $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/fa/messages.json b/apps/browser/src/_locales/fa/messages.json
index 2b3d66ad104..0f5616cd001 100644
--- a/apps/browser/src/_locales/fa/messages.json
+++ b/apps/browser/src/_locales/fa/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Autofill - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Copy $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/fi/messages.json b/apps/browser/src/_locales/fi/messages.json
index a37bd0235c1..ff71b93a62d 100644
--- a/apps/browser/src/_locales/fi/messages.json
+++ b/apps/browser/src/_locales/fi/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Organisaation holvin vienti"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Automaattitäytä - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Kopioi $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/fil/messages.json b/apps/browser/src/_locales/fil/messages.json
index 9f991843f4f..292c5fd0576 100644
--- a/apps/browser/src/_locales/fil/messages.json
+++ b/apps/browser/src/_locales/fil/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Autofill - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Copy $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/fr/messages.json b/apps/browser/src/_locales/fr/messages.json
index 32b6dd0296b..5ec06f52a76 100644
--- a/apps/browser/src/_locales/fr/messages.json
+++ b/apps/browser/src/_locales/fr/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Export du coffre de l'organisation"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Saisie automatique - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Copier $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/gl/messages.json b/apps/browser/src/_locales/gl/messages.json
index 6c0b9cce87b..c76c60114c9 100644
--- a/apps/browser/src/_locales/gl/messages.json
+++ b/apps/browser/src/_locales/gl/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exportar Caixa forte da organización"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Autoenchido - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Copy $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/he/messages.json b/apps/browser/src/_locales/he/messages.json
index 035bf9da48e..0d15c90c3d1 100644
--- a/apps/browser/src/_locales/he/messages.json
+++ b/apps/browser/src/_locales/he/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "מייצא כספת ארגון"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "מילוי אוטומטי - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "העתק $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/hi/messages.json b/apps/browser/src/_locales/hi/messages.json
index 748d9eb966b..6dc3dced829 100644
--- a/apps/browser/src/_locales/hi/messages.json
+++ b/apps/browser/src/_locales/hi/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "केवल $EMAIL$ से जुड़े अनुलग्नकों सहित व्यक्तिगत वॉल्ट आइटम ही निर्यात किए जाएंगे. संगठन वॉल्ट आइटम शामिल नहीं किए जाएंगे",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Autofill - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Copy $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/hr/messages.json b/apps/browser/src/_locales/hr/messages.json
index b88fc45493f..eda6d7267a3 100644
--- a/apps/browser/src/_locales/hr/messages.json
+++ b/apps/browser/src/_locales/hr/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Izvest će se samo stavke i privici osobnog trezora povezanog s $EMAIL$. Stavke organizacijskog trezora neće biti uključene",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Izvoz organizacijskog trezora"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Auto-ispuna - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Kopiraj $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/hu/messages.json b/apps/browser/src/_locales/hu/messages.json
index 80b1dbf095d..ec313c2cd10 100644
--- a/apps/browser/src/_locales/hu/messages.json
+++ b/apps/browser/src/_locales/hu/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Csak $EMAIL$ email címmel társított személyes széf elemek kerülnek exportálásra. Ebbe nem kerülnek be a szervezeti széf elemek.",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Szervezeti széf exportálása"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Automatikus kitöltés - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "$FIELD$, $VALUE$ másolása",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/id/messages.json b/apps/browser/src/_locales/id/messages.json
index 0146fb2a000..daccc3d8272 100644
--- a/apps/browser/src/_locales/id/messages.json
+++ b/apps/browser/src/_locales/id/messages.json
@@ -81,7 +81,7 @@
"message": "Petunjuk Kata Sandi Utama (opsional)"
},
"passwordStrengthScore": {
- "message": "Password strength score $SCORE$",
+ "message": "Skor kekuatan kata sandi $SCORE$",
"placeholders": {
"score": {
"content": "$1",
@@ -656,10 +656,10 @@
"message": "Verifikasikan identitas Anda"
},
"weDontRecognizeThisDevice": {
- "message": "We don't recognize this device. Enter the code sent to your email to verify your identity."
+ "message": "Kami tidak mengenali perangkat ini. Masukkan kode yang dikirim ke surel Anda untuk memverifikasi identitas Anda."
},
"continueLoggingIn": {
- "message": "Continue logging in"
+ "message": "Lanjutkan log masuk"
},
"yourVaultIsLocked": {
"message": "Brankas Anda terkunci. Verifikasi kata sandi utama Anda untuk melanjutkan."
@@ -869,19 +869,19 @@
"message": "Masuk ke Bitwarden"
},
"enterTheCodeSentToYourEmail": {
- "message": "Enter the code sent to your email"
+ "message": "Masukkan kode yang dikirim ke surel Anda"
},
"enterTheCodeFromYourAuthenticatorApp": {
- "message": "Enter the code from your authenticator app"
+ "message": "Masukkan kode dari aplikasi autentikator Anda"
},
"pressYourYubiKeyToAuthenticate": {
- "message": "Press your YubiKey to authenticate"
+ "message": "Sentuh YubiKey Anda untuk mengautentikasi"
},
"duoTwoFactorRequiredPageSubtitle": {
- "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in."
+ "message": "Log masuk dua langkah berganda diperlukan bagi akun Anda. Ikuti langkah di bawah untuk menyelesaikan log masuk."
},
"followTheStepsBelowToFinishLoggingIn": {
- "message": "Follow the steps below to finish logging in."
+ "message": "Ikuti langkah-langkah di bawah untuk menyelesaikan log masuk."
},
"restartRegistration": {
"message": "Mulai ulang pendaftaran"
@@ -1019,7 +1019,7 @@
"message": "Tanyakan untuk menambah sebuah benda jika benda itu tidak ditemukan di brankas Anda. Diterapkan ke seluruh akun yang telah masuk."
},
"showCardsInVaultViewV2": {
- "message": "Always show cards as Autofill suggestions on Vault view"
+ "message": "Selalu tampilan kartu sebagai saran isi otomatis pada tampilan Brankas"
},
"showCardsCurrentTab": {
"message": "Tamplikan kartu pada halaman Tab"
@@ -1028,7 +1028,7 @@
"message": "Buat tampilan daftar benda dari kartu pada halaman Tab untuk isi otomatis yang mudah."
},
"showIdentitiesInVaultViewV2": {
- "message": "Always show identities as Autofill suggestions on Vault view"
+ "message": "Selalu tampilan identitas sebagai saran isi otomatis pada tampilan Brankas"
},
"showIdentitiesCurrentTab": {
"message": "Tampilkan identitas pada halaman Tab"
@@ -1037,10 +1037,10 @@
"message": "Buat tampilan daftar benda dari identitas pada halaman Tab untuk isi otomatis yang mudah."
},
"clickToAutofillOnVault": {
- "message": "Click items to autofill on Vault view"
+ "message": "Klik butir untuk mengisi otomatis pada tampilan Brankas"
},
"clickToAutofill": {
- "message": "Click items in autofill suggestion to fill"
+ "message": "Klik butir dalam saran isi otomatis untuk mengisi"
},
"clearClipboard": {
"message": "Hapus Papan Klip",
@@ -1057,7 +1057,7 @@
"message": "Iya, Simpan Sekarang"
},
"loginSaveSuccessDetails": {
- "message": "$USERNAME$ saved to Bitwarden.",
+ "message": "$USERNAME$ disimpan ke Bitwarden.",
"placeholders": {
"username": {
"content": "$1"
@@ -1066,7 +1066,7 @@
"description": "Shown to user after login is saved."
},
"loginUpdatedSuccessDetails": {
- "message": "$USERNAME$ updated in Bitwarden.",
+ "message": "$USERNAME$ diperbarui di Bitwarden.",
"placeholders": {
"username": {
"content": "$1"
@@ -1075,35 +1075,35 @@
"description": "Shown to user after login is updated."
},
"saveAsNewLoginAction": {
- "message": "Save as new login",
+ "message": "Simpan sebagai log masuk baru",
"description": "Button text for saving login details as a new entry."
},
"updateLoginAction": {
- "message": "Update login",
+ "message": "Perbarui log masuk",
"description": "Button text for updating an existing login entry."
},
"saveLoginPrompt": {
- "message": "Save login?",
+ "message": "Simpan log masuk?",
"description": "Prompt asking the user if they want to save their login details."
},
"updateLoginPrompt": {
- "message": "Update existing login?",
+ "message": "Perbarui log masuk yang ada?",
"description": "Prompt asking the user if they want to update an existing login entry."
},
"loginSaveSuccess": {
- "message": "Login saved",
+ "message": "Log masuk disimpan",
"description": "Message displayed when login details are successfully saved."
},
"loginUpdateSuccess": {
- "message": "Login updated",
+ "message": "Log masuk diperbarui",
"description": "Message displayed when login details are successfully updated."
},
"saveFailure": {
- "message": "Error saving",
+ "message": "Kesalahan saat menyimpan",
"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": "Oh tidak! Kami tidak bisa menyimpan ini. Cobalah memasukkan rincian secara manual.",
"description": "Detailed error message shown when saving login details fails."
},
"enableChangedPasswordNotification": {
@@ -1422,7 +1422,7 @@
"message": "Ingat saya"
},
"dontAskAgainOnThisDeviceFor30Days": {
- "message": "Don't ask again on this device for 30 days"
+ "message": "Jangan tanyakan lagi pada perangkat ini untuk 30 hari"
},
"sendVerificationCodeEmailAgain": {
"message": "Kirim ulang email kode verifikasi"
@@ -1431,11 +1431,11 @@
"message": "Gunakan metode masuk dua langkah lainnya"
},
"selectAnotherMethod": {
- "message": "Select another method",
+ "message": "Pilih metode lain",
"description": "Select another two-step login method"
},
"useYourRecoveryCode": {
- "message": "Use your recovery code"
+ "message": "Gunakan kode pemulihan Anda"
},
"insertYubiKey": {
"message": "Masukkan YubiKey Anda ke port USB komputer Anda, lalu sentuh tombolnya."
@@ -1450,16 +1450,16 @@
"message": "Buka tab baru"
},
"openInNewTab": {
- "message": "Open in new tab"
+ "message": "Buka dalam tab baru"
},
"webAuthnAuthenticate": {
"message": "Autentikasi dengan WebAuthn."
},
"readSecurityKey": {
- "message": "Read security key"
+ "message": "Baca kunci keamanan"
},
"awaitingSecurityKeyInteraction": {
- "message": "Awaiting security key interaction..."
+ "message": "Menunggu interaksi kunci keamanan..."
},
"loginUnavailable": {
"message": "Info Masuk Tidak Tersedia"
@@ -1474,7 +1474,7 @@
"message": "Opsi Info Masuk Dua Langkah"
},
"selectTwoStepLoginMethod": {
- "message": "Select two-step login method"
+ "message": "Pilih metode log masuk dua langkah"
},
"recoveryCodeDesc": {
"message": "Kehilangan akses ke semua penyedia dua faktor Anda? Gunakan kode pemulihan untuk menonaktifkan semua penyedia dua faktor dari akun Anda."
@@ -1668,7 +1668,7 @@
"message": "Seret untuk mengurutkan"
},
"dragToReorder": {
- "message": "Drag to reorder"
+ "message": "Seret untuk mengubah urutan"
},
"cfTypeText": {
"message": "Teks"
@@ -2144,7 +2144,7 @@
"message": "Pembuat nama pengguna"
},
"useThisEmail": {
- "message": "Use this email"
+ "message": "Pakai surel ini"
},
"useThisPassword": {
"message": "Gunakan kata sandi ini"
@@ -2164,7 +2164,7 @@
"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": "Penyesuaian brankas"
},
"vaultTimeoutAction": {
"message": "Tindakan Batas Waktu Brankas"
@@ -2173,13 +2173,13 @@
"message": "Batas waktu tindakan"
},
"newCustomizationOptionsCalloutTitle": {
- "message": "New customization options"
+ "message": "Opsi penyesuaian baru"
},
"newCustomizationOptionsCalloutContent": {
- "message": "Customize your vault experience with quick copy actions, compact mode, and more!"
+ "message": "Sesuaikan pengalaman brankas Anda dengan aksi salin cepat, mode kompak, dan lainnya!"
},
"newCustomizationOptionsCalloutLink": {
- "message": "View all Appearance settings"
+ "message": "Lihat semua pengaturan Penampilan"
},
"lock": {
"message": "Kunci",
@@ -2437,10 +2437,10 @@
"description": "A category title describing the concept of web domains"
},
"blockedDomains": {
- "message": "Blocked domains"
+ "message": "Domain terblokir"
},
"learnMoreAboutBlockedDomains": {
- "message": "Learn more about blocked domains"
+ "message": "Pelajari lebih lanjut tentang domain yang diblokir"
},
"excludedDomains": {
"message": "Domain yang Dikecualikan"
@@ -2452,19 +2452,19 @@
"message": "Bitwarden tidak akan meminta untuk menyimpan rincian login untuk domain tersebut. Anda harus menyegarkan halaman agar perubahan diterapkan."
},
"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": "Isi otomatis dan fitur terkait lain tidak akan ditawarkan bagi situs-situs web ini. Anda mesti menyegarkan halaman agar perubahan berdampak."
},
"autofillBlockedNoticeV2": {
- "message": "Autofill is blocked for this website."
+ "message": "Isi otomatis diblokir bagi situs web ini."
},
"autofillBlockedNoticeGuidance": {
- "message": "Change this in settings"
+ "message": "Ubah ini di pengaturan"
},
"change": {
"message": "Ubah"
},
"changeButtonTitle": {
- "message": "Change password - $ITEMNAME$",
+ "message": "Ubah kata sandi - $ITEMNAME$",
"placeholders": {
"itemname": {
"content": "$1",
@@ -2473,10 +2473,10 @@
}
},
"atRiskPasswords": {
- "message": "At-risk passwords"
+ "message": "Kata sandi yang berrisiko"
},
"atRiskPasswordDescSingleOrg": {
- "message": "$ORGANIZATION$ is requesting you change one password because it is at-risk.",
+ "message": "$ORGANIZATION$ meminta Ada mengubah satu kata sandi karena itu berrisiko.",
"placeholders": {
"organization": {
"content": "$1",
@@ -2485,7 +2485,7 @@
}
},
"atRiskPasswordsDescSingleOrgPlural": {
- "message": "$ORGANIZATION$ is requesting you change the $COUNT$ passwords because they are at-risk.",
+ "message": "$ORGANIZATION$ meminta Anda mengubah $COUNT$ kata sandi karena mereka berrisiko.",
"placeholders": {
"organization": {
"content": "$1",
@@ -2498,7 +2498,7 @@
}
},
"atRiskPasswordsDescMultiOrgPlural": {
- "message": "Your organizations are requesting you change the $COUNT$ passwords because they are at-risk.",
+ "message": "Organisasi Anda meminta Anda mengubah $COUNT$ kata sandi karena mereka berrisiko.",
"placeholders": {
"count": {
"content": "$1",
@@ -2507,10 +2507,10 @@
}
},
"reviewAndChangeAtRiskPassword": {
- "message": "Review and change one at-risk password"
+ "message": "Tinjau dan ubah satu kata sandi berrisiko"
},
"reviewAndChangeAtRiskPasswordsPlural": {
- "message": "Review and change $COUNT$ at-risk passwords",
+ "message": "Tinjau dan ubah $COUNT$ kata sandi berrisiko",
"placeholders": {
"count": {
"content": "$1",
@@ -2519,7 +2519,7 @@
}
},
"changeAtRiskPasswordsFaster": {
- "message": "Change at-risk passwords faster"
+ "message": "Ubah lebih cepat kata sandi yang berrisiko"
},
"changeAtRiskPasswordsFasterDesc": {
"message": "Update your settings so you can quickly autofill your passwords and generate new ones"
@@ -2528,14 +2528,14 @@
"message": "Review at-risk logins"
},
"reviewAtRiskPasswords": {
- "message": "Review at-risk passwords"
+ "message": "Tinjau kata sandi yang berrisiko"
},
"reviewAtRiskLoginsSlideDesc": {
- "message": "Your organization passwords are at-risk because they are weak, reused, and/or exposed.",
+ "message": "Kata sandi organisasi Anda berrisiko karena mereka lemah, dipakai ulang, dan/atau terpapar.",
"description": "Description of the review at-risk login slide on the at-risk password page carousel"
},
"reviewAtRiskLoginSlideImgAlt": {
- "message": "Illustration of a list of logins that are at-risk"
+ "message": "Ilustrasi dari daftar log masuk yang berrisiko"
},
"generatePasswordSlideDesc": {
"message": "Quickly generate a strong, unique password with the Bitwarden autofill menu on the at-risk site.",
@@ -2561,7 +2561,7 @@
"message": "Turned on autofill"
},
"dismiss": {
- "message": "Dismiss"
+ "message": "Tutup"
},
"websiteItemLabel": {
"message": "Situs web $number$ (URI)",
@@ -2582,7 +2582,7 @@
}
},
"blockedDomainsSavedSuccess": {
- "message": "Blocked domain changes saved"
+ "message": "Perubahan domain yang diblokir disimpan"
},
"excludedDomainsSavedSuccess": {
"message": "Perubahan domain yang diabaikan telah disimpan"
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Mengekspor brankas organisasi"
},
@@ -3023,17 +3032,17 @@
"message": "Galat"
},
"decryptionError": {
- "message": "Decryption error"
+ "message": "Kesalahan dekripsi"
},
"couldNotDecryptVaultItemsBelow": {
- "message": "Bitwarden could not decrypt the vault item(s) listed below."
+ "message": "Bitwarden tidak bisa mendekripsi butir brankas yang tercantum di bawah."
},
"contactCSToAvoidDataLossPart1": {
"message": "Contact 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": "untuk menghindari lanjutan hilang data.",
"description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'"
},
"generateUsername": {
@@ -3168,7 +3177,7 @@
}
},
"forwaderInvalidOperation": {
- "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.",
+ "message": "$SERVICENAME$ menolak permintaan Anda. Harap hubungi penyedia layanan Anda untuk bantuan.",
"description": "Displayed when the user is forbidden from using the API by the forwarding service.",
"placeholders": {
"servicename": {
@@ -3178,7 +3187,7 @@
}
},
"forwaderInvalidOperationWithMessage": {
- "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$",
+ "message": "$SERVICENAME$ menolak permintaan Anda: $ERRORMESSAGE$",
"description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.",
"placeholders": {
"servicename": {
@@ -3327,13 +3336,13 @@
"message": "Sebuah pemberitahuan dikirim ke perangkat Anda."
},
"notificationSentDevicePart1": {
- "message": "Unlock Bitwarden on your device or on the"
+ "message": "Buka kunci Bitwarden pada perangkat Anda atau pada"
},
"notificationSentDeviceAnchor": {
- "message": "web app"
+ "message": "aplikasi web"
},
"notificationSentDevicePart2": {
- "message": "Make sure the Fingerprint phrase matches the one below before approving."
+ "message": "Pastikan frasa Sidik Jari cocok dengan yang di bawah sebelum menyetujui."
},
"aNotificationWasSentToYourDevice": {
"message": "Sebuah pemberitahuan telah dikirim ke perangkat Anda"
@@ -3348,7 +3357,7 @@
"message": "Memulai login"
},
"logInRequestSent": {
- "message": "Request sent"
+ "message": "Permintaan terkirim"
},
"exposedMasterPassword": {
"message": "Kata Sandi Utama yang Terpapar"
@@ -4080,7 +4089,7 @@
"message": "Akun aktif"
},
"bitwardenAccount": {
- "message": "Bitwarden account"
+ "message": "Akun Bitwarden"
},
"availableAccounts": {
"message": "Akun yang tersedia"
@@ -4203,10 +4212,10 @@
"message": "Kunci sandi dihapus"
},
"autofillSuggestions": {
- "message": "Autofill suggestions"
+ "message": "Saran isi otomatis"
},
"itemSuggestions": {
- "message": "Suggested items"
+ "message": "Butir yang disarankan"
},
"autofillSuggestionsTip": {
"message": "Simpan benda login untuk situs ini ke isi otomatis"
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Isi otomatis - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,8 +4303,22 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
- "message": "Copy $FIELD$, $VALUE$",
+ "message": "Salin $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
"placeholders": {
"field": {
@@ -4769,7 +4806,7 @@
}
},
"itemLocation": {
- "message": "Item Location"
+ "message": "Lokasi Item"
},
"fileSend": {
"message": "File Send"
@@ -4835,7 +4872,7 @@
"message": "File saved to device. Manage from your device downloads."
},
"showCharacterCount": {
- "message": "Show character count"
+ "message": "Tunjukkan cacah karakter"
},
"hideCharacterCount": {
"message": "Hide character count"
@@ -4896,15 +4933,15 @@
"description": "Heading for the password generator within the inline menu"
},
"passwordRegenerated": {
- "message": "Password regenerated",
+ "message": "Kata sandi dibuat ulang",
"description": "Notification message for when a password has been regenerated"
},
"saveLoginToBitwarden": {
- "message": "Save login to Bitwarden?",
+ "message": "Simpan log masuk ke Bitwarden?",
"description": "Confirmation message for saving a login to Bitwarden"
},
"spaceCharacterDescriptor": {
- "message": "Space",
+ "message": "Spasi",
"description": "Represents the space key in screen reader content as a readable word"
},
"tildeCharacterDescriptor": {
@@ -5051,22 +5088,22 @@
"message": "Beta"
},
"importantNotice": {
- "message": "Important notice"
+ "message": "Pemberitahuan penting"
},
"setupTwoStepLogin": {
- "message": "Set up two-step login"
+ "message": "Siapkan log masuk dua langkah"
},
"newDeviceVerificationNoticeContentPage1": {
- "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025."
+ "message": "Bitwarden akan mengirim suatu kode ke akun surel Anda untuk memverifikasi log masuk dari perangkat baru sejak Februari 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."
+ "message": "Anda dapat menyiapkan log masuk dua langkah sebagai cara alternatif untuk melindungi akun Anda atau mengubah surel Anda ke yang bisa Anda akses."
},
"remindMeLater": {
- "message": "Remind me later"
+ "message": "Ingatkan saya nanti"
},
"newDeviceVerificationNoticePageOneFormContent": {
- "message": "Do you have reliable access to your email, $EMAIL$?",
+ "message": "Apakah Anda punya akses yang handal ke surel Anda, $EMAIL$?",
"placeholders": {
"email": {
"content": "$1",
@@ -5081,7 +5118,7 @@
"message": "Ya, saya dapat mengakses surel saya secara handla"
},
"turnOnTwoStepLogin": {
- "message": "Turn on two-step login"
+ "message": "Nyalakan log masuk dua langkah"
},
"changeAcctEmail": {
"message": "Ubah surel akun"
diff --git a/apps/browser/src/_locales/it/messages.json b/apps/browser/src/_locales/it/messages.json
index cb8e414ca37..b18acbc79b8 100644
--- a/apps/browser/src/_locales/it/messages.json
+++ b/apps/browser/src/_locales/it/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Esportando cassaforte dell'organizzazione"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Riempi automaticamente - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Copia $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/ja/messages.json b/apps/browser/src/_locales/ja/messages.json
index b67d0b62a6e..a6dd0f709a2 100644
--- a/apps/browser/src/_locales/ja/messages.json
+++ b/apps/browser/src/_locales/ja/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "$EMAIL$ に関連付けられた個人用保管庫のアイテムのみが、添付ファイルを含めてエクスポートされます。組織用保管庫のアイテムは含まれません。",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "組織保管庫のエクスポート"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "自動入力 - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "$FIELD$ 「$VALUE$」 をコピー",
"description": "Title for a button that copies a field value to the clipboard.",
@@ -5066,7 +5103,7 @@
"message": "後で再通知"
},
"newDeviceVerificationNoticePageOneFormContent": {
- "message": "新しいメールアドレス $EMAIL$ はあなたが管理しているものですか?",
+ "message": "メールアドレス $EMAIL$ は、確実にアクセスできるものですか?",
"placeholders": {
"email": {
"content": "$1",
diff --git a/apps/browser/src/_locales/ka/messages.json b/apps/browser/src/_locales/ka/messages.json
index 818d7cdcd19..0e11594faac 100644
--- a/apps/browser/src/_locales/ka/messages.json
+++ b/apps/browser/src/_locales/ka/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Autofill - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Copy $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/km/messages.json b/apps/browser/src/_locales/km/messages.json
index c9c29611deb..0c087ef7de9 100644
--- a/apps/browser/src/_locales/km/messages.json
+++ b/apps/browser/src/_locales/km/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Autofill - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Copy $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/kn/messages.json b/apps/browser/src/_locales/kn/messages.json
index c8aff3a6488..31ea7daa668 100644
--- a/apps/browser/src/_locales/kn/messages.json
+++ b/apps/browser/src/_locales/kn/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Autofill - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Copy $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/ko/messages.json b/apps/browser/src/_locales/ko/messages.json
index cd54ac47506..92654f84e31 100644
--- a/apps/browser/src/_locales/ko/messages.json
+++ b/apps/browser/src/_locales/ko/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "조직 보관함을 내보내는 중"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "자동 완성 - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Copy $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/lt/messages.json b/apps/browser/src/_locales/lt/messages.json
index 8bf8a8f7518..e1536abdf83 100644
--- a/apps/browser/src/_locales/lt/messages.json
+++ b/apps/browser/src/_locales/lt/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Autofill - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Copy $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/lv/messages.json b/apps/browser/src/_locales/lv/messages.json
index 7b5f077f69d..789666ef2c1 100644
--- a/apps/browser/src/_locales/lv/messages.json
+++ b/apps/browser/src/_locales/lv/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Tiks izdoti tikai atsevišķi glabātavas vienumi, tajā skaitā pielikumi, kas ir saistīti ar $EMAIL$. Apvienības glabātavas vienumi netiks iekļauti",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Izgūst apvienības glabātavu"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "Apskatīt vienumu - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Automātiski aizpildīt - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Automātiskā aizpilde - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Ievietot starpliktuvē $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/ml/messages.json b/apps/browser/src/_locales/ml/messages.json
index 24a096db0ef..11af6b54202 100644
--- a/apps/browser/src/_locales/ml/messages.json
+++ b/apps/browser/src/_locales/ml/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Autofill - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Copy $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/mr/messages.json b/apps/browser/src/_locales/mr/messages.json
index 9a49998d3d9..472a8378bb7 100644
--- a/apps/browser/src/_locales/mr/messages.json
+++ b/apps/browser/src/_locales/mr/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Autofill - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Copy $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/my/messages.json b/apps/browser/src/_locales/my/messages.json
index c9c29611deb..0c087ef7de9 100644
--- a/apps/browser/src/_locales/my/messages.json
+++ b/apps/browser/src/_locales/my/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Autofill - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Copy $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/nb/messages.json b/apps/browser/src/_locales/nb/messages.json
index 0ff4ed9486a..9a52eeea4cb 100644
--- a/apps/browser/src/_locales/nb/messages.json
+++ b/apps/browser/src/_locales/nb/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Autoutfyll - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Copy $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/ne/messages.json b/apps/browser/src/_locales/ne/messages.json
index c9c29611deb..0c087ef7de9 100644
--- a/apps/browser/src/_locales/ne/messages.json
+++ b/apps/browser/src/_locales/ne/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Autofill - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Copy $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/nl/messages.json b/apps/browser/src/_locales/nl/messages.json
index 1c2b09ca3e3..80877839adb 100644
--- a/apps/browser/src/_locales/nl/messages.json
+++ b/apps/browser/src/_locales/nl/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Exporteert alleen de persoonlijke kluis-items, inclusief attachments, gerelateerd aan $EMAIL$. Geen kluis-items van de organisatie",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Organisatiekluis exporteren"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "Item weergeven - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Automatisch invullen - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Automatisch aanvullen - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "$FIELD$, $VALUE$ kopiëren",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/nn/messages.json b/apps/browser/src/_locales/nn/messages.json
index c9c29611deb..0c087ef7de9 100644
--- a/apps/browser/src/_locales/nn/messages.json
+++ b/apps/browser/src/_locales/nn/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Autofill - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Copy $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/or/messages.json b/apps/browser/src/_locales/or/messages.json
index c9c29611deb..0c087ef7de9 100644
--- a/apps/browser/src/_locales/or/messages.json
+++ b/apps/browser/src/_locales/or/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Autofill - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Copy $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/pl/messages.json b/apps/browser/src/_locales/pl/messages.json
index f4c1303bd18..c345972bd4a 100644
--- a/apps/browser/src/_locales/pl/messages.json
+++ b/apps/browser/src/_locales/pl/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Tylko poszczególne elementy sejfu łącznie z załącznikami powiązanymi z $EMAIL$ zostaną wyeksportowane. Elementy sejfu organizacji nie będą dołączone",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Eksportowanie sejfu organizacji"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "Zobacz element - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Autouzupełnij - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autouzupełnij - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Kopiuj $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/pt_BR/messages.json b/apps/browser/src/_locales/pt_BR/messages.json
index 0b5d569f443..5aacad23a93 100644
--- a/apps/browser/src/_locales/pt_BR/messages.json
+++ b/apps/browser/src/_locales/pt_BR/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exportando cofre da organização"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Auto-preenchimento - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Copiar $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/pt_PT/messages.json b/apps/browser/src/_locales/pt_PT/messages.json
index c1f1f51609c..0e467b65183 100644
--- a/apps/browser/src/_locales/pt_PT/messages.json
+++ b/apps/browser/src/_locales/pt_PT/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Apenas os itens individuais do cofre, incluindo os anexos associados a $EMAIL$, serão exportados. Os itens do cofre da organização não serão incluídos",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "A exportar o cofre da organização"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "Ver item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Preencher automaticamente - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Preencher automaticamente - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Copiar $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/ro/messages.json b/apps/browser/src/_locales/ro/messages.json
index 5ce73eb2e49..fa4d754e99d 100644
--- a/apps/browser/src/_locales/ro/messages.json
+++ b/apps/browser/src/_locales/ro/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Autofill - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Copy $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/ru/messages.json b/apps/browser/src/_locales/ru/messages.json
index 11d8de4acbe..c775bee3289 100644
--- a/apps/browser/src/_locales/ru/messages.json
+++ b/apps/browser/src/_locales/ru/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Будут экспортированы только отдельные элементы хранилища, включая вложения, связанные с $EMAIL$. Элементы хранилища организации включены не будут",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Экспорт хранилища организации"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Автозаполнение - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Скопировать $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/si/messages.json b/apps/browser/src/_locales/si/messages.json
index 721d16a2eee..12bac21e11f 100644
--- a/apps/browser/src/_locales/si/messages.json
+++ b/apps/browser/src/_locales/si/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Autofill - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Copy $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/sk/messages.json b/apps/browser/src/_locales/sk/messages.json
index 897db9f3176..cd4095f8ff5 100644
--- a/apps/browser/src/_locales/sk/messages.json
+++ b/apps/browser/src/_locales/sk/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Exportované budú iba položky osobného trezora spojené s $EMAIL$. Položky trezora organizácie nebudú zahrnuté v exporte",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exportovanie trezora organizácie"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "Zobraziť položku - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Automatické vyplnenie - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Automatické vyplnenie - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Kopírovať $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/sl/messages.json b/apps/browser/src/_locales/sl/messages.json
index f6a543ea5ea..c77d4cb55f4 100644
--- a/apps/browser/src/_locales/sl/messages.json
+++ b/apps/browser/src/_locales/sl/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Autofill - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Copy $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/sr/messages.json b/apps/browser/src/_locales/sr/messages.json
index 3c7bef94c13..5e325c6a97c 100644
--- a/apps/browser/src/_locales/sr/messages.json
+++ b/apps/browser/src/_locales/sr/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Извоз сефа организације"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Ауто-пуњење - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Копирај $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/sv/messages.json b/apps/browser/src/_locales/sv/messages.json
index 6db7a22490e..e3ff6986cae 100644
--- a/apps/browser/src/_locales/sv/messages.json
+++ b/apps/browser/src/_locales/sv/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Autofill - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Copy $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/te/messages.json b/apps/browser/src/_locales/te/messages.json
index c9c29611deb..0c087ef7de9 100644
--- a/apps/browser/src/_locales/te/messages.json
+++ b/apps/browser/src/_locales/te/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Autofill - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Copy $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/th/messages.json b/apps/browser/src/_locales/th/messages.json
index c545f802d64..e2225327664 100644
--- a/apps/browser/src/_locales/th/messages.json
+++ b/apps/browser/src/_locales/th/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Autofill - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Copy $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/tr/messages.json b/apps/browser/src/_locales/tr/messages.json
index e69b33d63af..e8788a00d70 100644
--- a/apps/browser/src/_locales/tr/messages.json
+++ b/apps/browser/src/_locales/tr/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Yalnızca $EMAIL$ ile ilişkili kişisel kasadaki kayıtlar ve dosyalar dışa aktarılacaktır. Kuruluş kasasındaki kayıtlar dahil edilmeyecektir",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Kuruluş kasasını dışa aktarma"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "Kaydı görüntüle - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Otomatik doldur - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Otomatik doldur - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Kopyala: $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/uk/messages.json b/apps/browser/src/_locales/uk/messages.json
index d18d90babff..6bd9ca7fc36 100644
--- a/apps/browser/src/_locales/uk/messages.json
+++ b/apps/browser/src/_locales/uk/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Експортування сховища організації"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Автозаповнення – $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Копіювати $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/vi/messages.json b/apps/browser/src/_locales/vi/messages.json
index db0da3b5874..35c553f72f1 100644
--- a/apps/browser/src/_locales/vi/messages.json
+++ b/apps/browser/src/_locales/vi/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Đang xuất dữ liệu kho tổ chức"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Tự động điền - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Copy $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/zh_CN/messages.json b/apps/browser/src/_locales/zh_CN/messages.json
index bdfaeb92531..fc6f70cb6a4 100644
--- a/apps/browser/src/_locales/zh_CN/messages.json
+++ b/apps/browser/src/_locales/zh_CN/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "仅会导出与 $EMAIL$ 关联的个人密码库项目(包括附件)。不包括组织密码库项目。",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "正在导出组织密码库"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "查看项目 - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "自动填充 - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "自动填充 - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "复制 $FIELD$,$VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/zh_TW/messages.json b/apps/browser/src/_locales/zh_TW/messages.json
index 37e450c8fd2..365aae61ebf 100644
--- a/apps/browser/src/_locales/zh_TW/messages.json
+++ b/apps/browser/src/_locales/zh_TW/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "正在匯出組織密碼庫"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "自動填入 - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Copy $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
From 8b14b0c09f0df761ba2ea28dd2c35a4f8d2e0f96 Mon Sep 17 00:00:00 2001
From: "bw-ghapp[bot]" <178206702+bw-ghapp[bot]@users.noreply.github.com>
Date: Fri, 28 Mar 2025 12:34:52 +0100
Subject: [PATCH 075/113] Autosync the updated translations (#14039)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
---
apps/web/src/locales/af/messages.json | 9 ++
apps/web/src/locales/ar/messages.json | 9 ++
apps/web/src/locales/az/messages.json | 9 ++
apps/web/src/locales/be/messages.json | 9 ++
apps/web/src/locales/bg/messages.json | 9 ++
apps/web/src/locales/bn/messages.json | 9 ++
apps/web/src/locales/bs/messages.json | 9 ++
apps/web/src/locales/ca/messages.json | 9 ++
apps/web/src/locales/cs/messages.json | 9 ++
apps/web/src/locales/cy/messages.json | 9 ++
apps/web/src/locales/da/messages.json | 9 ++
apps/web/src/locales/de/messages.json | 9 ++
apps/web/src/locales/el/messages.json | 9 ++
apps/web/src/locales/en_GB/messages.json | 9 ++
apps/web/src/locales/en_IN/messages.json | 9 ++
apps/web/src/locales/eo/messages.json | 9 ++
apps/web/src/locales/es/messages.json | 9 ++
apps/web/src/locales/et/messages.json | 9 ++
apps/web/src/locales/eu/messages.json | 9 ++
apps/web/src/locales/fa/messages.json | 9 ++
apps/web/src/locales/fi/messages.json | 9 ++
apps/web/src/locales/fil/messages.json | 9 ++
apps/web/src/locales/fr/messages.json | 9 ++
apps/web/src/locales/gl/messages.json | 9 ++
apps/web/src/locales/he/messages.json | 9 ++
apps/web/src/locales/hi/messages.json | 9 ++
apps/web/src/locales/hr/messages.json | 9 ++
apps/web/src/locales/hu/messages.json | 9 ++
apps/web/src/locales/id/messages.json | 9 ++
apps/web/src/locales/it/messages.json | 9 ++
apps/web/src/locales/ja/messages.json | 123 ++++++++++++-----------
apps/web/src/locales/ka/messages.json | 9 ++
apps/web/src/locales/km/messages.json | 9 ++
apps/web/src/locales/kn/messages.json | 9 ++
apps/web/src/locales/ko/messages.json | 9 ++
apps/web/src/locales/lv/messages.json | 9 ++
apps/web/src/locales/ml/messages.json | 9 ++
apps/web/src/locales/mr/messages.json | 9 ++
apps/web/src/locales/my/messages.json | 9 ++
apps/web/src/locales/nb/messages.json | 9 ++
apps/web/src/locales/ne/messages.json | 9 ++
apps/web/src/locales/nl/messages.json | 9 ++
apps/web/src/locales/nn/messages.json | 9 ++
apps/web/src/locales/or/messages.json | 9 ++
apps/web/src/locales/pl/messages.json | 9 ++
apps/web/src/locales/pt_BR/messages.json | 9 ++
apps/web/src/locales/pt_PT/messages.json | 9 ++
apps/web/src/locales/ro/messages.json | 9 ++
apps/web/src/locales/ru/messages.json | 9 ++
apps/web/src/locales/si/messages.json | 9 ++
apps/web/src/locales/sk/messages.json | 9 ++
apps/web/src/locales/sl/messages.json | 9 ++
apps/web/src/locales/sr/messages.json | 9 ++
apps/web/src/locales/sr_CS/messages.json | 9 ++
apps/web/src/locales/sv/messages.json | 9 ++
apps/web/src/locales/te/messages.json | 9 ++
apps/web/src/locales/th/messages.json | 9 ++
apps/web/src/locales/tr/messages.json | 9 ++
apps/web/src/locales/uk/messages.json | 9 ++
apps/web/src/locales/vi/messages.json | 9 ++
apps/web/src/locales/zh_CN/messages.json | 13 ++-
apps/web/src/locales/zh_TW/messages.json | 9 ++
62 files changed, 617 insertions(+), 59 deletions(-)
diff --git a/apps/web/src/locales/af/messages.json b/apps/web/src/locales/af/messages.json
index 3b9168b29b2..fa25494f4bf 100644
--- a/apps/web/src/locales/af/messages.json
+++ b/apps/web/src/locales/af/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.",
"placeholders": {
diff --git a/apps/web/src/locales/ar/messages.json b/apps/web/src/locales/ar/messages.json
index 13777f942de..4a289082039 100644
--- a/apps/web/src/locales/ar/messages.json
+++ b/apps/web/src/locales/ar/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.",
"placeholders": {
diff --git a/apps/web/src/locales/az/messages.json b/apps/web/src/locales/az/messages.json
index f092620b00a..dfafeab1877 100644
--- a/apps/web/src/locales/az/messages.json
+++ b/apps/web/src/locales/az/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Yalnız $EMAIL$ ilə əlaqələndirilmiş qoşmalar daxil olmaqla fərdi anbar elementləri xaricə köçürüləcək. Təşkilat anbar elementləri daxil edilməyəcək",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Yalnız $ORGANIZATION$ ilə əlaqələndirilmiş təşkilat seyfi xaricə köçürüləcək. Fərdi seyfdə və digər təşkilatlardakı elementlər daxil edilməyəcək.",
"placeholders": {
diff --git a/apps/web/src/locales/be/messages.json b/apps/web/src/locales/be/messages.json
index 2e5aeabff32..a22b463f4f2 100644
--- a/apps/web/src/locales/be/messages.json
+++ b/apps/web/src/locales/be/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Толькі сховішча арганізацыі, якія звязаны з $ORGANIZATION$ будуць экспартаваны. Элементы асабістага сховішча і элементы з іншых арганізацый не будуць уключаны.",
"placeholders": {
diff --git a/apps/web/src/locales/bg/messages.json b/apps/web/src/locales/bg/messages.json
index 04d4e25bb7c..0599d3a73f2 100644
--- a/apps/web/src/locales/bg/messages.json
+++ b/apps/web/src/locales/bg/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Ще бъдат изнесени само записите и прикачените файлове от личния трезор свързан с $EMAIL$. Записите в трезора на организацията няма да бъдат включени.",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Ще бъдат изнесени само записите от трезора свързан с $ORGANIZATION$. Записите в отделните лични трезори и тези в други организации няма да бъдат включени.",
"placeholders": {
diff --git a/apps/web/src/locales/bn/messages.json b/apps/web/src/locales/bn/messages.json
index 8c082c57e30..15ca72e1642 100644
--- a/apps/web/src/locales/bn/messages.json
+++ b/apps/web/src/locales/bn/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.",
"placeholders": {
diff --git a/apps/web/src/locales/bs/messages.json b/apps/web/src/locales/bs/messages.json
index 7efb47bd86c..2c0f177ad67 100644
--- a/apps/web/src/locales/bs/messages.json
+++ b/apps/web/src/locales/bs/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.",
"placeholders": {
diff --git a/apps/web/src/locales/ca/messages.json b/apps/web/src/locales/ca/messages.json
index a4f2aeb7f55..d394a75b70f 100644
--- a/apps/web/src/locales/ca/messages.json
+++ b/apps/web/src/locales/ca/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Només s'exportarà la caixa forta de l'organització associada a $ORGANIZATION$. No s'inclouran els elements de les caixes fortes individuals ni d'altres organitzacions.",
"placeholders": {
diff --git a/apps/web/src/locales/cs/messages.json b/apps/web/src/locales/cs/messages.json
index 46b510a6459..552a717ab82 100644
--- a/apps/web/src/locales/cs/messages.json
+++ b/apps/web/src/locales/cs/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Budou exportovány jen osobní položky trezoru včetně příloh spojené s účtem $EMAIL$. Nebudou zahrnuty položky trezoru v organizaci.",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Exportován bude jen trezor organizace přidružený k položce $ORGANIZATION$. Osobní položky trezoru a položky z jiných organizací nebudou zahrnuty.",
"placeholders": {
diff --git a/apps/web/src/locales/cy/messages.json b/apps/web/src/locales/cy/messages.json
index 2fd61e01526..ba8bd6bd8a4 100644
--- a/apps/web/src/locales/cy/messages.json
+++ b/apps/web/src/locales/cy/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.",
"placeholders": {
diff --git a/apps/web/src/locales/da/messages.json b/apps/web/src/locales/da/messages.json
index f8a335011e5..dddef79ddd0 100644
--- a/apps/web/src/locales/da/messages.json
+++ b/apps/web/src/locales/da/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Kun organisationsboksen tilknyttet $ORGANIZATION$ eksporteres. Emner i individuelle bokse eller andre organisationer medtages ikke.",
"placeholders": {
diff --git a/apps/web/src/locales/de/messages.json b/apps/web/src/locales/de/messages.json
index 135ab8af923..b5eeb9db7ce 100644
--- a/apps/web/src/locales/de/messages.json
+++ b/apps/web/src/locales/de/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Nur der mit $ORGANIZATION$ verbundene Organisationstresor wird exportiert. Einträge in persönlichen Tresoren oder anderen Organisationen werden nicht berücksichtigt.",
"placeholders": {
diff --git a/apps/web/src/locales/el/messages.json b/apps/web/src/locales/el/messages.json
index e20b5cc52a0..27e39f9798e 100644
--- a/apps/web/src/locales/el/messages.json
+++ b/apps/web/src/locales/el/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.",
"placeholders": {
diff --git a/apps/web/src/locales/en_GB/messages.json b/apps/web/src/locales/en_GB/messages.json
index b211ec070ab..b4f0ace607a 100644
--- a/apps/web/src/locales/en_GB/messages.json
+++ b/apps/web/src/locales/en_GB/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organisation vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Only the organisation vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organisations will not be included.",
"placeholders": {
diff --git a/apps/web/src/locales/en_IN/messages.json b/apps/web/src/locales/en_IN/messages.json
index 6b4b6e11ab3..a8c4bfca62e 100644
--- a/apps/web/src/locales/en_IN/messages.json
+++ b/apps/web/src/locales/en_IN/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organisation vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Only the organisation vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organisations will not be included.",
"placeholders": {
diff --git a/apps/web/src/locales/eo/messages.json b/apps/web/src/locales/eo/messages.json
index ce40767f4c1..c31274e6198 100644
--- a/apps/web/src/locales/eo/messages.json
+++ b/apps/web/src/locales/eo/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.",
"placeholders": {
diff --git a/apps/web/src/locales/es/messages.json b/apps/web/src/locales/es/messages.json
index 1c71a29f48b..80b982bd928 100644
--- a/apps/web/src/locales/es/messages.json
+++ b/apps/web/src/locales/es/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.",
"placeholders": {
diff --git a/apps/web/src/locales/et/messages.json b/apps/web/src/locales/et/messages.json
index f9a5b810ded..a55fce63349 100644
--- a/apps/web/src/locales/et/messages.json
+++ b/apps/web/src/locales/et/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.",
"placeholders": {
diff --git a/apps/web/src/locales/eu/messages.json b/apps/web/src/locales/eu/messages.json
index d0e99c73e66..a8b64594065 100644
--- a/apps/web/src/locales/eu/messages.json
+++ b/apps/web/src/locales/eu/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.",
"placeholders": {
diff --git a/apps/web/src/locales/fa/messages.json b/apps/web/src/locales/fa/messages.json
index 04cddc65f2f..4657d4a9732 100644
--- a/apps/web/src/locales/fa/messages.json
+++ b/apps/web/src/locales/fa/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "فقط گاوصدوق سازمان مرتبط با $ORGANIZATION$ برون ریزی خواهد شد. موارد موجود در گاوصندوقهای فردی یا سایر سازمانها شامل نمیشوند.",
"placeholders": {
diff --git a/apps/web/src/locales/fi/messages.json b/apps/web/src/locales/fi/messages.json
index 9427a4e8e22..38671c12f79 100644
--- a/apps/web/src/locales/fi/messages.json
+++ b/apps/web/src/locales/fi/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Vain organisaatioon $ORGANIZATION$ liitetyn holvin kohteet viedään. Yksityisen holvin ja muiden organisaatioiden kohteita ei sisällytetä.",
"placeholders": {
diff --git a/apps/web/src/locales/fil/messages.json b/apps/web/src/locales/fil/messages.json
index 414bbda91d8..0a07cf17526 100644
--- a/apps/web/src/locales/fil/messages.json
+++ b/apps/web/src/locales/fil/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.",
"placeholders": {
diff --git a/apps/web/src/locales/fr/messages.json b/apps/web/src/locales/fr/messages.json
index 95aa573f14b..e26b5ef1250 100644
--- a/apps/web/src/locales/fr/messages.json
+++ b/apps/web/src/locales/fr/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Seuls les éléments individuels du coffre et les pièces jointes associées à $EMAIL$ seront exportés. Les éléments du coffre de l'organisation ne seront pas inclus",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Seul le coffre d'organisation associé à $ORGANIZATION$ sera exporté. Les éléments dans les coffres individuels ou d'autres organisations ne seront pas inclus.",
"placeholders": {
diff --git a/apps/web/src/locales/gl/messages.json b/apps/web/src/locales/gl/messages.json
index 38a94f9307f..6964a867768 100644
--- a/apps/web/src/locales/gl/messages.json
+++ b/apps/web/src/locales/gl/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.",
"placeholders": {
diff --git a/apps/web/src/locales/he/messages.json b/apps/web/src/locales/he/messages.json
index 4df39c8496f..115482c021b 100644
--- a/apps/web/src/locales/he/messages.json
+++ b/apps/web/src/locales/he/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "רק כספת הארגון המשויכת עם $ORGANIZATION$ תיוצא. פריטים בכספת אישית או ארגונים אחרים לא יכללו.",
"placeholders": {
diff --git a/apps/web/src/locales/hi/messages.json b/apps/web/src/locales/hi/messages.json
index 1cab0dc843b..bdd11c6561f 100644
--- a/apps/web/src/locales/hi/messages.json
+++ b/apps/web/src/locales/hi/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.",
"placeholders": {
diff --git a/apps/web/src/locales/hr/messages.json b/apps/web/src/locales/hr/messages.json
index 65c42a0cbf0..5ae2ddcfc0b 100644
--- a/apps/web/src/locales/hr/messages.json
+++ b/apps/web/src/locales/hr/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Izvest će se samo stavke i privici osobnog trezora povezanog s $EMAIL$. Stavke organizacijskog trezora neće biti uključene",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Izvest će se samo organizacijski trezor povezan s $ORGANIZATION$. Stavke iz osobnih trezora i stavke iz drugih organizacija neće biti uključene.",
"placeholders": {
diff --git a/apps/web/src/locales/hu/messages.json b/apps/web/src/locales/hu/messages.json
index 3d6a2e258a4..b7a51e5048b 100644
--- a/apps/web/src/locales/hu/messages.json
+++ b/apps/web/src/locales/hu/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Csak $EMAIL$ email címmel társított személyes széf elemek kerülnek exportálásra. Ebbe nem kerülnek be a szervezeti széf elemek.",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Csak$ORGANIZATION$ névvel társított szervezeti széf elemek kerülnek exportálásra. Ebbe nem kerülnek be a személyes és más szervezeti széf elemek.",
"placeholders": {
diff --git a/apps/web/src/locales/id/messages.json b/apps/web/src/locales/id/messages.json
index 22b4e3a6132..dc3ba77328f 100644
--- a/apps/web/src/locales/id/messages.json
+++ b/apps/web/src/locales/id/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.",
"placeholders": {
diff --git a/apps/web/src/locales/it/messages.json b/apps/web/src/locales/it/messages.json
index 0cbc425d588..853c580c82d 100644
--- a/apps/web/src/locales/it/messages.json
+++ b/apps/web/src/locales/it/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Solo la cassaforte dell'organizzazione associata a $ORGANIZATION$ sarà esportata. Elementi nelle casseforti individuali o in altre organizzazioni non saranno inclusi.",
"placeholders": {
diff --git a/apps/web/src/locales/ja/messages.json b/apps/web/src/locales/ja/messages.json
index 6860b30b360..d445184774c 100644
--- a/apps/web/src/locales/ja/messages.json
+++ b/apps/web/src/locales/ja/messages.json
@@ -474,7 +474,7 @@
"message": "フォルダーを編集"
},
"editWithName": {
- "message": "Edit $ITEM$: $NAME$",
+ "message": "$ITEM$: $NAME$ を編集",
"placeholders": {
"item": {
"content": "$1",
@@ -1202,7 +1202,7 @@
"message": "デバイスとは何ですか?"
},
"aDeviceIs": {
- "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times."
+ "message": "デバイスとは、あなたがログインしている Bitwarden アプリの一意なインストールです。 再インストールや、アプリデータの消去、 Cookie の消去をすると、同じデバイスが複数回表示される場合があります。"
},
"logInInitiated": {
"message": "ログイン開始"
@@ -1894,22 +1894,22 @@
"message": "新しいデバイスからのログイン"
},
"turnOffNewDeviceLoginProtection": {
- "message": "Turn off new device login protection"
+ "message": "新しいデバイスからのログイン保護をオフにする"
},
"turnOnNewDeviceLoginProtection": {
- "message": "Turn on new device login protection"
+ "message": "新しいデバイスからのログイン保護をオンにする"
},
"turnOffNewDeviceLoginProtectionModalDesc": {
- "message": "Proceed below to turn off the verification emails bitwarden sends when you login from a new device."
+ "message": "続行すると、新しいデバイスからログインしたときに Bitwarden が送信する認証メールをオフにします。"
},
"turnOnNewDeviceLoginProtectionModalDesc": {
- "message": "Proceed below to have bitwarden send you verification emails when you login from a new device."
+ "message": "続行すると、新しいデバイスからログインする際に Bitwarden から認証メールを送信します。"
},
"turnOffNewDeviceLoginProtectionWarning": {
- "message": "With new device login protection turned off, anyone with your master password can access your account from any device. To protect your account without verification emails, set up two-step login."
+ "message": "新しいデバイスからのログイン保護をオフにすると、マスターパスワードを持つ人は誰でも、どのデバイスからでもあなたのアカウントにアクセスできます。認証メールなしでアカウントを保護するには、2段階認証によるログインを設定してください。"
},
"accountNewDeviceLoginProtectionSaved": {
- "message": "New device login protection changes saved"
+ "message": "新しいデバイスからのログイン保護の変更が保存されました"
},
"sessionsDeauthorized": {
"message": "全てのセッションを無効化"
@@ -2175,7 +2175,7 @@
"message": "2段階認証を有効にすると Bitwarden アカウントから永久に閉め出されてしまうことがあります。リカバリーコードがあれば、通常の2段階認証プロバイダを使えなくなったとき (デバイスの紛失等) でもアカウントにアクセスできます。アカウントにアクセスできなくなっても Bitwarden はサポート出来ないため、リカバリーコードを書き出すか印刷し安全な場所で保管しておくことを推奨します。"
},
"yourSingleUseRecoveryCode": {
- "message": "Your single-use recovery code can be used to turn off two-step login in the event that you lose access to your two-step login provider. Bitwarden recommends you write down the recovery code and keep it in a safe place."
+ "message": "2段階認証プロバイダーへのアクセスを失った場合は、使い捨てのリカバリーコードを使用して2段階認証をオフにできます。 Bitwarden では、リカバリーコードを書き留めて安全な場所に保管することをお勧めしています。"
},
"viewRecoveryCode": {
"message": "リカバリーコードを確認"
@@ -2216,7 +2216,7 @@
"message": "管理"
},
"manageCollection": {
- "message": "Manage collection"
+ "message": "コレクションの管理"
},
"viewItems": {
"message": "アイテムを表示"
@@ -2429,7 +2429,7 @@
"message": "セキュリティキーの読み取り中に問題が発生しました。もう一度やり直して下さい。"
},
"twoFactorWebAuthnWarning1": {
- "message": "Due to platform limitations, WebAuthn cannot be used on all Bitwarden applications. You should set up another two-step login provider so that you can access your account when WebAuthn cannot be used."
+ "message": "プラットフォームの制限により、 WebAuthn は全ての Bitwarden アプリケーションで使用できるわけではありません。 WebAuthn が使用できない場合に備えて、他の2段階認証プロバイダーを設定しておくことをおすすめします。"
},
"twoFactorRecoveryYourCode": {
"message": "二段階認証のリカバリーコード"
@@ -3396,10 +3396,10 @@
}
},
"inviteSingleEmailDesc": {
- "message": "You have 1 invite remaining."
+ "message": "招待は残り 1 個です。"
},
"inviteZeroEmailDesc": {
- "message": "You have 0 invites remaining."
+ "message": "招待は残り 0 個です。"
},
"userUsingTwoStep": {
"message": "このユーザーはアカウントを保護するため二段階認証を利用しています。"
@@ -3853,7 +3853,7 @@
}
},
"unlinkedSso": {
- "message": "Unlinked SSO."
+ "message": "SSO のリンクを解除しました。"
},
"unlinkedSsoUser": {
"message": "ユーザー $ID$ にリンクされていない SSO",
@@ -3904,22 +3904,22 @@
"message": "デバイス"
},
"loginStatus": {
- "message": "Login status"
+ "message": "ログイン状態"
},
"firstLogin": {
- "message": "First login"
+ "message": "初回ログイン"
},
"trusted": {
- "message": "Trusted"
+ "message": "信頼済み"
},
"needsApproval": {
- "message": "Needs approval"
+ "message": "承認が必要"
},
"areYouTryingtoLogin": {
- "message": "Are you trying to log in?"
+ "message": "ログインしようとしていますか?"
},
"logInAttemptBy": {
- "message": "Login attempt by $EMAIL$",
+ "message": "$EMAIL$ によるログインの試行",
"placeholders": {
"email": {
"content": "$1",
@@ -3928,7 +3928,7 @@
}
},
"deviceType": {
- "message": "Device Type"
+ "message": "デバイスタイプ"
},
"ipAddress": {
"message": "IP アドレス"
@@ -3965,7 +3965,7 @@
"message": "たった今"
},
"requestedXMinutesAgo": {
- "message": "Requested $MINUTES$ minutes ago",
+ "message": "$MINUTES$ 分前に要求されました",
"placeholders": {
"minutes": {
"content": "$1",
@@ -4097,10 +4097,10 @@
"message": "サポートされていないブラウザを使用しています。ウェブ保管庫が正しく動作しないかもしれません。"
},
"youHaveAPendingLoginRequest": {
- "message": "You have a pending login request from another device."
+ "message": "別のデバイスからの保留中のログインリクエストがあります。"
},
"reviewLoginRequest": {
- "message": "Review login request"
+ "message": "ログインリクエストの内容を確認"
},
"freeTrialEndPromptCount": {
"message": "無料体験はあと $COUNT$ 日で終了します。",
@@ -4197,7 +4197,7 @@
"message": "もし通常の二段階認証の方法でアカウントにアクセスできなくなった場合、リカバリーコードにより全ての二段階認証プロバイダを無効化することができます。"
},
"logInBelowUsingYourSingleUseRecoveryCode": {
- "message": "Log in below using your single-use recovery code. This will turn off all two-step providers on your account."
+ "message": "使い捨てのリカバリーコードを使用して以下からログインしてください。これにより、アカウントのすべての2段階認証プロバイダーが無効になります。"
},
"recoverAccountTwoStep": {
"message": "二段階認証ログインの回復"
@@ -4509,7 +4509,7 @@
}
},
"reorderFieldUp": {
- "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$",
+ "message": "$LABEL$ を上に移動しました。$INDEX$ / $LENGTH$",
"placeholders": {
"label": {
"content": "$1",
@@ -4526,7 +4526,7 @@
}
},
"reorderFieldDown": {
- "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$",
+ "message": "$LABEL$ を下に移動しました。$INDEX$ / $LENGTH$",
"placeholders": {
"label": {
"content": "$1",
@@ -4814,7 +4814,7 @@
"message": "マスターパスワードの強度に最低要件を設定する。"
},
"passwordStrengthScore": {
- "message": "Password strength score $SCORE$",
+ "message": "パスワードの強度スコア $SCORE$",
"placeholders": {
"score": {
"content": "$1",
@@ -5103,14 +5103,14 @@
"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": {
@@ -5120,11 +5120,11 @@
}
},
"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."
},
"sendTypeTextToShare": {
- "message": "Text to share"
+ "message": "共有するテキスト"
},
"sendTypeFile": {
"message": "ファイル"
@@ -5133,7 +5133,7 @@
"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": {
@@ -5161,14 +5161,14 @@
"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."
},
"deletionDate": {
"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": {
@@ -5218,7 +5218,7 @@
"message": "保留中の削除"
},
"hideTextByDefault": {
- "message": "Hide text by default"
+ "message": "デフォルトでテキストを隠す"
},
"expired": {
"message": "期限切れ"
@@ -5689,7 +5689,7 @@
"message": "削除と有効期限の保存中にエラーが発生しました。"
},
"hideYourEmail": {
- "message": "Hide your email address from viewers."
+ "message": "閲覧者にメールアドレスを見せないようにします。"
},
"webAuthnFallbackMsg": {
"message": "二段階認証を確認するには、下のボタンをクリックしてください。"
@@ -5698,10 +5698,10 @@
"message": "WebAuthn の認証"
},
"readSecurityKey": {
- "message": "Read security key"
+ "message": "セキュリティキーの読み取り"
},
"awaitingSecurityKeyInteraction": {
- "message": "Awaiting security key interaction..."
+ "message": "セキュリティキーとの通信を待ち受け中…"
},
"webAuthnNotSupported": {
"message": "WebAuthn はこのブラウザではサポートされていません。"
@@ -5917,17 +5917,17 @@
"message": "エラー"
},
"decryptionError": {
- "message": "Decryption error"
+ "message": "復号エラー"
},
"couldNotDecryptVaultItemsBelow": {
- "message": "Bitwarden could not decrypt the vault item(s) listed below."
+ "message": "Bitwarden は以下の保管庫のアイテムを復号できませんでした。"
},
"contactCSToAvoidDataLossPart1": {
- "message": "Contact customer success",
+ "message": "カスタマーサクセスに問い合わせて、",
"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.'"
},
"accountRecoveryManageUsers": {
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "$EMAIL$ に関連付けられた個人用保管庫のアイテムのみが、添付ファイルを含めてエクスポートされます。組織用保管庫のアイテムは含まれません。",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "$ORGANIZATION$ に関連付けられた組織保管庫のみがエクスポートされます。個々の保管庫または他の組織にあるアイテムは含まれません。",
"placeholders": {
@@ -6845,7 +6854,7 @@
"message": "ドメインに設定されたキャッチオール受信トレイを使用します。"
},
"useThisEmail": {
- "message": "Use this email"
+ "message": "このメールアドレスを使う"
},
"random": {
"message": "ランダム",
@@ -6997,7 +7006,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": {
@@ -7007,7 +7016,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": {
@@ -10159,7 +10168,7 @@
}
},
"userLeftOrganization": {
- "message": "User $ID$ left organization",
+ "message": "ユーザー $ID$ が組織から脱退しました",
"placeholders": {
"id": {
"content": "$1",
@@ -10168,7 +10177,7 @@
}
},
"suspendedOrganizationTitle": {
- "message": "The $ORGANIZATION$ is suspended",
+ "message": "$ORGANIZATION$ は一時停止されています",
"placeholders": {
"organization": {
"content": "$1",
@@ -10177,25 +10186,25 @@
}
},
"suspendedUserOrgMessage": {
- "message": "Contact your organization owner for assistance."
+ "message": "組織の所有者にお問い合わせください。"
},
"suspendedOwnerOrgMessage": {
- "message": "To regain access to your organization, add a payment method."
+ "message": "組織へのアクセスを取り戻すには、支払い方法を追加してください。"
},
"deleteMembers": {
- "message": "Delete members"
+ "message": "メンバーを削除"
},
"noSelectedMembersApplicable": {
- "message": "This action is not applicable to any of the selected members."
+ "message": "この操作は、選択されたメンバーには適用できません。"
},
"deletedSuccessfully": {
- "message": "Deleted successfully"
+ "message": "正常に削除されました"
},
"freeFamiliesSponsorship": {
- "message": "Remove Free Bitwarden Families sponsorship"
+ "message": "Bitwarden ファミリープランの無償利用へのスポンサーを解除"
},
"freeFamiliesSponsorshipPolicyDesc": {
- "message": "Do not allow members to redeem a Families plan through this organization."
+ "message": "メンバーがこの組織を通じてファミリープランを引き換えられないようにします。"
},
"verifyBankAccountWithStatementDescriptorWarning": {
"message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the organization's billing page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended."
@@ -10545,6 +10554,6 @@
"message": "These events are examples only and do not reflect real events within your Bitwarden organization."
},
"cannotCreateCollection": {
- "message": "Free organizations may have up to 2 collections. Upgrade to a paid plan to add more collections."
+ "message": "無料版の組織ではコレクションは 2 つまでです。さらにコレクションを追加するには有料プランにアップグレードしてください。"
}
}
diff --git a/apps/web/src/locales/ka/messages.json b/apps/web/src/locales/ka/messages.json
index f5f4bfb3ae1..5ace08617ff 100644
--- a/apps/web/src/locales/ka/messages.json
+++ b/apps/web/src/locales/ka/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.",
"placeholders": {
diff --git a/apps/web/src/locales/km/messages.json b/apps/web/src/locales/km/messages.json
index f334131b69b..59eac9b372b 100644
--- a/apps/web/src/locales/km/messages.json
+++ b/apps/web/src/locales/km/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.",
"placeholders": {
diff --git a/apps/web/src/locales/kn/messages.json b/apps/web/src/locales/kn/messages.json
index dbf2cab3bb5..8132c9a2db5 100644
--- a/apps/web/src/locales/kn/messages.json
+++ b/apps/web/src/locales/kn/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.",
"placeholders": {
diff --git a/apps/web/src/locales/ko/messages.json b/apps/web/src/locales/ko/messages.json
index 1f3bbda3736..6e59f0ccf88 100644
--- a/apps/web/src/locales/ko/messages.json
+++ b/apps/web/src/locales/ko/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "$ORGANIZATION$ 조직과 연관된 조직 보관함만 내보내기됩니다. 개인 보관함이나 다른 조직의 항목은 포함되지 않습니다.",
"placeholders": {
diff --git a/apps/web/src/locales/lv/messages.json b/apps/web/src/locales/lv/messages.json
index 7c8c3b2831e..e59f6078bf5 100644
--- a/apps/web/src/locales/lv/messages.json
+++ b/apps/web/src/locales/lv/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Tiks izdoti tikai atsevišķi glabātavas vienumi, tajā skaitā pielikumi, kas ir saistīti ar $EMAIL$. Apvienības glabātavas vienumi netiks iekļauti",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Tiks izgūta tikai apvienības glabātava, kas ir saistīta ar $ORGANIZATION$. Atsevišķu glabātavu vai citu apvienību vienumi netiks iekļauti.",
"placeholders": {
diff --git a/apps/web/src/locales/ml/messages.json b/apps/web/src/locales/ml/messages.json
index 46ce3b62533..10ae27a2139 100644
--- a/apps/web/src/locales/ml/messages.json
+++ b/apps/web/src/locales/ml/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.",
"placeholders": {
diff --git a/apps/web/src/locales/mr/messages.json b/apps/web/src/locales/mr/messages.json
index f334131b69b..59eac9b372b 100644
--- a/apps/web/src/locales/mr/messages.json
+++ b/apps/web/src/locales/mr/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.",
"placeholders": {
diff --git a/apps/web/src/locales/my/messages.json b/apps/web/src/locales/my/messages.json
index f334131b69b..59eac9b372b 100644
--- a/apps/web/src/locales/my/messages.json
+++ b/apps/web/src/locales/my/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.",
"placeholders": {
diff --git a/apps/web/src/locales/nb/messages.json b/apps/web/src/locales/nb/messages.json
index 24285b8cb9f..bc36ba82ff1 100644
--- a/apps/web/src/locales/nb/messages.json
+++ b/apps/web/src/locales/nb/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.",
"placeholders": {
diff --git a/apps/web/src/locales/ne/messages.json b/apps/web/src/locales/ne/messages.json
index 8183efd5f9b..c947c0bb716 100644
--- a/apps/web/src/locales/ne/messages.json
+++ b/apps/web/src/locales/ne/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.",
"placeholders": {
diff --git a/apps/web/src/locales/nl/messages.json b/apps/web/src/locales/nl/messages.json
index 6b752b5a195..1bc731e3ceb 100644
--- a/apps/web/src/locales/nl/messages.json
+++ b/apps/web/src/locales/nl/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Exporteert alleen de persoonlijke kluis-items, inclusief attachments, gerelateerd aan $EMAIL$. Geen kluis-items van de organisatie",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Exporteert alleen de organisatiekluis van $ORGANIZATION$. Geen persoonlijke kluis-items of items van andere organisaties.",
"placeholders": {
diff --git a/apps/web/src/locales/nn/messages.json b/apps/web/src/locales/nn/messages.json
index 23d4d3b3089..ae64568bd79 100644
--- a/apps/web/src/locales/nn/messages.json
+++ b/apps/web/src/locales/nn/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.",
"placeholders": {
diff --git a/apps/web/src/locales/or/messages.json b/apps/web/src/locales/or/messages.json
index f334131b69b..59eac9b372b 100644
--- a/apps/web/src/locales/or/messages.json
+++ b/apps/web/src/locales/or/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.",
"placeholders": {
diff --git a/apps/web/src/locales/pl/messages.json b/apps/web/src/locales/pl/messages.json
index 9ef5c95bf86..f793b249148 100644
--- a/apps/web/src/locales/pl/messages.json
+++ b/apps/web/src/locales/pl/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Tylko poszczególne elementy sejfu łącznie z załącznikami powiązanymi z $EMAIL$ zostaną wyeksportowane. Elementy sejfu organizacji nie będą dołączone",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Tylko sejf organizacji powiązany z $ORGANIZATION$ zostanie wyeksportowany. Pozycje w poszczególnych sejfach lub innych organizacji nie będą uwzględnione.",
"placeholders": {
diff --git a/apps/web/src/locales/pt_BR/messages.json b/apps/web/src/locales/pt_BR/messages.json
index ea43fd4eae6..3ac5afa3110 100644
--- a/apps/web/src/locales/pt_BR/messages.json
+++ b/apps/web/src/locales/pt_BR/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Apenas o cofre da organização associado com $ORGANIZATION$ será exportado. Itens do cofre pessoal e itens de outras organizações não serão incluídos.",
"placeholders": {
diff --git a/apps/web/src/locales/pt_PT/messages.json b/apps/web/src/locales/pt_PT/messages.json
index 9bbda05c93f..1cd3f9c9b1b 100644
--- a/apps/web/src/locales/pt_PT/messages.json
+++ b/apps/web/src/locales/pt_PT/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Apenas os itens individuais do cofre, incluindo os anexos associados a $EMAIL$, serão exportados. Os itens do cofre da organização não serão incluídos",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Apenas o cofre da organização associado a $ORGANIZATION$ será exportado. Os itens em cofres individuais ou noutras organizações não serão incluídos.",
"placeholders": {
diff --git a/apps/web/src/locales/ro/messages.json b/apps/web/src/locales/ro/messages.json
index c57ad26d251..f67c9550655 100644
--- a/apps/web/src/locales/ro/messages.json
+++ b/apps/web/src/locales/ro/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.",
"placeholders": {
diff --git a/apps/web/src/locales/ru/messages.json b/apps/web/src/locales/ru/messages.json
index a31d13d227e..2bdaeb63206 100644
--- a/apps/web/src/locales/ru/messages.json
+++ b/apps/web/src/locales/ru/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Будут экспортированы только отдельные элементы хранилища, включая вложения, связанные с $EMAIL$. Элементы хранилища организации включены не будут",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Будет экспортировано только хранилище организации, связанное с $ORGANIZATION$. Элементы из личных хранилищ и из других организаций включены не будут.",
"placeholders": {
diff --git a/apps/web/src/locales/si/messages.json b/apps/web/src/locales/si/messages.json
index fc79da1352b..bbb01d1aa49 100644
--- a/apps/web/src/locales/si/messages.json
+++ b/apps/web/src/locales/si/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.",
"placeholders": {
diff --git a/apps/web/src/locales/sk/messages.json b/apps/web/src/locales/sk/messages.json
index fdf57456a4c..339a73bbffb 100644
--- a/apps/web/src/locales/sk/messages.json
+++ b/apps/web/src/locales/sk/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Exportované budú iba položy osobného trezora spojené s $EMAIL$. Položky trezora organizácie nebudú zahrnuté v exporte",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Exportované budú iba položky trezora organizácie spojené s $ORGANIZATION$. Položky osobného trezora a položky z iných organizácií nebudú zahrnuté.",
"placeholders": {
diff --git a/apps/web/src/locales/sl/messages.json b/apps/web/src/locales/sl/messages.json
index 1652841b650..d14b4f13ecc 100644
--- a/apps/web/src/locales/sl/messages.json
+++ b/apps/web/src/locales/sl/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.",
"placeholders": {
diff --git a/apps/web/src/locales/sr/messages.json b/apps/web/src/locales/sr/messages.json
index 005fa7602b5..2ac4a713189 100644
--- a/apps/web/src/locales/sr/messages.json
+++ b/apps/web/src/locales/sr/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Биће извезен само сеф организације повезан са $ORGANIZATION$. Ставке у појединачним сефовима или другим организацијама неће бити укључене.",
"placeholders": {
diff --git a/apps/web/src/locales/sr_CS/messages.json b/apps/web/src/locales/sr_CS/messages.json
index d141d8301fc..d84f3192dea 100644
--- a/apps/web/src/locales/sr_CS/messages.json
+++ b/apps/web/src/locales/sr_CS/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.",
"placeholders": {
diff --git a/apps/web/src/locales/sv/messages.json b/apps/web/src/locales/sv/messages.json
index f7d6414c36c..e4cc2cdeb67 100644
--- a/apps/web/src/locales/sv/messages.json
+++ b/apps/web/src/locales/sv/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.",
"placeholders": {
diff --git a/apps/web/src/locales/te/messages.json b/apps/web/src/locales/te/messages.json
index f334131b69b..59eac9b372b 100644
--- a/apps/web/src/locales/te/messages.json
+++ b/apps/web/src/locales/te/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.",
"placeholders": {
diff --git a/apps/web/src/locales/th/messages.json b/apps/web/src/locales/th/messages.json
index 7d2685807b3..3b05ffc9000 100644
--- a/apps/web/src/locales/th/messages.json
+++ b/apps/web/src/locales/th/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.",
"placeholders": {
diff --git a/apps/web/src/locales/tr/messages.json b/apps/web/src/locales/tr/messages.json
index 030fd0dd9b2..ef8701ce4b1 100644
--- a/apps/web/src/locales/tr/messages.json
+++ b/apps/web/src/locales/tr/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Yalnızca $EMAIL$ ile ilişkili kişisel kasadaki kayıtlar ve dosyalar dışa aktarılacaktır. Kuruluş kasasındaki kayıtlar dahil edilmeyecektir",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.",
"placeholders": {
diff --git a/apps/web/src/locales/uk/messages.json b/apps/web/src/locales/uk/messages.json
index 7d9eb316821..e175fe99db8 100644
--- a/apps/web/src/locales/uk/messages.json
+++ b/apps/web/src/locales/uk/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Буде експортовано лише сховище організації, пов'язане з $ORGANIZATION$. Записи особистих сховищ або інших організацій не будуть включені.",
"placeholders": {
diff --git a/apps/web/src/locales/vi/messages.json b/apps/web/src/locales/vi/messages.json
index f0a0215f823..f7e8f9f4037 100644
--- a/apps/web/src/locales/vi/messages.json
+++ b/apps/web/src/locales/vi/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.",
"placeholders": {
diff --git a/apps/web/src/locales/zh_CN/messages.json b/apps/web/src/locales/zh_CN/messages.json
index 00bf9bc5973..6bb79aa1971 100644
--- a/apps/web/src/locales/zh_CN/messages.json
+++ b/apps/web/src/locales/zh_CN/messages.json
@@ -2958,7 +2958,7 @@
"description": "Past tense status of an invoice. ex. Paid or unpaid."
},
"unpaid": {
- "message": "待支付",
+ "message": "未支付",
"description": "Past tense status of an invoice. ex. Paid or unpaid."
},
"transactions": {
@@ -4799,7 +4799,7 @@
"message": "您必须至少选择一个集合。"
},
"couldNotChargeCardPayInvoice": {
- "message": "我们无法从您的支付卡中扣款。请查看并支付下面列出的待支付账单。"
+ "message": "我们无法从您的支付卡中扣款。请查看并支付下面列出的未支付账单。"
},
"minLength": {
"message": "最小长度"
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "仅会导出与 $EMAIL$ 关联的个人密码库项目(包括附件)。不包括组织密码库项目。",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "仅会导出与 $ORGANIZATION$ 关联的组织密码库数据。不包括个人密码库和其他组织中的项目。",
"placeholders": {
diff --git a/apps/web/src/locales/zh_TW/messages.json b/apps/web/src/locales/zh_TW/messages.json
index beca8f74485..c23def1464d 100644
--- a/apps/web/src/locales/zh_TW/messages.json
+++ b/apps/web/src/locales/zh_TW/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "僅匯出與 $ORGANIZATION$ 相關的組織密碼庫項目。個人密碼庫項目或其他組織中的項目將不包括在内。",
"placeholders": {
From 7ed21453937eb18ed9af96b9b57342f8f54f5f7d Mon Sep 17 00:00:00 2001
From: Daniel James Smith <2670567+djsmith85@users.noreply.github.com>
Date: Fri, 28 Mar 2025 16:20:36 +0100
Subject: [PATCH 076/113] Set correct filename extensions on vault-export
(#14018)
Co-authored-by: Daniel James Smith
---
.../src/services/individual-vault-export.service.ts | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
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 0fc1f336b90..765de042d32 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
@@ -81,7 +81,7 @@ export class IndividualVaultExportService
return {
type: "text/plain",
data: await this.buildPasswordExport(exportVault.data, password),
- fileName: ExportHelper.getFileName("json"),
+ fileName: ExportHelper.getFileName("", "encrypted_json"),
} as ExportedVaultAsString;
}
@@ -126,7 +126,7 @@ export class IndividualVaultExportService
return {
type: "application/zip",
data: blobData,
- fileName: ExportHelper.getFileName("json"),
+ fileName: ExportHelper.getFileName("", "json"),
} as ExportedVaultAsBlob;
}
@@ -185,14 +185,14 @@ export class IndividualVaultExportService
return {
type: "text/plain",
data: this.buildCsvExport(decFolders, decCiphers),
- fileName: ExportHelper.getFileName("csv"),
+ fileName: ExportHelper.getFileName("", "csv"),
} as ExportedVaultAsString;
}
return {
type: "text/plain",
data: this.buildJsonExport(decFolders, decCiphers),
- fileName: ExportHelper.getFileName("json"),
+ fileName: ExportHelper.getFileName("", "json"),
} as ExportedVaultAsString;
}
@@ -250,7 +250,7 @@ export class IndividualVaultExportService
return {
type: "text/plain",
data: JSON.stringify(jsonDoc, null, " "),
- fileName: ExportHelper.getFileName("json"),
+ fileName: ExportHelper.getFileName("", "json"),
} as ExportedVaultAsString;
}
From 6204e2a092c50ed192532a0caac2cbbe2283ba4b Mon Sep 17 00:00:00 2001
From: Vicki League
Date: Fri, 28 Mar 2025 13:41:53 -0400
Subject: [PATCH 077/113] [CL-550] Update cipher form story (#14019)
---
libs/vault/src/cipher-form/cipher-form.mdx | 3 ++
.../src/cipher-form/cipher-form.stories.ts | 50 +++++++++++++++----
2 files changed, 44 insertions(+), 9 deletions(-)
diff --git a/libs/vault/src/cipher-form/cipher-form.mdx b/libs/vault/src/cipher-form/cipher-form.mdx
index ed2e799b9f3..658fcd38d11 100644
--- a/libs/vault/src/cipher-form/cipher-form.mdx
+++ b/libs/vault/src/cipher-form/cipher-form.mdx
@@ -12,6 +12,9 @@ It is configured via a `CipherFormConfig` object that is passed to the component
create it. A default implementation of the `CipherFormConfigService` exists in the
`@bitwarden/vault` library.
+The cipher form has a slot for `attachment-button`, which should be included when the form is in
+`edit` mode.
+
diff --git a/libs/vault/src/cipher-form/cipher-form.stories.ts b/libs/vault/src/cipher-form/cipher-form.stories.ts
index 5d6464b4c79..52d70e8652a 100644
--- a/libs/vault/src/cipher-form/cipher-form.stories.ts
+++ b/libs/vault/src/cipher-form/cipher-form.stories.ts
@@ -29,7 +29,7 @@ import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { FolderView } from "@bitwarden/common/vault/models/view/folder.view";
import { LoginView } from "@bitwarden/common/vault/models/view/login.view";
-import { AsyncActionsModule, ButtonModule, ToastService } from "@bitwarden/components";
+import { AsyncActionsModule, ButtonModule, ItemModule, ToastService } from "@bitwarden/components";
import {
CipherFormConfig,
CipherFormGenerationService,
@@ -131,7 +131,7 @@ export default {
component: CipherFormComponent,
decorators: [
moduleMetadata({
- imports: [CipherFormModule, AsyncActionsModule, ButtonModule],
+ imports: [CipherFormModule, AsyncActionsModule, ButtonModule, ItemModule],
providers: [
{
provide: CipherFormService,
@@ -246,7 +246,7 @@ export default {
type Story = StoryObj;
-export const Default: Story = {
+export const Add: Story = {
render: (args) => {
return {
props: {
@@ -254,15 +254,28 @@ export const Default: Story = {
...args,
},
template: /*html*/ `
-
- Submit
+
`,
};
},
};
export const Edit: Story = {
- ...Default,
+ render: (args) => {
+ return {
+ props: {
+ onSave: actionsData.onSave,
+ ...args,
+ },
+ template: /*html*/ `
+
+
+ Attachments
+
+
+ `,
+ };
+ },
args: {
config: {
...defaultConfig,
@@ -273,7 +286,7 @@ export const Edit: Story = {
};
export const PartialEdit: Story = {
- ...Default,
+ ...Add,
args: {
config: {
...defaultConfig,
@@ -284,7 +297,7 @@ export const PartialEdit: Story = {
};
export const Clone: Story = {
- ...Default,
+ ...Add,
args: {
config: {
...defaultConfig,
@@ -294,8 +307,27 @@ export const Clone: Story = {
},
};
+export const WithSubmitButton: Story = {
+ render: (args) => {
+ return {
+ props: {
+ onSave: actionsData.onSave,
+ ...args,
+ },
+ template: /*html*/ `
+
+
+
+
+ Submit
+
+ `,
+ };
+ },
+};
+
export const NoPersonalOwnership: Story = {
- ...Default,
+ ...Add,
args: {
config: {
...defaultConfig,
From d5f033efa2a50e82780dd140b0431eb7bb455def Mon Sep 17 00:00:00 2001
From: Alec Rippberger <127791530+alec-livefront@users.noreply.github.com>
Date: Fri, 28 Mar 2025 12:51:20 -0500
Subject: [PATCH 078/113] refactor(auth): [PM-9179] remove deprecated
TwoFactorComponents
Remove deprecated TwoFactorComponentsV1 and TwoFactorOptionsComponentV1 components, related functionality (unauthUiRefreshSwap) and orphaned styles/translation messages.
---
apps/browser/src/_locales/en/messages.json | 36 --
.../two-factor-options-v1.component.html | 29 -
.../popup/two-factor-options-v1.component.ts | 56 --
.../auth/popup/two-factor-v1.component.html | 196 -------
.../src/auth/popup/two-factor-v1.component.ts | 260 ---------
apps/browser/src/popup/app-routing.module.ts | 53 +-
apps/browser/src/popup/app.module.ts | 4 -
apps/browser/src/popup/scss/misc.scss | 10 -
apps/desktop/src/app/app-routing.module.ts | 39 +-
apps/desktop/src/app/app.module.ts | 4 -
.../auth/two-factor-options-v1.component.html | 33 --
.../auth/two-factor-options-v1.component.ts | 24 -
.../src/auth/two-factor-v1.component.html | 175 ------
.../src/auth/two-factor-v1.component.ts | 190 -------
apps/desktop/src/locales/en/messages.json | 27 -
apps/desktop/src/scss/pages.scss | 2 -
.../auth/two-factor-options-v1.component.html | 45 --
.../auth/two-factor-options-v1.component.ts | 52 --
.../src/app/auth/two-factor-v1.component.html | 106 ----
.../src/app/auth/two-factor-v1.component.ts | 164 ------
apps/web/src/app/oss-routing.module.ts | 69 +--
.../src/app/shared/loose-components.module.ts | 6 -
apps/web/src/locales/en/messages.json | 28 +-
.../two-factor-options-v1.component.ts | 44 --
.../two-factor-v1.component.spec.ts | 505 -----------------
.../components/two-factor-v1.component.ts | 514 ------------------
.../functions/unauth-ui-refresh-route-swap.ts | 36 --
27 files changed, 55 insertions(+), 2652 deletions(-)
delete mode 100644 apps/browser/src/auth/popup/two-factor-options-v1.component.html
delete mode 100644 apps/browser/src/auth/popup/two-factor-options-v1.component.ts
delete mode 100644 apps/browser/src/auth/popup/two-factor-v1.component.html
delete mode 100644 apps/browser/src/auth/popup/two-factor-v1.component.ts
delete mode 100644 apps/desktop/src/auth/two-factor-options-v1.component.html
delete mode 100644 apps/desktop/src/auth/two-factor-options-v1.component.ts
delete mode 100644 apps/desktop/src/auth/two-factor-v1.component.html
delete mode 100644 apps/desktop/src/auth/two-factor-v1.component.ts
delete mode 100644 apps/web/src/app/auth/two-factor-options-v1.component.html
delete mode 100644 apps/web/src/app/auth/two-factor-options-v1.component.ts
delete mode 100644 apps/web/src/app/auth/two-factor-v1.component.html
delete mode 100644 apps/web/src/app/auth/two-factor-v1.component.ts
delete mode 100644 libs/angular/src/auth/components/two-factor-options-v1.component.ts
delete mode 100644 libs/angular/src/auth/components/two-factor-v1.component.spec.ts
delete mode 100644 libs/angular/src/auth/components/two-factor-v1.component.ts
delete mode 100644 libs/angular/src/auth/functions/unauth-ui-refresh-route-swap.ts
diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json
index 2c940ccdf5a..8c47db0d331 100644
--- a/apps/browser/src/_locales/en/messages.json
+++ b/apps/browser/src/_locales/en/messages.json
@@ -1391,24 +1391,12 @@
"premiumRequiredDesc": {
"message": "A Premium membership is required to use this feature."
},
- "enterVerificationCodeApp": {
- "message": "Enter the 6 digit verification code from your authenticator app."
- },
"authenticationTimeout": {
"message": "Authentication timeout"
},
"authenticationSessionTimedOut": {
"message": "The authentication session timed out. Please restart the login process."
},
- "enterVerificationCodeEmail": {
- "message": "Enter the 6 digit verification code that was emailed to $EMAIL$.",
- "placeholders": {
- "email": {
- "content": "$1",
- "example": "example@gmail.com"
- }
- }
- },
"verificationCodeEmailSent": {
"message": "Verification email sent to $EMAIL$.",
"placeholders": {
@@ -1418,18 +1406,9 @@
}
}
},
- "rememberMe": {
- "message": "Remember me"
- },
"dontAskAgainOnThisDeviceFor30Days": {
"message": "Don't ask again on this device for 30 days"
},
- "sendVerificationCodeEmailAgain": {
- "message": "Send verification code email again"
- },
- "useAnotherTwoStepMethod": {
- "message": "Use another two-step login method"
- },
"selectAnotherMethod": {
"message": "Select another method",
"description": "Select another two-step login method"
@@ -1437,18 +1416,9 @@
"useYourRecoveryCode": {
"message": "Use your recovery code"
},
- "insertYubiKey": {
- "message": "Insert your YubiKey into your computer's USB port, then touch its button."
- },
"insertU2f": {
"message": "Insert your security key into your computer's USB port. If it has a button, touch it."
},
- "webAuthnNewTab": {
- "message": "To start the WebAuthn 2FA verification. Click the button below to open a new tab and follow the instructions provided in the new tab."
- },
- "webAuthnNewTabOpen": {
- "message": "Open new tab"
- },
"openInNewTab": {
"message": "Open in new tab"
},
@@ -3848,15 +3818,9 @@
"duoHealthCheckResultsInNullAuthUrlError": {
"message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance."
},
- "launchDuoAndFollowStepsToFinishLoggingIn": {
- "message": "Launch Duo and follow the steps to finish logging in."
- },
"duoRequiredForAccount": {
"message": "Duo two-step login is required for your account."
},
- "popoutTheExtensionToCompleteLogin": {
- "message": "Popout the extension to complete login."
- },
"popoutExtension": {
"message": "Popout extension"
},
diff --git a/apps/browser/src/auth/popup/two-factor-options-v1.component.html b/apps/browser/src/auth/popup/two-factor-options-v1.component.html
deleted file mode 100644
index f25944aba65..00000000000
--- a/apps/browser/src/auth/popup/two-factor-options-v1.component.html
+++ /dev/null
@@ -1,29 +0,0 @@
-
-
- {{ "close" | i18n }}
-
-
- {{ "twoStepOptions" | i18n }}
-
-
-
-
-
-
-
- {{ p.name }}
- {{ p.description }}
-
-
- {{ "recoveryCodeTitle" | i18n }}
- {{ "recoveryCodeDesc" | i18n }}
-
-
-
-
diff --git a/apps/browser/src/auth/popup/two-factor-options-v1.component.ts b/apps/browser/src/auth/popup/two-factor-options-v1.component.ts
deleted file mode 100644
index 0c71421fc04..00000000000
--- a/apps/browser/src/auth/popup/two-factor-options-v1.component.ts
+++ /dev/null
@@ -1,56 +0,0 @@
-import { Component } from "@angular/core";
-import { ActivatedRoute, Router } from "@angular/router";
-
-import { TwoFactorOptionsComponentV1 as BaseTwoFactorOptionsComponent } from "@bitwarden/angular/auth/components/two-factor-options-v1.component";
-import {
- TwoFactorProviderDetails,
- TwoFactorService,
-} from "@bitwarden/common/auth/abstractions/two-factor.service";
-import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
-import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
-import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
-
-@Component({
- selector: "app-two-factor-options",
- templateUrl: "two-factor-options-v1.component.html",
-})
-export class TwoFactorOptionsComponentV1 extends BaseTwoFactorOptionsComponent {
- constructor(
- twoFactorService: TwoFactorService,
- router: Router,
- i18nService: I18nService,
- platformUtilsService: PlatformUtilsService,
- environmentService: EnvironmentService,
- private activatedRoute: ActivatedRoute,
- ) {
- super(twoFactorService, router, i18nService, platformUtilsService, window, environmentService);
- }
-
- close() {
- this.navigateTo2FA();
- }
-
- override async choose(p: TwoFactorProviderDetails) {
- await super.choose(p);
- await this.twoFactorService.setSelectedProvider(p.type);
-
- this.navigateTo2FA();
- }
-
- navigateTo2FA() {
- const sso = this.activatedRoute.snapshot.queryParamMap.get("sso") === "true";
-
- if (sso) {
- // Persist SSO flag back to the 2FA comp if it exists
- // in order for successful login logic to work properly for
- // SSO + 2FA in browser extension
- // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
- this.router.navigate(["2fa"], { queryParams: { sso: true } });
- } else {
- // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
- this.router.navigate(["2fa"]);
- }
- }
-}
diff --git a/apps/browser/src/auth/popup/two-factor-v1.component.html b/apps/browser/src/auth/popup/two-factor-v1.component.html
deleted file mode 100644
index 126b0ea5a99..00000000000
--- a/apps/browser/src/auth/popup/two-factor-v1.component.html
+++ /dev/null
@@ -1,196 +0,0 @@
-
diff --git a/apps/browser/src/auth/popup/two-factor-v1.component.ts b/apps/browser/src/auth/popup/two-factor-v1.component.ts
deleted file mode 100644
index 884e42bf73a..00000000000
--- a/apps/browser/src/auth/popup/two-factor-v1.component.ts
+++ /dev/null
@@ -1,260 +0,0 @@
-// FIXME: Update this file to be type safe and remove this and next line
-// @ts-strict-ignore
-import { Component, Inject, OnDestroy, OnInit } from "@angular/core";
-import { ActivatedRoute, Router } from "@angular/router";
-import { Subject, Subscription, firstValueFrom } from "rxjs";
-import { filter, first, takeUntil } from "rxjs/operators";
-
-import { TwoFactorComponentV1 as BaseTwoFactorComponent } from "@bitwarden/angular/auth/components/two-factor-v1.component";
-import { WINDOW } from "@bitwarden/angular/services/injection-tokens";
-import {
- LoginStrategyServiceAbstraction,
- LoginEmailServiceAbstraction,
- UserDecryptionOptionsServiceAbstraction,
-} from "@bitwarden/auth/common";
-import { ApiService } from "@bitwarden/common/abstractions/api.service";
-import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
-import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
-import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service";
-import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type";
-import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
-import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
-import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
-import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
-import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
-import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
-import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
-import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
-import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
-import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
-import { DialogService, ToastService } from "@bitwarden/components";
-
-import { BrowserApi } from "../../platform/browser/browser-api";
-import { ZonedMessageListenerService } from "../../platform/browser/zoned-message-listener.service";
-import BrowserPopupUtils from "../../platform/popup/browser-popup-utils";
-
-import { closeTwoFactorAuthWebAuthnPopout } from "./utils/auth-popout-window";
-
-@Component({
- selector: "app-two-factor",
- templateUrl: "two-factor-v1.component.html",
-})
-export class TwoFactorComponentV1 extends BaseTwoFactorComponent implements OnInit, OnDestroy {
- private destroy$ = new Subject();
- inPopout = BrowserPopupUtils.inPopout(window);
-
- constructor(
- loginStrategyService: LoginStrategyServiceAbstraction,
- router: Router,
- i18nService: I18nService,
- apiService: ApiService,
- platformUtilsService: PlatformUtilsService,
- private syncService: SyncService,
- environmentService: EnvironmentService,
- stateService: StateService,
- route: ActivatedRoute,
- private messagingService: MessagingService,
- logService: LogService,
- twoFactorService: TwoFactorService,
- appIdService: AppIdService,
- loginEmailService: LoginEmailServiceAbstraction,
- userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction,
- configService: ConfigService,
- ssoLoginService: SsoLoginServiceAbstraction,
- private dialogService: DialogService,
- masterPasswordService: InternalMasterPasswordServiceAbstraction,
- accountService: AccountService,
- toastService: ToastService,
- @Inject(WINDOW) protected win: Window,
- private browserMessagingApi: ZonedMessageListenerService,
- ) {
- super(
- loginStrategyService,
- router,
- i18nService,
- apiService,
- platformUtilsService,
- win,
- environmentService,
- stateService,
- route,
- logService,
- twoFactorService,
- appIdService,
- loginEmailService,
- userDecryptionOptionsService,
- ssoLoginService,
- configService,
- masterPasswordService,
- accountService,
- toastService,
- );
- this.onSuccessfulLogin = async () => {
- // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
- syncService.fullSync(true);
- };
-
- this.onSuccessfulLoginTde = async () => {
- // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
- syncService.fullSync(true);
- };
-
- this.onSuccessfulLoginTdeNavigate = async () => {
- this.win.close();
- };
-
- this.successRoute = "/tabs/vault";
- // FIXME: Chromium 110 has broken WebAuthn support in extensions via an iframe
- this.webAuthnNewTab = true;
- }
-
- async ngOnInit() {
- if (this.route.snapshot.paramMap.has("webAuthnResponse")) {
- // WebAuthn fallback response
- this.selectedProviderType = TwoFactorProviderType.WebAuthn;
- this.token = this.route.snapshot.paramMap.get("webAuthnResponse");
- this.onSuccessfulLogin = async () => {
- // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
- this.syncService.fullSync(true);
- this.messagingService.send("reloadPopup");
- window.close();
- };
- this.remember = this.route.snapshot.paramMap.get("remember") === "true";
- await this.doSubmit();
- return;
- }
-
- await super.ngOnInit();
- if (this.selectedProviderType == null) {
- return;
- }
-
- // WebAuthn prompt appears inside the popup on linux, and requires a larger popup width
- // than usual to avoid cutting off the dialog.
- if (this.selectedProviderType === TwoFactorProviderType.WebAuthn && (await this.isLinux())) {
- document.body.classList.add("linux-webauthn");
- }
-
- if (
- this.selectedProviderType === TwoFactorProviderType.Email &&
- BrowserPopupUtils.inPopup(window)
- ) {
- const confirmed = await this.dialogService.openSimpleDialog({
- title: { key: "warning" },
- content: { key: "popup2faCloseMessage" },
- type: "warning",
- });
- if (confirmed) {
- // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
- BrowserPopupUtils.openCurrentPagePopout(window);
- }
- }
-
- // eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
- this.route.queryParams.pipe(first()).subscribe(async (qParams) => {
- if (qParams.sso === "true") {
- this.onSuccessfulLogin = async () => {
- // This is not awaited so we don't pause the application while the sync is happening.
- // This call is executed by the service that lives in the background script so it will continue
- // the sync even if this tab closes.
- // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
- this.syncService.fullSync(true);
-
- // Force sidebars (FF && Opera) to reload while exempting current window
- // because we are just going to close the current window.
- BrowserApi.reloadOpenWindows(true);
-
- // We don't need this window anymore because the intent is for the user to be left
- // on the web vault screen which tells them to continue in the browser extension (sidebar or popup)
- await closeTwoFactorAuthWebAuthnPopout();
- };
- }
- });
- }
-
- async ngOnDestroy() {
- this.destroy$.next();
- this.destroy$.complete();
-
- if (this.selectedProviderType === TwoFactorProviderType.WebAuthn && (await this.isLinux())) {
- document.body.classList.remove("linux-webauthn");
- }
- super.ngOnDestroy();
- }
-
- anotherMethod() {
- const sso = this.route.snapshot.queryParamMap.get("sso") === "true";
-
- if (sso) {
- // We must persist this so when the user returns to the 2FA comp, the
- // proper onSuccessfulLogin logic is executed.
- // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
- this.router.navigate(["2fa-options"], { queryParams: { sso: true } });
- } else {
- // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
- this.router.navigate(["2fa-options"]);
- }
- }
-
- async popoutCurrentPage() {
- await BrowserPopupUtils.openCurrentPagePopout(window);
- }
-
- async isLinux() {
- return (await BrowserApi.getPlatformInfo()).os === "linux";
- }
-
- duoResultSubscription: Subscription;
- protected override setupDuoResultListener() {
- if (!this.duoResultSubscription) {
- this.duoResultSubscription = this.browserMessagingApi
- .messageListener$()
- .pipe(
- filter((msg: any) => msg.command === "duoResult"),
- takeUntil(this.destroy$),
- )
- .subscribe((msg: { command: string; code: string; state: string }) => {
- this.token = msg.code + "|" + msg.state;
- // This floating promise is intentional. We don't need to await the submit + awaiting in a subscription is not recommended.
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
- this.submit();
- });
- }
- }
-
- override async launchDuoFrameless() {
- if (this.duoFramelessUrl === null) {
- this.toastService.showToast({
- variant: "error",
- title: null,
- message: this.i18nService.t("duoHealthCheckResultsInNullAuthUrlError"),
- });
- return;
- }
-
- const duoHandOffMessage = {
- title: this.i18nService.t("youSuccessfullyLoggedIn"),
- message: this.i18nService.t("youMayCloseThisWindow"),
- isCountdown: false,
- };
-
- // we're using the connector here as a way to set a cookie with translations
- // before continuing to the duo frameless url
- const env = await firstValueFrom(this.environmentService.environment$);
- const launchUrl =
- env.getWebVaultUrl() +
- "/duo-redirect-connector.html" +
- "?duoFramelessUrl=" +
- encodeURIComponent(this.duoFramelessUrl) +
- "&handOffMessage=" +
- encodeURIComponent(JSON.stringify(duoHandOffMessage));
- this.platformUtilsService.launchUri(launchUrl);
- }
-}
diff --git a/apps/browser/src/popup/app-routing.module.ts b/apps/browser/src/popup/app-routing.module.ts
index e73f56fa2f6..9948b450e17 100644
--- a/apps/browser/src/popup/app-routing.module.ts
+++ b/apps/browser/src/popup/app-routing.module.ts
@@ -7,7 +7,6 @@ import {
EnvironmentSelectorRouteData,
ExtensionDefaultOverlayPosition,
} from "@bitwarden/angular/auth/components/environment-selector.component";
-import { unauthUiRefreshSwap } from "@bitwarden/angular/auth/functions/unauth-ui-refresh-route-swap";
import {
activeAuthGuard,
authGuard,
@@ -59,8 +58,6 @@ import {
import { RemovePasswordComponent } from "../auth/popup/remove-password.component";
import { SetPasswordComponent } from "../auth/popup/set-password.component";
import { AccountSecurityComponent } from "../auth/popup/settings/account-security.component";
-import { TwoFactorOptionsComponentV1 } from "../auth/popup/two-factor-options-v1.component";
-import { TwoFactorComponentV1 } from "../auth/popup/two-factor-v1.component";
import { UpdateTempPasswordComponent } from "../auth/popup/update-temp-password.component";
import { Fido2Component } from "../autofill/popup/fido2/fido2.component";
import { AutofillComponent } from "../autofill/popup/settings/autofill.component";
@@ -142,32 +139,6 @@ const routes: Routes = [
canActivate: [fido2AuthGuard],
data: { elevation: 1 } satisfies RouteDataProperties,
},
- ...unauthUiRefreshSwap(
- TwoFactorComponentV1,
- ExtensionAnonLayoutWrapperComponent,
- {
- path: "2fa",
- canActivate: [unauthGuardFn(unauthRouteOverrides)],
- data: { elevation: 1 } satisfies RouteDataProperties,
- },
- {
- path: "2fa",
- canActivate: [unauthGuardFn(unauthRouteOverrides), TwoFactorAuthGuard],
- children: [
- {
- path: "",
- component: TwoFactorAuthComponent,
- },
- ],
- data: {
- elevation: 1,
- pageTitle: {
- key: "verifyYourIdentity",
- },
- showBackButton: true,
- } satisfies RouteDataProperties & ExtensionAnonLayoutWrapperData,
- },
- ),
{
path: "",
component: ExtensionAnonLayoutWrapperComponent,
@@ -191,12 +162,6 @@ const routes: Routes = [
},
],
},
- {
- path: "2fa-options",
- component: TwoFactorOptionsComponentV1,
- canActivate: [unauthGuardFn(unauthRouteOverrides)],
- data: { elevation: 1 } satisfies RouteDataProperties,
- },
{
path: "device-verification",
component: ExtensionAnonLayoutWrapperComponent,
@@ -371,7 +336,6 @@ const routes: Routes = [
canActivate: [authGuard],
data: { elevation: 1 } satisfies RouteDataProperties,
},
-
{
path: "",
component: ExtensionAnonLayoutWrapperComponent,
@@ -567,6 +531,23 @@ const routes: Routes = [
},
],
},
+ {
+ path: "2fa",
+ canActivate: [unauthGuardFn(unauthRouteOverrides), TwoFactorAuthGuard],
+ children: [
+ {
+ path: "",
+ component: TwoFactorAuthComponent,
+ },
+ ],
+ data: {
+ elevation: 1,
+ pageTitle: {
+ key: "verifyYourIdentity",
+ },
+ showBackButton: true,
+ } satisfies RouteDataProperties & ExtensionAnonLayoutWrapperData,
+ },
],
},
{
diff --git a/apps/browser/src/popup/app.module.ts b/apps/browser/src/popup/app.module.ts
index 80cffa03b17..b2542679e06 100644
--- a/apps/browser/src/popup/app.module.ts
+++ b/apps/browser/src/popup/app.module.ts
@@ -25,8 +25,6 @@ import { SetPasswordComponent } from "../auth/popup/set-password.component";
import { AccountSecurityComponent } from "../auth/popup/settings/account-security.component";
import { VaultTimeoutInputComponent } from "../auth/popup/settings/vault-timeout-input.component";
import { SsoComponentV1 } from "../auth/popup/sso-v1.component";
-import { TwoFactorOptionsComponentV1 } from "../auth/popup/two-factor-options-v1.component";
-import { TwoFactorComponentV1 } from "../auth/popup/two-factor-v1.component";
import { UpdateTempPasswordComponent } from "../auth/popup/update-temp-password.component";
import { AutofillComponent } from "../autofill/popup/settings/autofill.component";
import { NotificationsSettingsComponent } from "../autofill/popup/settings/notifications.component";
@@ -93,8 +91,6 @@ import "../platform/popup/locales";
SetPasswordComponent,
SsoComponentV1,
TabsV2Component,
- TwoFactorComponentV1,
- TwoFactorOptionsComponentV1,
UpdateTempPasswordComponent,
UserVerificationComponent,
VaultTimeoutInputComponent,
diff --git a/apps/browser/src/popup/scss/misc.scss b/apps/browser/src/popup/scss/misc.scss
index d1308d26180..8aace90d0a6 100644
--- a/apps/browser/src/popup/scss/misc.scss
+++ b/apps/browser/src/popup/scss/misc.scss
@@ -103,16 +103,6 @@ p.lead {
margin: 0 !important;
}
-.no-vmargin {
- margin-top: 0 !important;
- margin-bottom: 0 !important;
-}
-
-.no-vpad {
- padding-top: 0 !important;
- padding-bottom: 0 !important;
-}
-
.display-block {
display: block !important;
}
diff --git a/apps/desktop/src/app/app-routing.module.ts b/apps/desktop/src/app/app-routing.module.ts
index 3bb130d321d..cd5064a87e4 100644
--- a/apps/desktop/src/app/app-routing.module.ts
+++ b/apps/desktop/src/app/app-routing.module.ts
@@ -6,7 +6,6 @@ import {
DesktopDefaultOverlayPosition,
EnvironmentSelectorComponent,
} from "@bitwarden/angular/auth/components/environment-selector.component";
-import { unauthUiRefreshSwap } from "@bitwarden/angular/auth/functions/unauth-ui-refresh-route-swap";
import {
authGuard,
lockGuard,
@@ -53,7 +52,6 @@ import { AccessibilityCookieComponent } from "../auth/accessibility-cookie.compo
import { maxAccountsGuardFn } from "../auth/guards/max-accounts.guard";
import { RemovePasswordComponent } from "../auth/remove-password.component";
import { SetPasswordComponent } from "../auth/set-password.component";
-import { TwoFactorComponentV1 } from "../auth/two-factor-v1.component";
import { UpdateTempPasswordComponent } from "../auth/update-temp-password.component";
import { VaultComponent } from "../vault/app/vault/vault.component";
@@ -76,28 +74,6 @@ const routes: Routes = [
children: [], // Children lets us have an empty component.
canActivate: [redirectGuard({ loggedIn: "/vault", loggedOut: "/login", locked: "/lock" })],
},
- ...unauthUiRefreshSwap(
- TwoFactorComponentV1,
- AnonLayoutWrapperComponent,
- {
- path: "2fa",
- },
- {
- path: "2fa",
- canActivate: [unauthGuardFn(), TwoFactorAuthGuard],
- children: [
- {
- path: "",
- component: TwoFactorAuthComponent,
- },
- ],
- data: {
- pageTitle: {
- key: "verifyYourIdentity",
- },
- } satisfies RouteDataProperties & AnonLayoutWrapperData,
- },
- ),
{
path: "authentication-timeout",
component: AnonLayoutWrapperComponent,
@@ -360,6 +336,21 @@ const routes: Routes = [
},
} satisfies AnonLayoutWrapperData,
},
+ {
+ path: "2fa",
+ canActivate: [unauthGuardFn(), TwoFactorAuthGuard],
+ children: [
+ {
+ path: "",
+ component: TwoFactorAuthComponent,
+ },
+ ],
+ data: {
+ pageTitle: {
+ key: "verifyYourIdentity",
+ },
+ } satisfies RouteDataProperties & AnonLayoutWrapperData,
+ },
],
},
];
diff --git a/apps/desktop/src/app/app.module.ts b/apps/desktop/src/app/app.module.ts
index b1b2864af5a..b717afe4a41 100644
--- a/apps/desktop/src/app/app.module.ts
+++ b/apps/desktop/src/app/app.module.ts
@@ -16,8 +16,6 @@ import { LoginModule } from "../auth/login/login.module";
import { RemovePasswordComponent } from "../auth/remove-password.component";
import { SetPasswordComponent } from "../auth/set-password.component";
import { SsoComponentV1 } from "../auth/sso-v1.component";
-import { TwoFactorOptionsComponentV1 } from "../auth/two-factor-options-v1.component";
-import { TwoFactorComponentV1 } from "../auth/two-factor-v1.component";
import { UpdateTempPasswordComponent } from "../auth/update-temp-password.component";
import { SshAgentService } from "../autofill/services/ssh-agent.service";
import { PremiumComponent } from "../billing/app/accounts/premium.component";
@@ -78,9 +76,7 @@ import { SharedModule } from "./shared/shared.module";
SetPasswordComponent,
SettingsComponent,
ShareComponent,
- TwoFactorComponentV1,
SsoComponentV1,
- TwoFactorOptionsComponentV1,
UpdateTempPasswordComponent,
VaultComponent,
VaultTimeoutInputComponent,
diff --git a/apps/desktop/src/auth/two-factor-options-v1.component.html b/apps/desktop/src/auth/two-factor-options-v1.component.html
deleted file mode 100644
index 6f87c666215..00000000000
--- a/apps/desktop/src/auth/two-factor-options-v1.component.html
+++ /dev/null
@@ -1,33 +0,0 @@
-
-
-
-
-
-
-
-
-
- {{ p.name }}
- {{ p.description }}
-
-
- {{ "recoveryCodeTitle" | i18n }}
- {{ "recoveryCodeDesc" | i18n }}
-
-
-
-
-
-
-
-
diff --git a/apps/desktop/src/auth/two-factor-options-v1.component.ts b/apps/desktop/src/auth/two-factor-options-v1.component.ts
deleted file mode 100644
index 1cb440a5f5f..00000000000
--- a/apps/desktop/src/auth/two-factor-options-v1.component.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-import { Component } from "@angular/core";
-import { Router } from "@angular/router";
-
-import { TwoFactorOptionsComponentV1 as BaseTwoFactorOptionsComponentV1 } from "@bitwarden/angular/auth/components/two-factor-options-v1.component";
-import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service";
-import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
-import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
-import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
-
-@Component({
- selector: "app-two-factor-options",
- templateUrl: "two-factor-options-v1.component.html",
-})
-export class TwoFactorOptionsComponentV1 extends BaseTwoFactorOptionsComponentV1 {
- constructor(
- twoFactorService: TwoFactorService,
- router: Router,
- i18nService: I18nService,
- platformUtilsService: PlatformUtilsService,
- environmentService: EnvironmentService,
- ) {
- super(twoFactorService, router, i18nService, platformUtilsService, window, environmentService);
- }
-}
diff --git a/apps/desktop/src/auth/two-factor-v1.component.html b/apps/desktop/src/auth/two-factor-v1.component.html
deleted file mode 100644
index 1f65d5070f6..00000000000
--- a/apps/desktop/src/auth/two-factor-v1.component.html
+++ /dev/null
@@ -1,175 +0,0 @@
-
-
diff --git a/apps/desktop/src/auth/two-factor-v1.component.ts b/apps/desktop/src/auth/two-factor-v1.component.ts
deleted file mode 100644
index 13c7d0a452b..00000000000
--- a/apps/desktop/src/auth/two-factor-v1.component.ts
+++ /dev/null
@@ -1,190 +0,0 @@
-// FIXME: Update this file to be type safe and remove this and next line
-// @ts-strict-ignore
-import { Component, Inject, NgZone, OnDestroy, ViewChild, ViewContainerRef } from "@angular/core";
-import { ActivatedRoute, Router } from "@angular/router";
-import { firstValueFrom } from "rxjs";
-
-import { TwoFactorComponentV1 as BaseTwoFactorComponent } from "@bitwarden/angular/auth/components/two-factor-v1.component";
-import { WINDOW } from "@bitwarden/angular/services/injection-tokens";
-import { ModalService } from "@bitwarden/angular/services/modal.service";
-import {
- LoginStrategyServiceAbstraction,
- LoginEmailServiceAbstraction,
- UserDecryptionOptionsServiceAbstraction,
-} from "@bitwarden/auth/common";
-import { ApiService } from "@bitwarden/common/abstractions/api.service";
-import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
-import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
-import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service";
-import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type";
-import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
-import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
-import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
-import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
-import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
-import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
-import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
-import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
-import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
-import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
-import { ToastService } from "@bitwarden/components";
-
-import { TwoFactorOptionsComponentV1 } from "./two-factor-options-v1.component";
-
-const BroadcasterSubscriptionId = "TwoFactorComponent";
-
-@Component({
- selector: "app-two-factor",
- templateUrl: "two-factor-v1.component.html",
-})
-export class TwoFactorComponentV1 extends BaseTwoFactorComponent implements OnDestroy {
- @ViewChild("twoFactorOptions", { read: ViewContainerRef, static: true })
- twoFactorOptionsModal: ViewContainerRef;
-
- showingModal = false;
- duoCallbackSubscriptionEnabled: boolean = false;
-
- constructor(
- loginStrategyService: LoginStrategyServiceAbstraction,
- router: Router,
- i18nService: I18nService,
- apiService: ApiService,
- platformUtilsService: PlatformUtilsService,
- syncService: SyncService,
- environmentService: EnvironmentService,
- private broadcasterService: BroadcasterService,
- private modalService: ModalService,
- stateService: StateService,
- private ngZone: NgZone,
- route: ActivatedRoute,
- logService: LogService,
- twoFactorService: TwoFactorService,
- appIdService: AppIdService,
- loginEmailService: LoginEmailServiceAbstraction,
- userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction,
- ssoLoginService: SsoLoginServiceAbstraction,
- configService: ConfigService,
- masterPasswordService: InternalMasterPasswordServiceAbstraction,
- accountService: AccountService,
- toastService: ToastService,
- @Inject(WINDOW) protected win: Window,
- ) {
- super(
- loginStrategyService,
- router,
- i18nService,
- apiService,
- platformUtilsService,
- win,
- environmentService,
- stateService,
- route,
- logService,
- twoFactorService,
- appIdService,
- loginEmailService,
- userDecryptionOptionsService,
- ssoLoginService,
- configService,
- masterPasswordService,
- accountService,
- toastService,
- );
- this.onSuccessfulLogin = async () => {
- // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
- syncService.fullSync(true);
- };
-
- this.onSuccessfulLoginTde = async () => {
- // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
- syncService.fullSync(true);
- };
- }
-
- async anotherMethod() {
- const [modal, childComponent] = await this.modalService.openViewRef(
- TwoFactorOptionsComponentV1,
- this.twoFactorOptionsModal,
- );
-
- // eslint-disable-next-line rxjs-angular/prefer-takeuntil
- modal.onShown.subscribe(() => {
- this.showingModal = true;
- });
- // eslint-disable-next-line rxjs-angular/prefer-takeuntil
- modal.onClosed.subscribe(() => {
- this.showingModal = false;
- });
-
- // eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
- childComponent.onProviderSelected.subscribe(async (provider: TwoFactorProviderType) => {
- modal.close();
- this.selectedProviderType = provider;
- await this.init();
- });
- // eslint-disable-next-line rxjs-angular/prefer-takeuntil
- childComponent.onRecoverSelected.subscribe(() => {
- modal.close();
- });
- }
-
- async submit() {
- await super.submit();
- if (this.captchaSiteKey) {
- const content = document.getElementById("content") as HTMLDivElement;
- content.setAttribute("style", "width:335px");
- }
- }
-
- protected override setupDuoResultListener() {
- if (!this.duoCallbackSubscriptionEnabled) {
- this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => {
- await this.ngZone.run(async () => {
- if (message.command === "duoCallback") {
- this.token = message.code + "|" + message.state;
- await this.submit();
- }
- });
- });
- this.duoCallbackSubscriptionEnabled = true;
- }
- }
-
- override async launchDuoFrameless() {
- if (this.duoFramelessUrl === null) {
- this.toastService.showToast({
- variant: "error",
- title: null,
- message: this.i18nService.t("duoHealthCheckResultsInNullAuthUrlError"),
- });
- return;
- }
-
- const duoHandOffMessage = {
- title: this.i18nService.t("youSuccessfullyLoggedIn"),
- message: this.i18nService.t("youMayCloseThisWindow"),
- isCountdown: false,
- };
-
- // we're using the connector here as a way to set a cookie with translations
- // before continuing to the duo frameless url
- const env = await firstValueFrom(this.environmentService.environment$);
- const launchUrl =
- env.getWebVaultUrl() +
- "/duo-redirect-connector.html" +
- "?duoFramelessUrl=" +
- encodeURIComponent(this.duoFramelessUrl) +
- "&handOffMessage=" +
- encodeURIComponent(JSON.stringify(duoHandOffMessage));
- this.platformUtilsService.launchUri(launchUrl);
- }
-
- ngOnDestroy(): void {
- if (this.duoCallbackSubscriptionEnabled) {
- this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
- this.duoCallbackSubscriptionEnabled = false;
- }
- }
-}
diff --git a/apps/desktop/src/locales/en/messages.json b/apps/desktop/src/locales/en/messages.json
index 07404b37fcd..42eebd98223 100644
--- a/apps/desktop/src/locales/en/messages.json
+++ b/apps/desktop/src/locales/en/messages.json
@@ -833,18 +833,6 @@
"continue": {
"message": "Continue"
},
- "enterVerificationCodeApp": {
- "message": "Enter the 6 digit verification code from your authenticator app."
- },
- "enterVerificationCodeEmail": {
- "message": "Enter the 6 digit verification code that was emailed to $EMAIL$.",
- "placeholders": {
- "email": {
- "content": "$1",
- "example": "example@gmail.com"
- }
- }
- },
"verificationCodeEmailSent": {
"message": "Verification email sent to $EMAIL$.",
"placeholders": {
@@ -854,18 +842,9 @@
}
}
},
- "rememberMe": {
- "message": "Remember me"
- },
"dontAskAgainOnThisDeviceFor30Days": {
"message": "Don't ask again on this device for 30 days"
},
- "sendVerificationCodeEmailAgain": {
- "message": "Send verification code email again"
- },
- "useAnotherTwoStepMethod": {
- "message": "Use another two-step login method"
- },
"selectAnotherMethod": {
"message": "Select another method",
"description": "Select another two-step login method"
@@ -873,9 +852,6 @@
"useYourRecoveryCode": {
"message": "Use your recovery code"
},
- "insertYubiKey": {
- "message": "Insert your YubiKey into your computer's USB port, then touch its button."
- },
"insertU2f": {
"message": "Insert your security key into your computer's USB port. If it has a button, touch it."
},
@@ -3224,9 +3200,6 @@
"duoHealthCheckResultsInNullAuthUrlError": {
"message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance."
},
- "launchDuoAndFollowStepsToFinishLoggingIn": {
- "message": "Launch Duo and follow the steps to finish logging in."
- },
"duoRequiredByOrgForAccount": {
"message": "Duo two-step login is required for your account."
},
diff --git a/apps/desktop/src/scss/pages.scss b/apps/desktop/src/scss/pages.scss
index b9559e13a26..ecb36aae662 100644
--- a/apps/desktop/src/scss/pages.scss
+++ b/apps/desktop/src/scss/pages.scss
@@ -27,7 +27,6 @@
#accessibility-cookie-page,
#register-page,
#hint-page,
-#two-factor-page,
#update-temp-password-page,
#remove-password-page {
padding-top: 20px;
@@ -48,7 +47,6 @@
#accessibility-cookie-page,
#register-page,
#hint-page,
-#two-factor-page,
#lock-page,
#update-temp-password-page {
.content {
diff --git a/apps/web/src/app/auth/two-factor-options-v1.component.html b/apps/web/src/app/auth/two-factor-options-v1.component.html
deleted file mode 100644
index 43c054060ea..00000000000
--- a/apps/web/src/app/auth/two-factor-options-v1.component.html
+++ /dev/null
@@ -1,45 +0,0 @@
-
-
- {{ "twoStepOptions" | i18n }}
-
-
-
-
-
-
-
{{ p.name }}
-
{{ p.description }}
-
-
-
- {{ "select" | i18n }}
-
-
-
-
-
-
-
-
-
-
{{ "recoveryCodeTitle" | i18n }}
-
{{ "recoveryCodeDesc" | i18n }}
-
-
-
- {{ "select" | i18n }}
-
-
-
-
-
-
-
- {{ "close" | i18n }}
-
-
-
diff --git a/apps/web/src/app/auth/two-factor-options-v1.component.ts b/apps/web/src/app/auth/two-factor-options-v1.component.ts
deleted file mode 100644
index 08665dcfcdd..00000000000
--- a/apps/web/src/app/auth/two-factor-options-v1.component.ts
+++ /dev/null
@@ -1,52 +0,0 @@
-import { DialogRef } from "@angular/cdk/dialog";
-import { Component } from "@angular/core";
-import { Router } from "@angular/router";
-
-import { TwoFactorOptionsComponentV1 as BaseTwoFactorOptionsComponentV1 } from "@bitwarden/angular/auth/components/two-factor-options-v1.component";
-import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service";
-import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type";
-import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
-import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
-import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
-import { DialogService } from "@bitwarden/components";
-
-export enum TwoFactorOptionsDialogResult {
- Provider = "Provider selected",
- Recover = "Recover selected",
-}
-
-export type TwoFactorOptionsDialogResultType = {
- result: TwoFactorOptionsDialogResult;
- type: TwoFactorProviderType;
-};
-
-@Component({
- selector: "app-two-factor-options",
- templateUrl: "two-factor-options-v1.component.html",
-})
-export class TwoFactorOptionsComponentV1 extends BaseTwoFactorOptionsComponentV1 {
- constructor(
- twoFactorService: TwoFactorService,
- router: Router,
- i18nService: I18nService,
- platformUtilsService: PlatformUtilsService,
- environmentService: EnvironmentService,
- private dialogRef: DialogRef,
- ) {
- super(twoFactorService, router, i18nService, platformUtilsService, window, environmentService);
- }
-
- async choose(p: any) {
- await super.choose(p);
- this.dialogRef.close({ result: TwoFactorOptionsDialogResult.Provider, type: p.type });
- }
-
- async recover() {
- await super.recover();
- this.dialogRef.close({ result: TwoFactorOptionsDialogResult.Recover });
- }
-
- static open(dialogService: DialogService) {
- return dialogService.open(TwoFactorOptionsComponentV1);
- }
-}
diff --git a/apps/web/src/app/auth/two-factor-v1.component.html b/apps/web/src/app/auth/two-factor-v1.component.html
deleted file mode 100644
index b78747e04c2..00000000000
--- a/apps/web/src/app/auth/two-factor-v1.component.html
+++ /dev/null
@@ -1,106 +0,0 @@
-
diff --git a/apps/web/src/app/auth/two-factor-v1.component.ts b/apps/web/src/app/auth/two-factor-v1.component.ts
deleted file mode 100644
index 9a9fab02de3..00000000000
--- a/apps/web/src/app/auth/two-factor-v1.component.ts
+++ /dev/null
@@ -1,164 +0,0 @@
-// FIXME: Update this file to be type safe and remove this and next line
-// @ts-strict-ignore
-import { Component, Inject, OnDestroy, OnInit, ViewChild, ViewContainerRef } from "@angular/core";
-import { FormBuilder, Validators } from "@angular/forms";
-import { ActivatedRoute, Router } from "@angular/router";
-import { Subject, takeUntil, lastValueFrom } from "rxjs";
-
-import { TwoFactorComponentV1 as BaseTwoFactorComponent } from "@bitwarden/angular/auth/components/two-factor-v1.component";
-import { WINDOW } from "@bitwarden/angular/services/injection-tokens";
-import {
- LoginStrategyServiceAbstraction,
- LoginEmailServiceAbstraction,
- UserDecryptionOptionsServiceAbstraction,
-} from "@bitwarden/auth/common";
-import { ApiService } from "@bitwarden/common/abstractions/api.service";
-import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
-import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
-import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service";
-import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result";
-import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
-import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
-import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
-import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
-import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
-import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
-import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
-import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
-import { DialogService, ToastService } from "@bitwarden/components";
-
-import {
- TwoFactorOptionsDialogResult,
- TwoFactorOptionsComponentV1,
- TwoFactorOptionsDialogResultType,
-} from "./two-factor-options-v1.component";
-
-@Component({
- selector: "app-two-factor",
- templateUrl: "two-factor-v1.component.html",
-})
-export class TwoFactorComponentV1 extends BaseTwoFactorComponent implements OnInit, OnDestroy {
- @ViewChild("twoFactorOptions", { read: ViewContainerRef, static: true })
- twoFactorOptionsModal: ViewContainerRef;
- formGroup = this.formBuilder.group({
- token: [
- "",
- {
- validators: [Validators.required],
- updateOn: "submit",
- },
- ],
- remember: [false],
- });
- private destroy$ = new Subject();
- constructor(
- loginStrategyService: LoginStrategyServiceAbstraction,
- router: Router,
- i18nService: I18nService,
- apiService: ApiService,
- platformUtilsService: PlatformUtilsService,
- stateService: StateService,
- environmentService: EnvironmentService,
- private dialogService: DialogService,
- route: ActivatedRoute,
- logService: LogService,
- twoFactorService: TwoFactorService,
- appIdService: AppIdService,
- loginEmailService: LoginEmailServiceAbstraction,
- userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction,
- ssoLoginService: SsoLoginServiceAbstraction,
- configService: ConfigService,
- masterPasswordService: InternalMasterPasswordServiceAbstraction,
- accountService: AccountService,
- toastService: ToastService,
- private formBuilder: FormBuilder,
- @Inject(WINDOW) protected win: Window,
- ) {
- super(
- loginStrategyService,
- router,
- i18nService,
- apiService,
- platformUtilsService,
- win,
- environmentService,
- stateService,
- route,
- logService,
- twoFactorService,
- appIdService,
- loginEmailService,
- userDecryptionOptionsService,
- ssoLoginService,
- configService,
- masterPasswordService,
- accountService,
- toastService,
- );
- this.onSuccessfulLoginNavigate = this.goAfterLogIn;
- }
- async ngOnInit() {
- await super.ngOnInit();
- this.formGroup.valueChanges.pipe(takeUntil(this.destroy$)).subscribe((value) => {
- this.token = value.token;
- this.remember = value.remember;
- });
- }
- submitForm = async () => {
- await this.submit();
- };
-
- async anotherMethod() {
- const dialogRef = TwoFactorOptionsComponentV1.open(this.dialogService);
- const response: TwoFactorOptionsDialogResultType = await lastValueFrom(dialogRef.closed);
- if (response.result === TwoFactorOptionsDialogResult.Provider) {
- this.selectedProviderType = response.type;
- await this.init();
- }
- }
-
- protected override handleMigrateEncryptionKey(result: AuthResult): boolean {
- if (!result.requiresEncryptionKeyMigration) {
- return false;
- }
- // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
- this.router.navigate(["migrate-legacy-encryption"]);
- return true;
- }
-
- goAfterLogIn = async () => {
- this.loginEmailService.clearValues();
- // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
- this.router.navigate([this.successRoute], {
- queryParams: {
- identifier: this.orgIdentifier,
- },
- });
- };
-
- private duoResultChannel: BroadcastChannel;
-
- protected override setupDuoResultListener() {
- if (!this.duoResultChannel) {
- this.duoResultChannel = new BroadcastChannel("duoResult");
- this.duoResultChannel.addEventListener("message", this.handleDuoResultMessage);
- }
- }
-
- private handleDuoResultMessage = async (msg: { data: { code: string; state: string } }) => {
- this.token = msg.data.code + "|" + msg.data.state;
- await this.submit();
- };
-
- async ngOnDestroy() {
- super.ngOnDestroy();
-
- if (this.duoResultChannel) {
- // clean up duo listener if it was initialized.
- this.duoResultChannel.removeEventListener("message", this.handleDuoResultMessage);
- this.duoResultChannel.close();
- }
- }
-}
diff --git a/apps/web/src/app/oss-routing.module.ts b/apps/web/src/app/oss-routing.module.ts
index c531f358b34..0334519516a 100644
--- a/apps/web/src/app/oss-routing.module.ts
+++ b/apps/web/src/app/oss-routing.module.ts
@@ -2,7 +2,6 @@ import { NgModule } from "@angular/core";
import { Route, RouterModule, Routes } from "@angular/router";
import { AuthenticationTimeoutComponent } from "@bitwarden/angular/auth/components/authentication-timeout.component";
-import { unauthUiRefreshSwap } from "@bitwarden/angular/auth/functions/unauth-ui-refresh-route-swap";
import {
authGuard,
lockGuard,
@@ -65,7 +64,6 @@ import { AccountComponent } from "./auth/settings/account/account.component";
import { EmergencyAccessComponent } from "./auth/settings/emergency-access/emergency-access.component";
import { EmergencyAccessViewComponent } from "./auth/settings/emergency-access/view/emergency-access-view.component";
import { SecurityRoutingModule } from "./auth/settings/security/security-routing.module";
-import { TwoFactorComponentV1 } from "./auth/two-factor-v1.component";
import { UpdatePasswordComponent } from "./auth/update-password.component";
import { UpdateTempPasswordComponent } from "./auth/update-temp-password.component";
import { VerifyEmailTokenComponent } from "./auth/verify-email-token.component";
@@ -378,51 +376,28 @@ const routes: Routes = [
},
],
},
- ...unauthUiRefreshSwap(
- TwoFactorComponentV1,
- TwoFactorAuthComponent,
- {
- path: "2fa",
- canActivate: [unauthGuardFn()],
- children: [
- {
- path: "",
- component: TwoFactorComponentV1,
- },
- {
- path: "",
- component: EnvironmentSelectorComponent,
- outlet: "environment-selector",
- },
- ],
- data: {
- pageTitle: {
- key: "verifyYourIdentity",
- },
- } satisfies RouteDataProperties & AnonLayoutWrapperData,
- },
- {
- path: "2fa",
- canActivate: [unauthGuardFn(), TwoFactorAuthGuard],
- children: [
- {
- path: "",
- component: TwoFactorAuthComponent,
- },
- {
- path: "",
- component: EnvironmentSelectorComponent,
- outlet: "environment-selector",
- },
- ],
- data: {
- pageTitle: {
- key: "verifyYourIdentity",
- },
- titleAreaMaxWidth: "md",
- } satisfies RouteDataProperties & AnonLayoutWrapperData,
- },
- ),
+ {
+ path: "2fa",
+ component: TwoFactorAuthComponent,
+ canActivate: [unauthGuardFn(), TwoFactorAuthGuard],
+ children: [
+ {
+ path: "",
+ component: TwoFactorAuthComponent,
+ },
+ {
+ path: "",
+ component: EnvironmentSelectorComponent,
+ outlet: "environment-selector",
+ },
+ ],
+ data: {
+ pageTitle: {
+ key: "verifyYourIdentity",
+ },
+ titleAreaMaxWidth: "md",
+ } satisfies RouteDataProperties & AnonLayoutWrapperData,
+ },
{
path: "lock",
canActivate: [deepLinkGuard(), lockGuard()],
diff --git a/apps/web/src/app/shared/loose-components.module.ts b/apps/web/src/app/shared/loose-components.module.ts
index f21a9338491..70dbf63f1f8 100644
--- a/apps/web/src/app/shared/loose-components.module.ts
+++ b/apps/web/src/app/shared/loose-components.module.ts
@@ -43,8 +43,6 @@ import { TwoFactorSetupComponent } from "../auth/settings/two-factor/two-factor-
import { TwoFactorVerifyComponent } from "../auth/settings/two-factor/two-factor-verify.component";
import { UserVerificationModule } from "../auth/shared/components/user-verification";
import { SsoComponentV1 } from "../auth/sso-v1.component";
-import { TwoFactorOptionsComponentV1 } from "../auth/two-factor-options-v1.component";
-import { TwoFactorComponentV1 } from "../auth/two-factor-v1.component";
import { UpdatePasswordComponent } from "../auth/update-password.component";
import { UpdateTempPasswordComponent } from "../auth/update-temp-password.component";
import { VerifyEmailTokenComponent } from "../auth/verify-email-token.component";
@@ -148,12 +146,10 @@ import { SharedModule } from "./shared.module";
SetPasswordComponent,
SponsoredFamiliesComponent,
SponsoringOrgRowComponent,
- TwoFactorComponentV1,
SsoComponentV1,
TwoFactorSetupAuthenticatorComponent,
TwoFactorSetupDuoComponent,
TwoFactorSetupEmailComponent,
- TwoFactorOptionsComponentV1,
TwoFactorRecoveryComponent,
TwoFactorSetupComponent,
TwoFactorVerifyComponent,
@@ -210,12 +206,10 @@ import { SharedModule } from "./shared.module";
SetPasswordComponent,
SponsoredFamiliesComponent,
SponsoringOrgRowComponent,
- TwoFactorComponentV1,
SsoComponentV1,
TwoFactorSetupAuthenticatorComponent,
TwoFactorSetupDuoComponent,
TwoFactorSetupEmailComponent,
- TwoFactorOptionsComponentV1,
TwoFactorSetupComponent,
TwoFactorVerifyComponent,
TwoFactorSetupWebAuthnComponent,
diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json
index b9ead1df44d..4133062a047 100644
--- a/apps/web/src/locales/en/messages.json
+++ b/apps/web/src/locales/en/messages.json
@@ -1456,18 +1456,6 @@
}
}
},
- "enterVerificationCodeApp": {
- "message": "Enter the 6 digit verification code from your authenticator app."
- },
- "enterVerificationCodeEmail": {
- "message": "Enter the 6 digit verification code that was emailed to $EMAIL$.",
- "placeholders": {
- "email": {
- "content": "$1",
- "example": "example@gmail.com"
- }
- }
- },
"verificationCodeEmailSent": {
"message": "Verification email sent to $EMAIL$.",
"placeholders": {
@@ -1477,18 +1465,10 @@
}
}
},
- "rememberMe": {
- "message": "Remember me"
- },
+
"dontAskAgainOnThisDeviceFor30Days": {
"message": "Don't ask again on this device for 30 days"
},
- "sendVerificationCodeEmailAgain": {
- "message": "Send verification code email again"
- },
- "useAnotherTwoStepMethod": {
- "message": "Use another two-step login method"
- },
"selectAnotherMethod": {
"message": "Select another method",
"description": "Select another two-step login method"
@@ -1496,9 +1476,6 @@
"useYourRecoveryCode": {
"message": "Use your recovery code"
},
- "insertYubiKey": {
- "message": "Insert your YubiKey into your computer's USB port, then touch its button."
- },
"insertU2f": {
"message": "Insert your security key into your computer's USB port. If it has a button, touch it."
},
@@ -7273,9 +7250,6 @@
"duoHealthCheckResultsInNullAuthUrlError": {
"message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance."
},
- "launchDuoAndFollowStepsToFinishLoggingIn": {
- "message": "Launch Duo and follow the steps to finish logging in."
- },
"duoRequiredByOrgForAccount": {
"message": "Duo two-step login is required for your account."
},
diff --git a/libs/angular/src/auth/components/two-factor-options-v1.component.ts b/libs/angular/src/auth/components/two-factor-options-v1.component.ts
deleted file mode 100644
index f02eabcc156..00000000000
--- a/libs/angular/src/auth/components/two-factor-options-v1.component.ts
+++ /dev/null
@@ -1,44 +0,0 @@
-import { Directive, EventEmitter, OnInit, Output } from "@angular/core";
-import { Router } from "@angular/router";
-import { firstValueFrom } from "rxjs";
-
-import {
- TwoFactorProviderDetails,
- TwoFactorService,
-} from "@bitwarden/common/auth/abstractions/two-factor.service";
-import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type";
-import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
-import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
-import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
-
-@Directive()
-export class TwoFactorOptionsComponentV1 implements OnInit {
- @Output() onProviderSelected = new EventEmitter();
- @Output() onRecoverSelected = new EventEmitter();
-
- providers: any[] = [];
-
- constructor(
- protected twoFactorService: TwoFactorService,
- protected router: Router,
- protected i18nService: I18nService,
- protected platformUtilsService: PlatformUtilsService,
- protected win: Window,
- protected environmentService: EnvironmentService,
- ) {}
-
- async ngOnInit() {
- this.providers = await this.twoFactorService.getSupportedProviders(this.win);
- }
-
- async choose(p: TwoFactorProviderDetails) {
- this.onProviderSelected.emit(p.type);
- }
-
- async recover() {
- const env = await firstValueFrom(this.environmentService.environment$);
- const webVault = env.getWebVaultUrl();
- this.platformUtilsService.launchUri(webVault + "/#/recover-2fa");
- this.onRecoverSelected.emit();
- }
-}
diff --git a/libs/angular/src/auth/components/two-factor-v1.component.spec.ts b/libs/angular/src/auth/components/two-factor-v1.component.spec.ts
deleted file mode 100644
index 47075acc758..00000000000
--- a/libs/angular/src/auth/components/two-factor-v1.component.spec.ts
+++ /dev/null
@@ -1,505 +0,0 @@
-import { Component } from "@angular/core";
-import { ComponentFixture, TestBed } from "@angular/core/testing";
-import { ActivatedRoute, convertToParamMap, Router } from "@angular/router";
-import { mock, MockProxy } from "jest-mock-extended";
-import { BehaviorSubject } from "rxjs";
-
-import { WINDOW } from "@bitwarden/angular/services/injection-tokens";
-import {
- LoginStrategyServiceAbstraction,
- LoginEmailServiceAbstraction,
- FakeKeyConnectorUserDecryptionOption as KeyConnectorUserDecryptionOption,
- FakeTrustedDeviceUserDecryptionOption as TrustedDeviceUserDecryptionOption,
- FakeUserDecryptionOptions as UserDecryptionOptions,
- UserDecryptionOptionsServiceAbstraction,
-} from "@bitwarden/auth/common";
-import { ApiService } from "@bitwarden/common/abstractions/api.service";
-import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
-import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
-import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service";
-import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result";
-import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason";
-import { TokenTwoFactorRequest } from "@bitwarden/common/auth/models/request/identity-token/token-two-factor.request";
-import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
-import { FakeMasterPasswordService } from "@bitwarden/common/key-management/master-password/services/fake-master-password.service";
-import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
-import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
-import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
-import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
-import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
-import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
-import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
-import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec";
-import { UserId } from "@bitwarden/common/types/guid";
-import { ToastService } from "@bitwarden/components";
-
-import { TwoFactorComponentV1 } from "./two-factor-v1.component";
-
-// test component that extends the TwoFactorComponent
-@Component({})
-class TestTwoFactorComponent extends TwoFactorComponentV1 {}
-
-interface TwoFactorComponentProtected {
- trustedDeviceEncRoute: string;
- changePasswordRoute: string;
- forcePasswordResetRoute: string;
- successRoute: string;
-}
-
-describe("TwoFactorComponent", () => {
- let component: TestTwoFactorComponent;
- let _component: TwoFactorComponentProtected;
-
- let fixture: ComponentFixture;
- const userId = "userId" as UserId;
-
- // Mock Services
- let mockLoginStrategyService: MockProxy;
- let mockRouter: MockProxy;
- let mockI18nService: MockProxy;
- let mockApiService: MockProxy;
- let mockPlatformUtilsService: MockProxy;
- let mockWin: MockProxy;
- let mockEnvironmentService: MockProxy;
- let mockStateService: MockProxy;
- let mockLogService: MockProxy;
- let mockTwoFactorService: MockProxy;
- let mockAppIdService: MockProxy;
- let mockLoginEmailService: MockProxy;
- let mockUserDecryptionOptionsService: MockProxy;
- let mockSsoLoginService: MockProxy;
- let mockConfigService: MockProxy;
- let mockMasterPasswordService: FakeMasterPasswordService;
- let mockAccountService: FakeAccountService;
- let mockToastService: MockProxy;
-
- let mockUserDecryptionOpts: {
- noMasterPassword: UserDecryptionOptions;
- withMasterPassword: UserDecryptionOptions;
- withMasterPasswordAndTrustedDevice: UserDecryptionOptions;
- withMasterPasswordAndTrustedDeviceWithManageResetPassword: UserDecryptionOptions;
- withMasterPasswordAndKeyConnector: UserDecryptionOptions;
- noMasterPasswordWithTrustedDevice: UserDecryptionOptions;
- noMasterPasswordWithTrustedDeviceWithManageResetPassword: UserDecryptionOptions;
- noMasterPasswordWithKeyConnector: UserDecryptionOptions;
- };
-
- let selectedUserDecryptionOptions: BehaviorSubject;
- let authenticationSessionTimeoutSubject: BehaviorSubject;
-
- beforeEach(() => {
- authenticationSessionTimeoutSubject = new BehaviorSubject(false);
- mockLoginStrategyService = mock();
- mockLoginStrategyService.authenticationSessionTimeout$ = authenticationSessionTimeoutSubject;
- mockRouter = mock();
- mockI18nService = mock();
- mockApiService = mock();
- mockPlatformUtilsService = mock();
- mockWin = mock();
- mockEnvironmentService = mock();
- mockStateService = mock();
- mockLogService = mock();
- mockTwoFactorService = mock();
- mockAppIdService = mock();
- mockLoginEmailService = mock();
- mockUserDecryptionOptionsService = mock();
- mockSsoLoginService = mock();
- mockConfigService = mock();
- mockAccountService = mockAccountServiceWith(userId);
- mockToastService = mock();
- mockMasterPasswordService = new FakeMasterPasswordService();
-
- mockUserDecryptionOpts = {
- noMasterPassword: new UserDecryptionOptions({
- hasMasterPassword: false,
- trustedDeviceOption: undefined,
- keyConnectorOption: undefined,
- }),
- withMasterPassword: new UserDecryptionOptions({
- hasMasterPassword: true,
- trustedDeviceOption: undefined,
- keyConnectorOption: undefined,
- }),
- withMasterPasswordAndTrustedDevice: new UserDecryptionOptions({
- hasMasterPassword: true,
- trustedDeviceOption: new TrustedDeviceUserDecryptionOption(true, false, false, false),
- keyConnectorOption: undefined,
- }),
- withMasterPasswordAndTrustedDeviceWithManageResetPassword: new UserDecryptionOptions({
- hasMasterPassword: true,
- trustedDeviceOption: new TrustedDeviceUserDecryptionOption(true, false, true, false),
- keyConnectorOption: undefined,
- }),
- withMasterPasswordAndKeyConnector: new UserDecryptionOptions({
- hasMasterPassword: true,
- trustedDeviceOption: undefined,
- keyConnectorOption: new KeyConnectorUserDecryptionOption("http://example.com"),
- }),
- noMasterPasswordWithTrustedDevice: new UserDecryptionOptions({
- hasMasterPassword: false,
- trustedDeviceOption: new TrustedDeviceUserDecryptionOption(true, false, false, false),
- keyConnectorOption: undefined,
- }),
- noMasterPasswordWithTrustedDeviceWithManageResetPassword: new UserDecryptionOptions({
- hasMasterPassword: false,
- trustedDeviceOption: new TrustedDeviceUserDecryptionOption(true, false, true, false),
- keyConnectorOption: undefined,
- }),
- noMasterPasswordWithKeyConnector: new UserDecryptionOptions({
- hasMasterPassword: false,
- trustedDeviceOption: undefined,
- keyConnectorOption: new KeyConnectorUserDecryptionOption("http://example.com"),
- }),
- };
-
- selectedUserDecryptionOptions = new BehaviorSubject(
- mockUserDecryptionOpts.withMasterPassword,
- );
- mockUserDecryptionOptionsService.userDecryptionOptions$ = selectedUserDecryptionOptions;
-
- TestBed.configureTestingModule({
- declarations: [TestTwoFactorComponent],
- providers: [
- { provide: LoginStrategyServiceAbstraction, useValue: mockLoginStrategyService },
- { provide: Router, useValue: mockRouter },
- { provide: I18nService, useValue: mockI18nService },
- { provide: ApiService, useValue: mockApiService },
- { provide: PlatformUtilsService, useValue: mockPlatformUtilsService },
- { provide: WINDOW, useValue: mockWin },
- { provide: EnvironmentService, useValue: mockEnvironmentService },
- { provide: StateService, useValue: mockStateService },
- {
- provide: ActivatedRoute,
- useValue: {
- snapshot: {
- // Default to standard 2FA flow - not SSO + 2FA
- queryParamMap: convertToParamMap({ sso: "false" }),
- },
- },
- },
- { provide: LogService, useValue: mockLogService },
- { provide: TwoFactorService, useValue: mockTwoFactorService },
- { provide: AppIdService, useValue: mockAppIdService },
- { provide: LoginEmailServiceAbstraction, useValue: mockLoginEmailService },
- {
- provide: UserDecryptionOptionsServiceAbstraction,
- useValue: mockUserDecryptionOptionsService,
- },
- { provide: SsoLoginServiceAbstraction, useValue: mockSsoLoginService },
- { provide: ConfigService, useValue: mockConfigService },
- { provide: InternalMasterPasswordServiceAbstraction, useValue: mockMasterPasswordService },
- { provide: AccountService, useValue: mockAccountService },
- { provide: ToastService, useValue: mockToastService },
- ],
- });
-
- fixture = TestBed.createComponent(TestTwoFactorComponent);
- component = fixture.componentInstance;
- _component = component as any;
- });
-
- afterEach(() => {
- // Reset all mocks after each test
- jest.resetAllMocks();
- });
-
- it("should create", () => {
- expect(component).toBeTruthy();
- });
-
- // Shared tests
- const testChangePasswordOnSuccessfulLogin = () => {
- it("navigates to the component's defined change password route when user doesn't have a MP and key connector isn't enabled", async () => {
- // Act
- await component.doSubmit();
-
- // Assert
- expect(mockRouter.navigate).toHaveBeenCalledTimes(1);
- expect(mockRouter.navigate).toHaveBeenCalledWith([_component.changePasswordRoute], {
- queryParams: {
- identifier: component.orgIdentifier,
- },
- });
- });
- };
-
- const testForceResetOnSuccessfulLogin = (reasonString: string) => {
- it(`navigates to the component's defined forcePasswordResetRoute route when response.forcePasswordReset is ${reasonString}`, async () => {
- // Act
- await component.doSubmit();
-
- // expect(mockRouter.navigate).toHaveBeenCalledTimes(1);
- expect(mockRouter.navigate).toHaveBeenCalledWith([_component.forcePasswordResetRoute], {
- queryParams: {
- identifier: component.orgIdentifier,
- },
- });
- });
- };
-
- describe("Standard 2FA scenarios", () => {
- describe("doSubmit", () => {
- const token = "testToken";
- const remember = false;
- const captchaToken = "testCaptchaToken";
-
- beforeEach(() => {
- component.token = token;
- component.remember = remember;
- component.captchaToken = captchaToken;
-
- selectedUserDecryptionOptions.next(mockUserDecryptionOpts.withMasterPassword);
- });
-
- it("calls authService.logInTwoFactor with correct parameters when form is submitted", async () => {
- // Arrange
- mockLoginStrategyService.logInTwoFactor.mockResolvedValue(new AuthResult());
-
- // Act
- await component.doSubmit();
-
- // Assert
- expect(mockLoginStrategyService.logInTwoFactor).toHaveBeenCalledWith(
- new TokenTwoFactorRequest(component.selectedProviderType, token, remember),
- captchaToken,
- );
- });
-
- it("should return when handleCaptchaRequired returns true", async () => {
- // Arrange
- const captchaSiteKey = "testCaptchaSiteKey";
- const authResult = new AuthResult();
- authResult.captchaSiteKey = captchaSiteKey;
-
- mockLoginStrategyService.logInTwoFactor.mockResolvedValue(authResult);
-
- // Note: the any casts are required b/c typescript cant recognize that
- // handleCaptureRequired is a method on TwoFactorComponent b/c it is inherited
- // from the CaptchaProtectedComponent
- const handleCaptchaRequiredSpy = jest
- .spyOn(component, "handleCaptchaRequired")
- .mockReturnValue(true);
-
- // Act
- const result = await component.doSubmit();
-
- // Assert
- expect(handleCaptchaRequiredSpy).toHaveBeenCalled();
- expect(result).toBeUndefined();
- });
-
- it("calls onSuccessfulLogin when defined", async () => {
- // Arrange
- component.onSuccessfulLogin = jest.fn().mockResolvedValue(undefined);
- mockLoginStrategyService.logInTwoFactor.mockResolvedValue(new AuthResult());
-
- // Act
- await component.doSubmit();
-
- // Assert
- expect(component.onSuccessfulLogin).toHaveBeenCalled();
- });
-
- it("calls loginEmailService.clearValues() when login is successful", async () => {
- // Arrange
- mockLoginStrategyService.logInTwoFactor.mockResolvedValue(new AuthResult());
- // spy on loginEmailService.clearValues
- const clearValuesSpy = jest.spyOn(mockLoginEmailService, "clearValues");
-
- // Act
- await component.doSubmit();
-
- // Assert
- expect(clearValuesSpy).toHaveBeenCalled();
- });
-
- describe("Set Master Password scenarios", () => {
- beforeEach(() => {
- const authResult = new AuthResult();
- mockLoginStrategyService.logInTwoFactor.mockResolvedValue(authResult);
- });
-
- describe("Given user needs to set a master password", () => {
- beforeEach(() => {
- // Only need to test the case where the user has no master password to test the primary change mp flow here
- selectedUserDecryptionOptions.next(mockUserDecryptionOpts.noMasterPassword);
- });
-
- testChangePasswordOnSuccessfulLogin();
- });
-
- it("does not navigate to the change password route when the user has key connector even if user has no master password", async () => {
- selectedUserDecryptionOptions.next(
- mockUserDecryptionOpts.noMasterPasswordWithKeyConnector,
- );
-
- await component.doSubmit();
-
- expect(mockRouter.navigate).not.toHaveBeenCalledWith([_component.changePasswordRoute], {
- queryParams: {
- identifier: component.orgIdentifier,
- },
- });
- });
- });
-
- describe("Force Master Password Reset scenarios", () => {
- [
- ForceSetPasswordReason.AdminForcePasswordReset,
- ForceSetPasswordReason.WeakMasterPassword,
- ].forEach((forceResetPasswordReason) => {
- const reasonString = ForceSetPasswordReason[forceResetPasswordReason];
-
- beforeEach(() => {
- // use standard user with MP because this test is not concerned with password reset.
- selectedUserDecryptionOptions.next(mockUserDecryptionOpts.withMasterPassword);
-
- const authResult = new AuthResult();
- authResult.forcePasswordReset = forceResetPasswordReason;
- mockLoginStrategyService.logInTwoFactor.mockResolvedValue(authResult);
- });
-
- testForceResetOnSuccessfulLogin(reasonString);
- });
- });
-
- it("calls onSuccessfulLoginNavigate when the callback is defined", async () => {
- // Arrange
- component.onSuccessfulLoginNavigate = jest.fn().mockResolvedValue(undefined);
- mockLoginStrategyService.logInTwoFactor.mockResolvedValue(new AuthResult());
-
- // Act
- await component.doSubmit();
-
- // Assert
- expect(component.onSuccessfulLoginNavigate).toHaveBeenCalled();
- });
-
- it("navigates to the component's defined success route when the login is successful and onSuccessfulLoginNavigate is undefined", async () => {
- mockLoginStrategyService.logInTwoFactor.mockResolvedValue(new AuthResult());
-
- // Act
- await component.doSubmit();
-
- // Assert
- expect(component.onSuccessfulLoginNavigate).not.toBeDefined();
-
- expect(mockRouter.navigate).toHaveBeenCalledTimes(1);
- expect(mockRouter.navigate).toHaveBeenCalledWith([_component.successRoute], undefined);
- });
- });
- });
-
- describe("SSO > 2FA scenarios", () => {
- beforeEach(() => {
- const mockActivatedRoute = TestBed.inject(ActivatedRoute);
- mockActivatedRoute.snapshot.queryParamMap.get = jest.fn().mockReturnValue("true");
- });
-
- describe("doSubmit", () => {
- const token = "testToken";
- const remember = false;
- const captchaToken = "testCaptchaToken";
-
- beforeEach(() => {
- component.token = token;
- component.remember = remember;
- component.captchaToken = captchaToken;
- });
-
- describe("Trusted Device Encryption scenarios", () => {
- beforeEach(() => {
- mockConfigService.getFeatureFlag.mockResolvedValue(true);
- });
-
- describe("Given Trusted Device Encryption is enabled and user needs to set a master password", () => {
- beforeEach(() => {
- selectedUserDecryptionOptions.next(
- mockUserDecryptionOpts.noMasterPasswordWithTrustedDeviceWithManageResetPassword,
- );
-
- const authResult = new AuthResult();
- mockLoginStrategyService.logInTwoFactor.mockResolvedValue(authResult);
- });
-
- it("navigates to the component's defined trusted device encryption route and sets correct flag when user doesn't have a MP and key connector isn't enabled", async () => {
- // Act
- await component.doSubmit();
-
- // Assert
- expect(mockMasterPasswordService.mock.setForceSetPasswordReason).toHaveBeenCalledWith(
- ForceSetPasswordReason.TdeUserWithoutPasswordHasPasswordResetPermission,
- userId,
- );
-
- expect(mockRouter.navigate).toHaveBeenCalledTimes(1);
- expect(mockRouter.navigate).toHaveBeenCalledWith(
- [_component.trustedDeviceEncRoute],
- undefined,
- );
- });
- });
-
- describe("Given Trusted Device Encryption is enabled, user doesn't need to set a MP, and forcePasswordReset is required", () => {
- [
- ForceSetPasswordReason.AdminForcePasswordReset,
- ForceSetPasswordReason.WeakMasterPassword,
- ].forEach((forceResetPasswordReason) => {
- const reasonString = ForceSetPasswordReason[forceResetPasswordReason];
-
- beforeEach(() => {
- // use standard user with MP because this test is not concerned with password reset.
- selectedUserDecryptionOptions.next(
- mockUserDecryptionOpts.withMasterPasswordAndTrustedDevice,
- );
-
- const authResult = new AuthResult();
- authResult.forcePasswordReset = forceResetPasswordReason;
- mockLoginStrategyService.logInTwoFactor.mockResolvedValue(authResult);
- });
-
- testForceResetOnSuccessfulLogin(reasonString);
- });
- });
-
- describe("Given Trusted Device Encryption is enabled, user doesn't need to set a MP, and forcePasswordReset is not required", () => {
- let authResult;
- beforeEach(() => {
- selectedUserDecryptionOptions.next(
- mockUserDecryptionOpts.withMasterPasswordAndTrustedDevice,
- );
-
- authResult = new AuthResult();
- authResult.forcePasswordReset = ForceSetPasswordReason.None;
- mockLoginStrategyService.logInTwoFactor.mockResolvedValue(authResult);
- });
-
- it("navigates to the component's defined trusted device encryption route when login is successful and onSuccessfulLoginTdeNavigate is undefined", async () => {
- await component.doSubmit();
-
- expect(mockRouter.navigate).toHaveBeenCalledTimes(1);
- expect(mockRouter.navigate).toHaveBeenCalledWith(
- [_component.trustedDeviceEncRoute],
- undefined,
- );
- });
-
- it("calls onSuccessfulLoginTdeNavigate instead of router.navigate when the callback is defined", async () => {
- component.onSuccessfulLoginTdeNavigate = jest.fn().mockResolvedValue(undefined);
-
- await component.doSubmit();
-
- expect(mockRouter.navigate).not.toHaveBeenCalled();
- expect(component.onSuccessfulLoginTdeNavigate).toHaveBeenCalled();
- });
- });
- });
- });
- });
-
- it("navigates to the timeout route when timeout expires", async () => {
- authenticationSessionTimeoutSubject.next(true);
-
- expect(mockRouter.navigate).toHaveBeenCalledWith(["authentication-timeout"]);
- });
-});
diff --git a/libs/angular/src/auth/components/two-factor-v1.component.ts b/libs/angular/src/auth/components/two-factor-v1.component.ts
deleted file mode 100644
index 3fda2685f5e..00000000000
--- a/libs/angular/src/auth/components/two-factor-v1.component.ts
+++ /dev/null
@@ -1,514 +0,0 @@
-// FIXME: Update this file to be type safe and remove this and next line
-// @ts-strict-ignore
-import { Directive, Inject, OnInit, OnDestroy } from "@angular/core";
-import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
-import { ActivatedRoute, NavigationExtras, Router } from "@angular/router";
-import { firstValueFrom } from "rxjs";
-import { first } from "rxjs/operators";
-
-import { WINDOW } from "@bitwarden/angular/services/injection-tokens";
-import {
- LoginStrategyServiceAbstraction,
- LoginEmailServiceAbstraction,
- TrustedDeviceUserDecryptionOption,
- UserDecryptionOptions,
- UserDecryptionOptionsServiceAbstraction,
-} from "@bitwarden/auth/common";
-import { ApiService } from "@bitwarden/common/abstractions/api.service";
-import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
-import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
-import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service";
-import { AuthenticationType } from "@bitwarden/common/auth/enums/authentication-type";
-import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type";
-import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result";
-import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason";
-import { TokenTwoFactorRequest } from "@bitwarden/common/auth/models/request/identity-token/token-two-factor.request";
-import { TwoFactorEmailRequest } from "@bitwarden/common/auth/models/request/two-factor-email.request";
-import { TwoFactorProviders } from "@bitwarden/common/auth/services/two-factor.service";
-import { WebAuthnIFrame } from "@bitwarden/common/auth/webauthn-iframe";
-import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
-import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
-import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
-import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
-import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
-import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
-import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
-import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
-import { ToastService } from "@bitwarden/components";
-
-import { CaptchaProtectedComponent } from "./captcha-protected.component";
-
-@Directive()
-export class TwoFactorComponentV1 extends CaptchaProtectedComponent implements OnInit, OnDestroy {
- token = "";
- remember = false;
- webAuthnReady = false;
- webAuthnNewTab = false;
- providers = TwoFactorProviders;
- providerType = TwoFactorProviderType;
- selectedProviderType: TwoFactorProviderType = TwoFactorProviderType.Authenticator;
- webAuthnSupported = false;
- webAuthn: WebAuthnIFrame = null;
- title = "";
- twoFactorEmail: string = null;
- formPromise: Promise;
- emailPromise: Promise;
- orgIdentifier: string = null;
-
- duoFramelessUrl: string = null;
- duoResultListenerInitialized = false;
-
- onSuccessfulLogin: () => Promise;
- onSuccessfulLoginNavigate: () => Promise;
-
- onSuccessfulLoginTde: () => Promise;
- onSuccessfulLoginTdeNavigate: () => Promise;
-
- protected loginRoute = "login";
-
- protected trustedDeviceEncRoute = "login-initiated";
- protected changePasswordRoute = "set-password";
- protected forcePasswordResetRoute = "update-temp-password";
- protected successRoute = "vault";
- protected twoFactorTimeoutRoute = "authentication-timeout";
-
- get isDuoProvider(): boolean {
- return (
- this.selectedProviderType === TwoFactorProviderType.Duo ||
- this.selectedProviderType === TwoFactorProviderType.OrganizationDuo
- );
- }
-
- constructor(
- protected loginStrategyService: LoginStrategyServiceAbstraction,
- protected router: Router,
- protected i18nService: I18nService,
- protected apiService: ApiService,
- protected platformUtilsService: PlatformUtilsService,
- @Inject(WINDOW) protected win: Window,
- protected environmentService: EnvironmentService,
- protected stateService: StateService,
- protected route: ActivatedRoute,
- protected logService: LogService,
- protected twoFactorService: TwoFactorService,
- protected appIdService: AppIdService,
- protected loginEmailService: LoginEmailServiceAbstraction,
- protected userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction,
- protected ssoLoginService: SsoLoginServiceAbstraction,
- protected configService: ConfigService,
- protected masterPasswordService: InternalMasterPasswordServiceAbstraction,
- protected accountService: AccountService,
- protected toastService: ToastService,
- ) {
- super(environmentService, i18nService, platformUtilsService, toastService);
-
- this.webAuthnSupported = this.platformUtilsService.supportsWebAuthn(win);
-
- // Add subscription to authenticationSessionTimeout$ and navigate to twoFactorTimeoutRoute if expired
- this.loginStrategyService.authenticationSessionTimeout$
- .pipe(takeUntilDestroyed())
- .subscribe(async (expired) => {
- if (!expired) {
- return;
- }
-
- try {
- await this.router.navigate([this.twoFactorTimeoutRoute]);
- } catch (err) {
- this.logService.error(`Failed to navigate to ${this.twoFactorTimeoutRoute} route`, err);
- }
- });
- }
-
- async ngOnInit() {
- if (!(await this.authing()) || (await this.twoFactorService.getProviders()) == null) {
- // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
- this.router.navigate([this.loginRoute]);
- return;
- }
-
- this.route.queryParams.pipe(first()).subscribe((qParams) => {
- if (qParams.identifier != null) {
- this.orgIdentifier = qParams.identifier;
- }
- });
-
- if (await this.needsLock()) {
- this.successRoute = "lock";
- }
-
- if (this.win != null && this.webAuthnSupported) {
- const env = await firstValueFrom(this.environmentService.environment$);
- const webVaultUrl = env.getWebVaultUrl();
- this.webAuthn = new WebAuthnIFrame(
- this.win,
- webVaultUrl,
- this.webAuthnNewTab,
- this.platformUtilsService,
- this.i18nService,
- (token: string) => {
- this.token = token;
- // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
- this.submit();
- },
- (error: string) => {
- this.toastService.showToast({
- variant: "error",
- title: this.i18nService.t("errorOccurred"),
- message: this.i18nService.t("webauthnCancelOrTimeout"),
- });
- },
- (info: string) => {
- if (info === "ready") {
- this.webAuthnReady = true;
- }
- },
- );
- }
-
- this.selectedProviderType = await this.twoFactorService.getDefaultProvider(
- this.webAuthnSupported,
- );
- await this.init();
- }
-
- ngOnDestroy(): void {
- this.cleanupWebAuthn();
- this.webAuthn = null;
- }
-
- async init() {
- if (this.selectedProviderType == null) {
- this.title = this.i18nService.t("loginUnavailable");
- return;
- }
-
- this.cleanupWebAuthn();
- this.title = (TwoFactorProviders as any)[this.selectedProviderType].name;
- const providerData = await this.twoFactorService.getProviders().then((providers) => {
- return providers.get(this.selectedProviderType);
- });
- switch (this.selectedProviderType) {
- case TwoFactorProviderType.WebAuthn:
- if (!this.webAuthnNewTab) {
- setTimeout(async () => {
- await this.authWebAuthn();
- }, 500);
- }
- break;
- case TwoFactorProviderType.Duo:
- case TwoFactorProviderType.OrganizationDuo:
- // Setup listener for duo-redirect.ts connector to send back the code
- if (!this.duoResultListenerInitialized) {
- // setup client specific duo result listener
- this.setupDuoResultListener();
- this.duoResultListenerInitialized = true;
- }
- // flow must be launched by user so they can choose to remember the device or not.
- this.duoFramelessUrl = providerData.AuthUrl;
- break;
- case TwoFactorProviderType.Email:
- this.twoFactorEmail = providerData.Email;
- if ((await this.twoFactorService.getProviders()).size > 1) {
- await this.sendEmail(false);
- }
- break;
- default:
- break;
- }
- }
-
- async submit() {
- await this.setupCaptcha();
-
- if (this.token == null || this.token === "") {
- this.toastService.showToast({
- variant: "error",
- title: this.i18nService.t("errorOccurred"),
- message: this.i18nService.t("verificationCodeRequired"),
- });
- return;
- }
-
- if (this.selectedProviderType === TwoFactorProviderType.WebAuthn) {
- if (this.webAuthn != null) {
- this.webAuthn.stop();
- } else {
- return;
- }
- } else if (
- this.selectedProviderType === TwoFactorProviderType.Email ||
- this.selectedProviderType === TwoFactorProviderType.Authenticator
- ) {
- this.token = this.token.replace(" ", "").trim();
- }
-
- await this.doSubmit();
- if (this.selectedProviderType === TwoFactorProviderType.WebAuthn && this.webAuthn != null) {
- this.webAuthn.start();
- }
- }
-
- async doSubmit() {
- this.formPromise = this.loginStrategyService.logInTwoFactor(
- new TokenTwoFactorRequest(this.selectedProviderType, this.token, this.remember),
- this.captchaToken,
- );
- const authResult: AuthResult = await this.formPromise;
-
- await this.handleLoginResponse(authResult);
- }
-
- protected handleMigrateEncryptionKey(result: AuthResult): boolean {
- if (!result.requiresEncryptionKeyMigration) {
- return false;
- }
-
- this.toastService.showToast({
- variant: "error",
- title: this.i18nService.t("errorOccured"),
- message: this.i18nService.t("encryptionKeyMigrationRequired"),
- });
- return true;
- }
-
- // Each client will have own implementation
- protected setupDuoResultListener(): void {}
-
- private async handleLoginResponse(authResult: AuthResult) {
- if (this.handleCaptchaRequired(authResult)) {
- return;
- } else if (this.handleMigrateEncryptionKey(authResult)) {
- return;
- }
-
- // Save off the OrgSsoIdentifier for use in the TDE flows
- // - TDE login decryption options component
- // - Browser SSO on extension open
- const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id;
- await this.ssoLoginService.setActiveUserOrganizationSsoIdentifier(this.orgIdentifier, userId);
- this.loginEmailService.clearValues();
-
- // note: this flow affects both TDE & standard users
- if (this.isForcePasswordResetRequired(authResult)) {
- return await this.handleForcePasswordReset(this.orgIdentifier);
- }
-
- const userDecryptionOpts = await firstValueFrom(
- this.userDecryptionOptionsService.userDecryptionOptions$,
- );
-
- const tdeEnabled = await this.isTrustedDeviceEncEnabled(userDecryptionOpts.trustedDeviceOption);
-
- if (tdeEnabled) {
- return await this.handleTrustedDeviceEncryptionEnabled(
- authResult,
- this.orgIdentifier,
- userDecryptionOpts,
- );
- }
-
- // User must set password if they don't have one and they aren't using either TDE or key connector.
- const requireSetPassword =
- !userDecryptionOpts.hasMasterPassword && userDecryptionOpts.keyConnectorOption === undefined;
-
- if (requireSetPassword || authResult.resetMasterPassword) {
- // Change implies going no password -> password in this case
- return await this.handleChangePasswordRequired(this.orgIdentifier);
- }
-
- return await this.handleSuccessfulLogin();
- }
-
- private async isTrustedDeviceEncEnabled(
- trustedDeviceOption: TrustedDeviceUserDecryptionOption,
- ): Promise {
- const ssoTo2faFlowActive = this.route.snapshot.queryParamMap.get("sso") === "true";
-
- return ssoTo2faFlowActive && trustedDeviceOption !== undefined;
- }
-
- private async handleTrustedDeviceEncryptionEnabled(
- authResult: AuthResult,
- orgIdentifier: string,
- userDecryptionOpts: UserDecryptionOptions,
- ): Promise {
- // If user doesn't have a MP, but has reset password permission, they must set a MP
- if (
- !userDecryptionOpts.hasMasterPassword &&
- userDecryptionOpts.trustedDeviceOption.hasManageResetPasswordPermission
- ) {
- // Set flag so that auth guard can redirect to set password screen after decryption (trusted or untrusted device)
- // Note: we cannot directly navigate to the set password screen in this scenario as we are in a pre-decryption state, and
- // if you try to set a new MP before decrypting, you will invalidate the user's data by making a new user key.
- const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id;
- await this.masterPasswordService.setForceSetPasswordReason(
- ForceSetPasswordReason.TdeUserWithoutPasswordHasPasswordResetPermission,
- userId,
- );
- }
-
- if (this.onSuccessfulLoginTde != null) {
- // Note: awaiting this will currently cause a hang on desktop & browser as they will wait for a full sync to complete
- // before navigating to the success route.
- // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
- this.onSuccessfulLoginTde();
- }
-
- // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
- this.navigateViaCallbackOrRoute(
- this.onSuccessfulLoginTdeNavigate,
- // Navigate to TDE page (if user was on trusted device and TDE has decrypted
- // their user key, the login-initiated guard will redirect them to the vault)
- [this.trustedDeviceEncRoute],
- );
- }
-
- private async handleChangePasswordRequired(orgIdentifier: string) {
- await this.router.navigate([this.changePasswordRoute], {
- queryParams: {
- identifier: orgIdentifier,
- },
- });
- }
-
- /**
- * Determines if a user needs to reset their password based on certain conditions.
- * Users can be forced to reset their password via an admin or org policy disallowing weak passwords.
- * Note: this is different from the SSO component login flow as a user can
- * login with MP and then have to pass 2FA to finish login and we can actually
- * evaluate if they have a weak password at that time.
- *
- * @param {AuthResult} authResult - The authentication result.
- * @returns {boolean} Returns true if a password reset is required, false otherwise.
- */
- private isForcePasswordResetRequired(authResult: AuthResult): boolean {
- const forceResetReasons = [
- ForceSetPasswordReason.AdminForcePasswordReset,
- ForceSetPasswordReason.WeakMasterPassword,
- ];
-
- return forceResetReasons.includes(authResult.forcePasswordReset);
- }
-
- private async handleForcePasswordReset(orgIdentifier: string) {
- // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
- this.router.navigate([this.forcePasswordResetRoute], {
- queryParams: {
- identifier: orgIdentifier,
- },
- });
- }
-
- private async handleSuccessfulLogin() {
- if (this.onSuccessfulLogin != null) {
- // Note: awaiting this will currently cause a hang on desktop & browser as they will wait for a full sync to complete
- // before navigating to the success route.
- // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
- this.onSuccessfulLogin();
- }
- await this.navigateViaCallbackOrRoute(this.onSuccessfulLoginNavigate, [this.successRoute]);
- }
-
- private async navigateViaCallbackOrRoute(
- callback: () => Promise,
- commands: unknown[],
- extras?: NavigationExtras,
- ): Promise {
- if (callback) {
- await callback();
- } else {
- await this.router.navigate(commands, extras);
- }
- }
-
- async sendEmail(doToast: boolean) {
- if (this.selectedProviderType !== TwoFactorProviderType.Email) {
- return;
- }
-
- if (this.emailPromise != null) {
- return;
- }
-
- if ((await this.loginStrategyService.getEmail()) == null) {
- this.toastService.showToast({
- variant: "error",
- title: this.i18nService.t("errorOccurred"),
- message: this.i18nService.t("sessionTimeout"),
- });
- return;
- }
-
- try {
- const request = new TwoFactorEmailRequest();
- request.email = await this.loginStrategyService.getEmail();
- request.masterPasswordHash = await this.loginStrategyService.getMasterPasswordHash();
- request.ssoEmail2FaSessionToken =
- await this.loginStrategyService.getSsoEmail2FaSessionToken();
- request.deviceIdentifier = await this.appIdService.getAppId();
- request.authRequestAccessCode = await this.loginStrategyService.getAccessCode();
- request.authRequestId = await this.loginStrategyService.getAuthRequestId();
- this.emailPromise = this.apiService.postTwoFactorEmail(request);
- await this.emailPromise;
- if (doToast) {
- this.toastService.showToast({
- variant: "success",
- title: null,
- message: this.i18nService.t("verificationCodeEmailSent", this.twoFactorEmail),
- });
- }
- } catch (e) {
- this.logService.error(e);
- }
-
- this.emailPromise = null;
- }
-
- async authWebAuthn() {
- const providerData = await this.twoFactorService.getProviders().then((providers) => {
- return providers.get(this.selectedProviderType);
- });
-
- if (!this.webAuthnSupported || this.webAuthn == null) {
- return;
- }
-
- this.webAuthn.init(providerData);
- }
-
- private cleanupWebAuthn() {
- if (this.webAuthn != null) {
- this.webAuthn.stop();
- this.webAuthn.cleanup();
- }
- }
-
- private async authing(): Promise {
- return (await firstValueFrom(this.loginStrategyService.currentAuthType$)) !== null;
- }
-
- private async needsLock(): Promise {
- const authType = await firstValueFrom(this.loginStrategyService.currentAuthType$);
- return authType == AuthenticationType.Sso || authType == AuthenticationType.UserApiKey;
- }
-
- async launchDuoFrameless() {
- if (this.duoFramelessUrl === null) {
- this.toastService.showToast({
- variant: "error",
- title: null,
- message: this.i18nService.t("duoHealthCheckResultsInNullAuthUrlError"),
- });
- return;
- }
-
- this.platformUtilsService.launchUri(this.duoFramelessUrl);
- }
-}
diff --git a/libs/angular/src/auth/functions/unauth-ui-refresh-route-swap.ts b/libs/angular/src/auth/functions/unauth-ui-refresh-route-swap.ts
deleted file mode 100644
index b19e73a7412..00000000000
--- a/libs/angular/src/auth/functions/unauth-ui-refresh-route-swap.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-import { Type, inject } from "@angular/core";
-import { Route, Routes } from "@angular/router";
-
-import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
-import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
-
-import { componentRouteSwap } from "../../utils/component-route-swap";
-
-/**
- * Helper function to swap between two components based on the UnauthenticatedExtensionUIRefresh feature flag.
- * We need this because the auth teams's authenticated UI will be refreshed as part of the MVP but the
- * unauthenticated UIs will not necessarily make the cut.
- * Note: Even though this is primarily an extension refresh initiative, this will be used across clients
- * as we are consolidating the unauthenticated UIs into single libs/auth components which affects all clients.
- * @param defaultComponent - The current non-refreshed component to render.
- * @param refreshedComponent - The new refreshed component to render.
- * @param options - The shared route options to apply to both components.
- * @param altOptions - The alt route options to apply to the alt component. If not provided, the base options will be used.
- */
-export function unauthUiRefreshSwap(
- defaultComponent: Type,
- refreshedComponent: Type,
- options: Route,
- altOptions?: Route,
-): Routes {
- return componentRouteSwap(
- defaultComponent,
- refreshedComponent,
- async () => {
- const configService = inject(ConfigService);
- return configService.getFeatureFlag(FeatureFlag.UnauthenticatedExtensionUIRefresh);
- },
- options,
- altOptions,
- );
-}
From f759e62aeb9e10ac0bcc7aba6e892025e6ebdb9d Mon Sep 17 00:00:00 2001
From: Addison Beck
Date: Fri, 28 Mar 2025 14:17:18 -0400
Subject: [PATCH 079/113] fix(browser): restore timer based background syncs
(#14031)
* docs: fix a typo
* fix(browser): restore timer-based background syncs
The browser extension was not performing scheduled background syncs every 30 minutes as expected. This was due to missing task scheduling code that was accidentally removed during the web push implementation (PR #11346).
This commit:
- Creates a new BackgroundSyncService to manage sync scheduling
- Properly initializes the sync interval in main.background.ts
- Adds a test to ensure the sync initialization code isn't accidentally removed again
- Organizes platform module structure to support the new service
Fixes PM-19396
* review: remove unecassary await keyword
---
.../src/background/main.background.spec.ts | 13 +++
.../browser/src/background/main.background.ts | 10 +-
apps/browser/tsconfig.json | 1 +
.../scheduling/task-scheduler.service.ts | 2 +-
.../background-sync.service.spec.ts | 107 ++++++++++++++++++
.../background-sync.service.ts | 44 +++++++
libs/platform/src/background-sync/index.ts | 1 +
libs/platform/src/index.ts | 1 +
libs/platform/tsconfig.json | 4 +-
tsconfig.json | 1 +
10 files changed, 178 insertions(+), 6 deletions(-)
create mode 100644 apps/browser/src/background/main.background.spec.ts
create mode 100644 libs/platform/src/background-sync/background-sync.service.spec.ts
create mode 100644 libs/platform/src/background-sync/background-sync.service.ts
create mode 100644 libs/platform/src/background-sync/index.ts
diff --git a/apps/browser/src/background/main.background.spec.ts b/apps/browser/src/background/main.background.spec.ts
new file mode 100644
index 00000000000..c2cd38b7a30
--- /dev/null
+++ b/apps/browser/src/background/main.background.spec.ts
@@ -0,0 +1,13 @@
+// This test skips all the initilization of the background script and just
+// focuses on making sure we don't accidently delete the initilization of
+// background vault syncing. This has happened before!
+describe("MainBackground sync task scheduling", () => {
+ it("includes code to schedule the sync interval task", () => {
+ // Get the bootstrap method source code as string
+ const { default: MainBackground } = jest.requireActual("./main.background");
+ const bootstrapSource = MainBackground.prototype.bootstrap.toString();
+
+ // Check that the source includes the critical sync interval scheduling code
+ expect(bootstrapSource).toContain("this.backgroundSyncService.init();");
+ });
+});
diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts
index e1f0b8bfc64..cae554c872c 100644
--- a/apps/browser/src/background/main.background.ts
+++ b/apps/browser/src/background/main.background.ts
@@ -127,7 +127,6 @@ import {
WebPushNotificationsApiService,
WorkerWebPushConnectionService,
} from "@bitwarden/common/platform/notifications/internal";
-import { ScheduledTaskNames } from "@bitwarden/common/platform/scheduling";
import { AppIdService } from "@bitwarden/common/platform/services/app-id.service";
import { ConfigApiService } from "@bitwarden/common/platform/services/config/config-api.service";
import { DefaultConfigService } from "@bitwarden/common/platform/services/config/default-config.service";
@@ -222,6 +221,7 @@ import {
KdfConfigService,
KeyService as KeyServiceAbstraction,
} from "@bitwarden/key-management";
+import { BackgroundSyncService } from "@bitwarden/platform/background-sync";
import {
IndividualVaultExportService,
IndividualVaultExportServiceAbstraction,
@@ -391,6 +391,7 @@ export default class MainBackground {
offscreenDocumentService: OffscreenDocumentService;
syncServiceListener: SyncServiceListener;
browserInitialInstallService: BrowserInitialInstallService;
+ backgroundSyncService: BackgroundSyncService;
webPushConnectionService: WorkerWebPushConnectionService | UnsupportedWebPushConnectionService;
themeStateService: DefaultThemeStateService;
@@ -585,9 +586,9 @@ export default class MainBackground {
this.logService,
this.stateProvider,
);
- this.taskSchedulerService.registerTaskHandler(ScheduledTaskNames.scheduleNextSyncInterval, () =>
- this.fullSync(),
- );
+
+ this.backgroundSyncService = new BackgroundSyncService(this.taskSchedulerService);
+ this.backgroundSyncService.register(() => this.fullSync());
this.environmentService = new BrowserEnvironmentService(
this.logService,
@@ -1368,6 +1369,7 @@ export default class MainBackground {
setTimeout(async () => {
await this.refreshBadge();
await this.fullSync(false);
+ this.backgroundSyncService.init();
this.notificationsService.startListening();
resolve();
}, 500);
diff --git a/apps/browser/tsconfig.json b/apps/browser/tsconfig.json
index e836af1327c..e24985f58af 100644
--- a/apps/browser/tsconfig.json
+++ b/apps/browser/tsconfig.json
@@ -29,6 +29,7 @@
"@bitwarden/key-management": ["../../libs/key-management/src"],
"@bitwarden/key-management-ui": ["../../libs/key-management-ui/src"],
"@bitwarden/platform": ["../../libs/platform/src"],
+ "@bitwarden/platform/*": ["../../libs/platform/src/*"],
"@bitwarden/send-ui": ["../../libs/tools/send/send-ui/src"],
"@bitwarden/tools-card": ["../../libs/tools/card/src"],
"@bitwarden/ui-common": ["../../libs/ui/common/src"],
diff --git a/libs/common/src/platform/scheduling/task-scheduler.service.ts b/libs/common/src/platform/scheduling/task-scheduler.service.ts
index 35a577dda7d..3ea80c3ee0a 100644
--- a/libs/common/src/platform/scheduling/task-scheduler.service.ts
+++ b/libs/common/src/platform/scheduling/task-scheduler.service.ts
@@ -11,7 +11,7 @@ import { ScheduledTaskName } from "./scheduled-task-name.enum";
* in the future but the task that is ran is NOT the remainder of your RXJS pipeline. The
* task you want ran must instead be registered in a location reachable on a service worker
* startup (on browser). An example of an acceptible location is the constructor of a service
- * you know is created in `MainBackground`. Uses of this API is other clients _can_ have the
+ * you know is created in `MainBackground`. Uses of this API in other clients _can_ have the
* `registerTaskHandler` call in more places, but in order to have it work across clients
* it is recommended to register it according to the rules of browser.
*
diff --git a/libs/platform/src/background-sync/background-sync.service.spec.ts b/libs/platform/src/background-sync/background-sync.service.spec.ts
new file mode 100644
index 00000000000..1deb907b151
--- /dev/null
+++ b/libs/platform/src/background-sync/background-sync.service.spec.ts
@@ -0,0 +1,107 @@
+import { mock, MockProxy } from "jest-mock-extended";
+
+import { TaskSchedulerService, ScheduledTaskNames } from "@bitwarden/common/platform/scheduling";
+
+import { BackgroundSyncService, DEFAULT_SYNC_INTERVAL_MS } from "./background-sync.service";
+
+describe("BackgroundSyncService", () => {
+ let taskSchedulerService: MockProxy;
+ let backgroundSyncService: BackgroundSyncService;
+
+ beforeEach(() => {
+ taskSchedulerService = mock();
+ backgroundSyncService = new BackgroundSyncService(taskSchedulerService);
+ });
+
+ describe("register", () => {
+ it("registers a task handler with the correct task name", () => {
+ // Arrange
+ const syncCallback = jest.fn().mockResolvedValue(undefined);
+
+ // Act
+ backgroundSyncService.register(syncCallback);
+
+ // Assert
+ expect(taskSchedulerService.registerTaskHandler).toHaveBeenCalledTimes(1);
+ expect(taskSchedulerService.registerTaskHandler).toHaveBeenCalledWith(
+ ScheduledTaskNames.scheduleNextSyncInterval,
+ syncCallback,
+ );
+ });
+ });
+
+ describe("init", () => {
+ it("schedules the sync interval task with default interval", () => {
+ // Act
+ backgroundSyncService.init();
+
+ // Assert
+ expect(taskSchedulerService.setInterval).toHaveBeenCalledTimes(1);
+ expect(taskSchedulerService.setInterval).toHaveBeenCalledWith(
+ ScheduledTaskNames.scheduleNextSyncInterval,
+ DEFAULT_SYNC_INTERVAL_MS,
+ );
+ });
+
+ it("schedules the sync interval task with custom interval", () => {
+ // Arrange
+ const customInterval = 60000; // 1 minute
+
+ // Act
+ backgroundSyncService.init(customInterval);
+
+ // Assert
+ expect(taskSchedulerService.setInterval).toHaveBeenCalledTimes(1);
+ expect(taskSchedulerService.setInterval).toHaveBeenCalledWith(
+ ScheduledTaskNames.scheduleNextSyncInterval,
+ customInterval,
+ );
+ });
+
+ it("correctly handles zero interval by using default", () => {
+ // Act
+ backgroundSyncService.init(0);
+
+ // Assert
+ expect(taskSchedulerService.setInterval).toHaveBeenCalledTimes(1);
+ expect(taskSchedulerService.setInterval).toHaveBeenCalledWith(
+ ScheduledTaskNames.scheduleNextSyncInterval,
+ DEFAULT_SYNC_INTERVAL_MS,
+ );
+ });
+
+ it("correctly handles negative interval by using default", () => {
+ // Act
+ backgroundSyncService.init(-1000);
+
+ // Assert
+ expect(taskSchedulerService.setInterval).toHaveBeenCalledTimes(1);
+ expect(taskSchedulerService.setInterval).toHaveBeenCalledWith(
+ ScheduledTaskNames.scheduleNextSyncInterval,
+ DEFAULT_SYNC_INTERVAL_MS,
+ );
+ });
+ });
+
+ describe("full integration", () => {
+ it("registers and initializes correctly in sequence", () => {
+ // Arrange
+ const syncCallback = jest.fn().mockResolvedValue(undefined);
+ const customInterval = 45000; // 45 seconds
+
+ // Act
+ backgroundSyncService.register(syncCallback);
+ backgroundSyncService.init(customInterval);
+
+ // Assert
+ expect(taskSchedulerService.registerTaskHandler).toHaveBeenCalledWith(
+ ScheduledTaskNames.scheduleNextSyncInterval,
+ syncCallback,
+ );
+ expect(taskSchedulerService.setInterval).toHaveBeenCalledWith(
+ ScheduledTaskNames.scheduleNextSyncInterval,
+ customInterval,
+ );
+ });
+ });
+});
diff --git a/libs/platform/src/background-sync/background-sync.service.ts b/libs/platform/src/background-sync/background-sync.service.ts
new file mode 100644
index 00000000000..dc1b49d399e
--- /dev/null
+++ b/libs/platform/src/background-sync/background-sync.service.ts
@@ -0,0 +1,44 @@
+import { TaskSchedulerService, ScheduledTaskNames } from "@bitwarden/common/platform/scheduling";
+
+/**
+ * The default interval between background syncs.
+ * 300,000ms = 5 minutes
+ */
+export const DEFAULT_SYNC_INTERVAL_MS = 300000;
+
+/**
+ * Service responsible for registering and managing background synchronization for the browser extension.
+ * Handles scheduling of periodic sync operations using the task scheduler infrastructure.
+ */
+
+export class BackgroundSyncService {
+ /**
+ * Creates a new instance of BackgroundSyncService.
+ * @param taskSchedulerService - Service that handles scheduling and execution of periodic tasks
+ */
+ constructor(private taskSchedulerService: TaskSchedulerService) {}
+
+ /**
+ * Registers a callback function to be executed when the sync interval task is triggered.
+ * This associates the sync task name with the provided callback in the task scheduler.
+ *
+ * @param syncCallback - The function to execute when the sync task is triggered
+ */
+ register(syncCallback: () => Promise) {
+ this.taskSchedulerService.registerTaskHandler(
+ ScheduledTaskNames.scheduleNextSyncInterval,
+ syncCallback,
+ );
+ }
+
+ /**
+ * Initializes the background sync service by scheduling the sync interval task.
+ * This sets up a recurring timer that triggers the registered sync callback at regular intervals.
+ *
+ * @param intervalMs - The interval in milliseconds between sync operations (defaults to 300000ms/5 minutes)
+ */
+ init(intervalMs: number = DEFAULT_SYNC_INTERVAL_MS) {
+ intervalMs = intervalMs < 1 ? DEFAULT_SYNC_INTERVAL_MS : intervalMs;
+ this.taskSchedulerService.setInterval(ScheduledTaskNames.scheduleNextSyncInterval, intervalMs);
+ }
+}
diff --git a/libs/platform/src/background-sync/index.ts b/libs/platform/src/background-sync/index.ts
new file mode 100644
index 00000000000..adfeec608be
--- /dev/null
+++ b/libs/platform/src/background-sync/index.ts
@@ -0,0 +1 @@
+export * from "./background-sync.service";
diff --git a/libs/platform/src/index.ts b/libs/platform/src/index.ts
index f11ec102845..3fabe3fad1a 100644
--- a/libs/platform/src/index.ts
+++ b/libs/platform/src/index.ts
@@ -1 +1,2 @@
export * from "./services/browser-service";
+export * from "./background-sync";
diff --git a/libs/platform/tsconfig.json b/libs/platform/tsconfig.json
index eaa021247d8..898f9e41c6a 100644
--- a/libs/platform/tsconfig.json
+++ b/libs/platform/tsconfig.json
@@ -1,7 +1,9 @@
{
"extends": "../shared/tsconfig",
"compilerOptions": {
- "paths": {}
+ "paths": {
+ "@bitwarden/common/*": ["../common/src/*"]
+ }
},
"include": ["src", "spec"],
"exclude": ["node_modules", "dist"]
diff --git a/tsconfig.json b/tsconfig.json
index fb50f1e7033..c82851d50c8 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -35,6 +35,7 @@
"@bitwarden/key-management-ui": ["./libs/key-management-ui/src"],
"@bitwarden/node/*": ["./libs/node/src/*"],
"@bitwarden/platform": ["./libs/platform/src"],
+ "@bitwarden/platform/*": ["./libs/platform/src/*"],
"@bitwarden/send-ui": ["./libs/tools/send/send-ui/src"],
"@bitwarden/tools-card": ["./libs/tools/card/src"],
"@bitwarden/ui-common": ["./libs/ui/common/src"],
From 66a914badfd65046ce5969163e957ef8f919f5a8 Mon Sep 17 00:00:00 2001
From: Jason Ng
Date: Fri, 28 Mar 2025 15:50:30 -0400
Subject: [PATCH 080/113] [PM-19654] add hideIcon option to extension anon
layout (#14045)
---
.../extension-anon-layout-wrapper.component.html | 1 +
.../extension-anon-layout-wrapper.component.ts | 10 ++++++++++
.../extension-anon-layout-wrapper.stories.ts | 2 ++
.../anon-layout/anon-layout.component.html | 2 +-
.../angular/anon-layout/anon-layout.component.ts | 1 +
.../angular/anon-layout/anon-layout.stories.ts | 16 ++++++++++++++++
6 files changed, 31 insertions(+), 1 deletion(-)
diff --git a/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.component.html b/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.component.html
index 88a3b1c3076..54cb5203a87 100644
--- a/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.component.html
+++ b/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.component.html
@@ -21,6 +21,7 @@
[hideLogo]="true"
[maxWidth]="maxWidth"
[hideFooter]="hideFooter"
+ [hideIcon]="hideIcon"
>
diff --git a/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.component.ts b/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.component.ts
index 10ef65d0654..51dbb6503d7 100644
--- a/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.component.ts
+++ b/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.component.ts
@@ -26,6 +26,7 @@ export interface ExtensionAnonLayoutWrapperData extends AnonLayoutWrapperData {
showBackButton?: boolean;
showLogo?: boolean;
hideFooter?: boolean;
+ hideIcon?: boolean;
}
@Component({
@@ -48,6 +49,7 @@ export class ExtensionAnonLayoutWrapperComponent implements OnInit, OnDestroy {
protected showAcctSwitcher: boolean;
protected showBackButton: boolean;
protected showLogo: boolean = true;
+ protected hideIcon: boolean = false;
protected pageTitle: string;
protected pageSubtitle: string;
@@ -129,6 +131,10 @@ export class ExtensionAnonLayoutWrapperComponent implements OnInit, OnDestroy {
if (firstChildRouteData["showLogo"] !== undefined) {
this.showLogo = Boolean(firstChildRouteData["showLogo"]);
}
+
+ if (firstChildRouteData["hideIcon"] !== undefined) {
+ this.hideIcon = Boolean(firstChildRouteData["hideIcon"]);
+ }
}
private listenForServiceDataChanges() {
@@ -180,6 +186,10 @@ export class ExtensionAnonLayoutWrapperComponent implements OnInit, OnDestroy {
if (data.showLogo !== undefined) {
this.showLogo = data.showLogo;
}
+
+ if (data.hideIcon !== undefined) {
+ this.hideIcon = data.hideIcon;
+ }
}
private handleStringOrTranslation(value: string | Translation): string {
diff --git a/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.stories.ts b/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.stories.ts
index 841eefda0ad..a0990485d49 100644
--- a/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.stories.ts
+++ b/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.stories.ts
@@ -242,6 +242,7 @@ const initialData: ExtensionAnonLayoutWrapperData = {
showAcctSwitcher: true,
showBackButton: true,
showLogo: true,
+ hideIcon: false,
};
const changedData: ExtensionAnonLayoutWrapperData = {
@@ -255,6 +256,7 @@ const changedData: ExtensionAnonLayoutWrapperData = {
showAcctSwitcher: false,
showBackButton: false,
showLogo: false,
+ hideIcon: false,
};
@Component({
diff --git a/libs/auth/src/angular/anon-layout/anon-layout.component.html b/libs/auth/src/angular/anon-layout/anon-layout.component.html
index 4120ea59002..f31a5500b43 100644
--- a/libs/auth/src/angular/anon-layout/anon-layout.component.html
+++ b/libs/auth/src/angular/anon-layout/anon-layout.component.html
@@ -17,7 +17,7 @@
class="tw-text-center tw-mb-4 sm:tw-mb-6"
[ngClass]="{ 'tw-max-w-md tw-mx-auto': titleAreaMaxWidth === 'md' }"
>
-
+
diff --git a/libs/auth/src/angular/anon-layout/anon-layout.component.ts b/libs/auth/src/angular/anon-layout/anon-layout.component.ts
index 05ddb9614f1..1ca4ccd2432 100644
--- a/libs/auth/src/angular/anon-layout/anon-layout.component.ts
+++ b/libs/auth/src/angular/anon-layout/anon-layout.component.ts
@@ -39,6 +39,7 @@ export class AnonLayoutComponent implements OnInit, OnChanges {
@Input() showReadonlyHostname: boolean;
@Input() hideLogo: boolean = false;
@Input() hideFooter: boolean = false;
+ @Input() hideIcon: boolean = false;
/**
* Max width of the title area content
diff --git a/libs/auth/src/angular/anon-layout/anon-layout.stories.ts b/libs/auth/src/angular/anon-layout/anon-layout.stories.ts
index c7e15d9dcfa..34d561d5210 100644
--- a/libs/auth/src/angular/anon-layout/anon-layout.stories.ts
+++ b/libs/auth/src/angular/anon-layout/anon-layout.stories.ts
@@ -163,6 +163,22 @@ export const WithCustomIcon: Story = {
}),
};
+export const HideIcon: Story = {
+ render: (args) => ({
+ props: args,
+ template:
+ // Projected content (the
) and styling is just a sample and can be replaced with any content/styling.
+ `
+
+
+
Primary Projected Content Area (customizable)
+
Lorem ipsum dolor sit amet consectetur adipisicing elit. Necessitatibus illum vero, placeat recusandae esse ratione eius minima veniam nemo, quas beatae! Impedit molestiae alias sapiente explicabo. Sapiente corporis ipsa numquam?
+
+
+ `,
+ }),
+};
+
export const HideLogo: Story = {
render: (args) => ({
props: args,
From 907abc9dae3ecb5b994d9e6d852d60576ec64bdb Mon Sep 17 00:00:00 2001
From: Todd Martin <106564991+trmartin4@users.noreply.github.com>
Date: Sun, 30 Mar 2025 15:49:52 -0400
Subject: [PATCH 081/113] Complete feature flag grouping by team (#14054)
* Completed feature flag grouping
* Added organization of default value section.
* Clarified comment.
---
libs/common/src/enums/feature-flag.enum.ts | 52 +++++++++++++---------
1 file changed, 31 insertions(+), 21 deletions(-)
diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts
index c5119cd5206..1907f6539c5 100644
--- a/libs/common/src/enums/feature-flag.enum.ts
+++ b/libs/common/src/enums/feature-flag.enum.ts
@@ -2,6 +2,8 @@
* Feature flags.
*
* Flags MUST be short lived and SHALL be removed once enabled.
+ *
+ * Flags should be grouped by team to have visibility of ownership and cleanup.
*/
export enum FeatureFlag {
/* Admin Console Team */
@@ -9,6 +11,11 @@ export enum FeatureFlag {
VerifiedSsoDomainEndpoint = "pm-12337-refactor-sso-details-endpoint",
LimitItemDeletion = "pm-15493-restrict-item-deletion-to-can-manage-permission",
SsoExternalIdVisibility = "pm-18630-sso-external-id-visibility",
+ AccountDeprovisioningBanner = "pm-17120-account-deprovisioning-admin-console-banner",
+
+ /* Auth */
+ PM9112_DeviceApprovalPersistence = "pm-9112-device-approval-persistence",
+ UnauthenticatedExtensionUIRefresh = "unauth-ui-refresh",
/* Autofill */
BlockBrowserInjectionsByDomain = "block-browser-injections-by-domain",
@@ -21,6 +28,18 @@ export enum FeatureFlag {
NotificationBarAddLoginImprovements = "notification-bar-add-login-improvements",
NotificationRefresh = "notification-refresh",
UseTreeWalkerApiForPageDetailsCollection = "use-tree-walker-api-for-page-details-collection",
+ MacOsNativeCredentialSync = "macos-native-credential-sync",
+
+ /* Billing */
+ TrialPaymentOptional = "PM-8163-trial-payment",
+ PM15179_AddExistingOrgsFromProviderPortal = "pm-15179-add-existing-orgs-from-provider-portal",
+ PM12276_BreadcrumbEventLogs = "pm-12276-breadcrumbing-for-business-features",
+ PM18794_ProviderPaymentMethod = "pm-18794-provider-payment-method",
+
+ /* Key Management */
+ PrivateKeyRegeneration = "pm-12241-private-key-regeneration",
+ UserKeyRotationV2 = "userkey-rotation-v2",
+ PM4154_BulkEncryptionService = "PM-4154-bulk-encryption-service",
/* Tools */
ItemShare = "item-share",
@@ -36,21 +55,7 @@ export enum FeatureFlag {
NewDeviceVerificationPermanentDismiss = "new-device-permanent-dismiss",
VaultBulkManagementAction = "vault-bulk-management-action",
SecurityTasks = "security-tasks",
-
- /* Auth */
- PM9112_DeviceApprovalPersistence = "pm-9112-device-approval-persistence",
-
- UserKeyRotationV2 = "userkey-rotation-v2",
- PM4154_BulkEncryptionService = "PM-4154-bulk-encryption-service",
- UnauthenticatedExtensionUIRefresh = "unauth-ui-refresh",
CipherKeyEncryption = "cipher-key-encryption",
- TrialPaymentOptional = "PM-8163-trial-payment",
- MacOsNativeCredentialSync = "macos-native-credential-sync",
- PrivateKeyRegeneration = "pm-12241-private-key-regeneration",
- AccountDeprovisioningBanner = "pm-17120-account-deprovisioning-admin-console-banner",
- PM15179_AddExistingOrgsFromProviderPortal = "pm-15179-add-existing-orgs-from-provider-portal",
- PM12276_BreadcrumbEventLogs = "pm-12276-breadcrumbing-for-business-features",
- PM18794_ProviderPaymentMethod = "pm-18794-provider-payment-method",
}
export type AllowedFeatureFlagTypes = boolean | number | string;
@@ -63,6 +68,8 @@ const FALSE = false as boolean;
*
* DO NOT enable previously disabled flags, REMOVE them instead.
* We support true as a value as we prefer flags to "enable" not "disable".
+ *
+ * Flags should be grouped by team to have visibility of ownership and cleanup.
*/
export const DefaultFeatureFlagValue = {
/* Admin Console Team */
@@ -70,6 +77,7 @@ export const DefaultFeatureFlagValue = {
[FeatureFlag.VerifiedSsoDomainEndpoint]: FALSE,
[FeatureFlag.LimitItemDeletion]: FALSE,
[FeatureFlag.SsoExternalIdVisibility]: FALSE,
+ [FeatureFlag.AccountDeprovisioningBanner]: FALSE,
/* Autofill */
[FeatureFlag.BlockBrowserInjectionsByDomain]: FALSE,
@@ -82,6 +90,7 @@ export const DefaultFeatureFlagValue = {
[FeatureFlag.NotificationBarAddLoginImprovements]: FALSE,
[FeatureFlag.NotificationRefresh]: FALSE,
[FeatureFlag.UseTreeWalkerApiForPageDetailsCollection]: FALSE,
+ [FeatureFlag.MacOsNativeCredentialSync]: FALSE,
/* Tools */
[FeatureFlag.ItemShare]: FALSE,
@@ -97,21 +106,22 @@ export const DefaultFeatureFlagValue = {
[FeatureFlag.NewDeviceVerificationPermanentDismiss]: FALSE,
[FeatureFlag.VaultBulkManagementAction]: FALSE,
[FeatureFlag.SecurityTasks]: FALSE,
+ [FeatureFlag.CipherKeyEncryption]: FALSE,
/* Auth */
[FeatureFlag.PM9112_DeviceApprovalPersistence]: FALSE,
-
- [FeatureFlag.UserKeyRotationV2]: FALSE,
- [FeatureFlag.PM4154_BulkEncryptionService]: FALSE,
[FeatureFlag.UnauthenticatedExtensionUIRefresh]: FALSE,
- [FeatureFlag.CipherKeyEncryption]: FALSE,
+
+ /* Billing */
[FeatureFlag.TrialPaymentOptional]: FALSE,
- [FeatureFlag.MacOsNativeCredentialSync]: FALSE,
- [FeatureFlag.PrivateKeyRegeneration]: FALSE,
- [FeatureFlag.AccountDeprovisioningBanner]: FALSE,
[FeatureFlag.PM15179_AddExistingOrgsFromProviderPortal]: FALSE,
[FeatureFlag.PM12276_BreadcrumbEventLogs]: FALSE,
[FeatureFlag.PM18794_ProviderPaymentMethod]: FALSE,
+
+ /* Key Management */
+ [FeatureFlag.PrivateKeyRegeneration]: FALSE,
+ [FeatureFlag.UserKeyRotationV2]: FALSE,
+ [FeatureFlag.PM4154_BulkEncryptionService]: FALSE,
} satisfies Record
;
export type DefaultFeatureFlagValueType = typeof DefaultFeatureFlagValue;
From 56672a3568da7ef0a79de6d18f0ab6a6baab47b1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Micha=C5=82=20Ch=C4=99ci=C5=84ski?=
Date: Mon, 31 Mar 2025 12:59:47 +0200
Subject: [PATCH 082/113] [BRE-714] Enhance TestFlight desktop publishing
(#13871)
* Update TestFlight deployment to use Fastlane for app uploads
* Update TestFlight deployment to use Fastlane for app uploads
* Fix
* Fix create secret for fastlane
* Fix create secret for fastlane
* Fix create secret for fastlane
* Install gsed to use sed on macos runner
* Create test file
* Fix test
* Use actual token
* Add TestFlight distribution option for QA testing
* Update .github/workflows/build-desktop.yml
Co-authored-by: MtnBurrit0 <77340197+mimartin12@users.noreply.github.com>
* Add if to secret construction for fastlane
---------
Co-authored-by: MtnBurrit0 <77340197+mimartin12@users.noreply.github.com>
---
.github/workflows/build-desktop.yml | 42 ++++++++++++++++++++++++-----
1 file changed, 35 insertions(+), 7 deletions(-)
diff --git a/.github/workflows/build-desktop.yml b/.github/workflows/build-desktop.yml
index 48ecca540e8..72b60da97a1 100644
--- a/.github/workflows/build-desktop.yml
+++ b/.github/workflows/build-desktop.yml
@@ -33,6 +33,10 @@ on:
description: "Custom SDK branch"
required: false
type: string
+ testflight_distribute:
+ description: "Force distribute to TestFlight regardless of branch (useful for QA testing on feature branches)"
+ type: boolean
+ default: true
defaults:
run:
@@ -1208,21 +1212,45 @@ jobs:
path: apps/desktop/dist/mas-universal/Bitwarden-${{ env._PACKAGE_VERSION }}-universal.pkg
if-no-files-found: error
+ - name: Create secrets for Fastlane
+ if: |
+ github.event_name != 'pull_request_target'
+ && (inputs.testflight_distribute || github.ref == 'refs/heads/main' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix-rc-desktop')
+ run: |
+ brew install gsed
+
+ KEY_WITHOUT_NEWLINES=$(gsed -E ':a;N;$!ba;s/\r{0,1}\n/\\n/g' ~/private_keys/AuthKey_6TV9MKN3GP.p8)
+
+ cat << EOF > ~/secrets/appstoreconnect-fastlane.json
+ {
+ "issuer_id": "${{ secrets.APP_STORE_CONNECT_TEAM_ISSUER }}",
+ "key_id": "6TV9MKN3GP",
+ "key": "$KEY_WITHOUT_NEWLINES"
+ }
+ EOF
+
- name: Deploy to TestFlight
id: testflight-deploy
if: |
github.event_name != 'pull_request_target'
- && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix-rc-desktop')
+ && (inputs.testflight_distribute || github.ref == 'refs/heads/main' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix-rc-desktop')
env:
APP_STORE_CONNECT_TEAM_ISSUER: ${{ secrets.APP_STORE_CONNECT_TEAM_ISSUER }}
APP_STORE_CONNECT_AUTH_KEY: 6TV9MKN3GP
+ BRANCH: ${{ github.ref }}
run: |
- xcrun altool \
- --upload-app \
- --type macos \
- --file "$(find ./dist/mas-universal/Bitwarden*.pkg)" \
- --apiKey $APP_STORE_CONNECT_AUTH_KEY \
- --apiIssuer $APP_STORE_CONNECT_TEAM_ISSUER
+
+ GIT_CHANGE="$(git show -s --format=%s)"
+
+ BRANCH=$(echo $BRANCH | sed 's/refs\/heads\///')
+
+ CHANGELOG="$BRANCH: $GIT_CHANGE"
+
+ fastlane pilot upload \
+ --app_identifier "com.bitwarden.desktop" \
+ --changelog "$CHANGELOG" \
+ --api_key_path $HOME/secrets/appstoreconnect-fastlane.json \
+ --pkg "$(find ./dist/mas-universal/Bitwarden*.pkg)"
- name: Post message to a Slack channel
id: slack-message
From 51bfbcf09056775e60fd24b11e37570e46835e85 Mon Sep 17 00:00:00 2001
From: Todd Martin <106564991+trmartin4@users.noreply.github.com>
Date: Mon, 31 Mar 2025 09:11:47 -0400
Subject: [PATCH 083/113] chore(UI Refresh): [PM-19679] Remove
unauth-ui-refresh flag from clients
* Completed feature flag grouping
* Added organization of default value section.
* Clarified comment.
* Removed flag
* Removed merge error that duplicated comment.
---
libs/common/src/enums/feature-flag.enum.ts | 2 --
1 file changed, 2 deletions(-)
diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts
index 1907f6539c5..798053c09d0 100644
--- a/libs/common/src/enums/feature-flag.enum.ts
+++ b/libs/common/src/enums/feature-flag.enum.ts
@@ -15,7 +15,6 @@ export enum FeatureFlag {
/* Auth */
PM9112_DeviceApprovalPersistence = "pm-9112-device-approval-persistence",
- UnauthenticatedExtensionUIRefresh = "unauth-ui-refresh",
/* Autofill */
BlockBrowserInjectionsByDomain = "block-browser-injections-by-domain",
@@ -110,7 +109,6 @@ export const DefaultFeatureFlagValue = {
/* Auth */
[FeatureFlag.PM9112_DeviceApprovalPersistence]: FALSE,
- [FeatureFlag.UnauthenticatedExtensionUIRefresh]: FALSE,
/* Billing */
[FeatureFlag.TrialPaymentOptional]: FALSE,
From 49924512b87098077440bcd702bd11ef318d19de Mon Sep 17 00:00:00 2001
From: Daniel James Smith <2670567+djsmith85@users.noreply.github.com>
Date: Mon, 31 Mar 2025 15:55:20 +0200
Subject: [PATCH 084/113] [PM-19656] Fix zip option not being set correctly
after navigating to Admin Console (#14058)
* Simplify if to reduce nesting
* Start subscribing to changes of the vaultSelector as soon as possible during ngOnInit
---------
Co-authored-by: Daniel James Smith
---
.../src/components/export.component.ts | 30 +++++++++----------
1 file changed, 14 insertions(+), 16 deletions(-)
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 aecc6dcc330..c8efe093762 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
@@ -225,6 +225,20 @@ export class ExportComponent implements OnInit, OnDestroy, AfterViewInit {
),
);
+ combineLatest([
+ this.exportForm.controls.vaultSelector.valueChanges,
+ this.isExportAttachmentsEnabled$,
+ ])
+ .pipe(takeUntil(this.destroy$))
+ .subscribe(([value, isExportAttachmentsEnabled]) => {
+ this.organizationId = value !== "myVault" ? value : undefined;
+
+ this.formatOptions = this.formatOptions.filter((option) => option.value !== "zip");
+ if (value === "myVault" && isExportAttachmentsEnabled) {
+ this.formatOptions.push({ name: ".zip (with attachments)", value: "zip" });
+ }
+ });
+
merge(
this.exportForm.get("format").valueChanges,
this.exportForm.get("fileEncryptionType").valueChanges,
@@ -322,22 +336,6 @@ export class ExportComponent implements OnInit, OnDestroy, AfterViewInit {
takeUntil(this.destroy$),
)
.subscribe();
-
- combineLatest([
- this.exportForm.controls.vaultSelector.valueChanges,
- this.isExportAttachmentsEnabled$,
- ])
- .pipe(takeUntil(this.destroy$))
- .subscribe(([value, isExportAttachmentsEnabled]) => {
- this.organizationId = value !== "myVault" ? value : undefined;
- if (value === "myVault" && isExportAttachmentsEnabled) {
- if (!this.formatOptions.some((option) => option.value === "zip")) {
- this.formatOptions.push({ name: ".zip (with attachments)", value: "zip" });
- }
- } else {
- this.formatOptions = this.formatOptions.filter((option) => option.value !== "zip");
- }
- });
}
ngAfterViewInit(): void {
From 740d0251b8d57ceab78443f6023e86078ecfa481 Mon Sep 17 00:00:00 2001
From: cyprain-okeke <108260115+cyprain-okeke@users.noreply.github.com>
Date: Mon, 31 Mar 2025 15:08:03 +0100
Subject: [PATCH 085/113] [PM-19368]Add new collection from individual vault is
not displaying Upgrade option rather than Save (#13965)
* Resolve the pop up issue and update button
* Rename a method properly
---
.../collection-dialog.component.ts | 29 ++++++++++++++++---
1 file changed, 25 insertions(+), 4 deletions(-)
diff --git a/apps/web/src/app/admin-console/organizations/shared/components/collection-dialog/collection-dialog.component.ts b/apps/web/src/app/admin-console/organizations/shared/components/collection-dialog/collection-dialog.component.ts
index 88746dc708b..1214c0ca411 100644
--- a/apps/web/src/app/admin-console/organizations/shared/components/collection-dialog/collection-dialog.component.ts
+++ b/apps/web/src/app/admin-console/organizations/shared/components/collection-dialog/collection-dialog.component.ts
@@ -13,6 +13,8 @@ import {
Subject,
switchMap,
takeUntil,
+ tap,
+ filter,
} from "rxjs";
import { first } from "rxjs/operators";
@@ -189,10 +191,29 @@ export class CollectionDialogComponent implements OnInit, OnDestroy {
this.formGroup.updateValueAndValidity();
}
- this.organizationSelected.valueChanges.pipe(takeUntil(this.destroy$)).subscribe((_) => {
- this.organizationSelected.markAsTouched();
- this.formGroup.updateValueAndValidity();
- });
+ this.organizationSelected.valueChanges
+ .pipe(
+ tap((_) => {
+ if (this.organizationSelected.errors?.cannotCreateCollections) {
+ this.buttonDisplayName = ButtonType.Upgrade;
+ } else {
+ this.buttonDisplayName = ButtonType.Save;
+ }
+ }),
+ filter(() => this.organizationSelected.errors?.cannotCreateCollections),
+ switchMap((value) => this.findOrganizationById(value)),
+ takeUntil(this.destroy$),
+ )
+ .subscribe((org) => {
+ this.orgExceedingCollectionLimit = org;
+ this.organizationSelected.markAsTouched();
+ this.formGroup.updateValueAndValidity();
+ });
+ }
+
+ async findOrganizationById(orgId: string): Promise {
+ const organizations = await firstValueFrom(this.organizations$);
+ return organizations.find((org) => org.id === orgId);
}
async loadOrg(orgId: string) {
From 646c7198aa2bb7953b6ee69bb619acf2ea9d1b8e Mon Sep 17 00:00:00 2001
From: cyprain-okeke <108260115+cyprain-okeke@users.noreply.github.com>
Date: Mon, 31 Mar 2025 15:10:36 +0100
Subject: [PATCH 086/113] Changes to add validation (#13762)
---
.../app/billing/organizations/change-plan-dialog.component.ts | 1 +
1 file changed, 1 insertion(+)
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 dc748e9ee41..a11858f3be8 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
@@ -733,6 +733,7 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
submit = async () => {
if (this.taxComponent !== undefined && !this.taxComponent.validate()) {
+ this.taxComponent.markAllAsTouched();
return;
}
From 0311681803f4e97d3ceb15c15efcd63655c3b95c Mon Sep 17 00:00:00 2001
From: Daniel James Smith <2670567+djsmith85@users.noreply.github.com>
Date: Mon, 31 Mar 2025 16:55:04 +0200
Subject: [PATCH 087/113] Fix filename not including "_encrypted_" when
selecting encrypted vault exports (#14066)
Co-authored-by: Daniel James Smith
---
.../src/services/individual-vault-export.service.ts | 2 +-
.../vault-export-core/src/services/org-vault-export.service.ts | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
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 765de042d32..489a28b4c79 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
@@ -250,7 +250,7 @@ export class IndividualVaultExportService
return {
type: "text/plain",
data: JSON.stringify(jsonDoc, null, " "),
- fileName: ExportHelper.getFileName("", "json"),
+ fileName: ExportHelper.getFileName("", "encrypted_json"),
} as ExportedVaultAsString;
}
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 f9ecd778c23..8f6494edb70 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
@@ -109,7 +109,7 @@ export class OrganizationVaultExportService
data: onlyManagedCollections
? await this.getEncryptedManagedExport(organizationId)
: await this.getOrganizationEncryptedExport(organizationId),
- fileName: ExportHelper.getFileName("org", "json"),
+ fileName: ExportHelper.getFileName("org", "encrypted_json"),
} as ExportedVaultAsString;
}
From 22039d038d7d88c92247c856f9358fb8e11c018b Mon Sep 17 00:00:00 2001
From: Bernd Schoolmann
Date: Mon, 31 Mar 2025 16:58:02 +0200
Subject: [PATCH 088/113] [PM-3475] Remove deprecated keys (#13266)
* Remove deprecated keys
* Fix cli build
* Fix build
---
.../browser/src/background/main.background.ts | 2 -
.../service-container/service-container.ts | 2 -
.../src/services/jslib-services.module.ts | 2 -
.../abstractions/pin.service.abstraction.ts | 13 +-
.../pin/pin.service.implementation.ts | 171 ++----------------
.../common/services/pin/pin.service.spec.ts | 164 ++---------------
.../services/master-password.service.ts | 13 --
.../services/vault-timeout.service.ts | 1 -
.../platform/abstractions/state.service.ts | 8 -
.../src/platform/models/domain/account.ts | 2 -
.../src/platform/services/state.service.ts | 41 -----
.../src/abstractions/key.service.ts | 8 -
libs/key-management/src/key.service.spec.ts | 8 -
libs/key-management/src/key.service.ts | 24 ---
14 files changed, 32 insertions(+), 427 deletions(-)
diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts
index cae554c872c..eec07b5b1ed 100644
--- a/apps/browser/src/background/main.background.ts
+++ b/apps/browser/src/background/main.background.ts
@@ -655,9 +655,7 @@ export default class MainBackground {
this.kdfConfigService,
this.keyGenerationService,
this.logService,
- this.masterPasswordService,
this.stateProvider,
- this.stateService,
);
this.keyService = new DefaultKeyService(
diff --git a/apps/cli/src/service-container/service-container.ts b/apps/cli/src/service-container/service-container.ts
index 5bc07f63c32..6a4651bcd5a 100644
--- a/apps/cli/src/service-container/service-container.ts
+++ b/apps/cli/src/service-container/service-container.ts
@@ -436,9 +436,7 @@ export class ServiceContainer {
this.kdfConfigService,
this.keyGenerationService,
this.logService,
- this.masterPasswordService,
this.stateProvider,
- this.stateService,
);
this.keyService = new KeyService(
diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts
index 37220b5195d..6334e5815d6 100644
--- a/libs/angular/src/services/jslib-services.module.ts
+++ b/libs/angular/src/services/jslib-services.module.ts
@@ -1173,9 +1173,7 @@ const safeProviders: SafeProvider[] = [
KdfConfigService,
KeyGenerationServiceAbstraction,
LogService,
- MasterPasswordServiceAbstraction,
StateProvider,
- StateServiceAbstraction,
],
}),
safeProvider({
diff --git a/libs/auth/src/common/abstractions/pin.service.abstraction.ts b/libs/auth/src/common/abstractions/pin.service.abstraction.ts
index 0d0f29dff40..16550888b94 100644
--- a/libs/auth/src/common/abstractions/pin.service.abstraction.ts
+++ b/libs/auth/src/common/abstractions/pin.service.abstraction.ts
@@ -1,4 +1,4 @@
-import { EncString, EncryptedString } from "@bitwarden/common/platform/models/domain/enc-string";
+import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
import { UserId } from "@bitwarden/common/types/guid";
import { PinKey, UserKey } from "@bitwarden/common/types/key";
import { KdfConfig } from "@bitwarden/key-management";
@@ -90,17 +90,6 @@ export abstract class PinServiceAbstraction {
*/
abstract clearUserKeyEncryptedPin(userId: UserId): Promise;
- /**
- * Gets the old MasterKey, encrypted by the PinKey (formerly called `pinProtected`).
- * Deprecated and used for migration purposes only.
- */
- abstract getOldPinKeyEncryptedMasterKey: (userId: UserId) => Promise;
-
- /**
- * Clears the old MasterKey, encrypted by the PinKey.
- */
- abstract clearOldPinKeyEncryptedMasterKey: (userId: UserId) => Promise;
-
/**
* Makes a PinKey from the provided PIN.
*/
diff --git a/libs/auth/src/common/services/pin/pin.service.implementation.ts b/libs/auth/src/common/services/pin/pin.service.implementation.ts
index 0f6ac05f381..99fb725c295 100644
--- a/libs/auth/src/common/services/pin/pin.service.implementation.ts
+++ b/libs/auth/src/common/services/pin/pin.service.implementation.ts
@@ -4,11 +4,9 @@ import { firstValueFrom, map } from "rxjs";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
-import { MasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
import { KeyGenerationService } from "@bitwarden/common/platform/abstractions/key-generation.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
-import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { EncString, EncryptedString } from "@bitwarden/common/platform/models/domain/enc-string";
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import {
@@ -18,7 +16,7 @@ import {
UserKeyDefinition,
} from "@bitwarden/common/platform/state";
import { UserId } from "@bitwarden/common/types/guid";
-import { MasterKey, PinKey, UserKey } from "@bitwarden/common/types/key";
+import { PinKey, UserKey } from "@bitwarden/common/types/key";
import { KdfConfig, KdfConfigService } from "@bitwarden/key-management";
import { PinServiceAbstraction } from "../../abstractions/pin.service.abstraction";
@@ -73,19 +71,6 @@ export const USER_KEY_ENCRYPTED_PIN = new UserKeyDefinition(
},
);
-/**
- * The old MasterKey, encrypted by the PinKey (formerly called `pinProtected`).
- * Deprecated and used for migration purposes only.
- */
-export const OLD_PIN_KEY_ENCRYPTED_MASTER_KEY = new UserKeyDefinition(
- PIN_DISK,
- "oldPinKeyEncryptedMasterKey",
- {
- deserializer: (jsonValue) => jsonValue,
- clearOn: ["logout"],
- },
-);
-
export class PinService implements PinServiceAbstraction {
constructor(
private accountService: AccountService,
@@ -94,9 +79,7 @@ export class PinService implements PinServiceAbstraction {
private kdfConfigService: KdfConfigService,
private keyGenerationService: KeyGenerationService,
private logService: LogService,
- private masterPasswordService: MasterPasswordServiceAbstraction,
private stateProvider: StateProvider,
- private stateService: StateService,
) {}
async getPinKeyEncryptedUserKeyPersistent(userId: UserId): Promise {
@@ -190,9 +173,7 @@ export class PinService implements PinServiceAbstraction {
this.accountService.accounts$.pipe(map((accounts) => accounts[userId].email)),
);
const kdfConfig = await this.kdfConfigService.getKdfConfig();
-
const pinKey = await this.makePinKey(pin, email, kdfConfig);
-
return await this.encryptService.encrypt(userKey.key, pinKey);
}
@@ -242,20 +223,6 @@ export class PinService implements PinServiceAbstraction {
return await this.encryptService.encrypt(pin, userKey);
}
- async getOldPinKeyEncryptedMasterKey(userId: UserId): Promise {
- this.validateUserId(userId, "Cannot get oldPinKeyEncryptedMasterKey.");
-
- return await firstValueFrom(
- this.stateProvider.getUserState$(OLD_PIN_KEY_ENCRYPTED_MASTER_KEY, userId),
- );
- }
-
- async clearOldPinKeyEncryptedMasterKey(userId: UserId): Promise {
- this.validateUserId(userId, "Cannot clear oldPinKeyEncryptedMasterKey.");
-
- await this.stateProvider.setUserState(OLD_PIN_KEY_ENCRYPTED_MASTER_KEY, null, userId);
- }
-
async makePinKey(pin: string, salt: string, kdfConfig: KdfConfig): Promise {
const pinKey = await this.keyGenerationService.deriveKeyFromPassword(pin, salt, kdfConfig);
return (await this.keyGenerationService.stretchKey(pinKey)) as PinKey;
@@ -264,23 +231,13 @@ export class PinService implements PinServiceAbstraction {
async getPinLockType(userId: UserId): Promise {
this.validateUserId(userId, "Cannot get PinLockType.");
- /**
- * We can't check the `userKeyEncryptedPin` (formerly called `protectedPin`) for both because old
- * accounts only used it for MP on Restart
- */
const aUserKeyEncryptedPinIsSet = !!(await this.getUserKeyEncryptedPin(userId));
const aPinKeyEncryptedUserKeyPersistentIsSet =
!!(await this.getPinKeyEncryptedUserKeyPersistent(userId));
- const anOldPinKeyEncryptedMasterKeyIsSet =
- !!(await this.getOldPinKeyEncryptedMasterKey(userId));
- if (aPinKeyEncryptedUserKeyPersistentIsSet || anOldPinKeyEncryptedMasterKeyIsSet) {
+ if (aPinKeyEncryptedUserKeyPersistentIsSet) {
return "PERSISTENT";
- } else if (
- aUserKeyEncryptedPinIsSet &&
- !aPinKeyEncryptedUserKeyPersistentIsSet &&
- !anOldPinKeyEncryptedMasterKeyIsSet
- ) {
+ } else if (aUserKeyEncryptedPinIsSet && !aPinKeyEncryptedUserKeyPersistentIsSet) {
return "EPHEMERAL";
} else {
return "DISABLED";
@@ -302,7 +259,7 @@ export class PinService implements PinServiceAbstraction {
case "DISABLED":
return false;
case "PERSISTENT":
- // The above getPinLockType call ensures that we have either a PinKeyEncryptedUserKey or OldPinKeyEncryptedMasterKey set.
+ // The above getPinLockType call ensures that we have either a PinKeyEncryptedUserKey set.
return true;
case "EPHEMERAL": {
// The above getPinLockType call ensures that we have a UserKeyEncryptedPin set.
@@ -326,31 +283,21 @@ export class PinService implements PinServiceAbstraction {
try {
const pinLockType = await this.getPinLockType(userId);
- const requireMasterPasswordOnClientRestart = pinLockType === "EPHEMERAL";
- const { pinKeyEncryptedUserKey, oldPinKeyEncryptedMasterKey } =
- await this.getPinKeyEncryptedKeys(pinLockType, userId);
+ const pinKeyEncryptedUserKey = await this.getPinKeyEncryptedKeys(pinLockType, userId);
const email = await firstValueFrom(
this.accountService.accounts$.pipe(map((accounts) => accounts[userId].email)),
);
const kdfConfig = await this.kdfConfigService.getKdfConfig();
- let userKey: UserKey;
-
- if (oldPinKeyEncryptedMasterKey) {
- userKey = await this.decryptAndMigrateOldPinKeyEncryptedMasterKey(
- userId,
- pin,
- email,
- kdfConfig,
- requireMasterPasswordOnClientRestart,
- oldPinKeyEncryptedMasterKey,
- );
- } else {
- userKey = await this.decryptUserKey(userId, pin, email, kdfConfig, pinKeyEncryptedUserKey);
- }
-
+ const userKey: UserKey = await this.decryptUserKey(
+ userId,
+ pin,
+ email,
+ kdfConfig,
+ pinKeyEncryptedUserKey,
+ );
if (!userKey) {
this.logService.warning(`User key null after pin key decryption.`);
return null;
@@ -394,109 +341,23 @@ export class PinService implements PinServiceAbstraction {
}
/**
- * Creates a new `pinKeyEncryptedUserKey` and clears the `oldPinKeyEncryptedMasterKey`.
- * @returns UserKey
- */
- private async decryptAndMigrateOldPinKeyEncryptedMasterKey(
- userId: UserId,
- pin: string,
- email: string,
- kdfConfig: KdfConfig,
- requireMasterPasswordOnClientRestart: boolean,
- oldPinKeyEncryptedMasterKey: EncString,
- ): Promise {
- this.validateUserId(userId, "Cannot decrypt and migrate oldPinKeyEncryptedMasterKey.");
-
- const masterKey = await this.decryptMasterKeyWithPin(
- userId,
- pin,
- email,
- kdfConfig,
- oldPinKeyEncryptedMasterKey,
- );
-
- const encUserKey = await this.stateService.getEncryptedCryptoSymmetricKey({ userId: userId });
-
- const userKey = await this.masterPasswordService.decryptUserKeyWithMasterKey(
- masterKey,
- userId,
- encUserKey ? new EncString(encUserKey) : undefined,
- );
-
- const pinKeyEncryptedUserKey = await this.createPinKeyEncryptedUserKey(pin, userKey, userId);
- await this.storePinKeyEncryptedUserKey(
- pinKeyEncryptedUserKey,
- requireMasterPasswordOnClientRestart,
- userId,
- );
-
- const userKeyEncryptedPin = await this.createUserKeyEncryptedPin(pin, userKey);
- await this.setUserKeyEncryptedPin(userKeyEncryptedPin, userId);
-
- await this.clearOldPinKeyEncryptedMasterKey(userId);
-
- return userKey;
- }
-
- // Only for migration purposes
- private async decryptMasterKeyWithPin(
- userId: UserId,
- pin: string,
- salt: string,
- kdfConfig: KdfConfig,
- oldPinKeyEncryptedMasterKey?: EncString,
- ): Promise {
- this.validateUserId(userId, "Cannot decrypt master key with PIN.");
-
- if (!oldPinKeyEncryptedMasterKey) {
- const oldPinKeyEncryptedMasterKeyString = await this.getOldPinKeyEncryptedMasterKey(userId);
-
- if (oldPinKeyEncryptedMasterKeyString == null) {
- throw new Error("No oldPinKeyEncrytedMasterKey found.");
- }
-
- oldPinKeyEncryptedMasterKey = new EncString(oldPinKeyEncryptedMasterKeyString);
- }
-
- const pinKey = await this.makePinKey(pin, salt, kdfConfig);
- const masterKey = await this.encryptService.decryptToBytes(oldPinKeyEncryptedMasterKey, pinKey);
-
- return new SymmetricCryptoKey(masterKey) as MasterKey;
- }
-
- /**
- * Gets the user's `pinKeyEncryptedUserKey` (persistent or ephemeral) and `oldPinKeyEncryptedMasterKey`
+ * Gets the user's `pinKeyEncryptedUserKey` (persistent or ephemeral)
* (if one exists) based on the user's PinLockType.
*
- * @remarks The `oldPinKeyEncryptedMasterKey` (formerly `pinProtected`) is only used for migration and
- * will be null for all migrated accounts.
* @throws If PinLockType is 'DISABLED' or if userId is not provided
*/
private async getPinKeyEncryptedKeys(
pinLockType: PinLockType,
userId: UserId,
- ): Promise<{ pinKeyEncryptedUserKey: EncString; oldPinKeyEncryptedMasterKey?: EncString }> {
+ ): Promise {
this.validateUserId(userId, "Cannot get PinKey encrypted keys.");
switch (pinLockType) {
case "PERSISTENT": {
- const pinKeyEncryptedUserKey = await this.getPinKeyEncryptedUserKeyPersistent(userId);
- const oldPinKeyEncryptedMasterKey = await this.getOldPinKeyEncryptedMasterKey(userId);
-
- return {
- pinKeyEncryptedUserKey,
- oldPinKeyEncryptedMasterKey: oldPinKeyEncryptedMasterKey
- ? new EncString(oldPinKeyEncryptedMasterKey)
- : undefined,
- };
+ return await this.getPinKeyEncryptedUserKeyPersistent(userId);
}
case "EPHEMERAL": {
- const pinKeyEncryptedUserKey = await this.getPinKeyEncryptedUserKeyEphemeral(userId);
-
- return {
- pinKeyEncryptedUserKey,
- oldPinKeyEncryptedMasterKey: undefined, // Going forward, we only migrate non-ephemeral version
- };
+ return await this.getPinKeyEncryptedUserKeyEphemeral(userId);
}
case "DISABLED":
throw new Error("Pin is disabled");
diff --git a/libs/auth/src/common/services/pin/pin.service.spec.ts b/libs/auth/src/common/services/pin/pin.service.spec.ts
index 794d08b63b2..ebdc36219ef 100644
--- a/libs/auth/src/common/services/pin/pin.service.spec.ts
+++ b/libs/auth/src/common/services/pin/pin.service.spec.ts
@@ -1,11 +1,9 @@
import { mock } from "jest-mock-extended";
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 { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
import { KeyGenerationService } from "@bitwarden/common/platform/abstractions/key-generation.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
-import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
@@ -15,14 +13,13 @@ import {
mockAccountServiceWith,
} from "@bitwarden/common/spec";
import { UserId } from "@bitwarden/common/types/guid";
-import { MasterKey, PinKey, UserKey } from "@bitwarden/common/types/key";
+import { PinKey, UserKey } from "@bitwarden/common/types/key";
import { DEFAULT_KDF_CONFIG, KdfConfigService } from "@bitwarden/key-management";
import {
PinService,
PIN_KEY_ENCRYPTED_USER_KEY_PERSISTENT,
PIN_KEY_ENCRYPTED_USER_KEY_EPHEMERAL,
- OLD_PIN_KEY_ENCRYPTED_MASTER_KEY,
USER_KEY_ENCRYPTED_PIN,
PinLockType,
} from "./pin.service.implementation";
@@ -31,7 +28,6 @@ describe("PinService", () => {
let sut: PinService;
let accountService: FakeAccountService;
- let masterPasswordService: FakeMasterPasswordService;
let stateProvider: FakeStateProvider;
const cryptoFunctionService = mock();
@@ -39,11 +35,9 @@ describe("PinService", () => {
const kdfConfigService = mock();
const keyGenerationService = mock