From 750ff736cd80973056824e03acc876934b9af76e Mon Sep 17 00:00:00 2001 From: Matt Gibson Date: Tue, 6 Dec 2022 16:26:42 -0600 Subject: [PATCH 1/9] [ps-2003] Ps/ps 1854 fix pin (#4193) * Await in `has` calls. * Add disk cache to browser synced items Note: `Map` doesn't serialize nicely so it's easier to swap over to a `Record` object for out cache * Mock and await init promises in tests * Remove redundant settings checks --- .../session-syncer.spec.ts | 11 ++++--- .../session-sync-observable/session-syncer.ts | 9 ++++-- .../src/services/browser-state.service.ts | 5 ++++ .../src/services/memoryStorage.service.ts | 2 +- libs/common/src/services/state.service.ts | 30 ++++++++++++------- 5 files changed, 38 insertions(+), 19 deletions(-) diff --git a/apps/browser/src/decorators/session-sync-observable/session-syncer.spec.ts b/apps/browser/src/decorators/session-sync-observable/session-syncer.spec.ts index 00a0da433a5..c37b640f3f9 100644 --- a/apps/browser/src/decorators/session-sync-observable/session-syncer.spec.ts +++ b/apps/browser/src/decorators/session-sync-observable/session-syncer.spec.ts @@ -1,4 +1,4 @@ -import { awaitAsync as flushAsyncObservables } from "@bitwarden/angular/../test-utils"; +import { awaitAsync, awaitAsync as flushAsyncObservables } from "@bitwarden/angular/../test-utils"; import { mock, MockProxy } from "jest-mock-extended"; import { BehaviorSubject, ReplaySubject } from "rxjs"; @@ -30,6 +30,7 @@ describe("session syncer", () => { }); stateService = mock(); + stateService.hasInSessionMemory.mockResolvedValue(false); sut = new SessionSyncer(behaviorSubject, stateService, metaData); }); @@ -101,24 +102,26 @@ describe("session syncer", () => { expect(sut["ignoreNUpdates"]).toBe(1); }); - it("should grab an initial value from storage if it exists", () => { + it("should grab an initial value from storage if it exists", async () => { stateService.hasInSessionMemory.mockResolvedValue(true); //Block a call to update const updateSpy = jest.spyOn(sut as any, "update").mockImplementation(); sut.init(); + await awaitAsync(); expect(updateSpy).toHaveBeenCalledWith(); }); - it("should not grab an initial value from storage if it does not exist", () => { + it("should not grab an initial value from storage if it does not exist", async () => { stateService.hasInSessionMemory.mockResolvedValue(false); //Block a call to update const updateSpy = jest.spyOn(sut as any, "update").mockImplementation(); sut.init(); + await awaitAsync(); - expect(updateSpy).toHaveBeenCalledWith(); + expect(updateSpy).not.toHaveBeenCalled(); }); }); diff --git a/apps/browser/src/decorators/session-sync-observable/session-syncer.ts b/apps/browser/src/decorators/session-sync-observable/session-syncer.ts index 68294b68c3d..91b371ef817 100644 --- a/apps/browser/src/decorators/session-sync-observable/session-syncer.ts +++ b/apps/browser/src/decorators/session-sync-observable/session-syncer.ts @@ -42,9 +42,12 @@ export class SessionSyncer { } this.observe(); - if (this.stateService.hasInSessionMemory(this.metaData.sessionKey)) { - this.update(); - } + // must be synchronous + this.stateService.hasInSessionMemory(this.metaData.sessionKey).then((hasInSessionMemory) => { + if (hasInSessionMemory) { + this.update(); + } + }); this.listenForUpdates(); } diff --git a/apps/browser/src/services/browser-state.service.ts b/apps/browser/src/services/browser-state.service.ts index 24630dcbaef..57873f23073 100644 --- a/apps/browser/src/services/browser-state.service.ts +++ b/apps/browser/src/services/browser-state.service.ts @@ -28,6 +28,11 @@ export class BrowserStateService protected activeAccountSubject: BehaviorSubject; @sessionSync({ ctor: Boolean }) protected activeAccountUnlockedSubject: BehaviorSubject; + @sessionSync({ + initializer: Account.fromJSON as any, // TODO: Remove this any when all any types are removed from Account + initializeAs: "record", + }) + protected accountDiskCache: BehaviorSubject>; protected accountDeserializer = Account.fromJSON; diff --git a/libs/common/src/services/memoryStorage.service.ts b/libs/common/src/services/memoryStorage.service.ts index 0911992c3cb..cfb94193e5c 100644 --- a/libs/common/src/services/memoryStorage.service.ts +++ b/libs/common/src/services/memoryStorage.service.ts @@ -18,7 +18,7 @@ export class MemoryStorageService } async has(key: string): Promise { - return this.get(key) != null; + return (await this.get(key)) != null; } save(key: string, obj: any): Promise { diff --git a/libs/common/src/services/state.service.ts b/libs/common/src/services/state.service.ts index fe787f21c0e..2a2ce53d622 100644 --- a/libs/common/src/services/state.service.ts +++ b/libs/common/src/services/state.service.ts @@ -79,7 +79,7 @@ export class StateService< private hasBeenInited = false; private isRecoveredSession = false; - private accountDiskCache = new Map(); + protected accountDiskCache = new BehaviorSubject>({}); // default account serializer, must be overridden by child class protected accountDeserializer = Account.fromJSON as (json: Jsonify) => TAccount; @@ -2383,7 +2383,7 @@ export class StateService< } if (this.useAccountCache) { - const cachedAccount = this.accountDiskCache.get(options.userId); + const cachedAccount = this.accountDiskCache.value[options.userId]; if (cachedAccount != null) { return cachedAccount; } @@ -2397,9 +2397,7 @@ export class StateService< )) : await this.storageService.get(options.userId, options); - if (this.useAccountCache) { - this.accountDiskCache.set(options.userId, account); - } + this.setDiskCache(options.userId, account); return account; } @@ -2430,9 +2428,7 @@ export class StateService< await storageLocation.save(`${options.userId}`, account, options); - if (this.useAccountCache) { - this.accountDiskCache.delete(options.userId); - } + this.deleteDiskCache(options.userId); } protected async saveAccountToMemory(account: TAccount): Promise { @@ -2643,9 +2639,7 @@ export class StateService< userId = userId ?? state.activeUserId; delete state.accounts[userId]; - if (this.useAccountCache) { - this.accountDiskCache.delete(userId); - } + this.deleteDiskCache(userId); return state; }); @@ -2770,6 +2764,20 @@ export class StateService< await this.setState(updatedState); }); } + + private setDiskCache(key: string, value: TAccount, options?: StorageOptions) { + if (this.useAccountCache) { + this.accountDiskCache.value[key] = value; + this.accountDiskCache.next(this.accountDiskCache.value); + } + } + + private deleteDiskCache(key: string) { + if (this.useAccountCache) { + delete this.accountDiskCache.value[key]; + this.accountDiskCache.next(this.accountDiskCache.value); + } + } } function withPrototypeForArrayMembers( From c0025735817b12dfa6303751dd0814754e2b9c4d Mon Sep 17 00:00:00 2001 From: Shane Melton Date: Thu, 8 Dec 2022 08:15:50 -0800 Subject: [PATCH 2/9] [EC-623] Reusable Search Input Component (#4082) * [EC-623] Introduce shared organization module and search input component * [EC-623] Add search input story * [EC-623] Rename search input component tag prefix from bit->app --- .../app/organizations/organization.module.ts | 5 +- .../search-input/search-input.component.html | 21 ++++++++ .../search-input/search-input.component.ts | 54 +++++++++++++++++++ .../search-input/search-input.stories.ts | 36 +++++++++++++ .../web/src/app/organizations/shared/index.ts | 1 + .../shared/shared-organization.module.ts | 12 +++++ 6 files changed, 126 insertions(+), 3 deletions(-) create mode 100644 apps/web/src/app/organizations/shared/components/search-input/search-input.component.html create mode 100644 apps/web/src/app/organizations/shared/components/search-input/search-input.component.ts create mode 100644 apps/web/src/app/organizations/shared/components/search-input/search-input.stories.ts create mode 100644 apps/web/src/app/organizations/shared/index.ts create mode 100644 apps/web/src/app/organizations/shared/shared-organization.module.ts diff --git a/apps/web/src/app/organizations/organization.module.ts b/apps/web/src/app/organizations/organization.module.ts index f32e4f53104..d894f53e098 100644 --- a/apps/web/src/app/organizations/organization.module.ts +++ b/apps/web/src/app/organizations/organization.module.ts @@ -1,11 +1,10 @@ import { NgModule } from "@angular/core"; -import { SharedModule } from "../shared"; - import { AccessSelectorModule } from "./components/access-selector"; import { OrganizationsRoutingModule } from "./organization-routing.module"; +import { SharedOrganizationModule } from "./shared"; @NgModule({ - imports: [SharedModule, AccessSelectorModule, OrganizationsRoutingModule], + imports: [SharedOrganizationModule, AccessSelectorModule, OrganizationsRoutingModule], }) export class OrganizationModule {} diff --git a/apps/web/src/app/organizations/shared/components/search-input/search-input.component.html b/apps/web/src/app/organizations/shared/components/search-input/search-input.component.html new file mode 100644 index 00000000000..2396dc34232 --- /dev/null +++ b/apps/web/src/app/organizations/shared/components/search-input/search-input.component.html @@ -0,0 +1,21 @@ + +
+ + +
diff --git a/apps/web/src/app/organizations/shared/components/search-input/search-input.component.ts b/apps/web/src/app/organizations/shared/components/search-input/search-input.component.ts new file mode 100644 index 00000000000..4bdcf665cd5 --- /dev/null +++ b/apps/web/src/app/organizations/shared/components/search-input/search-input.component.ts @@ -0,0 +1,54 @@ +import { Component, Input } from "@angular/core"; +import { ControlValueAccessor, NG_VALUE_ACCESSOR } from "@angular/forms"; + +let nextId = 0; + +@Component({ + selector: "app-search-input", + templateUrl: "./search-input.component.html", + providers: [ + { + provide: NG_VALUE_ACCESSOR, + multi: true, + useExisting: SearchInputComponent, + }, + ], +}) +export class SearchInputComponent implements ControlValueAccessor { + private notifyOnChange: (v: string) => void; + private notifyOnTouch: () => void; + + protected id = `search-id-${nextId++}`; + protected searchText: string; + + @Input() disabled: boolean; + @Input() placeholder: string; + + onChange(searchText: string) { + if (this.notifyOnChange != undefined) { + this.notifyOnChange(searchText); + } + } + + onTouch() { + if (this.notifyOnTouch != undefined) { + this.notifyOnTouch(); + } + } + + registerOnChange(fn: (v: string) => void): void { + this.notifyOnChange = fn; + } + + registerOnTouched(fn: () => void): void { + this.notifyOnTouch = fn; + } + + writeValue(searchText: string): void { + this.searchText = searchText; + } + + setDisabledState(isDisabled: boolean) { + this.disabled = isDisabled; + } +} diff --git a/apps/web/src/app/organizations/shared/components/search-input/search-input.stories.ts b/apps/web/src/app/organizations/shared/components/search-input/search-input.stories.ts new file mode 100644 index 00000000000..ee72e91ad5f --- /dev/null +++ b/apps/web/src/app/organizations/shared/components/search-input/search-input.stories.ts @@ -0,0 +1,36 @@ +import { FormsModule, ReactiveFormsModule } from "@angular/forms"; +import { Meta, moduleMetadata, Story } from "@storybook/angular"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { InputModule } from "@bitwarden/components/src/input/input.module"; + +import { PreloadedEnglishI18nModule } from "../../../../tests/preloaded-english-i18n.module"; + +import { SearchInputComponent } from "./search-input.component"; + +export default { + title: "Web/Organizations/Search Input", + component: SearchInputComponent, + decorators: [ + moduleMetadata({ + imports: [ + InputModule, + FormsModule, + ReactiveFormsModule, + PreloadedEnglishI18nModule, + JslibModule, + ], + providers: [], + }), + ], +} as Meta; + +const Template: Story = (args: SearchInputComponent) => ({ + props: args, + template: ` + + `, +}); + +export const Default = Template.bind({}); +Default.args = {}; diff --git a/apps/web/src/app/organizations/shared/index.ts b/apps/web/src/app/organizations/shared/index.ts new file mode 100644 index 00000000000..941a5ddd635 --- /dev/null +++ b/apps/web/src/app/organizations/shared/index.ts @@ -0,0 +1 @@ +export * from "./shared-organization.module"; diff --git a/apps/web/src/app/organizations/shared/shared-organization.module.ts b/apps/web/src/app/organizations/shared/shared-organization.module.ts new file mode 100644 index 00000000000..ce5c45f0fcd --- /dev/null +++ b/apps/web/src/app/organizations/shared/shared-organization.module.ts @@ -0,0 +1,12 @@ +import { NgModule } from "@angular/core"; + +import { SharedModule } from "../../shared"; + +import { SearchInputComponent } from "./components/search-input/search-input.component"; + +@NgModule({ + imports: [SharedModule], + declarations: [SearchInputComponent], + exports: [SharedModule, SearchInputComponent], +}) +export class SharedOrganizationModule {} From c2606287783adbf95bdfc94e496d805fabdebff4 Mon Sep 17 00:00:00 2001 From: Shane Melton Date: Thu, 8 Dec 2022 15:08:21 -0800 Subject: [PATCH 3/9] Add 'search' as a valid InputType (#4202) --- libs/components/src/form-field/form-field-control.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/libs/components/src/form-field/form-field-control.ts b/libs/components/src/form-field/form-field-control.ts index d47696edde4..52f3018f796 100644 --- a/libs/components/src/form-field/form-field-control.ts +++ b/libs/components/src/form-field/form-field-control.ts @@ -1,4 +1,11 @@ -export type InputTypes = "text" | "password" | "number" | "datetime-local" | "email" | "checkbox"; +export type InputTypes = + | "text" + | "password" + | "number" + | "datetime-local" + | "email" + | "checkbox" + | "search"; export abstract class BitFormFieldControl { ariaDescribedBy: string; From 43945cd05d819fd7c1af9e55c85f9805dd5c32bc Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 9 Dec 2022 11:05:21 +0100 Subject: [PATCH 4/9] Autosync the updated translations (#4203) 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/da/messages.json | 455 ++++++++++--------- 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/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 | 9 + 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/lv/messages.json | 9 + apps/desktop/src/locales/me/messages.json | 9 + apps/desktop/src/locales/ml/messages.json | 9 + apps/desktop/src/locales/nb/messages.json | 9 + apps/desktop/src/locales/nl/messages.json | 9 + apps/desktop/src/locales/nn/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 | 13 +- 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/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 | 11 +- apps/desktop/src/locales/zh_TW/messages.json | 11 +- 55 files changed, 722 insertions(+), 227 deletions(-) diff --git a/apps/desktop/src/locales/af/messages.json b/apps/desktop/src/locales/af/messages.json index 0d4c31cde12..35c966e4d97 100644 --- a/apps/desktop/src/locales/af/messages.json +++ b/apps/desktop/src/locales/af/messages.json @@ -1779,6 +1779,15 @@ "disabled": { "message": "Gedeaktiveer" }, + "removePassword": { + "message": "Remove password" + }, + "removedPassword": { + "message": "Password removed" + }, + "removePasswordConfirmation": { + "message": "Are you sure you want to remove the password?" + }, "maxAccessCountReached": { "message": "Maks toegangsaantal bereik" }, diff --git a/apps/desktop/src/locales/ar/messages.json b/apps/desktop/src/locales/ar/messages.json index f53447c68e1..8e3f4886849 100644 --- a/apps/desktop/src/locales/ar/messages.json +++ b/apps/desktop/src/locales/ar/messages.json @@ -1779,6 +1779,15 @@ "disabled": { "message": "معطّل" }, + "removePassword": { + "message": "إزالة كلمة المرور" + }, + "removedPassword": { + "message": "تمت إزالة كلمة المرور" + }, + "removePasswordConfirmation": { + "message": "هل أنت متأكد من أنك تريد إزالة كلمة المرور؟" + }, "maxAccessCountReached": { "message": "تم بلوغ الحد الأقصى لعدد الدخول" }, diff --git a/apps/desktop/src/locales/az/messages.json b/apps/desktop/src/locales/az/messages.json index 3d3585997b2..e8b08493b78 100644 --- a/apps/desktop/src/locales/az/messages.json +++ b/apps/desktop/src/locales/az/messages.json @@ -1779,6 +1779,15 @@ "disabled": { "message": "Sıradan çıxarıldı" }, + "removePassword": { + "message": "Parolu çıxart" + }, + "removedPassword": { + "message": "Parol çıxarıldı" + }, + "removePasswordConfirmation": { + "message": "Parolu çıxartmaq istədiyinizə əminsiniz?" + }, "maxAccessCountReached": { "message": "Maksimal müraciət sayına çatıldı" }, diff --git a/apps/desktop/src/locales/be/messages.json b/apps/desktop/src/locales/be/messages.json index 247d15f2d47..72e2cdd935e 100644 --- a/apps/desktop/src/locales/be/messages.json +++ b/apps/desktop/src/locales/be/messages.json @@ -1779,6 +1779,15 @@ "disabled": { "message": "Адключана" }, + "removePassword": { + "message": "Выдаліць пароль" + }, + "removedPassword": { + "message": "Пароль выдалены" + }, + "removePasswordConfirmation": { + "message": "Вы сапраўды хочаце выдаліць пароль?" + }, "maxAccessCountReached": { "message": "Дасягнута максімальная колькасць доступаў" }, diff --git a/apps/desktop/src/locales/bg/messages.json b/apps/desktop/src/locales/bg/messages.json index a6c64d38a77..0580db0ee0f 100644 --- a/apps/desktop/src/locales/bg/messages.json +++ b/apps/desktop/src/locales/bg/messages.json @@ -1779,6 +1779,15 @@ "disabled": { "message": "Изключено" }, + "removePassword": { + "message": "Премахване на паролата" + }, + "removedPassword": { + "message": "Паролата е премахната" + }, + "removePasswordConfirmation": { + "message": "Наистина ли искате да премахнете паролата?" + }, "maxAccessCountReached": { "message": "Достигнат е максималният брой достъпвания" }, diff --git a/apps/desktop/src/locales/bn/messages.json b/apps/desktop/src/locales/bn/messages.json index 0a8416dd2fe..6f5f9114fe4 100644 --- a/apps/desktop/src/locales/bn/messages.json +++ b/apps/desktop/src/locales/bn/messages.json @@ -1779,6 +1779,15 @@ "disabled": { "message": "Disabled" }, + "removePassword": { + "message": "Remove password" + }, + "removedPassword": { + "message": "Password removed" + }, + "removePasswordConfirmation": { + "message": "Are you sure you want to remove the password?" + }, "maxAccessCountReached": { "message": "Max access count reached" }, diff --git a/apps/desktop/src/locales/bs/messages.json b/apps/desktop/src/locales/bs/messages.json index a3b4053cc91..f8c13b11090 100644 --- a/apps/desktop/src/locales/bs/messages.json +++ b/apps/desktop/src/locales/bs/messages.json @@ -1779,6 +1779,15 @@ "disabled": { "message": "Disabled" }, + "removePassword": { + "message": "Remove password" + }, + "removedPassword": { + "message": "Password removed" + }, + "removePasswordConfirmation": { + "message": "Are you sure you want to remove the password?" + }, "maxAccessCountReached": { "message": "Max access count reached" }, diff --git a/apps/desktop/src/locales/ca/messages.json b/apps/desktop/src/locales/ca/messages.json index 3848160332e..3026d335592 100644 --- a/apps/desktop/src/locales/ca/messages.json +++ b/apps/desktop/src/locales/ca/messages.json @@ -1779,6 +1779,15 @@ "disabled": { "message": "Deshabilitat" }, + "removePassword": { + "message": "Suprimeix la contrasenya" + }, + "removedPassword": { + "message": "Contrasenya suprimida" + }, + "removePasswordConfirmation": { + "message": "Esteu segur que voleu suprimir la contrasenya?" + }, "maxAccessCountReached": { "message": "S'ha assolit el recompte màxim d'accesos" }, diff --git a/apps/desktop/src/locales/cs/messages.json b/apps/desktop/src/locales/cs/messages.json index cca2d86a35a..5714a5b0c74 100644 --- a/apps/desktop/src/locales/cs/messages.json +++ b/apps/desktop/src/locales/cs/messages.json @@ -1779,6 +1779,15 @@ "disabled": { "message": "Zakázáno" }, + "removePassword": { + "message": "Remove password" + }, + "removedPassword": { + "message": "Password removed" + }, + "removePasswordConfirmation": { + "message": "Are you sure you want to remove the password?" + }, "maxAccessCountReached": { "message": "Dosažen maximální počet přístupů" }, diff --git a/apps/desktop/src/locales/da/messages.json b/apps/desktop/src/locales/da/messages.json index c7d1aca6626..6a19d1279bf 100644 --- a/apps/desktop/src/locales/da/messages.json +++ b/apps/desktop/src/locales/da/messages.json @@ -33,7 +33,7 @@ "message": "Samlinger" }, "searchVault": { - "message": "Søg i Boks" + "message": "Søg i boks" }, "addItem": { "message": "Tilføj emne" @@ -61,7 +61,7 @@ } }, "moveToOrgDesc": { - "message": "Vælg den organisation, som du vil flytte dette emne til. Flytning overfører ejerskab af emnet til organisationen, og du vil efter flytningen ikke længere være den direkte ejer af emnet." + "message": "Vælg en organisation, hvortil dette emne skal flyttes. Flytning overfører ejerskab af emnet til organisationen, og efter flytningen vil man ikke længere være den direkte ejer af emnet." }, "attachments": { "message": "Vedhæftninger" @@ -92,7 +92,7 @@ "message": "Brugernavn" }, "password": { - "message": "Kodeord" + "message": "Adgangskode" }, "passphrase": { "message": "Adgangssætning" @@ -123,20 +123,20 @@ "description": "Copy value to clipboard" }, "minimizeOnCopyToClipboard": { - "message": "Minimér, når du kopierer til udklipsholder" + "message": "Minimér under kopiering til udklipsholder" }, "minimizeOnCopyToClipboardDesc": { - "message": "Minimér programmet, når du kopierer et elements data til udklipsholderen." + "message": "Minimér applikation under kopiering af emnes data til udklipsholder." }, "toggleVisibility": { "message": "Slå synlighed til/fra" }, "toggleCollapse": { - "message": "Fold sammen/fold ud", + "message": "Fold sammen/ud", "description": "Toggling an expand/collapse state." }, "cardholderName": { - "message": "Kortindehaverens navn" + "message": "Kortholdernavn" }, "number": { "message": "Nummer" @@ -148,7 +148,7 @@ "message": "Udløb" }, "securityCode": { - "message": "Sikkerhedskode" + "message": "Bekræftelseskode" }, "identityName": { "message": "Identitetsnavn" @@ -163,7 +163,7 @@ "message": "Pasnummer" }, "licenseNumber": { - "message": "Licensnummer" + "message": "Kørekortnummer" }, "email": { "message": "E-mail" @@ -178,7 +178,7 @@ "message": "Premium kræves" }, "premiumRequiredDesc": { - "message": "Premium-medlemskab kræves for at anvende denne funktion." + "message": "Premium-medlemskab kræves for brug af denne funktion." }, "errorOccurred": { "message": "En fejl er opstået." @@ -230,16 +230,16 @@ "message": "Titel" }, "mr": { - "message": "Hr" + "message": "Hr." }, "mrs": { "message": "Fru" }, "ms": { - "message": "Frøken" + "message": "Frk." }, "dr": { - "message": "Dr" + "message": "Dr." }, "expirationMonth": { "message": "Udløbsmåned" @@ -251,7 +251,7 @@ "message": "Vælg" }, "other": { - "message": "Andet" + "message": "Andre" }, "generatePassword": { "message": "Generér adgangskode" @@ -308,13 +308,13 @@ "message": "Redigér" }, "authenticatorKeyTotp": { - "message": "Autentificeringsnøgle (TOTP)" + "message": "Godkendelsesnøgle (TOTP)" }, "folder": { "message": "Mappe" }, "newCustomField": { - "message": "Nyt brugerdefineret felt" + "message": "Nyt tilpasset felt" }, "value": { "message": "Værdi" @@ -343,16 +343,16 @@ "message": "Fjern" }, "nameRequired": { - "message": "Navn er påkrævet." + "message": "Navn er obligatorisk." }, "addedItem": { - "message": "Tilføjede element" + "message": "Emne tilføjet" }, "editedItem": { - "message": "Regiderede element" + "message": "Emne gemt" }, "deleteItem": { - "message": "Slet element" + "message": "Slet emne" }, "deleteFolder": { "message": "Slet mappe" @@ -361,19 +361,19 @@ "message": "Slet vedhæftning" }, "deleteItemConfirmation": { - "message": "Er du sikker på, at du sende til papirkurven?" + "message": "Send til papirkurven, sikker?" }, "deletedItem": { - "message": "Element sendt til papirkurven" + "message": "Emne sendt til papirkurven" }, "overwritePasswordConfirmation": { - "message": "Er du sikker på, at du vil overskrive den aktuelle adgangskode?" + "message": "Sikker på, at den aktuelle adgangskode skal overskrives?" }, "overwriteUsername": { "message": "Overskriv brugernavn" }, "overwriteUsernameConfirmation": { - "message": "Er du sikker på, at du vil overskrive det aktuelle brugernavn?" + "message": "Sikker på, at det aktuelle brugernavn skal overskrives?" }, "noneFolder": { "message": "Ingen mappe", @@ -389,7 +389,7 @@ "message": "Regenerér adgangskode" }, "copyPassword": { - "message": "Kopiér kodeord" + "message": "Kopiér adgangskode" }, "copyUri": { "message": "Kopiér URI" @@ -401,10 +401,10 @@ "message": "Længde" }, "uppercase": { - "message": "Store bogstaver (A-Z)" + "message": "Majuskler (A-Z)" }, "lowercase": { - "message": "Små bogstaver (a-z)" + "message": "Minuskler (a-z)" }, "numbers": { "message": "Cifre (0-9)" @@ -455,13 +455,13 @@ "message": "Tilføj ny vedhæftning" }, "deletedAttachment": { - "message": "Slettet vedhæftning" + "message": "Vedhæftning slettet" }, "deleteAttachmentConfirmation": { - "message": "Er du sikker på du vil slette denne vedhæftning?" + "message": "Sikker på, at denne vedhæftning skal slettes?" }, "attachmentSaved": { - "message": "Den vedhæftede fil er blevet gemt." + "message": "Vedhæftning gemt" }, "file": { "message": "Fil" @@ -473,22 +473,22 @@ "message": "Maksimum filstørrelse er 500 MB." }, "updateKey": { - "message": "Du kan ikke bruge denne funktion, før du opdaterer din krypteringsnøgle." + "message": "Denne funktion kan ikke bruges, før din krypteringsnøgle opdateres." }, "editedFolder": { - "message": "Redigerede mappe" + "message": "Mappe gemt" }, "addedFolder": { - "message": "Tilføjede mappe" + "message": "Mappe tilføjet" }, "deleteFolderConfirmation": { - "message": "Er du sikker på du vil slette denne mappe?" + "message": "Sikker på, at denne mappe skal slettes?" }, "deletedFolder": { - "message": "Slettede mappe" + "message": "Mappe slettet" }, "loginOrCreateNewAccount": { - "message": "Log ind eller opret en ny konto for at få adgang til din sikre boks." + "message": "Log ind, eller opret en ny konto, for at få tilgå din sikre boks." }, "createAccount": { "message": "Opret konto" @@ -503,16 +503,16 @@ "message": "Hovedadgangskode" }, "masterPassDesc": { - "message": "Hovedadgangskoden er den adgangskode, du bruger til at få adgang til din boks. Det er meget vigtigt, at du ikke glemmer din hovedadgangskode. Der er ingen måde hvorpå koden kan genoprettes, i tilfælde af at du glemmer den." + "message": "Hovedadgangskoden er den adgangskode, man bruger for at kunne tilgå sin boks. Det er uhyre vigtigt, at hovedadgangskoden ikke glemmes, da den ikke vil kunne genoprettes, såfremt man glemmer den." }, "masterPassHintDesc": { - "message": "Et tip til hovedadgangskoden kan hjælpe dig med at huske din adgangskode, hvis du glemmer den." + "message": "Et tip til hovedadgangskoden kan hjælpe hukommelsen på vej, såfremt man glemmer koden." }, "reTypeMasterPass": { - "message": "Gentast hovedadgangskode" + "message": "Angiv hovedadgangskode igen" }, "masterPassHint": { - "message": "Hovedadgangskodetip (valgfri)" + "message": "Hovedadgangskodetip (valgfrit)" }, "settings": { "message": "Indstillinger" @@ -527,16 +527,16 @@ "message": "Få hovedadgangskodetip" }, "emailRequired": { - "message": "E-mailadresse er påkrævet." + "message": "E-mailadresse er obligatorisk." }, "invalidEmail": { "message": "Ugyldig e-mailadresse." }, "masterPasswordRequired": { - "message": "Hovedadgangskode er påkrævet." + "message": "Hovedadgangskode er obligatorisk." }, "confirmMasterPasswordRequired": { - "message": "Hovedadgangskode kræves angivet igen." + "message": "Angivelse af hovedadgangskode igen er obligatorisk." }, "masterPasswordMinlength": { "message": "Hovedadgangskode skal være mindst 8 tegn." @@ -545,19 +545,19 @@ "message": "De to adgangskoder matcher ikke." }, "newAccountCreated": { - "message": "Din nye konto er oprettet! Du kan nu logge ind." + "message": "Den nye konto er oprettet! Der kan nu logges ind." }, "masterPassSent": { - "message": "Vi har sendt dig en e-mail med dit hovedadgangskodetip." + "message": "Der er sendt en e-mail til dig med dit hovedadgangskodetip." }, "unexpectedError": { - "message": "Der opstod en uventet fejl." + "message": "En uventet fejl opstod." }, "itemInformation": { - "message": "Elementinformation" + "message": "Emneinformation" }, "noItemsInList": { - "message": "Der er ingen elementer at vise." + "message": "Ingen emner at vise." }, "sendVerificationCode": { "message": "Send en bekræftelseskode til din e-mail" @@ -575,7 +575,7 @@ "message": "Bekræft din identitet for at fortsætte." }, "verificationCodeRequired": { - "message": "Bekræftelseskode er påkrævet." + "message": "Bekræftelseskode er obligatorisk." }, "invalidVerificationCode": { "message": "Ugyldig bekræftelseskode" @@ -584,10 +584,10 @@ "message": "Fortsæt" }, "enterVerificationCodeApp": { - "message": "Indtast den 6-cifrede verifikationskode fra din autentificeringsapp." + "message": "Angiv den 6-cifrede bekræftelseskode fra godkendelses-appen." }, "enterVerificationCodeEmail": { - "message": "Indtast den 6-cifrede verifikationskode, der blev sendt til $EMAIL$.", + "message": "Angiv den 6-cifrede bekræftelseskode, der blev sendt til $EMAIL$.", "placeholders": { "email": { "content": "$1", @@ -596,7 +596,7 @@ } }, "verificationCodeEmailSent": { - "message": "Bekræftelses-email sendt til $EMAIL$.", + "message": "Bekræftelsese-mail sendt til $EMAIL$.", "placeholders": { "email": { "content": "$1", @@ -608,10 +608,10 @@ "message": "Husk mig" }, "sendVerificationCodeEmailAgain": { - "message": "Send verifikationskode-email igen" + "message": "Send bekræftelseskodee-mail igen" }, "useAnotherTwoStepMethod": { - "message": "Brug en anden to-trins-loginmetode" + "message": "Brug en anden totrins-loginmetode" }, "insertYubiKey": { "message": "Indsæt din YubiKey i din computers USB-port og tryk derefter på dens knap." @@ -620,37 +620,37 @@ "message": "Indsæt din sikkerhedsnøgle i din computers USB-port. Hvis den har en knap, tryk på den." }, "recoveryCodeDesc": { - "message": "Mistet adgang til alle dine to-faktor-udbydere? Brug din genoprettelseskode til at deaktivere alle to-faktor udbydere på din konto." + "message": "Mistet adgang til alle dine tofaktorudbydere? Brug din genoprettelseskode til at deaktivere alle tofaktorudbydere på din konto." }, "recoveryCodeTitle": { "message": "Gendannelseskode" }, "authenticatorAppTitle": { - "message": "Autentificeringsapp" + "message": "Godkendelses-app" }, "authenticatorAppDesc": { - "message": "Brug en autentificeringsapp (f.eks. Authy eller Google Autentificering) til at generere tidsbaserede bekræftelseskoder.", + "message": "Brug en godkendelses-app (såsom Authy eller Google Autenticator) til at generere tidsbaserede bekræftelseskoder.", "description": "'Authy' and 'Google Authenticator' are product names and should not be translated." }, "yubiKeyTitle": { - "message": "YubiKey OTP sikkerhedsnøgle" + "message": "YubiKey OTP-sikkerhedsnøgle" }, "yubiKeyDesc": { - "message": "Brug en YubiKey til at få adgang til din konto. Virker med YubiKey 4, 4 Nano, 4C og NEO enheder." + "message": "Brug en YubiKey for at tilgå din konto. Fungerer med YubiKey 4-, 4 Nano-, 4C- samt NEO-enheder." }, "duoDesc": { - "message": "Bekræft med Duo sikkerhed ved hjælp af Duo Mobile app, SMS, telefonopkald eller U2F sikkerhedsnøgle.", + "message": "Bekræft med Duo Security vha. Duo Mobile-app, SMS, telefonopkald eller U2F-sikkerhedsnøgle.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, "duoOrganizationDesc": { - "message": "Bekræft med Duo Security for din organisation ved hjælp af Duo Mobile app, SMS, telefonopkald eller U2F-sikkerhedsnøgle.", + "message": "Bekræft med Duo Security for din organisation vha. Duo Mobile-app, SMS, telefonopkald eller U2F-sikkerhedsnøgle.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, "webAuthnTitle": { "message": "FIDO2 WebAuthn" }, "webAuthnDesc": { - "message": "Brug en hvilken som helst WebAuthn-aktiveret sikkerhedsnøgle til at få adgang til din konto." + "message": "Brug enhver type WebAuthn-kompatibel sikkerhedsnøgle for at tilgå din konto." }, "emailTitle": { "message": "E-mail" @@ -662,49 +662,49 @@ "message": "Login utilgængelig" }, "noTwoStepProviders": { - "message": "Denne konto har to-trins login aktiveret, men ingen af de konfigurerede to-trinsudbydere understøttes af denne enhed." + "message": "Denne konto har totrins-login aktiveret, men ingen af de opsatte totrinsudbydere understøttes af denne enhed." }, "noTwoStepProviders2": { - "message": "Tilføj venligst yderligere udbydere, der understøttes bedre på tværs af enheder (såsom en autentificeringsapp)." + "message": "Tilføj yderligere udbydere, som er bedre understøttet på tværs af enheder (såsom en godkendelses-app)." }, "twoStepOptions": { - "message": "To-trins-login indstillinger" + "message": "Totrins-login indstillinger" }, "selfHostedEnvironment": { "message": "Selv-hostet miljø" }, "selfHostedEnvironmentFooter": { - "message": "Angiv grund-URL'en i din lokal-hostede Bitwarden-installation." + "message": "Angiv grund-URL'en til den lokal-hostede Bitwarden-installation." }, "customEnvironment": { - "message": "Brugerdefineret miljø" + "message": "Tilpasset miljø" }, "customEnvironmentFooter": { - "message": "Til avancerede brugere. Du kan angive grund URL'en for hver tjeneste uafhængigt." + "message": "Til avancerede brugere. Hver tjenestes basis-URL kan angives uafhængigt." }, "baseUrl": { - "message": "Server URL" + "message": "Server-URL" }, "apiUrl": { - "message": "API server URL" + "message": "API-server URL" }, "webVaultUrl": { - "message": "Web-boks server URL" + "message": "Web-boks server-URL" }, "identityUrl": { - "message": "Identitetsserver URL" + "message": "Identitetsserver-URL" }, "notificationsUrl": { - "message": "Meddelelsesserver URL" + "message": "Notifikationsserver-URL" }, "iconsUrl": { - "message": "Ikonserver URL" + "message": "Ikonserver-URL" }, "environmentSaved": { - "message": "Miljøets URLs er blevet gemt." + "message": "Miljø-URL'er gemt" }, "ok": { - "message": "Ok" + "message": "OK" }, "yes": { "message": "Ja" @@ -716,7 +716,7 @@ "message": "Overskriv adgangskode" }, "learnMore": { - "message": "Lær mere" + "message": "Læs mere" }, "featureUnavailable": { "message": "Funktion utilgængelig" @@ -725,22 +725,22 @@ "message": "Logget ud" }, "loginExpired": { - "message": "Din login-session er udløbet." + "message": "Loginsessionen er udløbet." }, "logOutConfirmation": { - "message": "Er du sikker på, at du vil logge ud?" + "message": "Sikker på, at du vil logge ud?" }, "logOut": { "message": "Log ud" }, "addNewLogin": { - "message": "Tilføj nyt login" + "message": "Nyt login" }, "addNewItem": { - "message": "Tilføj nyt element" + "message": "Nyt emne" }, "addNewFolder": { - "message": "Tilføj ny mappe" + "message": "Ny mappe" }, "view": { "message": "Vis" @@ -773,13 +773,13 @@ "message": "Følg os" }, "syncVault": { - "message": "Synkronisér boks" + "message": "Synk boks" }, "changeMasterPass": { "message": "Skift hovedadgangskode" }, "changeMasterPasswordConfirmation": { - "message": "Du kan ændre din hovedadgangskode i bitwarden.com web-boksen. Vil du besøge hjemmesiden nu?" + "message": "Man kan ændre sin hovedadgangskode via bitwarden.com web-boksen. Besøg webstedet nu?" }, "fingerprintPhrase": { "message": "Fingeraftrykssætning", @@ -793,22 +793,22 @@ "message": "Gå til web-boks" }, "getMobileApp": { - "message": "Hent mobilapp" + "message": "Hent mobil-app" }, "getBrowserExtension": { "message": "Hent browserudvidelse" }, "syncingComplete": { - "message": "Synkronisering fuldført" + "message": "Synkning fuldført" }, "syncingFailed": { - "message": "Synkronisering mislykkedes" + "message": "Synkning mislykkedes" }, "yourVaultIsLocked": { "message": "Din boks er låst. Bekræft din identitet for at fortsætte." }, "unlock": { - "message": "Lås op" + "message": "Oplås" }, "loggedInAsOn": { "message": "Logget ind som $EMAIL$ på $HOSTNAME$.", @@ -827,10 +827,10 @@ "message": "Ugyldig hovedadgangskode" }, "twoStepLoginConfirmation": { - "message": "To-trins login gør din konto mere sikker ved at kræve, at du verificerer dit login med en anden enhed, med en sikkerhedsnøgle, autentificerings app, SMS, telefonopkald eller email. To-trins login kan aktiveres i bitwarden.com web-boksen. Vil du besøge hjemmesiden nu?" + "message": "Totrins-login gør kontoen mere sikker ved at kræve, at man bekræfter sit login med en anden enhed, såsom en sikkerhedsnøgle, godkendelses-app, SMS, telefonopkald eller e-mail. Totrins-login kan aktiveres via bitwarden.com web-boksen. Besøg webstedet nu?" }, "twoStepLogin": { - "message": "To-trins login" + "message": "Totrins-login" }, "vaultTimeout": { "message": "Boks timeout" @@ -894,7 +894,7 @@ "description": "Clipboard is the operating system thing where you copy/paste data to on your device." }, "clearClipboardDesc": { - "message": "Fjern automatisk kopierede data fra din udklipsholder.", + "message": "Ryd automatisk kopierede data fra udklipsholderen.", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." }, "enableFavicon": { @@ -904,64 +904,64 @@ "message": "Vis et genkendeligt billede ud for hvert login." }, "enableMinToTray": { - "message": "Minimer til bakkeikon" + "message": "Minimér som bakkeikon" }, "enableMinToTrayDesc": { - "message": "Når vinduet minimeres, vis i stedet et ikon i meddelelsesområdet." + "message": "Når vinduet minimeres, vis i stedet et ikon i systembakken." }, "enableMinToMenuBar": { - "message": "Minimér til menulinjen" + "message": "Minimér til menubjælke" }, "enableMinToMenuBarDesc": { - "message": "Når vinduet minimeres, vis i stedet et ikon på menulinjen." + "message": "Når vinduet minimeres, vis i stedet et ikon på menubjælken." }, "enableCloseToTray": { - "message": "Luk ned til ikon i meddelelsesområdet" + "message": "Luk ned til bakkeikon" }, "enableCloseToTrayDesc": { - "message": "Når vinduet lukkes, vis i stedet et ikon i meddelelsesområdet." + "message": "Når vinduet lukkes, vis i stedet et ikon i systembakken." }, "enableCloseToMenuBar": { - "message": "Luk til menulinjen" + "message": "Luk til menubjælke" }, "enableCloseToMenuBarDesc": { - "message": "Når vinduet lukkes, vis i stedet et ikon på menulinjen." + "message": "Når vinduet lukkes, vis i stedet et ikon på menubjælken." }, "enableTray": { - "message": "Aktivér bakkeikon" + "message": "Vis bakkeikon" }, "enableTrayDesc": { - "message": "Vis altid et ikon i meddelelsesområdet." + "message": "Vis altid et ikon i systembakken." }, "startToTray": { - "message": "Start som ikon i meddelelsesområdet" + "message": "Start som bakkeikon" }, "startToTrayDesc": { - "message": "Når applikationen startes første gang, skal der kun vises et ikon i meddelelsesområdet." + "message": "Når applikationen startes første gang, vis kun et ikon i systembakken." }, "startToMenuBar": { - "message": "Start på menulinjen" + "message": "Start på menubjælke" }, "startToMenuBarDesc": { - "message": "Når applikationen startes første gang, skal der kun vises et ikon på menulinjen." + "message": "Når applikationen startes første gang, vis kun et ikon på menubjælken." }, "openAtLogin": { "message": "Start automatisk ved login" }, "openAtLoginDesc": { - "message": "Start Bitwarden skrivebordsapplikationen automatisk ved login." + "message": "Start automatisk Bitwarden-computerapplikationen ved login." }, "alwaysShowDock": { "message": "Vis altid i Dock" }, "alwaysShowDockDesc": { - "message": "Vis Bitwarden-ikonet i Dock, selv når det er minimeret til menulinjen." + "message": "Vis Bitwarden-ikonet i Dock, selv når minimeret til menubjælken." }, "confirmTrayTitle": { - "message": "Bekræft deaktivering af bakke" + "message": "Bekræft skjulning af bakke" }, "confirmTrayDesc": { - "message": "Deaktivering af denne indstilling vil også deaktivere alle andre indstillinger relateret til bakken." + "message": "Deaktivering af denne indstilling deaktiverer alle øvrige indstillinger relateret til bakken." }, "language": { "message": "Sprog" @@ -976,11 +976,11 @@ "message": "Skift applikationens farvetema." }, "dark": { - "message": "Mørk", + "message": "Mørkt", "description": "Dark color" }, "light": { - "message": "Lys", + "message": "Lyst", "description": "Light color" }, "copy": { @@ -1003,7 +1003,7 @@ "message": "Genstart for at opdatere" }, "restartToUpdateDesc": { - "message": "Version $VERSION_NUM$ er klar til at installere. Du skal genstarte applikationen for at færdiggøre installationen. Vil du genstarte og opdatere nu?", + "message": "Version $VERSION_NUM$ er klar til installation. Applikationen skal genstartes for at færdiggøre installationen. Genstart og opdatér nu?", "placeholders": { "version_num": { "content": "$1", @@ -1015,7 +1015,7 @@ "message": "Opdatering tilgængelig" }, "updateAvailableDesc": { - "message": "En opdatering blev fundet. Vil du hente den nu?" + "message": "En opdatering er fundet. Download den nu?" }, "restart": { "message": "Genstart" @@ -1024,7 +1024,7 @@ "message": "Senere" }, "noUpdatesAvailable": { - "message": "Ingen opdateringer er tilgængelige i øjeblikket. Du anvender den nyeste version." + "message": "Ingen opdateringer tilgængelige pt. Seneste version anvendes." }, "updateError": { "message": "Opdateringsfejl" @@ -1040,7 +1040,7 @@ "description": "Copy credit card number" }, "copySecurityCode": { - "message": "Kopiér kortverifikationskode", + "message": "Kopiér bekræftelseskode", "description": "Copy credit card security code (CVV)" }, "premiumMembership": { @@ -1050,49 +1050,49 @@ "message": "Håndtér medlemsskab" }, "premiumManageAlert": { - "message": "Du kan håndtere dit medlemskab i bitwarden.com web-boksen. Vil du besøge hjemmesiden nu?" + "message": "Medlemskab kan håndteres via bitwarden.com web-boksen. Besøg webstedet nu?" }, "premiumRefresh": { "message": "Opdatér medlemskab" }, "premiumNotCurrentMember": { - "message": "Du er i øjeblikket ikke premium-medlem." + "message": "Du er ikke pt. Premium-medlem." }, "premiumSignUpAndGet": { - "message": "Tilmeld dig et premium medlemskab og få:" + "message": "Tilmeld dig et Premium-medlemskab og få:" }, "premiumSignUpStorage": { - "message": "1 GB krypteret lager til vedhæftede filer." + "message": "1 GB krypteret lagerplads til filvedhæftninger." }, "premiumSignUpTwoStep": { - "message": "Yderligere to-trins-loginmuligheder såsom YubiKey, FIDO U2F og Duo." + "message": "Yderligere totrins-loginmuligheder, såsom YubiKey, FIDO U2F og Duo." }, "premiumSignUpReports": { "message": "Adgangskodehygiejne, kontosundhed og rapporter om datalæk til at holde din boks sikker." }, "premiumSignUpTotp": { - "message": "TOTP verifikationskode (2FA) generator til logins i din boks." + "message": "TOTP bekræftelseskode (2FA) generator til logins i din boks." }, "premiumSignUpSupport": { "message": "Prioriteret kundeservice." }, "premiumSignUpFuture": { - "message": "Alle fremtidige premium-funktioner. Mere kommer snart!" + "message": "Alle fremtidige Premium-funktioner. Flere kommer snart!" }, "premiumPurchase": { - "message": "Køb premium" + "message": "Køb Premium" }, "premiumPurchaseAlert": { - "message": "Du kan købe premium-medlemskab i bitwarden.com web-boksen. Vil du besøge hjemmesiden nu?" + "message": "Premium-medlemskab kan købes via bitwarden.com web-boksen. Besøg webstedet nu?" }, "premiumCurrentMember": { - "message": "Du er premium-medlem!" + "message": "Du er Premium-medlem!" }, "premiumCurrentMemberThanks": { - "message": "Tak fordi du støtter Bitwarden." + "message": "Tak for støtten til Bitwarden." }, "premiumPrice": { - "message": "Alt dette for kun $PRICE$ /år!", + "message": "Alt dette for kun $PRICE$/år!", "placeholders": { "price": { "content": "$1", @@ -1140,7 +1140,7 @@ "message": "Nulstil zoom" }, "toggleFullScreen": { - "message": "Aktivér/deaktivér fuld skærm" + "message": "Fuldskærm til/fra" }, "reload": { "message": "Genindlæs" @@ -1194,10 +1194,10 @@ "message": "Vindue" }, "checkPassword": { - "message": "Undersøg om adgangskoden er blevet afsløret." + "message": "Tjek om adgangskode er kompromitteret." }, "passwordExposed": { - "message": "Denne adgangskode er blevet afsløret $VALUE$ gang(e) i datalæk. Du burde skifte den.", + "message": "Denne adgangskode er kompromitteret $VALUE$ gang(e) i datalæk og bør straks skiftes.", "placeholders": { "value": { "content": "$1", @@ -1206,10 +1206,10 @@ } }, "passwordSafe": { - "message": "Denne adgangskode er ikke fundet i nogen kendte datalæk. Den burde være sikker at bruge." + "message": "Denne adgangskode er ikke fundet i nogen kendte datalæk og bør derfor være sikker at bruge." }, "baseDomain": { - "message": "Grund-domæne", + "message": "Basisdomæne", "description": "Domain name. Ex. website.com" }, "domainName": { @@ -1239,7 +1239,7 @@ "description": "Default URI match detection for auto-fill." }, "toggleOptions": { - "message": "Skift indstillinger" + "message": "Slå indstillinger til/fra" }, "organization": { "message": "Organisation", @@ -1252,7 +1252,7 @@ "message": "Afslut" }, "showHide": { - "message": "Vis / skjul", + "message": "Vis/skjul", "description": "Text for a button that toggles the visibility of the window. Shows the window when it is hidden or hides the window if it is currently open." }, "hideToTray": { @@ -1281,7 +1281,7 @@ "message": "Filformat" }, "hCaptchaUrl": { - "message": "hCaptcha URL", + "message": "hCaptcha-URL", "description": "hCaptcha is the name of a website, should not be translated" }, "loadAccessibilityCookie": { @@ -1292,14 +1292,14 @@ "description": "ex. Register as an accessibility user at hcaptcha.com" }, "copyPasteLink": { - "message": "Kopiér og indsæt linket, der er sendt til din e-mail nedenfor" + "message": "Kopiér og indsæt linket, der er sendt til din e-mail, nedenfor" }, "enterhCaptchaUrl": { "message": "Indtast URL for at indlæse tilgængelighedscookie til hCaptcha", "description": "hCaptcha is the name of a website, should not be translated" }, "hCaptchaUrlRequired": { - "message": "hCaptcha-URL er påkrævet", + "message": "hCaptcha-URL er obligatorisk", "description": "hCaptcha is the name of a website, should not be translated" }, "invalidUrl": { @@ -1322,16 +1322,16 @@ "message": "Bekræft eksport af boks" }, "exportWarningDesc": { - "message": "Denne eksport indeholder dine boksdata i ukrypteret form. Du bør ikke gemme eller sende den eksporterede fil over usikre kanaler (f.eks. e-mail). Slet den straks efter at du er færdig med at bruge den." + "message": "Denne eksport indeholder dine boksdata i ukrypteret form. Den eksporterede fil bør ikke gemmes eller sendes via usikre kanaler (såsom e-mail). Slet den straks efter du er færdig med at bruge den." }, "encExportKeyWarningDesc": { - "message": "Denne eksport krypterer dine data vha. din kontos krypteringsnøgle. Roterer du på et tidspunkt denne kontokrypteringsnøgle, skal du eksportere igen, da du ikke vil kunne dekryptere denne eksportfil." + "message": "Denne eksport krypterer dine data vha. din kontos krypteringsnøgle. Roteres kontokrypteringsnøglen på et tidspunkt, skal der eksportes igen, da du ikke vil kunne dekryptere denne eksportfil." }, "encExportAccountWarningDesc": { - "message": "Kontokrypteringsnøgler er unikke for hver Bitwarden-brugerkonto, så du kan ikke importere en krypteret eksport til en anden konto." + "message": "Kontokrypteringsnøgler er unikke for hver Bitwarden-brugerkonto, så en krypteret eksport kan ikke importeres til en anden konto." }, "noOrganizationsList": { - "message": "Du tilhører ikke nogen organisationer. Organisationer giver dig mulighed for at dele elementer med andre brugere på en sikker måde." + "message": "Du tilhører ikke nogen organisationer. Organisationer muliggør deling af emner med andre brugere på sikker vis." }, "noCollectionsInList": { "message": "Der er ingen samlinger at vise." @@ -1340,7 +1340,7 @@ "message": "Ejerskab" }, "whoOwnsThisItem": { - "message": "Hvem ejer dette element?" + "message": "Hvem ejer dette emne?" }, "strong": { "message": "Stærk", @@ -1358,35 +1358,35 @@ "message": "Svag hovedadgangskode" }, "weakMasterPasswordDesc": { - "message": "Hovedadgangskoden du har valgt er svag. Du skal bruge en stærk hovedadgangskode (eller en adgangssætning) for at beskytte din Bitwarden-konto korrekt. Er du sikker på, at du vil bruge denne hovedadgangskode?" + "message": "Den valgte hovedadgangskode er svag. Der skal bruges en stærk hovedadgangskode (eller adgangssætning) for at beskytte din Bitwarden-konto korrekt. Sikker på, at du vil bruge denne hovedadgangskode?" }, "pin": { - "message": "Pinkode", + "message": "PIN-kode", "description": "PIN code. Ex. The short code (often numeric) that you use to unlock a device." }, "unlockWithPin": { - "message": "Lås op med pinkode" + "message": "Oplås med PIN-kode" }, "setYourPinCode": { - "message": "Indstil din pinkode til at låse Bitwarden op. Dine pin-indstillinger nulstilles, hvis du nogensinde logger helt ud af programmet." + "message": "Opsæt PIN-kode til oplåsning af Bitwarden. PIN-indstillinger nulstilles, hvis du nogensinde logger helt ud af applikationen." }, "pinRequired": { - "message": "Pinkode er påkrævet." + "message": "PIN-kode kræves." }, "invalidPin": { - "message": "Ugyldig pinkode." + "message": "Ugyldig PIN-kode." }, "unlockWithWindowsHello": { - "message": "Lås op med Windows Hello" + "message": "Oplås med Windows Hello" }, "windowsHelloConsentMessage": { "message": "Bekræft for Bitwarden." }, "unlockWithTouchId": { - "message": "Lås op med Touch ID" + "message": "Oplås med Touch ID" }, "touchIdConsentMessage": { - "message": "lås din boks op" + "message": "oplås din boks" }, "autoPromptWindowsHello": { "message": "Bed om Windows Hello ved start" @@ -1404,7 +1404,7 @@ "message": "Fortsæt nedenfor for at slette din konto og alle boks-data." }, "deleteAccountWarning": { - "message": "Sletning af din konto er permanent. Det kan ikke fortrydes." + "message": "Sletning af din konto er permanent og irreversibel." }, "accountDeleted": { "message": "Konto slettet" @@ -1413,41 +1413,41 @@ "message": "Din konto er blevet lukket, og alle tilknyttede data er blevet slettet." }, "preferences": { - "message": "Indstillinger" + "message": "Præferencer" }, "enableMenuBar": { - "message": "Aktivér menulinjeikon" + "message": "Vis menubjælkeikon" }, "enableMenuBarDesc": { - "message": "Vis altid et ikon på menulinjen." + "message": "Vis altid et ikon på menubjælken." }, "hideToMenuBar": { - "message": "Skjul på menulinjen" + "message": "Skjul til menubjælke" }, "selectOneCollection": { - "message": "Du skal vælge minimum én samling." + "message": "Minimum én samling skal vælges." }, "premiumUpdated": { - "message": "Du har opgraderet til premium." + "message": "Du har opgraderet til Premium." }, "restore": { "message": "Gendan" }, "premiumManageAlertAppStore": { - "message": "Du kan administrere dit abonnement fra app butikken. Vil du besøge app butikken nu?" + "message": "Du kan håndtere abonnementet fra App Store. Besøge App Store nu?" }, "legal": { "message": "Juridisk", "description": "Noun. As in 'legal documents', like our terms of service and privacy policy." }, "termsOfService": { - "message": "Servicevilkår" + "message": "Tjenestevilkår" }, "privacyPolicy": { "message": "Fortrolighedspolitik" }, "unsavedChangesConfirmation": { - "message": "Er du sikker på, at du vil lukke? Hvis du lukker nu, gemmes dine nuværende oplysninger ikke." + "message": "Sikker på, at du vil lukke? Lukkes nu, gemmes dine nuværende oplysninger ikke." }, "unsavedChangesTitle": { "message": "Ikke-gemte ændringer" @@ -1479,34 +1479,34 @@ "message": "Søg i papirkurven" }, "permanentlyDeleteItem": { - "message": "Slet element permanent" + "message": "Slet emne permanent" }, "permanentlyDeleteItemConfirmation": { - "message": "Er du sikker på, at du vil slette dette element permanent?" + "message": "Sikker på, at du vil slette dette emne permanent?" }, "permanentlyDeletedItem": { - "message": "Element slettet permanent" + "message": "Emne slettet permanent" }, "restoreItem": { - "message": "Gendan element" + "message": "Gendan emne" }, "restoreItemConfirmation": { - "message": "Er du sikker på, at du vil gendanne dette element?" + "message": "Sikker på, at du vil gendanne dette emne?" }, "restoredItem": { - "message": "Element gendannet" + "message": "Emne gendannet" }, "permanentlyDelete": { "message": "Slette permanent" }, "vaultTimeoutLogOutConfirmation": { - "message": "Ved at logge ud fjernes al adgang til din boks og kræver online-godkendelse efter timeout-perioden. Er du sikker på, at du vil bruge denne indstilling?" + "message": "Udlogning fjerner al adgang til din boks og vil kræve online-godkendelse efter timeout-perioden. Sikker på, at du vil bruge denne indstilling?" }, "vaultTimeoutLogOutConfirmationTitle": { "message": "Bekræft timeout-handling" }, "enterpriseSingleSignOn": { - "message": "Virksomheds Single Sign On" + "message": "Virksomheds Single Sign-On" }, "setMasterPassword": { "message": "Indstil hovedadgangskode" @@ -1542,16 +1542,16 @@ } }, "policyInEffectUppercase": { - "message": "Indeholder ét eller flere store bogstaver" + "message": "Indeholder én eller flere majuskler" }, "policyInEffectLowercase": { - "message": "Indeholder ét eller flere små bogstaver" + "message": "Indeholder én eller flere minuskler" }, "policyInEffectNumbers": { "message": "Indeholder ét eller flere cifre" }, "policyInEffectSpecial": { - "message": "Indeholder ét eller flere af følgende specialtegn $CHARS$", + "message": "Indeholder ét eller flere af flg. specialtegn $CHARS$", "placeholders": { "chars": { "content": "$1", @@ -1563,10 +1563,10 @@ "message": "Din nye hovedadgangskode opfylder ikke politikkravene." }, "acceptPolicies": { - "message": "Ved at markere dette felt accepterer du følgende:" + "message": "Ved at afkrydse dette felt accepterer du flg.:" }, "acceptPoliciesRequired": { - "message": "Tjenestevilkår og fortrolighedspolitik er ikke blevet accepteret." + "message": "Tjenestevilkår og Fortrolighedspolitik er ikke blevet accepteret." }, "enableBrowserIntegration": { "message": "Tillad browserintegration" @@ -1575,37 +1575,37 @@ "message": "Bruges til biometri i browser." }, "enableDuckDuckGoBrowserIntegration": { - "message": "Tillad DuckDuckGo browserintegration" + "message": "Tillad DuckDuckGo-browserintegration" }, "enableDuckDuckGoBrowserIntegrationDesc": { - "message": "Brug din Bitwarden-boks, når du browser med DuckDuckGo." + "message": "Brug din Bitwarden-boks, når der surfes med DuckDuckGo." }, "browserIntegrationUnsupportedTitle": { "message": "Browserintegration understøttes ikke" }, "browserIntegrationMasOnlyDesc": { - "message": "Desværre understøttes browserintegration kun i Mac App Store-versionen indtil videre." + "message": "Desværre understøttes browserintegration indtil videre kun i Mac App Store-versionen." }, "browserIntegrationWindowsStoreDesc": { - "message": "Desværre understøttes browserintegration i øjeblikket ikke i Windows Store-versionen." + "message": "Desværre understøttes browserintegration pt. ikke i Microsoft Store-versionen." }, "browserIntegrationLinuxDesc": { - "message": "Desværre understøttes browserintegration i øjeblikket ikke i linux-versionen." + "message": "Desværre understøttes browserintegration pt. ikke i Linux-versionen." }, "enableBrowserIntegrationFingerprint": { - "message": "Kræv verifikation for browserintegration" + "message": "Kræv bekræftelse for browserintegration" }, "enableBrowserIntegrationFingerprintDesc": { - "message": "Tilføj et ekstra sikkerhedslag ved at kræve bekræftelse af fingeraftrykssætning, når du opretter forbindelse mellem dit skrivebord og din browser. Dette kræver brugerhandling og verifikation, hver gang en forbindelse oprettes." + "message": "Tilføj et ekstra sikkerhedslag ved at kræve bekræftelse af fingeraftrykssætning, når der oprettes forbindelse mellem din computer og din browser. Dette kræver brugerhandling og bekræftelse, hver gang der forbindelse oprettes." }, "approve": { "message": "Godkend" }, "verifyBrowserTitle": { - "message": "Verificér browserforbindelse" + "message": "Bekræft browserforbindelse" }, "verifyBrowserDesc": { - "message": "Sørg for, at det viste fingeraftryk er identisk med det fingeraftryk, der vises i browserudvidelsen." + "message": "Sørg for, at det viste fingeraftryk er identisk med det i browserudvidelsen viste fingeraftryk." }, "verifyNativeMessagingConnectionTitle": { "message": "$APPID$ ønsker at oprette forbindelse til Bitwarden", @@ -1617,22 +1617,22 @@ } }, "verifyNativeMessagingConnectionDesc": { - "message": "Vil du godkende denne anmodning?" + "message": "Godkend denne anmodning?" }, "verifyNativeMessagingConnectionWarning": { - "message": "Hvis du ikke indledte denne anmodning, skal du ikke godkende den." + "message": "Har du ikke indledt denne anmodning, så godkend den ikke." }, "biometricsNotEnabledTitle": { - "message": "Biometri ikke aktiveret" + "message": "Biometri ikke opsat" }, "biometricsNotEnabledDesc": { - "message": "Browserbiometri kræver, at desktop-biometri er aktiveret i indstillingerne først." + "message": "Browserbiometri kræver, at computerbiometri er opsat i indstillingerne først." }, "personalOwnershipSubmitError": { - "message": "På grund af en virksomhedspolitik er du begrænset til at gemme elementer i din personlige boks. Skift ejerskabsindstillingen til en organisation, og vælg blandt de tilgængelige samlinger." + "message": "Grundet en virksomhedspolitik forhindres du i at gemme emner i din personlige boks. Skift ejerskabsindstillingen til en organisation, og vælg blandt de tilgængelige samlinger." }, "hintEqualsPassword": { - "message": "Dit adgangskodetip kan ikke være det samme som din adgangskode." + "message": "Adgangskodetip og adgangskoden må ikke være identiske." }, "personalOwnershipPolicyInEffect": { "message": "En organisationspolitik påvirker dine ejerskabsmuligheder." @@ -1684,7 +1684,7 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "currentAccessCount": { - "message": "Aktuelt antal tilgange" + "message": "Aktuelt tilgangsantal" }, "disableSend": { "message": "Deaktivér denne Send, så ingen kan tilgå den.", @@ -1699,11 +1699,11 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendLink": { - "message": "Send link", + "message": "Send-link", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendLinkLabel": { - "message": "Send link", + "message": "Send-link", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "textHiddenByDefault": { @@ -1711,11 +1711,11 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "createdSend": { - "message": "Send oprettet", + "message": "Send tilføjet", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "editedSend": { - "message": "Send opdateret", + "message": "Send gemt", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deletedSend": { @@ -1723,14 +1723,14 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "newPassword": { - "message": "Nyt kodeord" + "message": "Ny adgangskode" }, "whatTypeOfSend": { "message": "Hvilken type Send er dette?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "createSend": { - "message": "Opret Send", + "message": "Ny Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendTextDesc": { @@ -1755,22 +1755,22 @@ "message": "Tilpasset" }, "deleteSendConfirmation": { - "message": "Er du sikker på, at du vil slette denne Send?", + "message": "Sikker på, at du vil slette denne Send?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "copySendLinkToClipboard": { - "message": "Kopiér Send link til udklipsholder", + "message": "Kopiér Send-link til udklipsholder", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "copySendLinkOnSave": { - "message": "Kopier linket for at dele denne Send til min udklipsholder ved gem." + "message": "Kopiér linket for at dele denne Send til udklipsholderen ved gem." }, "sendDisabled": { - "message": "Send deaktiveret", + "message": "Send fjernet", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendDisabledWarning": { - "message": "Du kan grundet en virksomhedspolitik kun slette en eksisterende Send.", + "message": "Grundet en virksomhedspolitik kan kun en eksisterende Send slettes.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "copyLink": { @@ -1779,6 +1779,15 @@ "disabled": { "message": "Deaktiveret" }, + "removePassword": { + "message": "Fjern adgangskode" + }, + "removedPassword": { + "message": "Adgangskode fjernet" + }, + "removePasswordConfirmation": { + "message": "Sikker på, at adgangskoden skal fjernes?" + }, "maxAccessCountReached": { "message": "Maksimalt adgangsantal nået" }, @@ -1810,7 +1819,7 @@ "message": "Bekræftelse af hovedadgangskode" }, "passwordConfirmationDesc": { - "message": "Denne handling er beskyttet. For at fortsætte, indtast venligst din hovedadgangskode igen for at bekræfte din identitet." + "message": "Denne handling er beskyttet. For at fortsætte, så bekræft din identitet ved at angive din hovedadgangskode igen." }, "updatedMasterPassword": { "message": "Hovedadgangskode opdateret" @@ -1819,7 +1828,7 @@ "message": "Opdatér hovedadgangskode" }, "updateMasterPasswordWarning": { - "message": "Dit hovedadgangskode blev for nylig ændret af en administrator i din organisation. For at få adgang til boksen skal du opdatere din hovedadgangskode nu. Hvis du fortsætter, logges du ud af din nuværende session, hvilket kræver, at du logger ind igen. Aktive sessioner på andre enheder kan fortsætte med at være aktive i op til én time." + "message": "Din hovedadgangskode blev for nylig ændret af en organisations-admin. For at tilgå boksen, så opdatér din hovedadgangskode nu. Fortsættes, logges du ud af den nuværende session og vil skulle logger ind igen. Aktive sessioner på andre enheder kan forblive aktive i op til én time." }, "hours": { "message": "Timer" @@ -1828,7 +1837,7 @@ "message": "Minutter" }, "vaultTimeoutPolicyInEffect": { - "message": "Din organisations politikker påvirker din boks-timeout. Maksimalt tilladt boks-timeout er $HOURS$ time(r) og $MINUTES$ minut(ter)", + "message": "Organisationspolitikker påvirker din boks-timeout. Maksimalt tilladt boks-timeout er $HOURS$ time(r) og $MINUTES$ minut(ter)", "placeholders": { "hours": { "content": "$1", @@ -1841,7 +1850,7 @@ } }, "vaultTimeoutTooLarge": { - "message": "Din boks-timeout overskrider de begrænsninger, der er fastsat af din organisation." + "message": "Din boks-timeout overskrider de organisationsbestemte restriktioner." }, "resetPasswordPolicyAutoEnroll": { "message": "Auto-indrullering" @@ -1850,7 +1859,7 @@ "message": "Denne organisation har en virksomhedspolitik, der automatisk tilmelder dig til nulstilling af adgangskode. Tilmelding giver organisationsadministratorer mulighed for at skifte din hovedadgangskode." }, "vaultExportDisabled": { - "message": "Bokseksport deaktiveret" + "message": "Bokseksport fjernet" }, "personalVaultExportPolicyInEffect": { "message": "En eller flere organisationspolitikker forhindrer dig i at eksportere din personlige boks." @@ -1865,7 +1874,7 @@ "message": "Hovedadgangskode fjernet." }, "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ bruger SSO med en selv-hostet nøgleserver. En hovedadgangskode er ikke længere påkrævet for at logge ind for medlemmer af denne organisation.", + "message": "$ORGANIZATION$ bruger SSO med en selv-hostet nøgleserver. Organisationsmedlemmer behøver ikke længere hovedadgangskode for at logge ind.", "placeholders": { "organization": { "content": "$1", @@ -1877,7 +1886,7 @@ "message": "Forlad organisation" }, "leaveOrganizationConfirmation": { - "message": "Er du sikker på, at du vil forlade denne organisation?" + "message": "Sikker på, at du vil forlade denne organisation?" }, "leftOrganization": { "message": "Du har forladt organisationen." @@ -1889,19 +1898,19 @@ "message": "Lås alle bokse" }, "accountLimitReached": { - "message": "Der må ikke være logget på mere end 5 konti på samme tid." + "message": "Maksimalt 5 konti kan være logget ind samtidigt." }, "accountPreferences": { - "message": "Indstillinger" + "message": "Præferencer" }, "appPreferences": { - "message": "Appindstillinger (alle konti)" + "message": "App-indstillinger (alle konti)" }, "accountSwitcherLimitReached": { - "message": "Kontogrænsen er nået. Log ud af en konto for at tilføje en anden." + "message": "Kontokvote nået. Log ud af en konto for at tilføje en anden." }, "settingsTitle": { - "message": "Appindstillinger for $EMAIL$", + "message": "App-indstillinger for $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -1916,13 +1925,13 @@ "message": "Indstillinger" }, "sessionTimeout": { - "message": "Din session er udløbet. Gå tilbage og prøv at logge ind igen." + "message": "Session udløbet. Gå tilbage og prøv at logge ind igen." }, "exportingPersonalVaultTitle": { - "message": "Eksporterer personlig boks" + "message": "Eksporterer individuel boks" }, "exportingPersonalVaultDescription": { - "message": "Kun de personlige bokselementer tilknyttet $EMAIL$ eksporteres. Organisationsbokseelementer medtages ikke.", + "message": "Kun de individuelle boksemner tilknyttet $EMAIL$ eksporteres. Organisationsboksemner medtages ikke.", "placeholders": { "email": { "content": "$1", @@ -1934,13 +1943,13 @@ "message": "Låst" }, "unlocked": { - "message": "Låst op" + "message": "Oplåst" }, "generator": { "message": "Generator" }, "whatWouldYouLikeToGenerate": { - "message": "Hvad vil du gerne generere?" + "message": "Hvad ønskes genereret?" }, "passwordType": { "message": "Adgangskodetype" @@ -1965,7 +1974,7 @@ "message": "Fang-alle e-mail" }, "catchallEmailDesc": { - "message": "Brug dit domænes konfigurerede fang-alle-indbakke." + "message": "Brug domænets opsatte fang-alle indbakke." }, "random": { "message": "Tilfældig" @@ -1974,7 +1983,7 @@ "message": "Tilfældigt ord" }, "websiteName": { - "message": "Hjemmeside navn" + "message": "Webstedsnavn" }, "service": { "message": "Tjeneste" @@ -2008,13 +2017,13 @@ "message": "Premium-abonnement kræves" }, "organizationIsDisabled": { - "message": "Organisationen er deaktiveret." + "message": "Organisationen suspenderet" }, "disabledOrganizationFilterError": { - "message": "Elementer i deaktiverede organisationer kan ikke tilgås. Kontakt din organisationsejer for at få hjælp." + "message": "Emner i suspenderede organisationer kan ikke tilgås. Kontakt organisationsejeren for hjælp." }, "neverLockWarning": { - "message": "Er du sikker på, at du vil bruge indstillingen \"Aldrig\"? Hvis du sætter dine låseindstillinger til \"Aldrig\" gemmes din boks krypteringsnøgle på din enhed. Hvis du bruger denne indstilling, skal du sikre dig, at du holder din enhed ordentligt beskyttet." + "message": "Sikker på, at indstillingen \"Aldrig\" skal bruges? Sættes låseindstillinger til \"Aldrig\", gemmes din bokskrypteringsnøgle på enheden. Bruges denne indstilling, så sørg for at holde din enhed ordentligt beskyttet." }, "cardBrandMir": { "message": "Mir" diff --git a/apps/desktop/src/locales/de/messages.json b/apps/desktop/src/locales/de/messages.json index 3b8cee65475..f815f0183d8 100644 --- a/apps/desktop/src/locales/de/messages.json +++ b/apps/desktop/src/locales/de/messages.json @@ -1779,6 +1779,15 @@ "disabled": { "message": "Deaktiviert" }, + "removePassword": { + "message": "Passwort entfernen" + }, + "removedPassword": { + "message": "Passwort entfernt" + }, + "removePasswordConfirmation": { + "message": "Bist du sicher, dass du das Passwort entfernen möchtest?" + }, "maxAccessCountReached": { "message": "Maximale Zugriffsanzahl erreicht" }, diff --git a/apps/desktop/src/locales/el/messages.json b/apps/desktop/src/locales/el/messages.json index 449f9871685..dd09aeb4c3e 100644 --- a/apps/desktop/src/locales/el/messages.json +++ b/apps/desktop/src/locales/el/messages.json @@ -1779,6 +1779,15 @@ "disabled": { "message": "Απενεργοποιημένο" }, + "removePassword": { + "message": "Remove password" + }, + "removedPassword": { + "message": "Password removed" + }, + "removePasswordConfirmation": { + "message": "Are you sure you want to remove the password?" + }, "maxAccessCountReached": { "message": "Φτάσατε στον μέγιστο αριθμό πρόσβασης" }, diff --git a/apps/desktop/src/locales/en_GB/messages.json b/apps/desktop/src/locales/en_GB/messages.json index 084e90d0048..c57c32ec3ba 100644 --- a/apps/desktop/src/locales/en_GB/messages.json +++ b/apps/desktop/src/locales/en_GB/messages.json @@ -1779,6 +1779,15 @@ "disabled": { "message": "Disabled" }, + "removePassword": { + "message": "Remove password" + }, + "removedPassword": { + "message": "Password removed" + }, + "removePasswordConfirmation": { + "message": "Are you sure you want to remove the password?" + }, "maxAccessCountReached": { "message": "Max access count reached" }, diff --git a/apps/desktop/src/locales/en_IN/messages.json b/apps/desktop/src/locales/en_IN/messages.json index 20a1fc3930e..f595db5a73b 100644 --- a/apps/desktop/src/locales/en_IN/messages.json +++ b/apps/desktop/src/locales/en_IN/messages.json @@ -1779,6 +1779,15 @@ "disabled": { "message": "Disabled" }, + "removePassword": { + "message": "Remove password" + }, + "removedPassword": { + "message": "Password removed" + }, + "removePasswordConfirmation": { + "message": "Are you sure you want to remove the password?" + }, "maxAccessCountReached": { "message": "Max access count reached" }, diff --git a/apps/desktop/src/locales/eo/messages.json b/apps/desktop/src/locales/eo/messages.json index 8c665ad12d9..8b3ff691e1f 100644 --- a/apps/desktop/src/locales/eo/messages.json +++ b/apps/desktop/src/locales/eo/messages.json @@ -1779,6 +1779,15 @@ "disabled": { "message": "Disabled" }, + "removePassword": { + "message": "Remove password" + }, + "removedPassword": { + "message": "Password removed" + }, + "removePasswordConfirmation": { + "message": "Are you sure you want to remove the password?" + }, "maxAccessCountReached": { "message": "Max access count reached" }, diff --git a/apps/desktop/src/locales/es/messages.json b/apps/desktop/src/locales/es/messages.json index fa0ef7a3866..9721b815f1d 100644 --- a/apps/desktop/src/locales/es/messages.json +++ b/apps/desktop/src/locales/es/messages.json @@ -1779,6 +1779,15 @@ "disabled": { "message": "Deshabilitado" }, + "removePassword": { + "message": "Eliminar contraseña" + }, + "removedPassword": { + "message": "Contraseña eliminada" + }, + "removePasswordConfirmation": { + "message": "¿Está seguro que desea eliminar la contraseña?" + }, "maxAccessCountReached": { "message": "Se ha alcanzado el número máximo de accesos" }, diff --git a/apps/desktop/src/locales/et/messages.json b/apps/desktop/src/locales/et/messages.json index 1568209bf9f..0bed5c58854 100644 --- a/apps/desktop/src/locales/et/messages.json +++ b/apps/desktop/src/locales/et/messages.json @@ -1779,6 +1779,15 @@ "disabled": { "message": "Keelatud" }, + "removePassword": { + "message": "Eemalda parool" + }, + "removedPassword": { + "message": "Parool on eemaldatud" + }, + "removePasswordConfirmation": { + "message": "Oled kindel, et soovid selle parooli eemaldada?" + }, "maxAccessCountReached": { "message": "Maksimaalne ligipääsude arv on saavutatud" }, diff --git a/apps/desktop/src/locales/eu/messages.json b/apps/desktop/src/locales/eu/messages.json index 8d6452a5015..25f8e32f380 100644 --- a/apps/desktop/src/locales/eu/messages.json +++ b/apps/desktop/src/locales/eu/messages.json @@ -1779,6 +1779,15 @@ "disabled": { "message": "Desgaitua" }, + "removePassword": { + "message": "Remove password" + }, + "removedPassword": { + "message": "Password removed" + }, + "removePasswordConfirmation": { + "message": "Are you sure you want to remove the password?" + }, "maxAccessCountReached": { "message": "Sarbide kopuru maximoa gaindituta" }, diff --git a/apps/desktop/src/locales/fa/messages.json b/apps/desktop/src/locales/fa/messages.json index bbae26fb906..b7906064d66 100644 --- a/apps/desktop/src/locales/fa/messages.json +++ b/apps/desktop/src/locales/fa/messages.json @@ -1779,6 +1779,15 @@ "disabled": { "message": "غیرفعال شد" }, + "removePassword": { + "message": "Remove password" + }, + "removedPassword": { + "message": "Password removed" + }, + "removePasswordConfirmation": { + "message": "Are you sure you want to remove the password?" + }, "maxAccessCountReached": { "message": "به حداکثر تعداد دسترسی رسیده است" }, diff --git a/apps/desktop/src/locales/fi/messages.json b/apps/desktop/src/locales/fi/messages.json index d0a0bd56cf2..c2abc647565 100644 --- a/apps/desktop/src/locales/fi/messages.json +++ b/apps/desktop/src/locales/fi/messages.json @@ -1779,6 +1779,15 @@ "disabled": { "message": "Poistettu käytöstä" }, + "removePassword": { + "message": "Poista salasana" + }, + "removedPassword": { + "message": "Salasana poistettiin" + }, + "removePasswordConfirmation": { + "message": "Haluatko varmasti poistaa salasanan?" + }, "maxAccessCountReached": { "message": "Käyttökertojen enimmäismäärä on saavutettu" }, diff --git a/apps/desktop/src/locales/fil/messages.json b/apps/desktop/src/locales/fil/messages.json index 1e3ed274ccc..5ca479375d5 100644 --- a/apps/desktop/src/locales/fil/messages.json +++ b/apps/desktop/src/locales/fil/messages.json @@ -1779,6 +1779,15 @@ "disabled": { "message": "Disabled" }, + "removePassword": { + "message": "Remove password" + }, + "removedPassword": { + "message": "Password removed" + }, + "removePasswordConfirmation": { + "message": "Are you sure you want to remove the password?" + }, "maxAccessCountReached": { "message": "Max access count reached" }, diff --git a/apps/desktop/src/locales/fr/messages.json b/apps/desktop/src/locales/fr/messages.json index 8601d607135..86b357480e4 100644 --- a/apps/desktop/src/locales/fr/messages.json +++ b/apps/desktop/src/locales/fr/messages.json @@ -1779,6 +1779,15 @@ "disabled": { "message": "Désactivé" }, + "removePassword": { + "message": "Remove password" + }, + "removedPassword": { + "message": "Password removed" + }, + "removePasswordConfirmation": { + "message": "Are you sure you want to remove the password?" + }, "maxAccessCountReached": { "message": "Nombre maximum d'accès atteint" }, diff --git a/apps/desktop/src/locales/he/messages.json b/apps/desktop/src/locales/he/messages.json index 6db87d185bf..1cf3d4afe9f 100644 --- a/apps/desktop/src/locales/he/messages.json +++ b/apps/desktop/src/locales/he/messages.json @@ -1779,6 +1779,15 @@ "disabled": { "message": "מושבת" }, + "removePassword": { + "message": "Remove password" + }, + "removedPassword": { + "message": "Password removed" + }, + "removePasswordConfirmation": { + "message": "Are you sure you want to remove the password?" + }, "maxAccessCountReached": { "message": "מספר הגישות המרבי הושג" }, diff --git a/apps/desktop/src/locales/hi/messages.json b/apps/desktop/src/locales/hi/messages.json index 266fb589e54..480944bc82d 100644 --- a/apps/desktop/src/locales/hi/messages.json +++ b/apps/desktop/src/locales/hi/messages.json @@ -1779,6 +1779,15 @@ "disabled": { "message": "Disabled" }, + "removePassword": { + "message": "Remove password" + }, + "removedPassword": { + "message": "Password removed" + }, + "removePasswordConfirmation": { + "message": "Are you sure you want to remove the password?" + }, "maxAccessCountReached": { "message": "Max access count reached" }, diff --git a/apps/desktop/src/locales/hr/messages.json b/apps/desktop/src/locales/hr/messages.json index 99ae141e59f..5fae487a97e 100644 --- a/apps/desktop/src/locales/hr/messages.json +++ b/apps/desktop/src/locales/hr/messages.json @@ -1779,6 +1779,15 @@ "disabled": { "message": "Onemogućeno" }, + "removePassword": { + "message": "Ukloni lozinku" + }, + "removedPassword": { + "message": "Lozinka uklonjena" + }, + "removePasswordConfirmation": { + "message": "Sigurno želiš ukloniti lozinku?" + }, "maxAccessCountReached": { "message": "Dostignut najveći broj pristupanja" }, diff --git a/apps/desktop/src/locales/hu/messages.json b/apps/desktop/src/locales/hu/messages.json index e71cb0a4524..0b3913e64c9 100644 --- a/apps/desktop/src/locales/hu/messages.json +++ b/apps/desktop/src/locales/hu/messages.json @@ -1779,6 +1779,15 @@ "disabled": { "message": "Letiltva" }, + "removePassword": { + "message": "Jelszó eltávolítása" + }, + "removedPassword": { + "message": "A jelszó eltávolításra került." + }, + "removePasswordConfirmation": { + "message": "Biztosan eltávolításra kerüljön ez a jelszó?" + }, "maxAccessCountReached": { "message": "A maximális hozzáférések száma elérésre került." }, diff --git a/apps/desktop/src/locales/id/messages.json b/apps/desktop/src/locales/id/messages.json index 52ea55bf761..b786add846a 100644 --- a/apps/desktop/src/locales/id/messages.json +++ b/apps/desktop/src/locales/id/messages.json @@ -1779,6 +1779,15 @@ "disabled": { "message": "Dinonaktifkan" }, + "removePassword": { + "message": "Remove password" + }, + "removedPassword": { + "message": "Password removed" + }, + "removePasswordConfirmation": { + "message": "Are you sure you want to remove the password?" + }, "maxAccessCountReached": { "message": "Jumlah akses maksimum tercapai" }, diff --git a/apps/desktop/src/locales/it/messages.json b/apps/desktop/src/locales/it/messages.json index b4f3e40b524..ca1cc90d3fe 100644 --- a/apps/desktop/src/locales/it/messages.json +++ b/apps/desktop/src/locales/it/messages.json @@ -1779,6 +1779,15 @@ "disabled": { "message": "Disabilitato" }, + "removePassword": { + "message": "Rimuovi la password" + }, + "removedPassword": { + "message": "Password rimossa" + }, + "removePasswordConfirmation": { + "message": "Sei sicuro di voler rimuovere la password?" + }, "maxAccessCountReached": { "message": "Numero massimo di accessi raggiunto" }, diff --git a/apps/desktop/src/locales/ja/messages.json b/apps/desktop/src/locales/ja/messages.json index 2bcdf30d0da..4a3e14ee76b 100644 --- a/apps/desktop/src/locales/ja/messages.json +++ b/apps/desktop/src/locales/ja/messages.json @@ -1779,6 +1779,15 @@ "disabled": { "message": "無効" }, + "removePassword": { + "message": "パスワードを削除" + }, + "removedPassword": { + "message": "パスワードを削除しました" + }, + "removePasswordConfirmation": { + "message": "パスワードを削除してもよろしいですか?" + }, "maxAccessCountReached": { "message": "最大アクセス数に達しました" }, diff --git a/apps/desktop/src/locales/ka/messages.json b/apps/desktop/src/locales/ka/messages.json index 1e3ed274ccc..5ca479375d5 100644 --- a/apps/desktop/src/locales/ka/messages.json +++ b/apps/desktop/src/locales/ka/messages.json @@ -1779,6 +1779,15 @@ "disabled": { "message": "Disabled" }, + "removePassword": { + "message": "Remove password" + }, + "removedPassword": { + "message": "Password removed" + }, + "removePasswordConfirmation": { + "message": "Are you sure you want to remove the password?" + }, "maxAccessCountReached": { "message": "Max access count reached" }, diff --git a/apps/desktop/src/locales/km/messages.json b/apps/desktop/src/locales/km/messages.json index 1e3ed274ccc..5ca479375d5 100644 --- a/apps/desktop/src/locales/km/messages.json +++ b/apps/desktop/src/locales/km/messages.json @@ -1779,6 +1779,15 @@ "disabled": { "message": "Disabled" }, + "removePassword": { + "message": "Remove password" + }, + "removedPassword": { + "message": "Password removed" + }, + "removePasswordConfirmation": { + "message": "Are you sure you want to remove the password?" + }, "maxAccessCountReached": { "message": "Max access count reached" }, diff --git a/apps/desktop/src/locales/kn/messages.json b/apps/desktop/src/locales/kn/messages.json index 9be6a61531f..69fb52d03b1 100644 --- a/apps/desktop/src/locales/kn/messages.json +++ b/apps/desktop/src/locales/kn/messages.json @@ -1779,6 +1779,15 @@ "disabled": { "message": "ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ" }, + "removePassword": { + "message": "Remove password" + }, + "removedPassword": { + "message": "Password removed" + }, + "removePasswordConfirmation": { + "message": "Are you sure you want to remove the password?" + }, "maxAccessCountReached": { "message": "ಗರಿಷ್ಠ ಪ್ರವೇಶ ಎಣಿಕೆ ತಲುಪಿದೆ" }, diff --git a/apps/desktop/src/locales/ko/messages.json b/apps/desktop/src/locales/ko/messages.json index d8ee402dc36..add26890398 100644 --- a/apps/desktop/src/locales/ko/messages.json +++ b/apps/desktop/src/locales/ko/messages.json @@ -1779,6 +1779,15 @@ "disabled": { "message": "비활성화됨" }, + "removePassword": { + "message": "Remove password" + }, + "removedPassword": { + "message": "Password removed" + }, + "removePasswordConfirmation": { + "message": "Are you sure you want to remove the password?" + }, "maxAccessCountReached": { "message": "최대 접근 횟수 도달" }, diff --git a/apps/desktop/src/locales/lv/messages.json b/apps/desktop/src/locales/lv/messages.json index 295684eb821..dc4996fd2b6 100644 --- a/apps/desktop/src/locales/lv/messages.json +++ b/apps/desktop/src/locales/lv/messages.json @@ -1779,6 +1779,15 @@ "disabled": { "message": "Atspējots" }, + "removePassword": { + "message": "Noņemt paroli" + }, + "removedPassword": { + "message": "Parole noņemta" + }, + "removePasswordConfirmation": { + "message": "Vai tiešām noņemt paroli?" + }, "maxAccessCountReached": { "message": "Sasniegts lielākais pieļaujamais piekļuvju skaits" }, diff --git a/apps/desktop/src/locales/me/messages.json b/apps/desktop/src/locales/me/messages.json index 6b1e5878487..19b8884194a 100644 --- a/apps/desktop/src/locales/me/messages.json +++ b/apps/desktop/src/locales/me/messages.json @@ -1779,6 +1779,15 @@ "disabled": { "message": "Disabled" }, + "removePassword": { + "message": "Remove password" + }, + "removedPassword": { + "message": "Password removed" + }, + "removePasswordConfirmation": { + "message": "Are you sure you want to remove the password?" + }, "maxAccessCountReached": { "message": "Max access count reached" }, diff --git a/apps/desktop/src/locales/ml/messages.json b/apps/desktop/src/locales/ml/messages.json index 37ad33342b8..85b09fc8f59 100644 --- a/apps/desktop/src/locales/ml/messages.json +++ b/apps/desktop/src/locales/ml/messages.json @@ -1779,6 +1779,15 @@ "disabled": { "message": "Disabled" }, + "removePassword": { + "message": "Remove password" + }, + "removedPassword": { + "message": "Password removed" + }, + "removePasswordConfirmation": { + "message": "Are you sure you want to remove the password?" + }, "maxAccessCountReached": { "message": "Max access count reached" }, diff --git a/apps/desktop/src/locales/nb/messages.json b/apps/desktop/src/locales/nb/messages.json index da7b50c9599..6a56aa9c095 100644 --- a/apps/desktop/src/locales/nb/messages.json +++ b/apps/desktop/src/locales/nb/messages.json @@ -1779,6 +1779,15 @@ "disabled": { "message": "Deaktivert" }, + "removePassword": { + "message": "Remove password" + }, + "removedPassword": { + "message": "Password removed" + }, + "removePasswordConfirmation": { + "message": "Are you sure you want to remove the password?" + }, "maxAccessCountReached": { "message": "Maksimalt antall tilganger nådd" }, diff --git a/apps/desktop/src/locales/nl/messages.json b/apps/desktop/src/locales/nl/messages.json index 38b3a24d418..2f078ce0390 100644 --- a/apps/desktop/src/locales/nl/messages.json +++ b/apps/desktop/src/locales/nl/messages.json @@ -1779,6 +1779,15 @@ "disabled": { "message": "Uitgeschakeld" }, + "removePassword": { + "message": "Wachtwoord verwijderen" + }, + "removedPassword": { + "message": "Wachtwoord verwijderd" + }, + "removePasswordConfirmation": { + "message": "Weet je zeker dat je het wachtwoord wilt verwijderen?" + }, "maxAccessCountReached": { "message": "Maximum aantal keren benaderd" }, diff --git a/apps/desktop/src/locales/nn/messages.json b/apps/desktop/src/locales/nn/messages.json index d0b68b8ec5c..d17f71dc94f 100644 --- a/apps/desktop/src/locales/nn/messages.json +++ b/apps/desktop/src/locales/nn/messages.json @@ -1779,6 +1779,15 @@ "disabled": { "message": "Disabled" }, + "removePassword": { + "message": "Remove password" + }, + "removedPassword": { + "message": "Password removed" + }, + "removePasswordConfirmation": { + "message": "Are you sure you want to remove the password?" + }, "maxAccessCountReached": { "message": "Max access count reached" }, diff --git a/apps/desktop/src/locales/pl/messages.json b/apps/desktop/src/locales/pl/messages.json index 263a2356b3b..bd37196ef7e 100644 --- a/apps/desktop/src/locales/pl/messages.json +++ b/apps/desktop/src/locales/pl/messages.json @@ -1779,6 +1779,15 @@ "disabled": { "message": "Wyłączona" }, + "removePassword": { + "message": "Usuń hasło" + }, + "removedPassword": { + "message": "Hasło zostało usunięte" + }, + "removePasswordConfirmation": { + "message": "Czy na pewno chcesz usunąć hasło?" + }, "maxAccessCountReached": { "message": "Maksymalna liczba dostępów została osiągnięta" }, diff --git a/apps/desktop/src/locales/pt_BR/messages.json b/apps/desktop/src/locales/pt_BR/messages.json index 39554984e43..50cebf294fc 100644 --- a/apps/desktop/src/locales/pt_BR/messages.json +++ b/apps/desktop/src/locales/pt_BR/messages.json @@ -1779,6 +1779,15 @@ "disabled": { "message": "Desativado" }, + "removePassword": { + "message": "Remove password" + }, + "removedPassword": { + "message": "Password removed" + }, + "removePasswordConfirmation": { + "message": "Are you sure you want to remove the password?" + }, "maxAccessCountReached": { "message": "Número máximo de acessos atingido" }, diff --git a/apps/desktop/src/locales/pt_PT/messages.json b/apps/desktop/src/locales/pt_PT/messages.json index 4ef10f4de82..e8062587d57 100644 --- a/apps/desktop/src/locales/pt_PT/messages.json +++ b/apps/desktop/src/locales/pt_PT/messages.json @@ -1779,6 +1779,15 @@ "disabled": { "message": "Desativado" }, + "removePassword": { + "message": "Remover palavra-passe" + }, + "removedPassword": { + "message": "Palavra-passe removida" + }, + "removePasswordConfirmation": { + "message": "Tem a certeza de que deseja remover a palavra-passe?" + }, "maxAccessCountReached": { "message": "Max access count reached" }, diff --git a/apps/desktop/src/locales/ro/messages.json b/apps/desktop/src/locales/ro/messages.json index 005d5ee8200..3a01bc0896d 100644 --- a/apps/desktop/src/locales/ro/messages.json +++ b/apps/desktop/src/locales/ro/messages.json @@ -1779,6 +1779,15 @@ "disabled": { "message": "Dezactivat" }, + "removePassword": { + "message": "Remove password" + }, + "removedPassword": { + "message": "Password removed" + }, + "removePasswordConfirmation": { + "message": "Are you sure you want to remove the password?" + }, "maxAccessCountReached": { "message": "S-a atins numărul maxim de accesări" }, diff --git a/apps/desktop/src/locales/ru/messages.json b/apps/desktop/src/locales/ru/messages.json index b2329fe2cf3..6bca91337f2 100644 --- a/apps/desktop/src/locales/ru/messages.json +++ b/apps/desktop/src/locales/ru/messages.json @@ -671,7 +671,7 @@ "message": "Настройки двухэтапной аутентификации" }, "selfHostedEnvironment": { - "message": "Окружение собственного хостинга" + "message": "Окружение пользовательского хостинга" }, "selfHostedEnvironmentFooter": { "message": "Укажите URL Bitwarden на вашем сервере." @@ -1468,7 +1468,7 @@ "message": "Чтобы получить доступ к хранилищу после выхода из него требуется повторная авторизация." }, "lock": { - "message": "Заблокировать", + "message": "Блокировка", "description": "Verb form: to make secure or inaccesible by" }, "trash": { @@ -1779,6 +1779,15 @@ "disabled": { "message": "Отключено" }, + "removePassword": { + "message": "Удалить пароль" + }, + "removedPassword": { + "message": "Пароль удален" + }, + "removePasswordConfirmation": { + "message": "Вы уверены, что хотите удалить пароль?" + }, "maxAccessCountReached": { "message": "Достигнут максимум обращений" }, diff --git a/apps/desktop/src/locales/si/messages.json b/apps/desktop/src/locales/si/messages.json index c7ceef8ad34..b2c108106a3 100644 --- a/apps/desktop/src/locales/si/messages.json +++ b/apps/desktop/src/locales/si/messages.json @@ -1779,6 +1779,15 @@ "disabled": { "message": "Disabled" }, + "removePassword": { + "message": "Remove password" + }, + "removedPassword": { + "message": "Password removed" + }, + "removePasswordConfirmation": { + "message": "Are you sure you want to remove the password?" + }, "maxAccessCountReached": { "message": "Max access count reached" }, diff --git a/apps/desktop/src/locales/sk/messages.json b/apps/desktop/src/locales/sk/messages.json index d6e3f2509f0..0edf4324977 100644 --- a/apps/desktop/src/locales/sk/messages.json +++ b/apps/desktop/src/locales/sk/messages.json @@ -1779,6 +1779,15 @@ "disabled": { "message": "Zakázané" }, + "removePassword": { + "message": "Odstrániť heslo" + }, + "removedPassword": { + "message": "Heslo bolo odstránené" + }, + "removePasswordConfirmation": { + "message": "Naozaj chcete odstrániť heslo?" + }, "maxAccessCountReached": { "message": "Bol dosiahnutý maximálny počet prístupov" }, diff --git a/apps/desktop/src/locales/sl/messages.json b/apps/desktop/src/locales/sl/messages.json index 928a90498b6..f12c05a33f9 100644 --- a/apps/desktop/src/locales/sl/messages.json +++ b/apps/desktop/src/locales/sl/messages.json @@ -1779,6 +1779,15 @@ "disabled": { "message": "Disabled" }, + "removePassword": { + "message": "Remove password" + }, + "removedPassword": { + "message": "Password removed" + }, + "removePasswordConfirmation": { + "message": "Are you sure you want to remove the password?" + }, "maxAccessCountReached": { "message": "Max access count reached" }, diff --git a/apps/desktop/src/locales/sr/messages.json b/apps/desktop/src/locales/sr/messages.json index 1c26f5b9312..aa9a195435e 100644 --- a/apps/desktop/src/locales/sr/messages.json +++ b/apps/desktop/src/locales/sr/messages.json @@ -1779,6 +1779,15 @@ "disabled": { "message": "Онемогућено" }, + "removePassword": { + "message": "Remove password" + }, + "removedPassword": { + "message": "Password removed" + }, + "removePasswordConfirmation": { + "message": "Are you sure you want to remove the password?" + }, "maxAccessCountReached": { "message": "Достигнут максималан број приступа" }, diff --git a/apps/desktop/src/locales/sv/messages.json b/apps/desktop/src/locales/sv/messages.json index fd93b0e6829..986eb8d6ffc 100644 --- a/apps/desktop/src/locales/sv/messages.json +++ b/apps/desktop/src/locales/sv/messages.json @@ -1779,6 +1779,15 @@ "disabled": { "message": "Inaktiverad" }, + "removePassword": { + "message": "Ta bort lösenord" + }, + "removedPassword": { + "message": "Tog bort lösenord" + }, + "removePasswordConfirmation": { + "message": "Är du säker på att du vill ta bort lösenordet?" + }, "maxAccessCountReached": { "message": "Det maximala antalet åtkomster har uppnåtts" }, diff --git a/apps/desktop/src/locales/th/messages.json b/apps/desktop/src/locales/th/messages.json index 44cefece66c..4b59d9b10ad 100644 --- a/apps/desktop/src/locales/th/messages.json +++ b/apps/desktop/src/locales/th/messages.json @@ -1779,6 +1779,15 @@ "disabled": { "message": "Disabled" }, + "removePassword": { + "message": "Remove password" + }, + "removedPassword": { + "message": "Password removed" + }, + "removePasswordConfirmation": { + "message": "Are you sure you want to remove the password?" + }, "maxAccessCountReached": { "message": "Max access count reached" }, diff --git a/apps/desktop/src/locales/tr/messages.json b/apps/desktop/src/locales/tr/messages.json index 87b6b6f07fc..56d33740dc5 100644 --- a/apps/desktop/src/locales/tr/messages.json +++ b/apps/desktop/src/locales/tr/messages.json @@ -1779,6 +1779,15 @@ "disabled": { "message": "Devre dışı" }, + "removePassword": { + "message": "Parolayı kaldır" + }, + "removedPassword": { + "message": "Parola kaldırıldı" + }, + "removePasswordConfirmation": { + "message": "Parolayı kaldırmak istediğinizden emin misiniz?" + }, "maxAccessCountReached": { "message": "Maksimum erişim sayısına ulaşıldı" }, diff --git a/apps/desktop/src/locales/uk/messages.json b/apps/desktop/src/locales/uk/messages.json index a38af8ea000..8ebcd5c3c27 100644 --- a/apps/desktop/src/locales/uk/messages.json +++ b/apps/desktop/src/locales/uk/messages.json @@ -1779,6 +1779,15 @@ "disabled": { "message": "Вимкнено" }, + "removePassword": { + "message": "Вилучити пароль" + }, + "removedPassword": { + "message": "Пароль вилучено" + }, + "removePasswordConfirmation": { + "message": "Ви дійсно хочете вилучити пароль?" + }, "maxAccessCountReached": { "message": "Досягнуто максимальну кількість доступів" }, diff --git a/apps/desktop/src/locales/vi/messages.json b/apps/desktop/src/locales/vi/messages.json index 0d7244c122d..61737bba670 100644 --- a/apps/desktop/src/locales/vi/messages.json +++ b/apps/desktop/src/locales/vi/messages.json @@ -1779,6 +1779,15 @@ "disabled": { "message": "Đã tắt" }, + "removePassword": { + "message": "Remove password" + }, + "removedPassword": { + "message": "Password removed" + }, + "removePasswordConfirmation": { + "message": "Are you sure you want to remove the password?" + }, "maxAccessCountReached": { "message": "Đã đạt đến số lượng truy cập tối đa" }, diff --git a/apps/desktop/src/locales/zh_CN/messages.json b/apps/desktop/src/locales/zh_CN/messages.json index 0d3c5c1903c..89d31056ae6 100644 --- a/apps/desktop/src/locales/zh_CN/messages.json +++ b/apps/desktop/src/locales/zh_CN/messages.json @@ -1779,6 +1779,15 @@ "disabled": { "message": "已禁用" }, + "removePassword": { + "message": "移除密码" + }, + "removedPassword": { + "message": "密码已移除" + }, + "removePasswordConfirmation": { + "message": "确定要移除此密码吗?" + }, "maxAccessCountReached": { "message": "已达最大访问次数" }, @@ -2035,7 +2044,7 @@ "message": "不是你?" }, "newAroundHere": { - "message": "新建在这里?" + "message": "初来乍到吗?" }, "loggingInTo": { "message": "正在登录到 $DOMAIN$", diff --git a/apps/desktop/src/locales/zh_TW/messages.json b/apps/desktop/src/locales/zh_TW/messages.json index 4eb5624dbd9..e35ec75a926 100644 --- a/apps/desktop/src/locales/zh_TW/messages.json +++ b/apps/desktop/src/locales/zh_TW/messages.json @@ -958,7 +958,7 @@ "message": "即使最小化到選單列,也在 Docker 中顯示 Bitwarden 圖示。" }, "confirmTrayTitle": { - "message": "確認禁用系統匣" + "message": "確認隱藏到系統匣" }, "confirmTrayDesc": { "message": "禁用此設定也將禁用所有其他系統匣相關設定。" @@ -1779,6 +1779,15 @@ "disabled": { "message": "已停用" }, + "removePassword": { + "message": "移除密碼" + }, + "removedPassword": { + "message": "已移除密碼" + }, + "removePasswordConfirmation": { + "message": "您確定要移除密碼嗎?" + }, "maxAccessCountReached": { "message": "已達最大存取次數" }, From dffef8ac1799824a613fec64365a3452f9dfeb0e Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Fri, 9 Dec 2022 11:21:07 +0100 Subject: [PATCH 5/9] SM-310 [] Secrets (#3355) * [SM-63] Secrets List overview (#3239) The purpose of this PR is to create a new component for the Secrets Manager project where all the secrets associated to a specific organization ID can be viewed. * [SM-63] Secrets List overview (#3239) The purpose of this PR is to create a new component for the Secrets Manager project where all the secrets associated to a specific organization ID can be viewed. * [SM-63] Display dates based off Figma (#3358) * Display dates based off Figma * Swapping date to medium format * [SM-185] Use feature flags for secrets (#3409) * Fix SM lint errors (#3526) * Fix SM lint errors * Update bitwarden_license/bit-web/src/app/sm/secrets/secrets.component.ts Co-authored-by: Oscar Hinton Co-authored-by: Oscar Hinton * [SM-65] Create/Edit Secrets Dialog (#3376) The purpose of this PR is to add a Create/Edit Secrets dialog component. * [SM-198] Empty Secrets View (#3585) * SM-198 Empty Secrets View * [SM-64] Soft delete secrets (#3549) * Soft delete secrets * SM-95-ProjectList (#3508) * Adding project list and creating a shared module for secrets * updates to style , temporarily using secrets results until API portion is completed * removing non project related options from the list, updting api call to call projects now * Adding view project option from drop down * Changes requested by Thomas * Changes requested by Thomas * suggested fixes * fixes after merge from master * Adding decrypting to project list * Update bitwarden_license/bit-web/src/app/sm/shared/sm-shared.module.ts Co-authored-by: Oscar Hinton * Update bitwarden_license/bit-web/src/app/sm/projects/project.service.ts Co-authored-by: Oscar Hinton * Update bitwarden_license/bit-web/src/app/sm/projects/project.service.ts Co-authored-by: Oscar Hinton * fix to projectRequest so name is type EncString instead of string * lint + prettier fixes * Oscar's suggestions - Removing this. from projectList * updating to use bitIconButton * Updating to use BitIconButton Co-authored-by: CarleyDiaz-Bitwarden <103955722+CarleyDiaz-Bitwarden@users.noreply.github.com> Co-authored-by: Oscar Hinton * Fix double edit secret dialog (#3645) * Fix typescript errors on secrets init (#3649) * Resolve breaking changes * Remove unecessary class * SM-198 Update empty list text. (#3652) * [SM-267] Minor visual fixes (#3673) * SM-96: Add/Edit Project for SM (#3616) * SM-96: Initial add for Add/Edit project * Update secrets.module.ts * Small fixes based on PR comments * SM-96: Small fixes + fix new project creation * Fully fix create / edit project * SM-96: Update toast text * Remove message with exclamation * SM-96: Fix broken build * SM-96: Remove disabled on save buttons for SM dialogs & switch to early exits * SM-96: Run linter * [SM-186] Service Accounts - Overview (#3653) * SM-186 Service Accounts Overview * Remove duplicate titles (#3659) * [SM-187] Create Service Account Dialog (#3710) * SM-187 Create Service Account Dialog * Fix renamed paths * SM Modal Updates (#3776) * Add type=button to cancel button on sm dialogs * Update new secret/project modal titles to match design * Add loading spinner for project and secret edit modals * Add max length to project name * Use Tailwind CSS class instead of custom and remove click handler * Fix spinner * Add buttonType=primary to project dialog save button * Fix loading change for secret dialog and use tw-text-center Co-authored-by: Hinton * [SM-113] Delete Projects Dialog (#3777) * SM-113 Add Delete Projects Dialog * [SM-306] Migrate secrets dialog to async form (#3849) * [SM-310] Prepare secrets manager for merge to master (#3885) * Remove Built In Validator on Project Delete (#3909) Handle all Project Delete validation through custom validator * [SM-312] Mark all inputs as touched when submitting (#3920) * Use new icon for no item (#3917) * Create navigation component (#3881) * [SM-150] Project / Secret mapping (#3912) * wip * removing added file * updates * handling projects and secrets mapping in UI * moving files and fixing errors * Update bitwarden_license/bit-web/src/app/secrets-manager/secrets/secrets-list.component.html Co-authored-by: Oscar Hinton * Decrypt the name * fixing the secrets-list.component bug * renaming file and view name * lint fixes * removing secret with projects list response, and other misc name changes * Adding back things I shouldnt have deleted * Update bitwarden_license/bit-web/src/app/secrets-manager/secrets/responses/secret-with-projects-list.response.ts Co-authored-by: Oscar Hinton Co-authored-by: Oscar Hinton * updating button (#3779) * [SM-300] Access Tokens (#3955) * [SM-301] fix: associate labels with inputs (#4058) * fix: wrap input in label * fix: update all label in projects and service accounts * [SM-196] Create Access Token Dialog (#4095) * Add create access token dialog * Use ServiceAccountView for access token creation * Set version to readonly for access token * DRY up Expiration Date & bug fix * Break out expiration options component * Move expiration-options to layout; Match FIGMA * Create Generic Key generator * Add getByServiceAccountId * Change to use keyMaterial and not the full key * Use access token id, not service account * Remove generic key generator * Swap to service account name placeholder * Swap ExpirationOptions to a CVA * No longer masking according to FIGMA * Remove schema comment * Code review updates * Update required logic and approach * Move ExpirationOptionsComponent into access Co-authored-by: Hinton * SM-99: Individual Project / Secrets Tab (#4011) Co-authored-by: Hinton * Fixes for the demo (#4159) * [SM-360] Add support for never expiring access tokens (#4150) * Add support for never expiring access tokens * Render performance fixes * Update bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/dialogs/expiration-options.component.ts Co-authored-by: Oscar Hinton Co-authored-by: Oscar Hinton * [SM-360] Fix access token display dialog for never expiring tokens (#4164) * Fix access token display dialog * Add disableClose to access token display dialog * [SM-299] Add license checks (#4078) * [SM-69] feature: create org-switcher, bit-nav-item, bit-nav-group, bit-nav-divider (#4073) * feat: create nav-item, nav-group, org-switcher * add tree variant; add stories; move to component library * render button if no link is present * fix routerLinkActive; add template comments; fix styles * update storybook stories * rename to route * a11y fixes * update stories * simplify tree nesting * rename nav-base component * add divider; finish org-switcher; add overview page skeleton * add nav-divider story * code review * rename components to CL naming scheme * fix iconButton focus color * apply code review changes * fix strict template route param * add ariaLabel input; update org-switcher a11y * add two way binding for nav-group open state; update stories * add toggle control to org-switcher * [SM-310] Disable Secrets Manager in QA (#4199) Co-authored-by: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com> Co-authored-by: Thomas Avery Co-authored-by: CarleyDiaz-Bitwarden <103955722+CarleyDiaz-Bitwarden@users.noreply.github.com> Co-authored-by: cd-bitwarden <106776772+cd-bitwarden@users.noreply.github.com> Co-authored-by: Colton Hurst Co-authored-by: Will Martin --- .storybook/main.js | 2 + .../app/accounts/delete-account.component.ts | 3 +- apps/web/config/development.json | 1 + apps/web/config/qa.json | 1 + apps/web/src/app/shared/shared.module.ts | 53 ++-- apps/web/src/locales/en/messages.json | 252 ++++++++++++++++++ apps/web/src/utils/flags.ts | 1 + .../bit-web/src/app/app-routing.module.ts | 3 +- .../dialogs/bulk-status-dialog.component.html | 33 +++ .../dialogs/bulk-status-dialog.component.ts | 40 +++ .../layout/filter.component.html | 1 + .../layout/filter.component.ts | 7 + .../layout/header.component.html | 56 ++++ .../layout/header.component.ts | 38 +++ .../layout/layout.component.html | 4 +- .../layout/layout.component.ts | 0 .../secrets-manager/layout/layout.stories.ts | 68 +++++ .../layout/navigation.component.html | 10 + .../layout/navigation.component.ts | 11 + .../layout/new-menu.component.html | 18 ++ .../layout/new-menu.component.ts | 67 +++++ .../layout/no-items.component.html | 16 ++ .../layout/no-items.component.ts | 11 + .../layout/org-switcher.component.html | 49 ++++ .../layout/org-switcher.component.ts | 33 +++ .../layout/secrets-manager-logo.ts | 7 + .../models/view/project-list.view.ts | 9 + .../models/view/project.view.ts | 9 + .../models/view/secret-list.view.ts | 12 + .../models/view/secret-project.view.ts | 6 + .../models/view/secret.view.ts | 11 + .../models/view/service-account.view.ts | 9 + .../overview/overview-routing.module.ts | 17 ++ .../overview/overview.component.html | 1 + .../overview/overview.component.ts | 7 + .../overview/overview.module.ts | 13 + .../project-delete-dialog.component.html | 35 +++ .../dialog/project-delete-dialog.component.ts | 122 +++++++++ .../dialog/project-dialog.component.html | 22 ++ .../dialog/project-dialog.component.ts | 97 +++++++ .../models/requests/project.request.ts | 5 + .../responses/project-list-item.response.ts | 18 ++ .../models/responses/project.response.ts | 18 ++ .../projects/project.service.ts | 135 ++++++++++ .../project/project-secrets.component.html | 20 ++ .../project/project-secrets.component.ts | 78 ++++++ .../projects/project/project.component.html | 8 + .../projects/project/project.component.ts | 24 ++ .../projects-list.component.html | 93 +++++++ .../projects-list/projects-list.component.ts | 65 +++++ .../projects/projects-routing.module.ts | 34 +++ .../projects/projects.module.ts | 25 ++ .../projects/projects/projects.component.html | 8 + .../projects/projects/projects.component.ts | 75 ++++++ .../secrets-manager.module.ts} | 6 +- .../dialog/secret-delete.component.html | 17 ++ .../secrets/dialog/secret-delete.component.ts | 37 +++ .../dialog/secret-dialog.component.html | 38 +++ .../secrets/dialog/secret-dialog.component.ts | 98 +++++++ .../secrets/requests/secret.request.ts | 6 + .../responses/secret-list-item.response.ts | 20 ++ .../responses/secret-project.response.ts | 12 + .../secret-with-projects-list.response.ts | 18 ++ .../secrets/responses/secret.response.ts | 22 ++ .../secrets-manager/secrets/secret.service.ts | 193 ++++++++++++++ .../secrets/secrets-routing.module.ts | 0 .../secrets/secrets.component.html | 7 + .../secrets/secrets.component.ts | 76 ++++++ .../secrets-manager/secrets/secrets.module.ts | 15 ++ .../access/access-list.component.html | 77 ++++++ .../access/access-list.component.ts | 40 +++ .../access/access-tokens.component.html | 4 + .../access/access-tokens.component.ts | 61 +++++ .../service-accounts/access/access.service.ts | 128 +++++++++ .../access-token-create-dialog.component.html | 44 +++ .../access-token-create-dialog.component.ts | 85 ++++++ .../access-token-dialog.component.html | 30 +++ .../dialogs/access-token-dialog.component.ts | 44 +++ .../dialogs/expiration-options.component.html | 21 ++ .../dialogs/expiration-options.component.ts | 114 ++++++++ .../service-account-dialog.component.html | 50 ++++ .../service-account-dialog.component.ts | 69 +++++ .../models/requests/access-token.request.ts | 8 + .../requests/service-account.request.ts | 5 + .../access-token-creation.response.ts | 20 ++ .../responses/access-tokens.response.ts | 20 ++ .../responses/service-account.response.ts | 18 ++ .../models/view/access-token.view.ts | 10 + .../service-account.component.html | 9 + .../service-account.component.ts | 7 + .../service-account.service.ts | 97 +++++++ .../service-accounts-list.component.html | 96 +++++++ .../service-accounts-list.component.ts | 58 ++++ .../service-accounts-routing.module.ts | 34 +++ .../service-accounts.component.html | 5 + .../service-accounts.component.ts | 52 ++++ .../service-accounts.module.ts | 31 +++ .../shared/secrets-list.component.html | 115 ++++++++ .../shared/secrets-list.component.ts | 63 +++++ .../shared/sm-shared.module.ts | 35 +++ .../app/secrets-manager/sm-routing.module.ts | 67 +++++ .../app/{sm => secrets-manager}/sm.guard.ts | 7 +- .../app/sm/layout/navigation.component.html | 13 - .../src/app/sm/layout/navigation.component.ts | 7 - .../src/app/sm/secrets/secrets.component.html | 1 - .../src/app/sm/secrets/secrets.component.ts | 7 - .../src/app/sm/secrets/secrets.module.ts | 12 - .../bit-web/src/app/sm/sm-routing.module.ts | 37 --- .../src/models/data/organization.data.ts | 2 + libs/common/src/models/domain/organization.ts | 6 + .../response/profile-organization.response.ts | 2 + .../components/src/avatar/avatar.component.ts | 2 +- libs/components/src/dialog/dialog.module.ts | 2 +- .../src/dialog/dialog/dialog.component.ts | 2 +- libs/components/src/index.ts | 5 +- libs/components/src/navigation/index.ts | 1 + .../src/navigation/nav-base.component.ts | 47 ++++ .../src/navigation/nav-divider.component.html | 1 + .../src/navigation/nav-divider.component.ts | 7 + .../src/navigation/nav-group.component.html | 46 ++++ .../src/navigation/nav-group.component.ts | 62 +++++ .../src/navigation/nav-group.stories.ts | 74 +++++ .../src/navigation/nav-item.component.html | 79 ++++++ .../src/navigation/nav-item.component.ts | 48 ++++ .../src/navigation/nav-item.stories.ts | 93 +++++++ .../src/navigation/navigation.module.ts | 18 ++ .../components/src/stories/colors.stories.mdx | 2 + .../tabs/tab-nav-bar/tab-link.component.ts | 2 +- libs/components/src/tw-theme.css | 4 + libs/components/tailwind.config.base.js | 5 + tailwind.config.js | 1 + tsconfig.json | 5 +- 132 files changed, 4325 insertions(+), 118 deletions(-) create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/layout/dialogs/bulk-status-dialog.component.html create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/layout/dialogs/bulk-status-dialog.component.ts create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/layout/filter.component.html create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/layout/filter.component.ts create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/layout/header.component.html create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/layout/header.component.ts rename bitwarden_license/bit-web/src/app/{sm => secrets-manager}/layout/layout.component.html (73%) rename bitwarden_license/bit-web/src/app/{sm => secrets-manager}/layout/layout.component.ts (100%) create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/layout/layout.stories.ts create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/layout/navigation.component.html create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/layout/navigation.component.ts create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/layout/new-menu.component.html create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/layout/new-menu.component.ts create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/layout/no-items.component.html create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/layout/no-items.component.ts create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/layout/org-switcher.component.html create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/layout/org-switcher.component.ts create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/layout/secrets-manager-logo.ts create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/models/view/project-list.view.ts create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/models/view/project.view.ts create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/models/view/secret-list.view.ts create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/models/view/secret-project.view.ts create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/models/view/secret.view.ts create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/models/view/service-account.view.ts create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/overview/overview-routing.module.ts create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.component.html create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.component.ts create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.module.ts create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/projects/dialog/project-delete-dialog.component.html create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/projects/dialog/project-delete-dialog.component.ts create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/projects/dialog/project-dialog.component.html create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/projects/dialog/project-dialog.component.ts create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/projects/models/requests/project.request.ts create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/projects/models/responses/project-list-item.response.ts create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/projects/models/responses/project.response.ts create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/projects/project.service.ts create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-secrets.component.html create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-secrets.component.ts create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project.component.html create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project.component.ts create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/projects/projects-list/projects-list.component.html create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/projects/projects-list/projects-list.component.ts create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/projects/projects-routing.module.ts create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/projects/projects.module.ts create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/projects/projects/projects.component.html create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/projects/projects/projects.component.ts rename bitwarden_license/bit-web/src/app/{sm/sm.module.ts => secrets-manager/secrets-manager.module.ts} (57%) create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/secrets/dialog/secret-delete.component.html create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/secrets/dialog/secret-delete.component.ts create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/secrets/dialog/secret-dialog.component.html create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/secrets/dialog/secret-dialog.component.ts create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/secrets/requests/secret.request.ts create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/secrets/responses/secret-list-item.response.ts create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/secrets/responses/secret-project.response.ts create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/secrets/responses/secret-with-projects-list.response.ts create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/secrets/responses/secret.response.ts create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/secrets/secret.service.ts rename bitwarden_license/bit-web/src/app/{sm => secrets-manager}/secrets/secrets-routing.module.ts (100%) create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/secrets/secrets.component.html create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/secrets/secrets.component.ts create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/secrets/secrets.module.ts create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/access-list.component.html create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/access-list.component.ts create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/access-tokens.component.html create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/access-tokens.component.ts create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/access.service.ts create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/dialogs/access-token-create-dialog.component.html create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/dialogs/access-token-create-dialog.component.ts create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/dialogs/access-token-dialog.component.html create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/dialogs/access-token-dialog.component.ts create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/dialogs/expiration-options.component.html create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/dialogs/expiration-options.component.ts create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/dialog/service-account-dialog.component.html create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/dialog/service-account-dialog.component.ts create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/models/requests/access-token.request.ts create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/models/requests/service-account.request.ts create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/models/responses/access-token-creation.response.ts create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/models/responses/access-tokens.response.ts create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/models/responses/service-account.response.ts create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/models/view/access-token.view.ts create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-account.component.html create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-account.component.ts create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-account.service.ts create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts-list.component.html create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts-list.component.ts create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts-routing.module.ts create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts.component.html create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts.component.ts create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts.module.ts create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/shared/secrets-list.component.html create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/shared/secrets-list.component.ts create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/shared/sm-shared.module.ts create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/sm-routing.module.ts rename bitwarden_license/bit-web/src/app/{sm => secrets-manager}/sm.guard.ts (52%) delete mode 100644 bitwarden_license/bit-web/src/app/sm/layout/navigation.component.html delete mode 100644 bitwarden_license/bit-web/src/app/sm/layout/navigation.component.ts delete mode 100644 bitwarden_license/bit-web/src/app/sm/secrets/secrets.component.html delete mode 100644 bitwarden_license/bit-web/src/app/sm/secrets/secrets.component.ts delete mode 100644 bitwarden_license/bit-web/src/app/sm/secrets/secrets.module.ts delete mode 100644 bitwarden_license/bit-web/src/app/sm/sm-routing.module.ts create mode 100644 libs/components/src/navigation/index.ts create mode 100644 libs/components/src/navigation/nav-base.component.ts create mode 100644 libs/components/src/navigation/nav-divider.component.html create mode 100644 libs/components/src/navigation/nav-divider.component.ts create mode 100644 libs/components/src/navigation/nav-group.component.html create mode 100644 libs/components/src/navigation/nav-group.component.ts create mode 100644 libs/components/src/navigation/nav-group.stories.ts create mode 100644 libs/components/src/navigation/nav-item.component.html create mode 100644 libs/components/src/navigation/nav-item.component.ts create mode 100644 libs/components/src/navigation/nav-item.stories.ts create mode 100644 libs/components/src/navigation/navigation.module.ts diff --git a/.storybook/main.js b/.storybook/main.js index 7c0d2d97a15..83fe7fef3dd 100644 --- a/.storybook/main.js +++ b/.storybook/main.js @@ -6,6 +6,8 @@ module.exports = { "../libs/components/src/**/*.stories.@(js|jsx|ts|tsx)", "../apps/web/src/**/*.stories.mdx", "../apps/web/src/**/*.stories.@(js|jsx|ts|tsx)", + "../bitwarden_license/bit-web/src/**/*.stories.mdx", + "../bitwarden_license/bit-web/src/**/*.stories.@(js|jsx|ts|tsx)", ], addons: [ "@storybook/addon-links", diff --git a/apps/desktop/src/app/accounts/delete-account.component.ts b/apps/desktop/src/app/accounts/delete-account.component.ts index 74e79b22900..6ee941df83b 100644 --- a/apps/desktop/src/app/accounts/delete-account.component.ts +++ b/apps/desktop/src/app/accounts/delete-account.component.ts @@ -5,8 +5,7 @@ import { AccountApiService } from "@bitwarden/common/abstractions/account/accoun import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; - -import { Verification } from "../../../../../libs/common/src/types/verification"; +import { Verification } from "@bitwarden/common/types/verification"; @Component({ selector: "app-delete-account", diff --git a/apps/web/config/development.json b/apps/web/config/development.json index f460a1659aa..15a09d724f6 100644 --- a/apps/web/config/development.json +++ b/apps/web/config/development.json @@ -11,6 +11,7 @@ }, "flags": { "showTrial": true, + "secretsManager": true, "showPasswordless": true } } diff --git a/apps/web/config/qa.json b/apps/web/config/qa.json index a0d1b0e88c3..00dcc66d627 100644 --- a/apps/web/config/qa.json +++ b/apps/web/config/qa.json @@ -11,6 +11,7 @@ }, "flags": { "showTrial": true, + "secretsManager": false, "showPasswordless": true } } diff --git a/apps/web/src/app/shared/shared.module.ts b/apps/web/src/app/shared/shared.module.ts index c217b9b12e5..7df440ea50e 100644 --- a/apps/web/src/app/shared/shared.module.ts +++ b/apps/web/src/app/shared/shared.module.ts @@ -13,10 +13,12 @@ import { BadgeModule, ButtonModule, CalloutModule, + DialogModule, FormFieldModule, IconButtonModule, IconModule, MenuModule, + NavigationModule, TableModule, TabsModule, } from "@bitwarden/components"; @@ -36,46 +38,55 @@ import "./locales"; CommonModule, DragDropModule, FormsModule, - InfiniteScrollModule, - JslibModule, ReactiveFormsModule, + InfiniteScrollModule, RouterModule, + ToastrModule, + JslibModule, + + // Component library + AsyncActionsModule, + AvatarModule, BadgeModule, ButtonModule, CalloutModule, - ToastrModule, - BadgeModule, - ButtonModule, - MenuModule, + DialogModule, FormFieldModule, - IconModule, - TabsModule, - TableModule, - AvatarModule, IconButtonModule, + IconModule, + MenuModule, + NavigationModule, + TableModule, + TabsModule, + + // Web specific ], exports: [ CommonModule, - AsyncActionsModule, DragDropModule, FormsModule, - InfiniteScrollModule, - JslibModule, ReactiveFormsModule, + InfiniteScrollModule, RouterModule, + ToastrModule, + JslibModule, + + // Component library + AsyncActionsModule, + AvatarModule, BadgeModule, ButtonModule, CalloutModule, - ToastrModule, - BadgeModule, - ButtonModule, - MenuModule, + DialogModule, FormFieldModule, - IconModule, - TabsModule, - TableModule, - AvatarModule, IconButtonModule, + IconModule, + MenuModule, + NavigationModule, + TableModule, + TabsModule, + + // Web specific ], providers: [DatePipe], bootstrap: [], diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 94cb80076d1..82c450cadd0 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -5487,6 +5487,258 @@ "multiSelectClearAll": { "message": "Clear all" }, + "projects":{ + "message": "Projects" + }, + "lastEdited":{ + "message": "Last Edited" + }, + "editSecret":{ + "message": "Edit Secret" + }, + "addSecret":{ + "message": "Add Secret" + }, + "copySecretName":{ + "message": "Copy Secret Name" + }, + "copySecretValue":{ + "message": "Copy Secret Value" + }, + "deleteSecret":{ + "message": "Delete Secret" + }, + "deleteSecrets":{ + "message": "Delete Secrets" + }, + "project":{ + "message": "Project" + }, + "editProject":{ + "message": "Edit Project" + }, + "viewProject":{ + "message": "View Project" + }, + "deleteProject":{ + "message": "Delete Project" + }, + "deleteProjects":{ + "message": "Delete Projects" + }, + "secret":{ + "message": "Secret" + }, + "serviceAccount":{ + "message": "Service Account" + }, + "serviceAccounts":{ + "message": "Service Accounts" + }, + "new":{ + "message": "New" + }, + "secrets":{ + "message":"Secrets" + }, + "nameValuePair":{ + "message":"Name/Value Pair" + }, + "secretEdited":{ + "message":"Secret edited" + }, + "secretCreated":{ + "message":"Secret created" + }, + "newSecret":{ + "message":"New Secret" + }, + "newServiceAccount":{ + "message":"New Service Account" + }, + "importSecrets":{ + "message":"Import Secrets" + }, + "secretsNoItemsTitle":{ + "message":"No secrets to show" + }, + "secretsNoItemsMessage":{ + "message": "To get started, add a new secret or import secrets." + }, + "serviceAccountsNoItemsTitle":{ + "message":"Nothing to show yet" + }, + "serviceAccountsNoItemsMessage":{ + "message": "Create a new Service Account to get started automating secret access." + }, + "searchSecrets":{ + "message":"Search Secrets" + }, + "deleteServiceAccounts":{ + "message":"Delete Service Accounts" + }, + "deleteServiceAccount":{ + "message":"Delete Service Account" + }, + "viewServiceAccount":{ + "message":"View Service Account" + }, + "searchServiceAccounts":{ + "message":"Search Service Accounts" + }, + "addProject":{ + "message": "Add Project" + }, + "projectEdited":{ + "message":"Project edited" + }, + "projectSaved":{ + "message":"Project saved" + }, + "projectCreated":{ + "message":"Project created" + }, + "projectName":{ + "message":"Project Name" + }, + "newProject":{ + "message":"New Project" + }, + "softDeleteSecretWarning":{ + "message":"Deleting secrets can affect existing integrations." + }, + "softDeletesSuccessToast":{ + "message":"Secrets sent to trash" + }, + "serviceAccountCreated":{ + "message":"Service Account Created" + }, + "smAccess":{ + "message":"Access" + }, + "projectCommaSecret":{ + "message":"Project, Secret" + }, + "serviceAccountName":{ + "message": "Service account name" + }, + "newSaSelectAccess":{ + "message": "Type or Select Projects or Secrets" + }, + "newSaTypeToFilter":{ + "message": "Type to Filter" + }, + "deleteProjectsToast":{ + "message": "Projects deleted" + }, + "deleteProjectToast":{ + "message": "The project and all associated secrets have been deleted" + }, + "deleteProjectDialogMessage": { + "message": "Deleting project $PROJECT$ is permanent and irreversible.", + "placeholders": { + "project": { + "content": "$1", + "example": "project name" + } + } + }, + "deleteProjectInputLabel": { + "message": "Type \"$CONFIRM$\" to continue", + "placeholders": { + "confirm": { + "content": "$1", + "example": "Delete 3 Projects" + } + } + }, + "deleteProjectConfirmMessage":{ + "message": "Delete $PROJECT$", + "placeholders": { + "project": { + "content": "$1", + "example": "project name" + } + } + }, + "deleteProjectsConfirmMessage":{ + "message": "Delete $COUNT$ Projects", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "deleteProjectsDialogMessage":{ + "message": "Deleting projects is permanent and irreversible." + }, + "projectsNoItemsTitle":{ + "message": "No projects to display" + }, + "projectsNoItemsMessage":{ + "message": "Add a new project to get started organizing secrets." + }, + "smConfirmationRequired":{ + "message": "Confirmation required" + }, + "bulkDeleteProjectsErrorMessage":{ + "message": "The following projects could not be deleted:" + }, + "softDeleteSuccessToast":{ + "message":"Secret sent to trash" + }, + "searchProjects":{ + "message":"Search Projects" + }, + "accessTokens": { + "message": "Access tokens" + }, + "createAccessToken": { + "message": "Create access token" + }, + "expires": { + "message": "Expires" + }, + "canRead": { + "message": "Can Read" + }, + "accessTokensNoItemsTitle": { + "message": "No access tokens to show" + }, + "accessTokensNoItemsDesc": { + "message": "To get started, create an access token" + }, + "downloadAccessToken": { + "message": "Download or copy before closing." + }, + "expiresOnAccessToken": { + "message": "Expires on:" + }, + "accessTokenCallOutTitle": { + "message": "Access tokens are not stored and cannot be retrieved" + }, + "copyToken": { + "message": "Copy token" + }, + "accessToken": { + "message": "Access token" + }, + "accessTokenExpirationRequired": { + "message": "Expiration date required" + }, + "accessTokenCreatedAndCopied": { + "message": "Access token created and copied to clipboard" + }, + "accessTokenPermissionsBetaNotification": { + "message": "Permissions management is unavailable for beta." + }, + "revokeAccessToken": { + "message": "Revoke Access Token" + }, + "submenu": { + "message": "Submenu" + }, "from": { "message": "From" }, diff --git a/apps/web/src/utils/flags.ts b/apps/web/src/utils/flags.ts index 195bc8e5f54..03e4b2a9c97 100644 --- a/apps/web/src/utils/flags.ts +++ b/apps/web/src/utils/flags.ts @@ -10,6 +10,7 @@ import { /* eslint-disable-next-line @typescript-eslint/ban-types */ export type Flags = { showTrial?: boolean; + secretsManager?: boolean; showPasswordless?: boolean; } & SharedFlags; diff --git a/bitwarden_license/bit-web/src/app/app-routing.module.ts b/bitwarden_license/bit-web/src/app/app-routing.module.ts index c61367ccdce..aea521f3b04 100644 --- a/bitwarden_license/bit-web/src/app/app-routing.module.ts +++ b/bitwarden_license/bit-web/src/app/app-routing.module.ts @@ -10,7 +10,8 @@ const routes: Routes = [ }, { path: "sm", - loadChildren: async () => (await import("./sm/sm.module")).SecretsManagerModule, + loadChildren: async () => + (await import("./secrets-manager/secrets-manager.module")).SecretsManagerModule, }, ]; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/layout/dialogs/bulk-status-dialog.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/layout/dialogs/bulk-status-dialog.component.html new file mode 100644 index 00000000000..69489b452b0 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/layout/dialogs/bulk-status-dialog.component.html @@ -0,0 +1,33 @@ + + + {{ data.title | i18n }} + + {{ data.details.length }} + {{ data.subTitle | i18n }} + + + +
+ {{ data.message | i18n }} + + + + {{ data.columnTitle | i18n }} + {{ "error" | i18n }} + + + + + {{ detail.name }} + {{ detail.errorMessage }} + + + +
+ +
+ +
+
diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/layout/dialogs/bulk-status-dialog.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/layout/dialogs/bulk-status-dialog.component.ts new file mode 100644 index 00000000000..08525646f80 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/layout/dialogs/bulk-status-dialog.component.ts @@ -0,0 +1,40 @@ +import { DialogRef, DIALOG_DATA } from "@angular/cdk/dialog"; +import { Component, Inject, OnInit } from "@angular/core"; + +export interface BulkStatusDetails { + title: string; + subTitle: string; + columnTitle: string; + message: string; + details: BulkOperationStatus[]; +} + +export class BulkOperationStatus { + id: string; + name: string; + errorMessage?: string; +} + +@Component({ + selector: "sm-bulk-status-dialog", + templateUrl: "./bulk-status-dialog.component.html", +}) +export class BulkStatusDialogComponent implements OnInit { + constructor(public dialogRef: DialogRef, @Inject(DIALOG_DATA) public data: BulkStatusDetails) {} + + ngOnInit(): void { + // TODO remove null checks once strictNullChecks in TypeScript is turned on. + if ( + !this.data.title || + !this.data.subTitle || + !this.data.columnTitle || + !this.data.message || + !(this.data.details?.length >= 1) + ) { + this.dialogRef.close(); + throw new Error( + "The bulk status dialog was not called with the appropriate operation values." + ); + } + } +} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/layout/filter.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/layout/filter.component.html new file mode 100644 index 00000000000..147e35c9cae --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/layout/filter.component.html @@ -0,0 +1 @@ + diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/layout/filter.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/layout/filter.component.ts new file mode 100644 index 00000000000..dd6112be997 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/layout/filter.component.ts @@ -0,0 +1,7 @@ +import { Component } from "@angular/core"; + +@Component({ + selector: "sm-filter", + templateUrl: "./filter.component.html", +}) +export class FilterComponent {} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/layout/header.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/layout/header.component.html new file mode 100644 index 00000000000..8f44da97aad --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/layout/header.component.html @@ -0,0 +1,56 @@ +
+

{{ routeData.title | i18n }}

+
+ +
+ + + + + + +
+
+ +
+ {{ "loggedInAs" | i18n }} + + {{ account.name }} + +
+
+ + + + + + {{ "accountSettings" | i18n }} + + + + {{ "getHelp" | i18n }} + + + + {{ "getApps" | i18n }} + + + + + + +
+
+
+
diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/layout/header.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/layout/header.component.ts new file mode 100644 index 00000000000..8ad4e6a68e2 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/layout/header.component.ts @@ -0,0 +1,38 @@ +import { Component, Input } from "@angular/core"; +import { ActivatedRoute } from "@angular/router"; +import { combineLatest, map, Observable } from "rxjs"; + +import { StateService } from "@bitwarden/common/abstractions/state.service"; +import { AccountProfile } from "@bitwarden/common/models/domain/account"; + +@Component({ + selector: "sm-header", + templateUrl: "./header.component.html", +}) +export class HeaderComponent { + @Input() title: string; + @Input() searchTitle: string; + + protected routeData$: Observable<{ title: string; searchTitle: string }>; + protected account$: Observable; + + constructor(private route: ActivatedRoute, private stateService: StateService) { + this.routeData$ = this.route.data.pipe( + map((params) => { + return { + title: params.title, + searchTitle: params.searchTitle, + }; + }) + ); + + this.account$ = combineLatest([ + this.stateService.activeAccount$, + this.stateService.accounts$, + ]).pipe( + map(([activeAccount, accounts]) => { + return accounts[activeAccount]?.profile; + }) + ); + } +} diff --git a/bitwarden_license/bit-web/src/app/sm/layout/layout.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/layout/layout.component.html similarity index 73% rename from bitwarden_license/bit-web/src/app/sm/layout/layout.component.html rename to bitwarden_license/bit-web/src/app/secrets-manager/layout/layout.component.html index 417272a29fe..5d15daf96b5 100644 --- a/bitwarden_license/bit-web/src/app/sm/layout/layout.component.html +++ b/bitwarden_license/bit-web/src/app/secrets-manager/layout/layout.component.html @@ -1,8 +1,8 @@
-
diff --git a/bitwarden_license/bit-web/src/app/sm/layout/layout.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/layout/layout.component.ts similarity index 100% rename from bitwarden_license/bit-web/src/app/sm/layout/layout.component.ts rename to bitwarden_license/bit-web/src/app/secrets-manager/layout/layout.component.ts diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/layout/layout.stories.ts b/bitwarden_license/bit-web/src/app/secrets-manager/layout/layout.stories.ts new file mode 100644 index 00000000000..419de00875f --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/layout/layout.stories.ts @@ -0,0 +1,68 @@ +import { Component } from "@angular/core"; +import { RouterModule } from "@angular/router"; +import { Meta, Story, moduleMetadata } from "@storybook/angular"; + +import { NavigationModule, IconModule } from "@bitwarden/components"; +import { PreloadedEnglishI18nModule } from "@bitwarden/web-vault/app/tests/preloaded-english-i18n.module"; + +import { LayoutComponent } from "./layout.component"; +import { NavigationComponent } from "./navigation.component"; + +@Component({ + selector: "story-content", + template: `

Content

`, +}) +class StoryContentComponent {} + +export default { + title: "Web/Layout", + component: LayoutComponent, + decorators: [ + moduleMetadata({ + imports: [ + RouterModule.forRoot( + [ + { + path: "", + component: LayoutComponent, + children: [ + { + path: "", + redirectTo: "secrets", + pathMatch: "full", + }, + { + path: "secrets", + component: StoryContentComponent, + data: { + title: "secrets", + searchTitle: "searchSecrets", + }, + }, + { + outlet: "sidebar", + path: "", + component: NavigationComponent, + }, + ], + }, + ], + { useHash: true } + ), + IconModule, + NavigationModule, + PreloadedEnglishI18nModule, + ], + declarations: [LayoutComponent, NavigationComponent, StoryContentComponent], + }), + ], +} as Meta; + +const Template: Story = (args) => ({ + props: args, + template: ` + + `, +}); + +export const Default = Template.bind({}); diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/layout/navigation.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/layout/navigation.component.html new file mode 100644 index 00000000000..c68b751eb83 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/layout/navigation.component.html @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/layout/navigation.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/layout/navigation.component.ts new file mode 100644 index 00000000000..40bfb717267 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/layout/navigation.component.ts @@ -0,0 +1,11 @@ +import { Component } from "@angular/core"; + +import { SecretsManagerLogo } from "./secrets-manager-logo"; + +@Component({ + selector: "sm-navigation", + templateUrl: "./navigation.component.html", +}) +export class NavigationComponent { + protected readonly logo = SecretsManagerLogo; +} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/layout/new-menu.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/layout/new-menu.component.html new file mode 100644 index 00000000000..67d54c90f9f --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/layout/new-menu.component.html @@ -0,0 +1,18 @@ + + + + + + + diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/layout/new-menu.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/layout/new-menu.component.ts new file mode 100644 index 00000000000..44e206f9a08 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/layout/new-menu.component.ts @@ -0,0 +1,67 @@ +import { Component, OnDestroy, OnInit } from "@angular/core"; +import { ActivatedRoute } from "@angular/router"; +import { Subject, takeUntil } from "rxjs"; + +import { DialogService } from "@bitwarden/components"; + +import { + ProjectDialogComponent, + ProjectOperation, +} from "../projects/dialog/project-dialog.component"; +import { + OperationType, + SecretDialogComponent, + SecretOperation, +} from "../secrets/dialog/secret-dialog.component"; +import { + ServiceAccountDialogComponent, + ServiceAccountOperation, +} from "../service-accounts/dialog/service-account-dialog.component"; + +@Component({ + selector: "sm-new-menu", + templateUrl: "./new-menu.component.html", +}) +export class NewMenuComponent implements OnInit, OnDestroy { + private organizationId: string; + private destroy$: Subject = new Subject(); + + constructor(private route: ActivatedRoute, private dialogService: DialogService) {} + + ngOnInit() { + this.route.params.pipe(takeUntil(this.destroy$)).subscribe((params: any) => { + this.organizationId = params.organizationId; + }); + } + + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } + + openSecretDialog() { + this.dialogService.open(SecretDialogComponent, { + data: { + organizationId: this.organizationId, + operation: OperationType.Add, + }, + }); + } + + openProjectDialog() { + this.dialogService.open(ProjectDialogComponent, { + data: { + organizationId: this.organizationId, + operation: OperationType.Add, + }, + }); + } + + openServiceAccountDialog() { + this.dialogService.open(ServiceAccountDialogComponent, { + data: { + organizationId: this.organizationId, + }, + }); + } +} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/layout/no-items.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/layout/no-items.component.html new file mode 100644 index 00000000000..c2add8e479d --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/layout/no-items.component.html @@ -0,0 +1,16 @@ +
+
+ +

+ +

+

+ +

+
+
+ +
+
diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/layout/no-items.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/layout/no-items.component.ts new file mode 100644 index 00000000000..bc516b5995c --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/layout/no-items.component.ts @@ -0,0 +1,11 @@ +import { Component } from "@angular/core"; + +import { Icons } from "@bitwarden/components"; + +@Component({ + selector: "sm-no-items", + templateUrl: "./no-items.component.html", +}) +export class NoItemsComponent { + protected icon = Icons.Search; +} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/layout/org-switcher.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/layout/org-switcher.component.html new file mode 100644 index 00000000000..9b678157caf --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/layout/org-switcher.component.html @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/layout/org-switcher.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/layout/org-switcher.component.ts new file mode 100644 index 00000000000..97cb46ee3c3 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/layout/org-switcher.component.ts @@ -0,0 +1,33 @@ +import { Component, EventEmitter, Input, Output } from "@angular/core"; +import { ActivatedRoute } from "@angular/router"; +import { combineLatest, map, Observable } from "rxjs"; + +import { OrganizationService } from "@bitwarden/common/abstractions/organization/organization.service.abstraction"; +import type { Organization } from "@bitwarden/common/models/domain/organization"; + +@Component({ + selector: "org-switcher", + templateUrl: "org-switcher.component.html", +}) +export class OrgSwitcherComponent { + protected organizations$: Observable = this.organizationService.organizations$; + protected activeOrganization$: Observable = combineLatest([ + this.route.paramMap, + this.organizationService.organizations$, + ]).pipe(map(([params, orgs]) => orgs.find((org) => org.id === params.get("organizationId")))); + + /** + * Is `true` if the expanded content is visible + */ + @Input() + open = false; + @Output() + openChange = new EventEmitter(); + + constructor(private route: ActivatedRoute, private organizationService: OrganizationService) {} + + protected toggle(event?: MouseEvent) { + event?.stopPropagation(); + this.open = !this.open; + } +} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/layout/secrets-manager-logo.ts b/bitwarden_license/bit-web/src/app/secrets-manager/layout/secrets-manager-logo.ts new file mode 100644 index 00000000000..890daea9528 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/layout/secrets-manager-logo.ts @@ -0,0 +1,7 @@ +import { svgIcon } from "@bitwarden/components"; + +export const SecretsManagerLogo = svgIcon` + + + +`; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/models/view/project-list.view.ts b/bitwarden_license/bit-web/src/app/secrets-manager/models/view/project-list.view.ts new file mode 100644 index 00000000000..fd747264b4c --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/models/view/project-list.view.ts @@ -0,0 +1,9 @@ +import { View } from "@bitwarden/common/models/view/view"; + +export class ProjectListView implements View { + id: string; + organizationId: string; + name: string; + creationDate: string; + revisionDate: string; +} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/models/view/project.view.ts b/bitwarden_license/bit-web/src/app/secrets-manager/models/view/project.view.ts new file mode 100644 index 00000000000..b2b75dc30b1 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/models/view/project.view.ts @@ -0,0 +1,9 @@ +import { View } from "@bitwarden/common/models/view/view"; + +export class ProjectView implements View { + id: string; + organizationId: string; + name: string; + creationDate: string; + revisionDate: string; +} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/models/view/secret-list.view.ts b/bitwarden_license/bit-web/src/app/secrets-manager/models/view/secret-list.view.ts new file mode 100644 index 00000000000..9b61c51cd60 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/models/view/secret-list.view.ts @@ -0,0 +1,12 @@ +import { View } from "@bitwarden/common/models/view/view"; + +import { SecretProjectView } from "./secret-project.view"; + +export class SecretListView implements View { + id: string; + organizationId: string; + name: string; + creationDate: string; + revisionDate: string; + projects: SecretProjectView[]; +} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/models/view/secret-project.view.ts b/bitwarden_license/bit-web/src/app/secrets-manager/models/view/secret-project.view.ts new file mode 100644 index 00000000000..6c7eaee6579 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/models/view/secret-project.view.ts @@ -0,0 +1,6 @@ +import { View } from "@bitwarden/common/models/view/view"; + +export class SecretProjectView implements View { + id: string; + name: string; +} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/models/view/secret.view.ts b/bitwarden_license/bit-web/src/app/secrets-manager/models/view/secret.view.ts new file mode 100644 index 00000000000..7c949c9dde2 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/models/view/secret.view.ts @@ -0,0 +1,11 @@ +import { View } from "@bitwarden/common/models/view/view"; + +export class SecretView implements View { + id: string; + organizationId: string; + name: string; + value: string; + note: string; + creationDate: string; + revisionDate: string; +} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/models/view/service-account.view.ts b/bitwarden_license/bit-web/src/app/secrets-manager/models/view/service-account.view.ts new file mode 100644 index 00000000000..638245bb15c --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/models/view/service-account.view.ts @@ -0,0 +1,9 @@ +import { View } from "@bitwarden/common/models/view/view"; + +export class ServiceAccountView implements View { + id: string; + organizationId: string; + name: string; + creationDate: string; + revisionDate: string; +} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview-routing.module.ts b/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview-routing.module.ts new file mode 100644 index 00000000000..e074eed765e --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview-routing.module.ts @@ -0,0 +1,17 @@ +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { OverviewComponent } from "./overview.component"; + +const routes: Routes = [ + { + path: "", + component: OverviewComponent, + }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], +}) +export class OverviewRoutingModule {} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.component.html new file mode 100644 index 00000000000..4192b1713ca --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.component.html @@ -0,0 +1 @@ + diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.component.ts new file mode 100644 index 00000000000..308459eb6e1 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.component.ts @@ -0,0 +1,7 @@ +import { Component } from "@angular/core"; + +@Component({ + selector: "sm-overview", + templateUrl: "./overview.component.html", +}) +export class OverviewComponent {} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.module.ts b/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.module.ts new file mode 100644 index 00000000000..2e1cee28c25 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.module.ts @@ -0,0 +1,13 @@ +import { NgModule } from "@angular/core"; + +import { SecretsManagerSharedModule } from "../shared/sm-shared.module"; + +import { OverviewRoutingModule } from "./overview-routing.module"; +import { OverviewComponent } from "./overview.component"; + +@NgModule({ + imports: [SecretsManagerSharedModule, OverviewRoutingModule], + declarations: [OverviewComponent], + providers: [], +}) +export class OverviewModule {} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/dialog/project-delete-dialog.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/projects/dialog/project-delete-dialog.component.html new file mode 100644 index 00000000000..98a964bd5e5 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/dialog/project-delete-dialog.component.html @@ -0,0 +1,35 @@ +
+ + + {{ title | i18n }} + + + {{ data.projects[0].name }} + + + {{ data.projects.length }} + {{ "projects" | i18n }} + + + + +
+ + {{ dialogContent }} + + + {{ dialogConfirmationLabel }} + + +
+ +
+ + +
+
+
diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/dialog/project-delete-dialog.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/projects/dialog/project-delete-dialog.component.ts new file mode 100644 index 00000000000..cb175ee7569 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/dialog/project-delete-dialog.component.ts @@ -0,0 +1,122 @@ +import { DialogRef, DIALOG_DATA } from "@angular/cdk/dialog"; +import { Component, Inject, OnInit } from "@angular/core"; +import { + FormControl, + FormGroup, + ValidationErrors, + ValidatorFn, + AbstractControl, +} from "@angular/forms"; + +import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; +import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; +import { DialogService } from "@bitwarden/components"; + +import { + BulkOperationStatus, + BulkStatusDetails, + BulkStatusDialogComponent, +} from "../../layout/dialogs/bulk-status-dialog.component"; +import { ProjectListView } from "../../models/view/project-list.view"; +import { ProjectService } from "../project.service"; + +export interface ProjectDeleteOperation { + projects: ProjectListView[]; +} + +@Component({ + selector: "sm-project-delete-dialog", + templateUrl: "./project-delete-dialog.component.html", +}) +export class ProjectDeleteDialogComponent implements OnInit { + formGroup = new FormGroup({ + confirmDelete: new FormControl("", [this.matchConfirmationMessageValidator()]), + }); + + constructor( + public dialogRef: DialogRef, + @Inject(DIALOG_DATA) public data: ProjectDeleteOperation, + private projectService: ProjectService, + private i18nService: I18nService, + private platformUtilsService: PlatformUtilsService, + private dialogService: DialogService + ) {} + + ngOnInit(): void { + if (!(this.data.projects?.length >= 1)) { + this.dialogRef.close(); + throw new Error( + "The project delete dialog was not called with the appropriate operation values." + ); + } + } + + get title() { + return this.data.projects.length === 1 ? "deleteProject" : "deleteProjects"; + } + + get dialogContent() { + return this.data.projects.length === 1 + ? this.i18nService.t("deleteProjectDialogMessage", this.data.projects[0].name) + : this.i18nService.t("deleteProjectsDialogMessage"); + } + + get dialogConfirmationLabel() { + return this.i18nService.t("deleteProjectInputLabel", this.dialogConfirmationMessage); + } + + submit = async () => { + this.formGroup.markAllAsTouched(); + + if (this.formGroup.invalid) { + return; + } + + await this.delete(); + this.dialogRef.close(); + }; + + async delete() { + const bulkResponses = await this.projectService.delete(this.data.projects); + + if (bulkResponses.find((response) => response.errorMessage)) { + this.openBulkStatusDialog(bulkResponses.filter((response) => response.errorMessage)); + return; + } + + const message = this.data.projects.length === 1 ? "deleteProjectToast" : "deleteProjectsToast"; + this.platformUtilsService.showToast("success", null, this.i18nService.t(message)); + } + + openBulkStatusDialog(bulkStatusResults: BulkOperationStatus[]) { + this.dialogService.open(BulkStatusDialogComponent, { + data: { + title: "deleteProjects", + subTitle: "projects", + columnTitle: "projectName", + message: "bulkDeleteProjectsErrorMessage", + details: bulkStatusResults, + }, + }); + } + + private get dialogConfirmationMessage() { + return this.data.projects?.length === 1 + ? this.i18nService.t("deleteProjectConfirmMessage", this.data.projects[0].name) + : this.i18nService.t("deleteProjectsConfirmMessage", this.data.projects?.length.toString()); + } + + private matchConfirmationMessageValidator(): ValidatorFn { + return (control: AbstractControl): ValidationErrors | null => { + if (this.dialogConfirmationMessage.toLowerCase() == control.value.toLowerCase()) { + return null; + } else { + return { + confirmationDoesntMatchError: { + message: this.i18nService.t("smConfirmationRequired"), + }, + }; + } + }; + } +} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/dialog/project-dialog.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/projects/dialog/project-dialog.component.html new file mode 100644 index 00000000000..89732452272 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/dialog/project-dialog.component.html @@ -0,0 +1,22 @@ +
+ + {{ title | i18n }} + +
+ +
+ + {{ "projectName" | i18n }} + + +
+
+ + +
+
+
diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/dialog/project-dialog.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/projects/dialog/project-dialog.component.ts new file mode 100644 index 00000000000..b9c563d6829 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/dialog/project-dialog.component.ts @@ -0,0 +1,97 @@ +import { DialogRef, DIALOG_DATA } from "@angular/cdk/dialog"; +import { Component, Inject, OnInit } from "@angular/core"; +import { FormControl, FormGroup, Validators } from "@angular/forms"; +import { Router } from "@angular/router"; + +import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; +import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; + +import { ProjectView } from "../../models/view/project.view"; +import { ProjectService } from "../../projects/project.service"; + +export enum OperationType { + Add, + Edit, +} + +export interface ProjectOperation { + organizationId: string; + operation: OperationType; + projectId?: string; +} + +@Component({ + selector: "sm-project-dialog", + templateUrl: "./project-dialog.component.html", +}) +export class ProjectDialogComponent implements OnInit { + protected formGroup = new FormGroup({ + name: new FormControl("", [Validators.required]), + }); + protected loading = false; + + constructor( + public dialogRef: DialogRef, + @Inject(DIALOG_DATA) private data: ProjectOperation, + private projectService: ProjectService, + private i18nService: I18nService, + private platformUtilsService: PlatformUtilsService, + private router: Router + ) {} + + async ngOnInit() { + if (this.data.operation === OperationType.Edit && this.data.projectId) { + await this.loadData(); + } else if (this.data.operation !== OperationType.Add) { + this.dialogRef.close(); + throw new Error(`The project dialog was not called with the appropriate operation values.`); + } + } + + async loadData() { + this.loading = true; + const project: ProjectView = await this.projectService.getByProjectId(this.data.projectId); + this.loading = false; + this.formGroup.setValue({ name: project.name }); + } + + get title() { + return this.data.operation === OperationType.Add ? "newProject" : "editProject"; + } + + submit = async () => { + this.formGroup.markAllAsTouched(); + + if (this.formGroup.invalid) { + return; + } + + const projectView = this.getProjectView(); + if (this.data.operation === OperationType.Add) { + const newProject = await this.createProject(projectView); + this.router.navigate(["sm", this.data.organizationId, "projects", newProject.id]); + } else { + projectView.id = this.data.projectId; + await this.updateProject(projectView); + } + this.dialogRef.close(); + }; + + private async createProject(projectView: ProjectView) { + const newProject = await this.projectService.create(this.data.organizationId, projectView); + this.platformUtilsService.showToast("success", null, this.i18nService.t("projectCreated")); + return newProject; + } + + private async updateProject(projectView: ProjectView) { + await this.projectService.update(this.data.organizationId, projectView); + this.platformUtilsService.showToast("success", null, this.i18nService.t("projectSaved")); + } + + private getProjectView() { + const projectView = new ProjectView(); + projectView.organizationId = this.data.organizationId; + projectView.name = this.formGroup.value.name; + return projectView; + } +} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/models/requests/project.request.ts b/bitwarden_license/bit-web/src/app/secrets-manager/projects/models/requests/project.request.ts new file mode 100644 index 00000000000..994c12e927f --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/models/requests/project.request.ts @@ -0,0 +1,5 @@ +import { EncString } from "@bitwarden/common/models/domain/enc-string"; + +export class ProjectRequest { + name: EncString; +} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/models/responses/project-list-item.response.ts b/bitwarden_license/bit-web/src/app/secrets-manager/projects/models/responses/project-list-item.response.ts new file mode 100644 index 00000000000..dce0f7ffbbf --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/models/responses/project-list-item.response.ts @@ -0,0 +1,18 @@ +import { BaseResponse } from "@bitwarden/common/models/response/base.response"; + +export class ProjectListItemResponse extends BaseResponse { + id: string; + organizationId: string; + name: string; + creationDate: string; + revisionDate: string; + + constructor(response: any) { + super(response); + this.id = this.getResponseProperty("Id"); + this.organizationId = this.getResponseProperty("OrganizationId"); + this.name = this.getResponseProperty("Name"); + this.creationDate = this.getResponseProperty("CreationDate"); + this.revisionDate = this.getResponseProperty("RevisionDate"); + } +} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/models/responses/project.response.ts b/bitwarden_license/bit-web/src/app/secrets-manager/projects/models/responses/project.response.ts new file mode 100644 index 00000000000..7603a35600e --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/models/responses/project.response.ts @@ -0,0 +1,18 @@ +import { BaseResponse } from "@bitwarden/common/models/response/base.response"; + +export class ProjectResponse extends BaseResponse { + id: string; + organizationId: string; + name: string; + creationDate: string; + revisionDate: string; + + constructor(response: any) { + super(response); + this.id = this.getResponseProperty("Id"); + this.organizationId = this.getResponseProperty("OrganizationId"); + this.name = this.getResponseProperty("Name"); + this.creationDate = this.getResponseProperty("CreationDate"); + this.revisionDate = this.getResponseProperty("RevisionDate"); + } +} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project.service.ts b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project.service.ts new file mode 100644 index 00000000000..59bf9e1d962 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project.service.ts @@ -0,0 +1,135 @@ +import { Injectable } from "@angular/core"; +import { Subject } from "rxjs"; + +import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { CryptoService } from "@bitwarden/common/abstractions/crypto.service"; +import { EncryptService } from "@bitwarden/common/abstractions/encrypt.service"; +import { EncString } from "@bitwarden/common/models/domain/enc-string"; +import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetric-crypto-key"; +import { ListResponse } from "@bitwarden/common/models/response/list.response"; + +import { BulkOperationStatus } from "../layout/dialogs/bulk-status-dialog.component"; +import { ProjectListView } from "../models/view/project-list.view"; +import { ProjectView } from "../models/view/project.view"; + +import { ProjectRequest } from "./models/requests/project.request"; +import { ProjectListItemResponse } from "./models/responses/project-list-item.response"; +import { ProjectResponse } from "./models/responses/project.response"; + +@Injectable({ + providedIn: "root", +}) +export class ProjectService { + protected _project = new Subject(); + project$ = this._project.asObservable(); + + constructor( + private cryptoService: CryptoService, + private apiService: ApiService, + private encryptService: EncryptService + ) {} + + async getByProjectId(projectId: string): Promise { + const r = await this.apiService.send("GET", "/projects/" + projectId, null, true, true); + const projectResponse = new ProjectResponse(r); + return await this.createProjectView(projectResponse); + } + + async getProjects(organizationId: string): Promise { + const r = await this.apiService.send( + "GET", + "/organizations/" + organizationId + "/projects", + null, + true, + true + ); + const results = new ListResponse(r, ProjectListItemResponse); + return await this.createProjectsListView(organizationId, results.data); + } + + async create(organizationId: string, projectView: ProjectView): Promise { + const request = await this.getProjectRequest(organizationId, projectView); + const r = await this.apiService.send( + "POST", + "/organizations/" + organizationId + "/projects", + request, + true, + true + ); + + const project = await this.createProjectView(new ProjectResponse(r)); + this._project.next(project); + return project; + } + + async update(organizationId: string, projectView: ProjectView) { + const request = await this.getProjectRequest(organizationId, projectView); + const r = await this.apiService.send("PUT", "/projects/" + projectView.id, request, true, true); + this._project.next(await this.createProjectView(new ProjectResponse(r))); + } + + async delete(projects: ProjectListView[]): Promise { + const projectIds = projects.map((project) => project.id); + const r = await this.apiService.send("POST", "/projects/delete", projectIds, true, true); + this._project.next(null); + return r.data.map((element: { id: string; error: string }) => { + const bulkOperationStatus = new BulkOperationStatus(); + bulkOperationStatus.id = element.id; + bulkOperationStatus.name = projects.find((project) => project.id == element.id).name; + bulkOperationStatus.errorMessage = element.error; + return bulkOperationStatus; + }); + } + + private async getOrganizationKey(organizationId: string): Promise { + return await this.cryptoService.getOrgKey(organizationId); + } + + private async getProjectRequest( + organizationId: string, + projectView: ProjectView + ): Promise { + const orgKey = await this.getOrganizationKey(organizationId); + const request = new ProjectRequest(); + request.name = await this.encryptService.encrypt(projectView.name, orgKey); + + return request; + } + + private async createProjectView(projectResponse: ProjectResponse): Promise { + const orgKey = await this.getOrganizationKey(projectResponse.organizationId); + + const projectView = new ProjectView(); + projectView.id = projectResponse.id; + projectView.organizationId = projectResponse.organizationId; + projectView.creationDate = projectResponse.creationDate; + projectView.revisionDate = projectResponse.revisionDate; + projectView.name = await this.encryptService.decryptToUtf8( + new EncString(projectResponse.name), + orgKey + ); + + return projectView; + } + + private async createProjectsListView( + organizationId: string, + projects: ProjectListItemResponse[] + ): Promise { + const orgKey = await this.getOrganizationKey(organizationId); + return await Promise.all( + projects.map(async (s: ProjectListItemResponse) => { + const projectListView = new ProjectListView(); + projectListView.id = s.id; + projectListView.organizationId = s.organizationId; + projectListView.name = await this.encryptService.decryptToUtf8( + new EncString(s.name), + orgKey + ); + projectListView.creationDate = s.creationDate; + projectListView.revisionDate = s.revisionDate; + return projectListView; + }) + ); + } +} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-secrets.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-secrets.component.html new file mode 100644 index 00000000000..e47c3cc2340 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-secrets.component.html @@ -0,0 +1,20 @@ + +
+ +
+ +
+ + +
+ +
+
diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-secrets.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-secrets.component.ts new file mode 100644 index 00000000000..1fa7774fa28 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-secrets.component.ts @@ -0,0 +1,78 @@ +import { Component } from "@angular/core"; +import { ActivatedRoute } from "@angular/router"; +import { combineLatestWith, Observable, startWith, switchMap } from "rxjs"; + +import { DialogService } from "@bitwarden/components"; + +import { SecretListView } from "../../models/view/secret-list.view"; +import { + SecretDeleteDialogComponent, + SecretDeleteOperation, +} from "../../secrets/dialog/secret-delete.component"; +import { + OperationType, + SecretDialogComponent, + SecretOperation, +} from "../../secrets/dialog/secret-dialog.component"; +import { SecretService } from "../../secrets/secret.service"; + +@Component({ + selector: "sm-project-secrets", + templateUrl: "./project-secrets.component.html", +}) +export class ProjectSecretsComponent { + secrets$: Observable; + + private organizationId: string; + private projectId: string; + + constructor( + private route: ActivatedRoute, + private secretService: SecretService, + private dialogService: DialogService + ) {} + + ngOnInit() { + this.secrets$ = this.secretService.secret$.pipe( + startWith(null), + combineLatestWith(this.route.params), + switchMap(async ([_, params]) => { + this.organizationId = params.organizationId; + this.projectId = params.projectId; + return await this.getSecretsByProject(); + }) + ); + } + + private async getSecretsByProject(): Promise { + return await this.secretService.getSecretsByProject(this.organizationId, this.projectId); + } + + openEditSecret(secretId: string) { + this.dialogService.open(SecretDialogComponent, { + data: { + organizationId: this.organizationId, + operation: OperationType.Edit, + secretId: secretId, + }, + }); + } + + openDeleteSecret(secretIds: string[]) { + this.dialogService.open(SecretDeleteDialogComponent, { + data: { + secretIds: secretIds, + }, + }); + } + + openNewSecretDialog() { + this.dialogService.open(SecretDialogComponent, { + data: { + organizationId: this.organizationId, + operation: OperationType.Add, + projectId: this.projectId, + }, + }); + } +} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project.component.html new file mode 100644 index 00000000000..f9c39aaed13 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project.component.html @@ -0,0 +1,8 @@ + + + + Secrets + Access + + + diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project.component.ts new file mode 100644 index 00000000000..8720b30c010 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project.component.ts @@ -0,0 +1,24 @@ +import { Component, OnInit } from "@angular/core"; +import { ActivatedRoute } from "@angular/router"; +import { Observable, switchMap } from "rxjs"; + +import { ProjectView } from "../../models/view/project.view"; +import { ProjectService } from "../project.service"; + +@Component({ + selector: "sm-project", + templateUrl: "./project.component.html", +}) +export class ProjectComponent implements OnInit { + project: Observable; + + constructor(private route: ActivatedRoute, private projectService: ProjectService) {} + + ngOnInit(): void { + this.project = this.route.params.pipe( + switchMap((params) => { + return this.projectService.getByProjectId(params.projectId); + }) + ); + } +} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/projects-list/projects-list.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/projects/projects-list/projects-list.component.html new file mode 100644 index 00000000000..ea18d386504 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/projects-list/projects-list.component.html @@ -0,0 +1,93 @@ +
+ +
+ + + {{ "projectsNoItemsTitle" | i18n }} + {{ "projectsNoItemsMessage" | i18n }} + + + + + + + + + + + {{ "name" | i18n }} + {{ "lastEdited" | i18n }} + + + + + + + + + + + + + + + {{ project.name }} + + {{ project.revisionDate | date: "medium" }} + + + + + + + + + + + + + + + diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/projects-list/projects-list.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/projects/projects-list/projects-list.component.ts new file mode 100644 index 00000000000..97bf650c2b9 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/projects-list/projects-list.component.ts @@ -0,0 +1,65 @@ +import { SelectionModel } from "@angular/cdk/collections"; +import { Component, EventEmitter, Input, OnDestroy, Output } from "@angular/core"; +import { Subject, takeUntil } from "rxjs"; + +import { ProjectListView } from "../../models/view/project-list.view"; + +@Component({ + selector: "sm-projects-list", + templateUrl: "./projects-list.component.html", +}) +export class ProjectsListComponent implements OnDestroy { + @Input() + get projects(): ProjectListView[] { + return this._projects; + } + set projects(projects: ProjectListView[]) { + this.selection.clear(); + this._projects = projects; + } + private _projects: ProjectListView[]; + + @Output() editProjectEvent = new EventEmitter(); + @Output() viewProjectEvent = new EventEmitter(); + @Output() deleteProjectEvent = new EventEmitter(); + @Output() onProjectCheckedEvent = new EventEmitter(); + @Output() newProjectEvent = new EventEmitter(); + @Output() importSecretsEvent = new EventEmitter(); + + private destroy$: Subject = new Subject(); + + selection = new SelectionModel(true, []); + + constructor() { + this.selection.changed + .pipe(takeUntil(this.destroy$)) + .subscribe((_) => this.onProjectCheckedEvent.emit(this.selection.selected)); + } + + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } + + isAllSelected() { + const numSelected = this.selection.selected.length; + const numRows = this.projects.length; + return numSelected === numRows; + } + + toggleAll() { + this.isAllSelected() + ? this.selection.clear() + : this.selection.select(...this.projects.map((s) => s.id)); + } + + deleteProject(projectId: string) { + this.deleteProjectEvent.emit(this.projects.filter((p) => p.id == projectId)); + } + + bulkDeleteProjects() { + this.deleteProjectEvent.emit( + this.projects.filter((project) => this.selection.isSelected(project.id)) + ); + } +} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/projects-routing.module.ts b/bitwarden_license/bit-web/src/app/secrets-manager/projects/projects-routing.module.ts new file mode 100644 index 00000000000..c0d6642e9db --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/projects-routing.module.ts @@ -0,0 +1,34 @@ +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { ProjectSecretsComponent } from "./project/project-secrets.component"; +import { ProjectComponent } from "./project/project.component"; +import { ProjectsComponent } from "./projects/projects.component"; + +const routes: Routes = [ + { + path: "", + component: ProjectsComponent, + }, + { + path: ":projectId", + component: ProjectComponent, + children: [ + { + path: "", + pathMatch: "full", + redirectTo: "secrets", + }, + { + path: "secrets", + component: ProjectSecretsComponent, + }, + ], + }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], +}) +export class ProjectsRoutingModule {} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/projects.module.ts b/bitwarden_license/bit-web/src/app/secrets-manager/projects/projects.module.ts new file mode 100644 index 00000000000..56c9840cdfd --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/projects.module.ts @@ -0,0 +1,25 @@ +import { NgModule } from "@angular/core"; + +import { SecretsManagerSharedModule } from "../shared/sm-shared.module"; + +import { ProjectDeleteDialogComponent } from "./dialog/project-delete-dialog.component"; +import { ProjectDialogComponent } from "./dialog/project-dialog.component"; +import { ProjectSecretsComponent } from "./project/project-secrets.component"; +import { ProjectComponent } from "./project/project.component"; +import { ProjectsListComponent } from "./projects-list/projects-list.component"; +import { ProjectsRoutingModule } from "./projects-routing.module"; +import { ProjectsComponent } from "./projects/projects.component"; + +@NgModule({ + imports: [SecretsManagerSharedModule, ProjectsRoutingModule], + declarations: [ + ProjectsComponent, + ProjectsListComponent, + ProjectDialogComponent, + ProjectDeleteDialogComponent, + ProjectComponent, + ProjectSecretsComponent, + ], + providers: [], +}) +export class ProjectsModule {} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/projects/projects.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/projects/projects/projects.component.html new file mode 100644 index 00000000000..040df33abc1 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/projects/projects.component.html @@ -0,0 +1,8 @@ + + + diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/projects/projects.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/projects/projects/projects.component.ts new file mode 100644 index 00000000000..1d362121717 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/projects/projects.component.ts @@ -0,0 +1,75 @@ +import { Component, OnInit } from "@angular/core"; +import { ActivatedRoute } from "@angular/router"; +import { combineLatestWith, Observable, startWith, switchMap } from "rxjs"; + +import { DialogService } from "@bitwarden/components"; + +import { ProjectListView } from "../../models/view/project-list.view"; +import { + ProjectDeleteDialogComponent, + ProjectDeleteOperation, +} from "../dialog/project-delete-dialog.component"; +import { + OperationType, + ProjectDialogComponent, + ProjectOperation, +} from "../dialog/project-dialog.component"; +import { ProjectService } from "../project.service"; + +@Component({ + selector: "sm-projects", + templateUrl: "./projects.component.html", +}) +export class ProjectsComponent implements OnInit { + projects$: Observable; + + private organizationId: string; + + constructor( + private route: ActivatedRoute, + private projectService: ProjectService, + private dialogService: DialogService + ) {} + + ngOnInit() { + this.projects$ = this.projectService.project$.pipe( + startWith(null), + combineLatestWith(this.route.params), + switchMap(async ([_, params]) => { + this.organizationId = params.organizationId; + return await this.getProjects(); + }) + ); + } + + private async getProjects(): Promise { + return await this.projectService.getProjects(this.organizationId); + } + + openEditProject(projectId: string) { + this.dialogService.open(ProjectDialogComponent, { + data: { + organizationId: this.organizationId, + operation: OperationType.Edit, + projectId: projectId, + }, + }); + } + + openNewProjectDialog() { + this.dialogService.open(ProjectDialogComponent, { + data: { + organizationId: this.organizationId, + operation: OperationType.Add, + }, + }); + } + + openDeleteProjectDialog(event: ProjectListView[]) { + this.dialogService.open(ProjectDeleteDialogComponent, { + data: { + projects: event, + }, + }); + } +} diff --git a/bitwarden_license/bit-web/src/app/sm/sm.module.ts b/bitwarden_license/bit-web/src/app/secrets-manager/secrets-manager.module.ts similarity index 57% rename from bitwarden_license/bit-web/src/app/sm/sm.module.ts rename to bitwarden_license/bit-web/src/app/secrets-manager/secrets-manager.module.ts index b87b961cafb..6510e95ab2f 100644 --- a/bitwarden_license/bit-web/src/app/sm/sm.module.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/secrets-manager.module.ts @@ -4,12 +4,14 @@ import { SharedModule } from "@bitwarden/web-vault/app/shared"; import { LayoutComponent } from "./layout/layout.component"; import { NavigationComponent } from "./layout/navigation.component"; +import { OrgSwitcherComponent } from "./layout/org-switcher.component"; +import { SecretsManagerSharedModule } from "./shared/sm-shared.module"; import { SecretsManagerRoutingModule } from "./sm-routing.module"; import { SMGuard } from "./sm.guard"; @NgModule({ - imports: [SharedModule, SecretsManagerRoutingModule], - declarations: [LayoutComponent, NavigationComponent], + imports: [SharedModule, SecretsManagerSharedModule, SecretsManagerRoutingModule], + declarations: [LayoutComponent, NavigationComponent, OrgSwitcherComponent], providers: [SMGuard], }) export class SecretsManagerModule {} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/secrets/dialog/secret-delete.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/secrets/dialog/secret-delete.component.html new file mode 100644 index 00000000000..301c36a901f --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/secrets/dialog/secret-delete.component.html @@ -0,0 +1,17 @@ + + {{ title | i18n }} + +
+ {{ "softDeleteSecretWarning" | i18n }} +
+ {{ "deleteItemConfirmation" | i18n }} +
+
+ + +
+
diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/secrets/dialog/secret-delete.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/secrets/dialog/secret-delete.component.ts new file mode 100644 index 00000000000..2056718781e --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/secrets/dialog/secret-delete.component.ts @@ -0,0 +1,37 @@ +import { DialogRef, DIALOG_DATA } from "@angular/cdk/dialog"; +import { Component, Inject } from "@angular/core"; + +import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; +import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; + +import { SecretService } from "../secret.service"; + +export interface SecretDeleteOperation { + secretIds: string[]; +} + +@Component({ + selector: "sm-secret-delete-dialog", + templateUrl: "./secret-delete.component.html", +}) +export class SecretDeleteDialogComponent { + constructor( + public dialogRef: DialogRef, + private secretService: SecretService, + private i18nService: I18nService, + private platformUtilsService: PlatformUtilsService, + @Inject(DIALOG_DATA) public data: SecretDeleteOperation + ) {} + + get title() { + return this.data.secretIds.length === 1 ? "deleteSecret" : "deleteSecrets"; + } + + delete = async () => { + await this.secretService.delete(this.data.secretIds); + this.dialogRef.close(); + const message = + this.data.secretIds.length === 1 ? "softDeleteSuccessToast" : "softDeletesSuccessToast"; + this.platformUtilsService.showToast("success", null, this.i18nService.t(message)); + }; +} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/secrets/dialog/secret-dialog.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/secrets/dialog/secret-dialog.component.html new file mode 100644 index 00000000000..6f88cb69c1e --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/secrets/dialog/secret-dialog.component.html @@ -0,0 +1,38 @@ +
+ + {{ title | i18n }} +
+
+ +
+ + +
+ + {{ "name" | i18n }} + + + + {{ "value" | i18n }} + + +
+ + {{ "notes" | i18n }} + + +
+ + +
+
+
+ + +
+
+
diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/secrets/dialog/secret-dialog.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/secrets/dialog/secret-dialog.component.ts new file mode 100644 index 00000000000..2f7a64b555d --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/secrets/dialog/secret-dialog.component.ts @@ -0,0 +1,98 @@ +import { DialogRef, DIALOG_DATA } from "@angular/cdk/dialog"; +import { Component, Inject, OnInit } from "@angular/core"; +import { FormControl, FormGroup, Validators } from "@angular/forms"; + +import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; +import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; + +import { SecretView } from "../../models/view/secret.view"; +import { SecretService } from "../secret.service"; + +export enum OperationType { + Add, + Edit, +} + +export interface SecretOperation { + organizationId: string; + operation: OperationType; + projectId?: string; + secretId?: string; +} + +@Component({ + selector: "sm-secret-dialog", + templateUrl: "./secret-dialog.component.html", +}) +export class SecretDialogComponent implements OnInit { + protected formGroup = new FormGroup({ + name: new FormControl("", [Validators.required]), + value: new FormControl("", [Validators.required]), + notes: new FormControl(""), + }); + protected loading = false; + + constructor( + public dialogRef: DialogRef, + @Inject(DIALOG_DATA) private data: SecretOperation, + private secretService: SecretService, + private i18nService: I18nService, + private platformUtilsService: PlatformUtilsService + ) {} + + async ngOnInit() { + if (this.data.operation === OperationType.Edit && this.data.secretId) { + await this.loadData(); + } else if (this.data.operation !== OperationType.Add) { + this.dialogRef.close(); + throw new Error(`The secret dialog was not called with the appropriate operation values.`); + } + } + + async loadData() { + this.loading = true; + const secret: SecretView = await this.secretService.getBySecretId(this.data.secretId); + this.loading = false; + this.formGroup.setValue({ name: secret.name, value: secret.value, notes: secret.note }); + } + + get title() { + return this.data.operation === OperationType.Add ? "newSecret" : "editSecret"; + } + + submit = async () => { + this.formGroup.markAllAsTouched(); + + if (this.formGroup.invalid) { + return; + } + + const secretView = this.getSecretView(); + if (this.data.operation === OperationType.Add) { + await this.createSecret(secretView, this.data.projectId); + } else { + secretView.id = this.data.secretId; + await this.updateSecret(secretView); + } + this.dialogRef.close(); + }; + + private async createSecret(secretView: SecretView, projectId?: string) { + await this.secretService.create(this.data.organizationId, secretView, projectId); + this.platformUtilsService.showToast("success", null, this.i18nService.t("secretCreated")); + } + + private async updateSecret(secretView: SecretView) { + await this.secretService.update(this.data.organizationId, secretView); + this.platformUtilsService.showToast("success", null, this.i18nService.t("secretEdited")); + } + + private getSecretView() { + const secretView = new SecretView(); + secretView.organizationId = this.data.organizationId; + secretView.name = this.formGroup.value.name; + secretView.value = this.formGroup.value.value; + secretView.note = this.formGroup.value.notes; + return secretView; + } +} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/secrets/requests/secret.request.ts b/bitwarden_license/bit-web/src/app/secrets-manager/secrets/requests/secret.request.ts new file mode 100644 index 00000000000..d84641341da --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/secrets/requests/secret.request.ts @@ -0,0 +1,6 @@ +export class SecretRequest { + key: string; + value: string; + note: string; + projectId?: string; +} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/secrets/responses/secret-list-item.response.ts b/bitwarden_license/bit-web/src/app/secrets-manager/secrets/responses/secret-list-item.response.ts new file mode 100644 index 00000000000..3819f95870d --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/secrets/responses/secret-list-item.response.ts @@ -0,0 +1,20 @@ +import { BaseResponse } from "@bitwarden/common/models/response/base.response"; + +export class SecretListItemResponse extends BaseResponse { + id: string; + organizationId: string; + name: string; + creationDate: string; + revisionDate: string; + projects: string[]; + + constructor(response: any) { + super(response); + this.id = this.getResponseProperty("Id"); + this.organizationId = this.getResponseProperty("OrganizationId"); + this.name = this.getResponseProperty("Key"); + this.creationDate = this.getResponseProperty("CreationDate"); + this.revisionDate = this.getResponseProperty("RevisionDate"); + this.projects = this.getResponseProperty("projects"); + } +} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/secrets/responses/secret-project.response.ts b/bitwarden_license/bit-web/src/app/secrets-manager/secrets/responses/secret-project.response.ts new file mode 100644 index 00000000000..1336b8111e5 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/secrets/responses/secret-project.response.ts @@ -0,0 +1,12 @@ +import { BaseResponse } from "@bitwarden/common/models/response/base.response"; + +export class SecretProjectResponse extends BaseResponse { + id: string; + name: string; + + constructor(response: any) { + super(response); + this.name = this.getResponseProperty("Name"); + this.id = this.getResponseProperty("Id"); + } +} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/secrets/responses/secret-with-projects-list.response.ts b/bitwarden_license/bit-web/src/app/secrets-manager/secrets/responses/secret-with-projects-list.response.ts new file mode 100644 index 00000000000..2b1d22b31f0 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/secrets/responses/secret-with-projects-list.response.ts @@ -0,0 +1,18 @@ +import { BaseResponse } from "@bitwarden/common/models/response/base.response"; + +import { SecretListItemResponse } from "./secret-list-item.response"; +import { SecretProjectResponse } from "./secret-project.response"; + +export class SecretWithProjectsListResponse extends BaseResponse { + secrets: SecretListItemResponse[]; + projects: SecretProjectResponse[]; + + constructor(response: any) { + super(response); + const secrets = this.getResponseProperty("secrets"); + const projects = this.getResponseProperty("projects"); + this.projects = + projects == null ? null : projects.map((k: any) => new SecretProjectResponse(k)); + this.secrets = secrets == null ? [] : secrets.map((dr: any) => new SecretListItemResponse(dr)); + } +} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/secrets/responses/secret.response.ts b/bitwarden_license/bit-web/src/app/secrets-manager/secrets/responses/secret.response.ts new file mode 100644 index 00000000000..c873ac62069 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/secrets/responses/secret.response.ts @@ -0,0 +1,22 @@ +import { BaseResponse } from "@bitwarden/common/models/response/base.response"; + +export class SecretResponse extends BaseResponse { + id: string; + organizationId: string; + name: string; + value: string; + note: string; + creationDate: string; + revisionDate: string; + + constructor(response: any) { + super(response); + this.id = this.getResponseProperty("Id"); + this.organizationId = this.getResponseProperty("OrganizationId"); + this.name = this.getResponseProperty("Key"); + this.value = this.getResponseProperty("Value"); + this.note = this.getResponseProperty("Note"); + this.creationDate = this.getResponseProperty("CreationDate"); + this.revisionDate = this.getResponseProperty("RevisionDate"); + } +} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/secrets/secret.service.ts b/bitwarden_license/bit-web/src/app/secrets-manager/secrets/secret.service.ts new file mode 100644 index 00000000000..1b87652ee68 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/secrets/secret.service.ts @@ -0,0 +1,193 @@ +import { Injectable } from "@angular/core"; +import { Subject } from "rxjs"; + +import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { CryptoService } from "@bitwarden/common/abstractions/crypto.service"; +import { EncryptService } from "@bitwarden/common/abstractions/encrypt.service"; +import { EncString } from "@bitwarden/common/models/domain/enc-string"; +import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetric-crypto-key"; + +import { SecretListView } from "../models/view/secret-list.view"; +import { SecretProjectView } from "../models/view/secret-project.view"; +import { SecretView } from "../models/view/secret.view"; + +import { SecretRequest } from "./requests/secret.request"; +import { SecretListItemResponse } from "./responses/secret-list-item.response"; +import { SecretProjectResponse } from "./responses/secret-project.response"; +import { SecretWithProjectsListResponse } from "./responses/secret-with-projects-list.response"; +import { SecretResponse } from "./responses/secret.response"; + +@Injectable({ + providedIn: "root", +}) +export class SecretService { + protected _secret: Subject = new Subject(); + + secret$ = this._secret.asObservable(); + + constructor( + private cryptoService: CryptoService, + private apiService: ApiService, + private encryptService: EncryptService + ) {} + + async getBySecretId(secretId: string): Promise { + const r = await this.apiService.send("GET", "/secrets/" + secretId, null, true, true); + const secretResponse = new SecretResponse(r); + return await this.createSecretView(secretResponse); + } + + async getSecrets(organizationId: string): Promise { + const r = await this.apiService.send( + "GET", + "/organizations/" + organizationId + "/secrets", + null, + true, + true + ); + + const results = new SecretWithProjectsListResponse(r); + return await this.createSecretsListView(organizationId, results); + } + + async getSecretsByProject(organizationId: string, projectId: string): Promise { + const r = await this.apiService.send( + "GET", + "/projects/" + projectId + "/secrets", + null, + true, + true + ); + + const results = new SecretWithProjectsListResponse(r); + return await this.createSecretsListView(organizationId, results); + } + + async create(organizationId: string, secretView: SecretView, projectId?: string) { + const request = await this.getSecretRequest(organizationId, secretView, projectId); + const r = await this.apiService.send( + "POST", + "/organizations/" + organizationId + "/secrets", + request, + true, + true + ); + this._secret.next(await this.createSecretView(new SecretResponse(r))); + } + + async update(organizationId: string, secretView: SecretView) { + const request = await this.getSecretRequest(organizationId, secretView); + const r = await this.apiService.send("PUT", "/secrets/" + secretView.id, request, true, true); + this._secret.next(await this.createSecretView(new SecretResponse(r))); + } + + async delete(secretIds: string[]) { + const r = await this.apiService.send("POST", "/secrets/delete", secretIds, true, true); + + const responseErrors: string[] = []; + r.data.forEach((element: { error: string }) => { + if (element.error) { + responseErrors.push(element.error); + } + }); + + // TODO waiting to hear back on how to display multiple errors. + // for now send as a list of strings to be displayed in toast. + if (responseErrors?.length >= 1) { + throw new Error(responseErrors.join(",")); + } + + this._secret.next(null); + } + + private async getOrganizationKey(organizationId: string): Promise { + return await this.cryptoService.getOrgKey(organizationId); + } + + private async getSecretRequest( + organizationId: string, + secretView: SecretView, + projectId?: string + ): Promise { + const orgKey = await this.getOrganizationKey(organizationId); + const request = new SecretRequest(); + const [key, value, note] = await Promise.all([ + this.encryptService.encrypt(secretView.name, orgKey), + this.encryptService.encrypt(secretView.value, orgKey), + this.encryptService.encrypt(secretView.note, orgKey), + ]); + request.key = key.encryptedString; + request.value = value.encryptedString; + request.note = note.encryptedString; + request.projectId = projectId; + return request; + } + + private async createSecretView(secretResponse: SecretResponse): Promise { + const orgKey = await this.getOrganizationKey(secretResponse.organizationId); + + const secretView = new SecretView(); + secretView.id = secretResponse.id; + secretView.organizationId = secretResponse.organizationId; + secretView.creationDate = secretResponse.creationDate; + secretView.revisionDate = secretResponse.revisionDate; + + const [name, value, note] = await Promise.all([ + this.encryptService.decryptToUtf8(new EncString(secretResponse.name), orgKey), + this.encryptService.decryptToUtf8(new EncString(secretResponse.value), orgKey), + this.encryptService.decryptToUtf8(new EncString(secretResponse.note), orgKey), + ]); + secretView.name = name; + secretView.value = value; + secretView.note = note; + + return secretView; + } + + private async createSecretsListView( + organizationId: string, + secrets: SecretWithProjectsListResponse + ): Promise { + const orgKey = await this.getOrganizationKey(organizationId); + + const projectsMappedToSecretsView = this.decryptProjectsMappedToSecrets( + orgKey, + secrets.projects + ); + + return await Promise.all( + secrets.secrets.map(async (s: SecretListItemResponse) => { + const secretListView = new SecretListView(); + secretListView.id = s.id; + secretListView.organizationId = s.organizationId; + secretListView.name = await this.encryptService.decryptToUtf8( + new EncString(s.name), + orgKey + ); + secretListView.creationDate = s.creationDate; + secretListView.revisionDate = s.revisionDate; + secretListView.projects = (await projectsMappedToSecretsView).filter((p) => + s.projects.includes(p.id) + ); + return secretListView; + }) + ); + } + + private async decryptProjectsMappedToSecrets( + orgKey: SymmetricCryptoKey, + projects: SecretProjectResponse[] + ): Promise { + return await Promise.all( + projects.map(async (s: SecretProjectResponse) => { + const projectsMappedToSecretView = new SecretProjectView(); + projectsMappedToSecretView.id = s.id; + projectsMappedToSecretView.name = await this.encryptService.decryptToUtf8( + new EncString(s.name), + orgKey + ); + return projectsMappedToSecretView; + }) + ); + } +} diff --git a/bitwarden_license/bit-web/src/app/sm/secrets/secrets-routing.module.ts b/bitwarden_license/bit-web/src/app/secrets-manager/secrets/secrets-routing.module.ts similarity index 100% rename from bitwarden_license/bit-web/src/app/sm/secrets/secrets-routing.module.ts rename to bitwarden_license/bit-web/src/app/secrets-manager/secrets/secrets-routing.module.ts diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/secrets/secrets.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/secrets/secrets.component.html new file mode 100644 index 00000000000..f5ebd11ebc9 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/secrets/secrets.component.html @@ -0,0 +1,7 @@ + + diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/secrets/secrets.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/secrets/secrets.component.ts new file mode 100644 index 00000000000..367416ea892 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/secrets/secrets.component.ts @@ -0,0 +1,76 @@ +import { Component, OnInit } from "@angular/core"; +import { ActivatedRoute } from "@angular/router"; +import { combineLatestWith, Observable, startWith, switchMap } from "rxjs"; + +import { DialogService } from "@bitwarden/components"; + +import { SecretListView } from "../models/view/secret-list.view"; + +import { + SecretDeleteDialogComponent, + SecretDeleteOperation, +} from "./dialog/secret-delete.component"; +import { + OperationType, + SecretDialogComponent, + SecretOperation, +} from "./dialog/secret-dialog.component"; +import { SecretService } from "./secret.service"; + +@Component({ + selector: "sm-secrets", + templateUrl: "./secrets.component.html", +}) +export class SecretsComponent implements OnInit { + secrets$: Observable; + + private organizationId: string; + + constructor( + private route: ActivatedRoute, + private secretService: SecretService, + private dialogService: DialogService + ) {} + + ngOnInit() { + this.secrets$ = this.secretService.secret$.pipe( + startWith(null), + combineLatestWith(this.route.params), + switchMap(async ([_, params]) => { + this.organizationId = params.organizationId; + return await this.getSecrets(); + }) + ); + } + + private async getSecrets(): Promise { + return await this.secretService.getSecrets(this.organizationId); + } + + openEditSecret(secretId: string) { + this.dialogService.open(SecretDialogComponent, { + data: { + organizationId: this.organizationId, + operation: OperationType.Edit, + secretId: secretId, + }, + }); + } + + openDeleteSecret(secretIds: string[]) { + this.dialogService.open(SecretDeleteDialogComponent, { + data: { + secretIds: secretIds, + }, + }); + } + + openNewSecretDialog() { + this.dialogService.open(SecretDialogComponent, { + data: { + organizationId: this.organizationId, + operation: OperationType.Add, + }, + }); + } +} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/secrets/secrets.module.ts b/bitwarden_license/bit-web/src/app/secrets-manager/secrets/secrets.module.ts new file mode 100644 index 00000000000..a137d9fdd5d --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/secrets/secrets.module.ts @@ -0,0 +1,15 @@ +import { NgModule } from "@angular/core"; + +import { SecretsManagerSharedModule } from "../shared/sm-shared.module"; + +import { SecretDeleteDialogComponent } from "./dialog/secret-delete.component"; +import { SecretDialogComponent } from "./dialog/secret-dialog.component"; +import { SecretsRoutingModule } from "./secrets-routing.module"; +import { SecretsComponent } from "./secrets.component"; + +@NgModule({ + imports: [SecretsManagerSharedModule, SecretsRoutingModule], + declarations: [SecretsComponent, SecretDialogComponent, SecretDeleteDialogComponent], + providers: [], +}) +export class SecretsModule {} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/access-list.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/access-list.component.html new file mode 100644 index 00000000000..dd53ccd08b4 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/access-list.component.html @@ -0,0 +1,77 @@ +
+ +
+ + + {{ "accessTokensNoItemsTitle" | i18n }} + {{ "accessTokensNoItemsDesc" | i18n }} + + + + + + + + + + {{ "name" | i18n }} + {{ "permissions" | i18n }} + {{ "expires" | i18n }} + {{ "lastEdited" | i18n }} + + + + + + + + + + + {{ token.name }} + {{ permission(token) | i18n }} + + {{ token.expireAt === null ? ("never" | i18n) : (token.expireAt | date: "medium") }} + + {{ token.revisionDate | date: "medium" }} + + + + + + + + + + diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/access-list.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/access-list.component.ts new file mode 100644 index 00000000000..483835b2bb6 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/access-list.component.ts @@ -0,0 +1,40 @@ +import { SelectionModel } from "@angular/cdk/collections"; +import { Component, EventEmitter, Input, Output } from "@angular/core"; + +import { AccessTokenView } from "../models/view/access-token.view"; + +@Component({ + selector: "sm-access-list", + templateUrl: "./access-list.component.html", +}) +export class AccessListComponent { + @Input() + get tokens(): AccessTokenView[] { + return this._tokens; + } + set tokens(secrets: AccessTokenView[]) { + this.selection.clear(); + this._tokens = secrets; + } + private _tokens: AccessTokenView[]; + + @Output() newAccessTokenEvent = new EventEmitter(); + + protected selection = new SelectionModel(true, []); + + isAllSelected() { + const numSelected = this.selection.selected.length; + const numRows = this.tokens.length; + return numSelected === numRows; + } + + toggleAll() { + this.isAllSelected() + ? this.selection.clear() + : this.selection.select(...this.tokens.map((s) => s.id)); + } + + protected permission(token: AccessTokenView) { + return "canRead"; + } +} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/access-tokens.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/access-tokens.component.html new file mode 100644 index 00000000000..3122d13ae21 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/access-tokens.component.html @@ -0,0 +1,4 @@ + diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/access-tokens.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/access-tokens.component.ts new file mode 100644 index 00000000000..8729dcb0150 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/access-tokens.component.ts @@ -0,0 +1,61 @@ +import { Component, OnInit } from "@angular/core"; +import { ActivatedRoute } from "@angular/router"; +import { combineLatestWith, Observable, startWith, switchMap } from "rxjs"; + +import { DialogService } from "@bitwarden/components"; + +import { ServiceAccountView } from "../../models/view/service-account.view"; +import { AccessTokenView } from "../models/view/access-token.view"; + +import { AccessService } from "./access.service"; +import { + AccessTokenOperation, + AccessTokenCreateDialogComponent, +} from "./dialogs/access-token-create-dialog.component"; + +@Component({ + selector: "sm-access-tokens", + templateUrl: "./access-tokens.component.html", +}) +export class AccessTokenComponent implements OnInit { + accessTokens$: Observable; + + private serviceAccountId: string; + private organizationId: string; + + constructor( + private route: ActivatedRoute, + private accessService: AccessService, + private dialogService: DialogService + ) {} + + ngOnInit() { + this.accessTokens$ = this.accessService.accessToken$.pipe( + startWith(null), + combineLatestWith(this.route.params), + switchMap(async ([_, params]) => { + this.organizationId = params.organizationId; + this.serviceAccountId = params.serviceAccountId; + return await this.getAccessTokens(); + }) + ); + } + + private async getAccessTokens(): Promise { + return await this.accessService.getAccessTokens(this.organizationId, this.serviceAccountId); + } + + async openNewAccessTokenDialog() { + // TODO once service account names are implemented in service account contents page pass in here. + const serviceAccountView = new ServiceAccountView(); + serviceAccountView.id = this.serviceAccountId; + serviceAccountView.name = "placeholder"; + + this.dialogService.open(AccessTokenCreateDialogComponent, { + data: { + organizationId: this.organizationId, + serviceAccountView: serviceAccountView, + }, + }); + } +} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/access.service.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/access.service.ts new file mode 100644 index 00000000000..a5ad5a06903 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/access.service.ts @@ -0,0 +1,128 @@ +import { Injectable } from "@angular/core"; +import { Subject } from "rxjs"; + +import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { CryptoService } from "@bitwarden/common/abstractions/crypto.service"; +import { CryptoFunctionService } from "@bitwarden/common/abstractions/cryptoFunction.service"; +import { EncryptService } from "@bitwarden/common/abstractions/encrypt.service"; +import { Utils } from "@bitwarden/common/misc/utils"; +import { EncString } from "@bitwarden/common/models/domain/enc-string"; +import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetric-crypto-key"; +import { ListResponse } from "@bitwarden/common/models/response/list.response"; + +import { AccessTokenRequest } from "../models/requests/access-token.request"; +import { AccessTokenCreationResponse } from "../models/responses/access-token-creation.response"; +import { AccessTokenResponse } from "../models/responses/access-tokens.response"; +import { AccessTokenView } from "../models/view/access-token.view"; + +@Injectable({ + providedIn: "root", +}) +export class AccessService { + private readonly _accessTokenVersion = "0"; + protected _accessToken: Subject = new Subject(); + + accessToken$ = this._accessToken.asObservable(); + + constructor( + private cryptoService: CryptoService, + private apiService: ApiService, + private cryptoFunctionService: CryptoFunctionService, + private encryptService: EncryptService + ) {} + + async getAccessTokens( + organizationId: string, + serviceAccountId: string + ): Promise { + const r = await this.apiService.send( + "GET", + "/service-accounts/" + serviceAccountId + "/access-tokens", + null, + true, + true + ); + const results = new ListResponse(r, AccessTokenResponse); + + return await this.createAccessTokenViews(organizationId, results.data); + } + + async createAccessToken( + organizationId: string, + serviceAccountId: string, + accessTokenView: AccessTokenView + ): Promise { + const keyMaterial = await this.cryptoFunctionService.randomBytes(16); + const key = await this.cryptoFunctionService.hkdf( + keyMaterial, + "bitwarden-accesstoken", + "sm-access-token", + 64, + "sha256" + ); + const encryptionKey = new SymmetricCryptoKey(key); + + const request = await this.createAccessTokenRequest( + organizationId, + encryptionKey, + accessTokenView + ); + const r = await this.apiService.send( + "POST", + "/service-accounts/" + serviceAccountId + "/access-tokens", + request, + true, + true + ); + const result = new AccessTokenCreationResponse(r); + this._accessToken.next(null); + const b64Key = Utils.fromBufferToB64(keyMaterial); + return `${this._accessTokenVersion}.${result.id}.${result.clientSecret}:${b64Key}`; + } + + private async createAccessTokenRequest( + organizationId: string, + encryptionKey: SymmetricCryptoKey, + accessTokenView: AccessTokenView + ): Promise { + const organizationKey = await this.getOrganizationKey(organizationId); + const accessTokenRequest = new AccessTokenRequest(); + const [name, encryptedPayload, key] = await Promise.all([ + await this.encryptService.encrypt(accessTokenView.name, organizationKey), + await this.encryptService.encrypt( + JSON.stringify({ encryptionKey: organizationKey.keyB64 }), + encryptionKey + ), + await this.encryptService.encrypt(encryptionKey.keyB64, organizationKey), + ]); + + accessTokenRequest.name = name; + accessTokenRequest.encryptedPayload = encryptedPayload; + accessTokenRequest.key = key; + accessTokenRequest.expireAt = accessTokenView.expireAt; + return accessTokenRequest; + } + + private async getOrganizationKey(organizationId: string): Promise { + return await this.cryptoService.getOrgKey(organizationId); + } + + private async createAccessTokenViews( + organizationId: string, + accessTokenResponses: AccessTokenResponse[] + ): Promise { + const orgKey = await this.getOrganizationKey(organizationId); + return await Promise.all( + accessTokenResponses.map(async (s) => { + const view = new AccessTokenView(); + view.id = s.id; + view.name = await this.encryptService.decryptToUtf8(new EncString(s.name), orgKey); + view.scopes = s.scopes; + view.expireAt = s.expireAt ? new Date(s.expireAt) : null; + view.creationDate = new Date(s.creationDate); + view.revisionDate = new Date(s.revisionDate); + return view; + }) + ); + } +} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/dialogs/access-token-create-dialog.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/dialogs/access-token-create-dialog.component.html new file mode 100644 index 00000000000..45a5c2ee7bf --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/dialogs/access-token-create-dialog.component.html @@ -0,0 +1,44 @@ +
+ + + {{ "createAccessToken" | i18n }} + + {{ data.serviceAccountView.name }} + + + +
+ + {{ "name" | i18n }} + + +
+ + {{ "permissions" | i18n }} + + + + {{ "accessTokenPermissionsBetaNotification" | i18n }} + +
+ +
+ +
+ + +
+
+
diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/dialogs/access-token-create-dialog.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/dialogs/access-token-create-dialog.component.ts new file mode 100644 index 00000000000..93ef185f6aa --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/dialogs/access-token-create-dialog.component.ts @@ -0,0 +1,85 @@ +import { DialogRef, DIALOG_DATA } from "@angular/cdk/dialog"; +import { Component, Inject, OnInit } from "@angular/core"; +import { FormControl, FormGroup, Validators } from "@angular/forms"; + +import { DialogService } from "@bitwarden/components"; + +import { ServiceAccountView } from "../../../models/view/service-account.view"; +import { AccessTokenView } from "../../models/view/access-token.view"; +import { AccessService } from "../access.service"; + +import { AccessTokenDetails, AccessTokenDialogComponent } from "./access-token-dialog.component"; + +export interface AccessTokenOperation { + organizationId: string; + serviceAccountView: ServiceAccountView; +} + +@Component({ + selector: "sm-access-token-create-dialog", + templateUrl: "./access-token-create-dialog.component.html", +}) +export class AccessTokenCreateDialogComponent implements OnInit { + protected formGroup = new FormGroup({ + name: new FormControl("", [Validators.required, Validators.maxLength(80)]), + expirationDateControl: new FormControl(null), + }); + protected loading = false; + + expirationDayOptions = [7, 30, 60]; + + constructor( + public dialogRef: DialogRef, + @Inject(DIALOG_DATA) public data: AccessTokenOperation, + private dialogService: DialogService, + private accessService: AccessService + ) {} + + async ngOnInit() { + if ( + !this.data.organizationId || + !this.data.serviceAccountView?.id || + !this.data.serviceAccountView?.name + ) { + this.dialogRef.close(); + throw new Error( + `The access token create dialog was not called with the appropriate operation values.` + ); + } + } + + submit = async () => { + this.formGroup.markAllAsTouched(); + if (this.formGroup.invalid) { + return; + } + const accessTokenView = new AccessTokenView(); + accessTokenView.name = this.formGroup.value.name; + accessTokenView.expireAt = this.formGroup.value.expirationDateControl; + const accessToken = await this.accessService.createAccessToken( + this.data.organizationId, + this.data.serviceAccountView.id, + accessTokenView + ); + this.openAccessTokenDialog( + this.data.serviceAccountView.name, + accessToken, + accessTokenView.expireAt + ); + this.dialogRef.close(); + }; + + private openAccessTokenDialog( + serviceAccountName: string, + accessToken: string, + expirationDate?: Date + ) { + this.dialogService.open(AccessTokenDialogComponent, { + data: { + subTitle: serviceAccountName, + expirationDate: expirationDate, + accessToken: accessToken, + }, + }); + } +} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/dialogs/access-token-dialog.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/dialogs/access-token-dialog.component.html new file mode 100644 index 00000000000..90e67ac865a --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/dialogs/access-token-dialog.component.html @@ -0,0 +1,30 @@ + + + {{ "createAccessToken" | i18n }} + + {{ data.subTitle }} + + + +
+ + {{ "downloadAccessToken" | i18n }}
+ {{ "expiresOnAccessToken" | i18n }} + {{ data.expirationDate === null ? ("never" | i18n) : (data.expirationDate | date: "medium") }} +
+ + + {{ "accessToken" | i18n }} + + + {{ "expiresOnAccessToken" | i18n }} + {{ data.expirationDate === null ? ("never" | i18n) : (data.expirationDate | date: "medium") }} +
+ +
+ +
+
diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/dialogs/access-token-dialog.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/dialogs/access-token-dialog.component.ts new file mode 100644 index 00000000000..620598fba7f --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/dialogs/access-token-dialog.component.ts @@ -0,0 +1,44 @@ +import { DialogRef, DIALOG_DATA } from "@angular/cdk/dialog"; +import { Component, Inject, OnInit } from "@angular/core"; + +import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; +import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; + +export interface AccessTokenDetails { + subTitle: string; + expirationDate?: Date; + accessToken: string; +} + +@Component({ + selector: "sm-access-token-dialog", + templateUrl: "./access-token-dialog.component.html", +}) +export class AccessTokenDialogComponent implements OnInit { + constructor( + public dialogRef: DialogRef, + @Inject(DIALOG_DATA) public data: AccessTokenDetails, + private platformUtilsService: PlatformUtilsService, + private i18nService: I18nService + ) { + this.dialogRef.disableClose = true; + } + + ngOnInit(): void { + // TODO remove null checks once strictNullChecks in TypeScript is turned on. + if (!this.data.subTitle || !this.data.accessToken) { + this.dialogRef.close(); + throw new Error("The access token dialog was not called with the appropriate values."); + } + } + + copyAccessToken(): void { + this.platformUtilsService.copyToClipboard(this.data.accessToken); + this.platformUtilsService.showToast( + "success", + null, + this.i18nService.t("accessTokenCreatedAndCopied") + ); + this.dialogRef.close(); + } +} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/dialogs/expiration-options.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/dialogs/expiration-options.component.html new file mode 100644 index 00000000000..f1aaf2cc31c --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/dialogs/expiration-options.component.html @@ -0,0 +1,21 @@ + + + {{ "expires" | i18n }} + + + + {{ "expirationDate" | i18n }} + + + diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/dialogs/expiration-options.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/dialogs/expiration-options.component.ts new file mode 100644 index 00000000000..132494ef47e --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/dialogs/expiration-options.component.ts @@ -0,0 +1,114 @@ +import { DatePipe } from "@angular/common"; +import { Component, Input, OnDestroy, OnInit } from "@angular/core"; +import { + AbstractControl, + ControlValueAccessor, + FormControl, + FormGroup, + NG_VALIDATORS, + NG_VALUE_ACCESSOR, + ValidationErrors, + Validator, + Validators, +} from "@angular/forms"; +import { Subject, takeUntil } from "rxjs"; + +@Component({ + selector: "sm-expiration-options", + templateUrl: "./expiration-options.component.html", + providers: [ + { + provide: NG_VALUE_ACCESSOR, + multi: true, + useExisting: ExpirationOptionsComponent, + }, + { + provide: NG_VALIDATORS, + multi: true, + useExisting: ExpirationOptionsComponent, + }, + ], +}) +export class ExpirationOptionsComponent + implements ControlValueAccessor, Validator, OnInit, OnDestroy +{ + private destroy$ = new Subject(); + + @Input() expirationDayOptions: number[]; + + @Input() set touched(val: boolean) { + if (val) { + this.form.markAllAsTouched(); + } + } + + currentDate = new Date(); + + protected form = new FormGroup({ + expires: new FormControl("never", [Validators.required]), + expireDateTime: new FormControl("", [Validators.required]), + }); + + constructor(private datePipe: DatePipe) {} + + async ngOnInit() { + this.form.valueChanges.pipe(takeUntil(this.destroy$)).subscribe(() => { + this._onChange(this.getExpiresDate()); + }); + } + + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } + + private _onChange = (_value: Date | null): void => undefined; + registerOnChange(fn: (value: Date | null) => void): void { + this._onChange = fn; + } + + onTouched = (): void => undefined; + registerOnTouched(fn: () => void): void { + this.onTouched = fn; + } + + validate(control: AbstractControl): ValidationErrors { + if ( + (this.form.value.expires == "custom" && this.form.value.expireDateTime) || + this.form.value.expires !== "custom" + ) { + return null; + } + return { + required: true, + }; + } + + writeValue(value: Date | null): void { + if (value == null) { + this.form.setValue({ expires: "never", expireDateTime: null }); + } + if (value) { + this.form.setValue({ + expires: "custom", + expireDateTime: this.datePipe.transform(value, "YYYY-MM-ddThh:mm"), + }); + } + } + + setDisabledState?(isDisabled: boolean): void { + isDisabled ? this.form.disable() : this.form.enable(); + } + + private getExpiresDate(): Date | null { + if (this.form.value.expires == "never") { + return null; + } + if (this.form.value.expires == "custom") { + return new Date(this.form.value.expireDateTime); + } + const currentDate = new Date(); + currentDate.setDate(currentDate.getDate() + Number(this.form.value.expires)); + return currentDate; + } +} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/dialog/service-account-dialog.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/dialog/service-account-dialog.component.html new file mode 100644 index 00000000000..9c41d839990 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/dialog/service-account-dialog.component.html @@ -0,0 +1,50 @@ +
+ + {{ "newServiceAccount" | i18n }} +
+ + {{ "serviceAccountName" | i18n }} + + +

{{ "smAccess" | i18n }}

+ + + {{ "newSaSelectAccess" | i18n }} + + + + + + + {{ "projectCommaSecret" | i18n }} + {{ "permissions" | i18n }} + + + + + + example + example + + + +
+
+ + +
+
+
diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/dialog/service-account-dialog.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/dialog/service-account-dialog.component.ts new file mode 100644 index 00000000000..642136bfa8f --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/dialog/service-account-dialog.component.ts @@ -0,0 +1,69 @@ +import { DialogRef, DIALOG_DATA } from "@angular/cdk/dialog"; +import { Component, Inject, OnInit } from "@angular/core"; +import { FormControl, FormGroup, Validators } from "@angular/forms"; + +import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; +import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; + +import { ProjectListView } from "../../models/view/project-list.view"; +import { SecretListView } from "../../models/view/secret-list.view"; +import { ServiceAccountView } from "../../models/view/service-account.view"; +import { ProjectService } from "../../projects/project.service"; +import { SecretService } from "../../secrets/secret.service"; +import { ServiceAccountService } from "../service-account.service"; + +export interface ServiceAccountOperation { + organizationId: string; +} + +@Component({ + selector: "sm-service-account-dialog", + templateUrl: "./service-account-dialog.component.html", +}) +export class ServiceAccountDialogComponent implements OnInit { + projects: ProjectListView[]; + secrets: SecretListView[]; + + formGroup = new FormGroup({ + name: new FormControl("", [Validators.required]), + }); + + constructor( + public dialogRef: DialogRef, + @Inject(DIALOG_DATA) private data: ServiceAccountOperation, + private serviceAccountService: ServiceAccountService, + private i18nService: I18nService, + private platformUtilsService: PlatformUtilsService, + private projectService: ProjectService, + private secretService: SecretService + ) {} + + async ngOnInit() { + this.projects = await this.projectService.getProjects(this.data.organizationId); + this.secrets = await this.secretService.getSecrets(this.data.organizationId); + } + + submit = async () => { + this.formGroup.markAllAsTouched(); + + if (this.formGroup.invalid) { + return; + } + + const serviceAccountView = this.getServiceAccountView(); + await this.serviceAccountService.create(this.data.organizationId, serviceAccountView); + this.platformUtilsService.showToast( + "success", + null, + this.i18nService.t("serviceAccountCreated") + ); + this.dialogRef.close(); + }; + + private getServiceAccountView() { + const serviceAccountView = new ServiceAccountView(); + serviceAccountView.organizationId = this.data.organizationId; + serviceAccountView.name = this.formGroup.value.name; + return serviceAccountView; + } +} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/models/requests/access-token.request.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/models/requests/access-token.request.ts new file mode 100644 index 00000000000..c570d3d2051 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/models/requests/access-token.request.ts @@ -0,0 +1,8 @@ +import { EncString } from "@bitwarden/common/models/domain/enc-string"; + +export class AccessTokenRequest { + name: EncString; + encryptedPayload: EncString; + key: EncString; + expireAt: Date; +} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/models/requests/service-account.request.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/models/requests/service-account.request.ts new file mode 100644 index 00000000000..8c62324a783 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/models/requests/service-account.request.ts @@ -0,0 +1,5 @@ +import { EncString } from "@bitwarden/common/models/domain/enc-string"; + +export class ServiceAccountRequest { + name: EncString; +} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/models/responses/access-token-creation.response.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/models/responses/access-token-creation.response.ts new file mode 100644 index 00000000000..b5b1c27b6f6 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/models/responses/access-token-creation.response.ts @@ -0,0 +1,20 @@ +import { BaseResponse } from "@bitwarden/common/models/response/base.response"; + +export class AccessTokenCreationResponse extends BaseResponse { + id: string; + name: string; + clientSecret: string; + expireAt?: string; + creationDate: string; + revisionDate: string; + + constructor(response: any) { + super(response); + this.id = this.getResponseProperty("Id"); + this.name = this.getResponseProperty("Name"); + this.clientSecret = this.getResponseProperty("ClientSecret"); + this.expireAt = this.getResponseProperty("ExpireAt"); + this.creationDate = this.getResponseProperty("CreationDate"); + this.revisionDate = this.getResponseProperty("RevisionDate"); + } +} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/models/responses/access-tokens.response.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/models/responses/access-tokens.response.ts new file mode 100644 index 00000000000..4a3fc16d037 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/models/responses/access-tokens.response.ts @@ -0,0 +1,20 @@ +import { BaseResponse } from "@bitwarden/common/models/response/base.response"; + +export class AccessTokenResponse extends BaseResponse { + id: string; + name: string; + scopes: string[]; + expireAt?: string; + creationDate: string; + revisionDate: string; + + constructor(response: any) { + super(response); + this.id = this.getResponseProperty("Id"); + this.name = this.getResponseProperty("Name"); + this.scopes = this.getResponseProperty("Scopes"); + this.expireAt = this.getResponseProperty("ExpireAt"); + this.creationDate = this.getResponseProperty("CreationDate"); + this.revisionDate = this.getResponseProperty("RevisionDate"); + } +} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/models/responses/service-account.response.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/models/responses/service-account.response.ts new file mode 100644 index 00000000000..7ece18f7d87 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/models/responses/service-account.response.ts @@ -0,0 +1,18 @@ +import { BaseResponse } from "@bitwarden/common/models/response/base.response"; + +export class ServiceAccountResponse extends BaseResponse { + id: string; + organizationId: string; + name: string; + creationDate: string; + revisionDate: string; + + constructor(response: any) { + super(response); + this.id = this.getResponseProperty("Id"); + this.organizationId = this.getResponseProperty("OrganizationId"); + this.name = this.getResponseProperty("Name"); + this.creationDate = this.getResponseProperty("CreationDate"); + this.revisionDate = this.getResponseProperty("RevisionDate"); + } +} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/models/view/access-token.view.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/models/view/access-token.view.ts new file mode 100644 index 00000000000..96faa40b8f5 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/models/view/access-token.view.ts @@ -0,0 +1,10 @@ +import { View } from "@bitwarden/common/models/view/view"; + +export class AccessTokenView implements View { + id: string; + name: string; + scopes: string[]; + expireAt?: Date; + creationDate: Date; + revisionDate: Date; +} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-account.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-account.component.html new file mode 100644 index 00000000000..c36c831597b --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-account.component.html @@ -0,0 +1,9 @@ + + + + {{ "secrets" | i18n }} + {{ "people" | i18n }} + {{ "accessTokens" | i18n }} + + + diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-account.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-account.component.ts new file mode 100644 index 00000000000..f355ede8e43 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-account.component.ts @@ -0,0 +1,7 @@ +import { Component } from "@angular/core"; + +@Component({ + selector: "sm-service-account", + templateUrl: "./service-account.component.html", +}) +export class ServiceAccountComponent {} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-account.service.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-account.service.ts new file mode 100644 index 00000000000..4870ac12f29 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-account.service.ts @@ -0,0 +1,97 @@ +import { Injectable } from "@angular/core"; +import { Subject } from "rxjs"; + +import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { CryptoService } from "@bitwarden/common/abstractions/crypto.service"; +import { EncryptService } from "@bitwarden/common/abstractions/encrypt.service"; +import { EncString } from "@bitwarden/common/models/domain/enc-string"; +import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetric-crypto-key"; +import { ListResponse } from "@bitwarden/common/models/response/list.response"; + +import { ServiceAccountView } from "../models/view/service-account.view"; + +import { ServiceAccountRequest } from "./models/requests/service-account.request"; +import { ServiceAccountResponse } from "./models/responses/service-account.response"; + +@Injectable({ + providedIn: "root", +}) +export class ServiceAccountService { + protected _serviceAccount: Subject = new Subject(); + + serviceAccount$ = this._serviceAccount.asObservable(); + + constructor( + private cryptoService: CryptoService, + private apiService: ApiService, + private encryptService: EncryptService + ) {} + + async getServiceAccounts(organizationId: string): Promise { + const r = await this.apiService.send( + "GET", + "/organizations/" + organizationId + "/service-accounts", + null, + true, + true + ); + const results = new ListResponse(r, ServiceAccountResponse); + return await this.createServiceAccountViews(organizationId, results.data); + } + + async create(organizationId: string, serviceAccountView: ServiceAccountView) { + const orgKey = await this.getOrganizationKey(organizationId); + const request = await this.getServiceAccountRequest(orgKey, serviceAccountView); + const r = await this.apiService.send( + "POST", + "/organizations/" + organizationId + "/service-accounts", + request, + true, + true + ); + this._serviceAccount.next( + await this.createServiceAccountView(orgKey, new ServiceAccountResponse(r)) + ); + } + + private async getOrganizationKey(organizationId: string): Promise { + return await this.cryptoService.getOrgKey(organizationId); + } + + private async getServiceAccountRequest( + organizationKey: SymmetricCryptoKey, + serviceAccountView: ServiceAccountView + ) { + const request = new ServiceAccountRequest(); + request.name = await this.encryptService.encrypt(serviceAccountView.name, organizationKey); + return request; + } + + private async createServiceAccountView( + organizationKey: SymmetricCryptoKey, + serviceAccountResponse: ServiceAccountResponse + ): Promise { + const serviceAccountView = new ServiceAccountView(); + serviceAccountView.id = serviceAccountResponse.id; + serviceAccountView.organizationId = serviceAccountResponse.organizationId; + serviceAccountView.creationDate = serviceAccountResponse.creationDate; + serviceAccountView.revisionDate = serviceAccountResponse.revisionDate; + serviceAccountView.name = await this.encryptService.decryptToUtf8( + new EncString(serviceAccountResponse.name), + organizationKey + ); + return serviceAccountView; + } + + private async createServiceAccountViews( + organizationId: string, + serviceAccountResponses: ServiceAccountResponse[] + ): Promise { + const orgKey = await this.getOrganizationKey(organizationId); + return await Promise.all( + serviceAccountResponses.map(async (s: ServiceAccountResponse) => { + return await this.createServiceAccountView(orgKey, s); + }) + ); + } +} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts-list.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts-list.component.html new file mode 100644 index 00000000000..6badffdfc5a --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts-list.component.html @@ -0,0 +1,96 @@ +
+ +
+ + + {{ "serviceAccountsNoItemsTitle" | i18n }} + {{ "serviceAccountsNoItemsMessage" | i18n }} + + + + + + + + + + {{ "name" | i18n }} + {{ "secrets" | i18n }} + {{ "lastEdited" | i18n }} + + + + + + + + + + + + + + + + {{ serviceAccount.name }} + + + + + 0 + + {{ serviceAccount.revisionDate | date: "medium" }} + + + + + + + {{ "viewServiceAccount" | i18n }} + + + + + + + + + + diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts-list.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts-list.component.ts new file mode 100644 index 00000000000..174af95368f --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts-list.component.ts @@ -0,0 +1,58 @@ +import { SelectionModel } from "@angular/cdk/collections"; +import { Component, EventEmitter, Input, OnDestroy, Output } from "@angular/core"; +import { Subject, takeUntil } from "rxjs"; + +import { ServiceAccountView } from "../models/view/service-account.view"; + +@Component({ + selector: "sm-service-accounts-list", + templateUrl: "./service-accounts-list.component.html", +}) +export class ServiceAccountsListComponent implements OnDestroy { + @Input() + get serviceAccounts(): ServiceAccountView[] { + return this._serviceAccounts; + } + set serviceAccounts(serviceAccounts: ServiceAccountView[]) { + this.selection.clear(); + this._serviceAccounts = serviceAccounts; + } + private _serviceAccounts: ServiceAccountView[]; + + @Output() newServiceAccountEvent = new EventEmitter(); + @Output() deleteServiceAccountsEvent = new EventEmitter(); + @Output() onServiceAccountCheckedEvent = new EventEmitter(); + + private destroy$: Subject = new Subject(); + + selection = new SelectionModel(true, []); + + constructor() { + this.selection.changed + .pipe(takeUntil(this.destroy$)) + .subscribe((_) => this.onServiceAccountCheckedEvent.emit(this.selection.selected)); + } + + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } + + isAllSelected() { + const numSelected = this.selection.selected.length; + const numRows = this.serviceAccounts.length; + return numSelected === numRows; + } + + toggleAll() { + this.isAllSelected() + ? this.selection.clear() + : this.selection.select(...this.serviceAccounts.map((s) => s.id)); + } + + bulkDeleteServiceAccounts() { + if (this.selection.selected.length >= 1) { + this.deleteServiceAccountsEvent.emit(this.selection.selected); + } + } +} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts-routing.module.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts-routing.module.ts new file mode 100644 index 00000000000..1db2110fecb --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts-routing.module.ts @@ -0,0 +1,34 @@ +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { AccessTokenComponent } from "./access/access-tokens.component"; +import { ServiceAccountComponent } from "./service-account.component"; +import { ServiceAccountsComponent } from "./service-accounts.component"; + +const routes: Routes = [ + { + path: "", + component: ServiceAccountsComponent, + }, + { + path: ":serviceAccountId", + component: ServiceAccountComponent, + children: [ + { + path: "", + pathMatch: "full", + redirectTo: "access", + }, + { + path: "access", + component: AccessTokenComponent, + }, + ], + }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], +}) +export class ServiceAccountsRoutingModule {} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts.component.html new file mode 100644 index 00000000000..27e6873d7be --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts.component.html @@ -0,0 +1,5 @@ + + diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts.component.ts new file mode 100644 index 00000000000..5c24559c10c --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts.component.ts @@ -0,0 +1,52 @@ +import { Component, OnInit } from "@angular/core"; +import { ActivatedRoute } from "@angular/router"; +import { combineLatestWith, Observable, startWith, switchMap } from "rxjs"; + +import { DialogService } from "@bitwarden/components"; + +import { ServiceAccountView } from "../models/view/service-account.view"; + +import { + ServiceAccountDialogComponent, + ServiceAccountOperation, +} from "./dialog/service-account-dialog.component"; +import { ServiceAccountService } from "./service-account.service"; + +@Component({ + selector: "sm-service-accounts", + templateUrl: "./service-accounts.component.html", +}) +export class ServiceAccountsComponent implements OnInit { + serviceAccounts$: Observable; + + private organizationId: string; + + constructor( + private route: ActivatedRoute, + private dialogService: DialogService, + private serviceAccountService: ServiceAccountService + ) {} + + ngOnInit() { + this.serviceAccounts$ = this.serviceAccountService.serviceAccount$.pipe( + startWith(null), + combineLatestWith(this.route.params), + switchMap(async ([_, params]) => { + this.organizationId = params.organizationId; + return await this.getServiceAccounts(); + }) + ); + } + + openNewServiceAccountDialog() { + this.dialogService.open(ServiceAccountDialogComponent, { + data: { + organizationId: this.organizationId, + }, + }); + } + + private async getServiceAccounts(): Promise { + return await this.serviceAccountService.getServiceAccounts(this.organizationId); + } +} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts.module.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts.module.ts new file mode 100644 index 00000000000..768f4e66677 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts.module.ts @@ -0,0 +1,31 @@ +import { NgModule } from "@angular/core"; + +import { SecretsManagerSharedModule } from "../shared/sm-shared.module"; + +import { AccessListComponent } from "./access/access-list.component"; +import { AccessTokenComponent } from "./access/access-tokens.component"; +import { AccessTokenCreateDialogComponent } from "./access/dialogs/access-token-create-dialog.component"; +import { AccessTokenDialogComponent } from "./access/dialogs/access-token-dialog.component"; +import { ExpirationOptionsComponent } from "./access/dialogs/expiration-options.component"; +import { ServiceAccountDialogComponent } from "./dialog/service-account-dialog.component"; +import { ServiceAccountComponent } from "./service-account.component"; +import { ServiceAccountsListComponent } from "./service-accounts-list.component"; +import { ServiceAccountsRoutingModule } from "./service-accounts-routing.module"; +import { ServiceAccountsComponent } from "./service-accounts.component"; + +@NgModule({ + imports: [SecretsManagerSharedModule, ServiceAccountsRoutingModule], + declarations: [ + AccessListComponent, + ExpirationOptionsComponent, + AccessTokenComponent, + AccessTokenCreateDialogComponent, + AccessTokenDialogComponent, + ServiceAccountComponent, + ServiceAccountDialogComponent, + ServiceAccountsComponent, + ServiceAccountsListComponent, + ], + providers: [], +}) +export class ServiceAccountsModule {} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/shared/secrets-list.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/shared/secrets-list.component.html new file mode 100644 index 00000000000..bd8adde3d28 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/shared/secrets-list.component.html @@ -0,0 +1,115 @@ +
+ +
+ + + {{ "secretsNoItemsTitle" | i18n }} + {{ "secretsNoItemsMessage" | i18n }} + + + + + + + + + + + {{ "name" | i18n }} + {{ "projects" | i18n }} + {{ "lastEdited" | i18n }} + + + + + + + + + + + + + + {{ secret.name }} + + + {{ project.name }} + + + {{ secret.revisionDate | date: "medium" }} + + + + + + + + + + + + + + + + + + + diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/shared/secrets-list.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/shared/secrets-list.component.ts new file mode 100644 index 00000000000..daae75fe9d4 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/shared/secrets-list.component.ts @@ -0,0 +1,63 @@ +import { SelectionModel } from "@angular/cdk/collections"; +import { Component, EventEmitter, Input, OnDestroy, Output } from "@angular/core"; +import { Subject, takeUntil } from "rxjs"; + +import { SecretListView } from "../models/view/secret-list.view"; + +@Component({ + selector: "sm-secrets-list", + templateUrl: "./secrets-list.component.html", +}) +export class SecretsListComponent implements OnDestroy { + @Input() + get secrets(): SecretListView[] { + return this._secrets; + } + set secrets(secrets: SecretListView[]) { + this.selection.clear(); + this._secrets = secrets; + } + private _secrets: SecretListView[]; + + @Output() editSecretEvent = new EventEmitter(); + @Output() copySecretNameEvent = new EventEmitter(); + @Output() copySecretValueEvent = new EventEmitter(); + @Output() projectsEvent = new EventEmitter(); + @Output() onSecretCheckedEvent = new EventEmitter(); + @Output() deleteSecretsEvent = new EventEmitter(); + @Output() newSecretEvent = new EventEmitter(); + @Output() importSecretsEvent = new EventEmitter(); + + private destroy$: Subject = new Subject(); + + selection = new SelectionModel(true, []); + + constructor() { + this.selection.changed + .pipe(takeUntil(this.destroy$)) + .subscribe((_) => this.onSecretCheckedEvent.emit(this.selection.selected)); + } + + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } + + isAllSelected() { + const numSelected = this.selection.selected.length; + const numRows = this.secrets.length; + return numSelected === numRows; + } + + toggleAll() { + this.isAllSelected() + ? this.selection.clear() + : this.selection.select(...this.secrets.map((s) => s.id)); + } + + bulkDeleteSecrets() { + if (this.selection.selected.length >= 1) { + this.deleteSecretsEvent.emit(this.selection.selected); + } + } +} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/shared/sm-shared.module.ts b/bitwarden_license/bit-web/src/app/secrets-manager/shared/sm-shared.module.ts new file mode 100644 index 00000000000..1460d4082d1 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/shared/sm-shared.module.ts @@ -0,0 +1,35 @@ +import { NgModule } from "@angular/core"; + +import { SharedModule } from "@bitwarden/web-vault/app/shared"; + +import { BulkStatusDialogComponent } from "../layout/dialogs/bulk-status-dialog.component"; +import { FilterComponent } from "../layout/filter.component"; +import { HeaderComponent } from "../layout/header.component"; +import { NewMenuComponent } from "../layout/new-menu.component"; +import { NoItemsComponent } from "../layout/no-items.component"; + +import { SecretsListComponent } from "./secrets-list.component"; + +@NgModule({ + imports: [SharedModule], + exports: [ + SharedModule, + BulkStatusDialogComponent, + FilterComponent, + HeaderComponent, + NewMenuComponent, + NoItemsComponent, + SecretsListComponent, + ], + declarations: [ + BulkStatusDialogComponent, + FilterComponent, + HeaderComponent, + NewMenuComponent, + NoItemsComponent, + SecretsListComponent, + ], + providers: [], + bootstrap: [], +}) +export class SecretsManagerSharedModule {} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/sm-routing.module.ts b/bitwarden_license/bit-web/src/app/secrets-manager/sm-routing.module.ts new file mode 100644 index 00000000000..a54553e3477 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/sm-routing.module.ts @@ -0,0 +1,67 @@ +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { Organization } from "@bitwarden/common/models/domain/organization"; +import { OrganizationPermissionsGuard } from "@bitwarden/web-vault/app/organizations/guards/org-permissions.guard"; +import { buildFlaggedRoute } from "@bitwarden/web-vault/app/oss-routing.module"; + +import { LayoutComponent } from "./layout/layout.component"; +import { NavigationComponent } from "./layout/navigation.component"; +import { OverviewModule } from "./overview/overview.module"; +import { ProjectsModule } from "./projects/projects.module"; +import { SecretsModule } from "./secrets/secrets.module"; +import { ServiceAccountsModule } from "./service-accounts/service-accounts.module"; +import { SMGuard } from "./sm.guard"; + +const routes: Routes = [ + buildFlaggedRoute("secretsManager", { + path: ":organizationId", + component: LayoutComponent, + canActivate: [OrganizationPermissionsGuard, SMGuard], + data: { + organizationPermissions: (org: Organization) => org.canAccessSecretsManager, + }, + children: [ + { + path: "", + component: NavigationComponent, + outlet: "sidebar", + }, + { + path: "secrets", + loadChildren: () => SecretsModule, + data: { + title: "secrets", + searchTitle: "searchSecrets", + }, + }, + { + path: "projects", + loadChildren: () => ProjectsModule, + data: { + title: "projects", + searchTitle: "searchProjects", + }, + }, + { + path: "service-accounts", + loadChildren: () => ServiceAccountsModule, + data: { + title: "serviceAccounts", + searchTitle: "searchServiceAccounts", + }, + }, + { + path: "", + loadChildren: () => OverviewModule, + pathMatch: "full", + }, + ], + }), +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], +}) +export class SecretsManagerRoutingModule {} diff --git a/bitwarden_license/bit-web/src/app/sm/sm.guard.ts b/bitwarden_license/bit-web/src/app/secrets-manager/sm.guard.ts similarity index 52% rename from bitwarden_license/bit-web/src/app/sm/sm.guard.ts rename to bitwarden_license/bit-web/src/app/secrets-manager/sm.guard.ts index f9bc86f12b3..b937c3ec07f 100644 --- a/bitwarden_license/bit-web/src/app/sm/sm.guard.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/sm.guard.ts @@ -1,13 +1,10 @@ import { Injectable } from "@angular/core"; import { ActivatedRouteSnapshot, CanActivate } from "@angular/router"; -import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; - @Injectable() export class SMGuard implements CanActivate { - constructor(private platformUtilsService: PlatformUtilsService) {} - async canActivate(route: ActivatedRouteSnapshot) { - return this.platformUtilsService.isDev(); + // TODO: Verify org + return true; } } diff --git a/bitwarden_license/bit-web/src/app/sm/layout/navigation.component.html b/bitwarden_license/bit-web/src/app/sm/layout/navigation.component.html deleted file mode 100644 index b664d4fcaaf..00000000000 --- a/bitwarden_license/bit-web/src/app/sm/layout/navigation.component.html +++ /dev/null @@ -1,13 +0,0 @@ -Bitwarden - - diff --git a/bitwarden_license/bit-web/src/app/sm/layout/navigation.component.ts b/bitwarden_license/bit-web/src/app/sm/layout/navigation.component.ts deleted file mode 100644 index 8814d8490da..00000000000 --- a/bitwarden_license/bit-web/src/app/sm/layout/navigation.component.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Component } from "@angular/core"; - -@Component({ - selector: "sm-navigation", - templateUrl: "./navigation.component.html", -}) -export class NavigationComponent {} diff --git a/bitwarden_license/bit-web/src/app/sm/secrets/secrets.component.html b/bitwarden_license/bit-web/src/app/sm/secrets/secrets.component.html deleted file mode 100644 index e17c4fdbc1c..00000000000 --- a/bitwarden_license/bit-web/src/app/sm/secrets/secrets.component.html +++ /dev/null @@ -1 +0,0 @@ -

Secrets

diff --git a/bitwarden_license/bit-web/src/app/sm/secrets/secrets.component.ts b/bitwarden_license/bit-web/src/app/sm/secrets/secrets.component.ts deleted file mode 100644 index 9b12584e527..00000000000 --- a/bitwarden_license/bit-web/src/app/sm/secrets/secrets.component.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Component } from "@angular/core"; - -@Component({ - selector: "sm-secrets", - templateUrl: "./secrets.component.html", -}) -export class SecretsComponent {} diff --git a/bitwarden_license/bit-web/src/app/sm/secrets/secrets.module.ts b/bitwarden_license/bit-web/src/app/sm/secrets/secrets.module.ts deleted file mode 100644 index b98e2a8f7f9..00000000000 --- a/bitwarden_license/bit-web/src/app/sm/secrets/secrets.module.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { CommonModule } from "@angular/common"; -import { NgModule } from "@angular/core"; - -import { SecretsRoutingModule } from "./secrets-routing.module"; -import { SecretsComponent } from "./secrets.component"; - -@NgModule({ - imports: [CommonModule, SecretsRoutingModule], - declarations: [SecretsComponent], - providers: [], -}) -export class SecretsModule {} diff --git a/bitwarden_license/bit-web/src/app/sm/sm-routing.module.ts b/bitwarden_license/bit-web/src/app/sm/sm-routing.module.ts deleted file mode 100644 index 040efdb16fa..00000000000 --- a/bitwarden_license/bit-web/src/app/sm/sm-routing.module.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { NgModule } from "@angular/core"; -import { RouterModule, Routes } from "@angular/router"; - -import { LayoutComponent } from "./layout/layout.component"; -import { NavigationComponent } from "./layout/navigation.component"; -import { SecretsModule } from "./secrets/secrets.module"; -import { SMGuard } from "./sm.guard"; - -const routes: Routes = [ - { - path: "", - component: LayoutComponent, - canActivate: [SMGuard], - children: [ - { - path: "", - component: NavigationComponent, - outlet: "sidebar", - }, - { - path: "secrets", - loadChildren: () => SecretsModule, - }, - { - path: "", - redirectTo: "secrets", - pathMatch: "full", - }, - ], - }, -]; - -@NgModule({ - imports: [RouterModule.forChild(routes)], - exports: [RouterModule], -}) -export class SecretsManagerRoutingModule {} diff --git a/libs/common/src/models/data/organization.data.ts b/libs/common/src/models/data/organization.data.ts index 85658d97644..feb8036ed6c 100644 --- a/libs/common/src/models/data/organization.data.ts +++ b/libs/common/src/models/data/organization.data.ts @@ -22,6 +22,7 @@ export class OrganizationData { useScim: boolean; useCustomPermissions: boolean; useResetPassword: boolean; + useSecretsManager: boolean; selfHost: boolean; usersGetPremium: boolean; seats: number; @@ -63,6 +64,7 @@ export class OrganizationData { this.useScim = response.useScim; this.useCustomPermissions = response.useCustomPermissions; this.useResetPassword = response.useResetPassword; + this.useSecretsManager = response.useSecretsManager; this.selfHost = response.selfHost; this.usersGetPremium = response.usersGetPremium; this.seats = response.seats; diff --git a/libs/common/src/models/domain/organization.ts b/libs/common/src/models/domain/organization.ts index ef87b3dee16..b0fdfeb036c 100644 --- a/libs/common/src/models/domain/organization.ts +++ b/libs/common/src/models/domain/organization.ts @@ -24,6 +24,7 @@ export class Organization { useScim: boolean; useCustomPermissions: boolean; useResetPassword: boolean; + useSecretsManager: boolean; selfHost: boolean; usersGetPremium: boolean; seats: number; @@ -69,6 +70,7 @@ export class Organization { this.useScim = obj.useScim; this.useCustomPermissions = obj.useCustomPermissions; this.useResetPassword = obj.useResetPassword; + this.useSecretsManager = obj.useSecretsManager; this.selfHost = obj.selfHost; this.usersGetPremium = obj.usersGetPremium; this.seats = obj.seats; @@ -206,6 +208,10 @@ export class Organization { return this.providerId != null || this.providerName != null; } + get canAccessSecretsManager() { + return this.useSecretsManager; + } + static fromJSON(json: Jsonify) { if (json == null) { return null; diff --git a/libs/common/src/models/response/profile-organization.response.ts b/libs/common/src/models/response/profile-organization.response.ts index 19137848291..562b71710e6 100644 --- a/libs/common/src/models/response/profile-organization.response.ts +++ b/libs/common/src/models/response/profile-organization.response.ts @@ -20,6 +20,7 @@ export class ProfileOrganizationResponse extends BaseResponse { useScim: boolean; useCustomPermissions: boolean; useResetPassword: boolean; + useSecretsManager: boolean; selfHost: boolean; usersGetPremium: boolean; seats: number; @@ -62,6 +63,7 @@ export class ProfileOrganizationResponse extends BaseResponse { this.useScim = this.getResponseProperty("UseScim") ?? false; this.useCustomPermissions = this.getResponseProperty("UseCustomPermissions") ?? false; this.useResetPassword = this.getResponseProperty("UseResetPassword"); + this.useSecretsManager = this.getResponseProperty("UseSecretsManager"); this.selfHost = this.getResponseProperty("SelfHost"); this.usersGetPremium = this.getResponseProperty("UsersGetPremium"); this.seats = this.getResponseProperty("Seats"); diff --git a/libs/components/src/avatar/avatar.component.ts b/libs/components/src/avatar/avatar.component.ts index 9db2192c5b1..bd1a13de86a 100644 --- a/libs/components/src/avatar/avatar.component.ts +++ b/libs/components/src/avatar/avatar.component.ts @@ -7,7 +7,7 @@ type SizeTypes = "large" | "default" | "small"; const SizeClasses: Record = { large: ["tw-h-16", "tw-w-16"], - default: ["tw-h-12", "tw-w-12"], + default: ["tw-h-10", "tw-w-10"], small: ["tw-h-7", "tw-w-7"], }; diff --git a/libs/components/src/dialog/dialog.module.ts b/libs/components/src/dialog/dialog.module.ts index 3492a38f10e..421ebded717 100644 --- a/libs/components/src/dialog/dialog.module.ts +++ b/libs/components/src/dialog/dialog.module.ts @@ -18,7 +18,7 @@ import { SimpleDialogComponent } from "./simple-dialog/simple-dialog.component"; DialogComponent, SimpleDialogComponent, ], - exports: [CdkDialogModule, DialogComponent, SimpleDialogComponent], + exports: [CdkDialogModule, DialogComponent, SimpleDialogComponent, DialogCloseDirective], providers: [DialogService], }) export class DialogModule {} diff --git a/libs/components/src/dialog/dialog/dialog.component.ts b/libs/components/src/dialog/dialog/dialog.component.ts index a457ed23650..f82be6cd6db 100644 --- a/libs/components/src/dialog/dialog/dialog.component.ts +++ b/libs/components/src/dialog/dialog/dialog.component.ts @@ -9,7 +9,7 @@ export class DialogComponent { @Input() dialogSize: "small" | "default" | "large" = "default"; private _disablePadding: boolean; - @Input() set disablePadding(value: boolean) { + @Input() set disablePadding(value: boolean | string) { this._disablePadding = coerceBooleanProperty(value); } get disablePadding() { diff --git a/libs/components/src/index.ts b/libs/components/src/index.ts index abae5395575..86af709cda1 100644 --- a/libs/components/src/index.ts +++ b/libs/components/src/index.ts @@ -5,6 +5,7 @@ export * from "./banner"; export * from "./button"; export * from "./callout"; export * from "./checkbox"; +export * from "./color-password"; export * from "./dialog"; export * from "./form-field"; export * from "./icon-button"; @@ -12,8 +13,8 @@ export * from "./icon"; export * from "./link"; export * from "./menu"; export * from "./multi-select"; -export * from "./tabs"; +export * from "./navigation"; export * from "./table"; +export * from "./tabs"; export * from "./toggle-group"; -export * from "./color-password"; export * from "./utils/i18n-mock.service"; diff --git a/libs/components/src/navigation/index.ts b/libs/components/src/navigation/index.ts new file mode 100644 index 00000000000..240b832ec70 --- /dev/null +++ b/libs/components/src/navigation/index.ts @@ -0,0 +1 @@ +export * from "./navigation.module"; diff --git a/libs/components/src/navigation/nav-base.component.ts b/libs/components/src/navigation/nav-base.component.ts new file mode 100644 index 00000000000..ce9d74a65ca --- /dev/null +++ b/libs/components/src/navigation/nav-base.component.ts @@ -0,0 +1,47 @@ +import { Directive, EventEmitter, Input, Output } from "@angular/core"; + +/** + * Base class used in `NavGroupComponent` and `NavItemComponent` + */ +@Directive() +export abstract class NavBaseComponent { + /** + * Text to display in main content + */ + @Input() text: string; + + /** + * `aria-label` for main content + */ + @Input() ariaLabel: string; + + /** + * Optional icon, e.g. `"bwi-collection"` + */ + @Input() icon: string; + + /** + * Route to be passed to internal `routerLink` + */ + @Input() route: string | any[]; + + /** + * If this item is used within a tree, set `variant` to `"tree"` + */ + @Input() variant: "default" | "tree" = "default"; + + /** + * Depth level when nested inside of a `'tree'` variant + */ + @Input() treeDepth = 0; + + /** + * If `true`, do not change styles when nav item is active. + */ + @Input() hideActiveStyles = false; + + /** + * Fires when main content is clicked + */ + @Output() mainContentClicked: EventEmitter = new EventEmitter(); +} diff --git a/libs/components/src/navigation/nav-divider.component.html b/libs/components/src/navigation/nav-divider.component.html new file mode 100644 index 00000000000..4f77a18a37a --- /dev/null +++ b/libs/components/src/navigation/nav-divider.component.html @@ -0,0 +1 @@ +
diff --git a/libs/components/src/navigation/nav-divider.component.ts b/libs/components/src/navigation/nav-divider.component.ts new file mode 100644 index 00000000000..e0c5cf98b7a --- /dev/null +++ b/libs/components/src/navigation/nav-divider.component.ts @@ -0,0 +1,7 @@ +import { Component } from "@angular/core"; + +@Component({ + selector: "bit-nav-divider", + templateUrl: "./nav-divider.component.html", +}) +export class NavDividerComponent {} diff --git a/libs/components/src/navigation/nav-group.component.html b/libs/components/src/navigation/nav-group.component.html new file mode 100644 index 00000000000..65da2dd9e03 --- /dev/null +++ b/libs/components/src/navigation/nav-group.component.html @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + +
+ +
diff --git a/libs/components/src/navigation/nav-group.component.ts b/libs/components/src/navigation/nav-group.component.ts new file mode 100644 index 00000000000..5829701c9fe --- /dev/null +++ b/libs/components/src/navigation/nav-group.component.ts @@ -0,0 +1,62 @@ +import { + AfterContentInit, + Component, + ContentChildren, + EventEmitter, + Input, + Output, + QueryList, +} from "@angular/core"; + +import { NavBaseComponent } from "./nav-base.component"; +import { NavItemComponent } from "./nav-item.component"; + +@Component({ + selector: "bit-nav-group", + templateUrl: "./nav-group.component.html", +}) +export class NavGroupComponent extends NavBaseComponent implements AfterContentInit { + @ContentChildren(NavGroupComponent, { + descendants: true, + }) + nestedGroups!: QueryList; + + @ContentChildren(NavItemComponent, { + descendants: true, + }) + nestedItems!: QueryList; + + /** + * UID for `[attr.aria-controls]` + */ + protected contentId = Math.random().toString(36).substring(2); + + /** + * Is `true` if the expanded content is visible + */ + @Input() + open = false; + @Output() + openChange = new EventEmitter(); + + protected toggle(event?: MouseEvent) { + event?.stopPropagation(); + this.open = !this.open; + } + + /** + * - For any nested NavGroupComponents or NavItemComponents, increment the `treeDepth` by 1. + */ + private initNestedStyles() { + if (this.variant !== "tree") { + return; + } + [...this.nestedGroups, ...this.nestedItems].forEach((navGroupOrItem) => { + navGroupOrItem.treeDepth += 1; + }); + } + + ngAfterContentInit(): void { + this.initNestedStyles(); + } +} diff --git a/libs/components/src/navigation/nav-group.stories.ts b/libs/components/src/navigation/nav-group.stories.ts new file mode 100644 index 00000000000..333adb0da09 --- /dev/null +++ b/libs/components/src/navigation/nav-group.stories.ts @@ -0,0 +1,74 @@ +import { RouterTestingModule } from "@angular/router/testing"; +import { Meta, moduleMetadata, Story } from "@storybook/angular"; + +import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; + +import { SharedModule } from "../shared/shared.module"; +import { I18nMockService } from "../utils/i18n-mock.service"; + +import { NavGroupComponent } from "./nav-group.component"; +import { NavigationModule } from "./navigation.module"; + +export default { + title: "Component Library/Nav/Nav Group", + component: NavGroupComponent, + decorators: [ + moduleMetadata({ + imports: [SharedModule, RouterTestingModule, NavigationModule], + providers: [ + { + provide: I18nService, + useFactory: () => { + return new I18nMockService({ + submenu: "submenu", + toggleCollapse: "toggle collapse", + }); + }, + }, + ], + }), + ], + parameters: { + design: { + type: "figma", + url: "https://www.figma.com/file/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=4687%3A86642", + }, + }, +} as Meta; + +export const Default: Story = (args) => ({ + props: args, + template: ` + + + + + + + + + + + `, +}); + +export const Tree: Story = (args) => ({ + props: args, + template: ` + + + + + + + + + + + + + + + + `, +}); diff --git a/libs/components/src/navigation/nav-item.component.html b/libs/components/src/navigation/nav-item.component.html new file mode 100644 index 00000000000..b6ce5a52f43 --- /dev/null +++ b/libs/components/src/navigation/nav-item.component.html @@ -0,0 +1,79 @@ +
+
+
+ +
+ +
+ +
+ + + + + + {{ text }} + + + + + + + + + + + + + + + + + +
+ +
+
+
diff --git a/libs/components/src/navigation/nav-item.component.ts b/libs/components/src/navigation/nav-item.component.ts new file mode 100644 index 00000000000..634aa7e7639 --- /dev/null +++ b/libs/components/src/navigation/nav-item.component.ts @@ -0,0 +1,48 @@ +import { Component, HostListener } from "@angular/core"; +import { IsActiveMatchOptions } from "@angular/router"; +import { BehaviorSubject, map } from "rxjs"; + +import { NavBaseComponent } from "./nav-base.component"; + +@Component({ + selector: "bit-nav-item", + templateUrl: "./nav-item.component.html", +}) +export class NavItemComponent extends NavBaseComponent { + /** + * Is `true` if `to` matches the current route + */ + private _active = false; + protected setActive(isActive: boolean) { + this._active = isActive; + } + protected get showActiveStyles() { + return this._active && !this.hideActiveStyles; + } + protected readonly rlaOptions: IsActiveMatchOptions = { + paths: "exact", + queryParams: "exact", + fragment: "ignored", + matrixParams: "ignored", + }; + + /** + * The design spec calls for the an outline to wrap the entire element when the template's anchor/button has :focus-visible. + * Usually, we would use :focus-within for this. However, that matches when a child element has :focus instead of :focus-visible. + * + * Currently, the browser does not have a pseudo selector that combines these two, e.g. :focus-visible-within (WICG/focus-visible#151) + * To make our own :focus-visible-within functionality, we use event delegation on the host and manually check if the focus target (denoted with the .fvw class) matches :focus-visible. We then map that state to some styles, so the entire component can have an outline. + */ + protected focusVisibleWithin$ = new BehaviorSubject(false); + protected fvwStyles$ = this.focusVisibleWithin$.pipe( + map((value) => (value ? "tw-z-10 tw-rounded tw-outline-none tw-ring tw-ring-text-alt2" : "")) + ); + @HostListener("focusin", ["$event.target"]) + onFocusIn(target: HTMLElement) { + this.focusVisibleWithin$.next(target.matches(".fvw:focus-visible")); + } + @HostListener("focusout") + onFocusOut() { + this.focusVisibleWithin$.next(false); + } +} diff --git a/libs/components/src/navigation/nav-item.stories.ts b/libs/components/src/navigation/nav-item.stories.ts new file mode 100644 index 00000000000..e1a71289226 --- /dev/null +++ b/libs/components/src/navigation/nav-item.stories.ts @@ -0,0 +1,93 @@ +import { RouterTestingModule } from "@angular/router/testing"; +import { Meta, moduleMetadata, Story } from "@storybook/angular"; + +import { IconButtonModule } from "../icon-button"; + +import { NavItemComponent } from "./nav-item.component"; +import { NavigationModule } from "./navigation.module"; + +export default { + title: "Component Library/Nav/Nav Item", + component: NavItemComponent, + decorators: [ + moduleMetadata({ + declarations: [], + imports: [RouterTestingModule, IconButtonModule, NavigationModule], + }), + ], + parameters: { + design: { + type: "figma", + url: "https://www.figma.com/file/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=4687%3A86642", + }, + }, +} as Meta; + +const Template: Story = (args: NavItemComponent) => ({ + props: args, + template: ` + + `, +}); + +export const Default = Template.bind({}); +Default.args = { + text: "Hello World", + icon: "bwi-filter", +}; + +export const WithoutIcon = Template.bind({}); +WithoutIcon.args = { + text: "Hello World", + icon: "", +}; + +export const WithoutRoute: Story = (args: NavItemComponent) => ({ + props: args, + template: ` + + `, +}); + +export const WithChildButtons: Story = (args: NavItemComponent) => ({ + props: args, + template: ` + + + + + + `, +}); + +export const MultipleItemsWithDivider: Story = (args: NavItemComponent) => ({ + props: args, + template: ` + + + + + + `, +}); diff --git a/libs/components/src/navigation/navigation.module.ts b/libs/components/src/navigation/navigation.module.ts new file mode 100644 index 00000000000..3685c1b9353 --- /dev/null +++ b/libs/components/src/navigation/navigation.module.ts @@ -0,0 +1,18 @@ +import { OverlayModule } from "@angular/cdk/overlay"; +import { CommonModule } from "@angular/common"; +import { NgModule } from "@angular/core"; +import { RouterModule } from "@angular/router"; + +import { IconButtonModule } from "../icon-button/icon-button.module"; +import { SharedModule } from "../shared/shared.module"; + +import { NavDividerComponent } from "./nav-divider.component"; +import { NavGroupComponent } from "./nav-group.component"; +import { NavItemComponent } from "./nav-item.component"; + +@NgModule({ + imports: [CommonModule, SharedModule, IconButtonModule, OverlayModule, RouterModule], + declarations: [NavDividerComponent, NavGroupComponent, NavItemComponent], + exports: [NavDividerComponent, NavGroupComponent, NavItemComponent], +}) +export class NavigationModule {} diff --git a/libs/components/src/stories/colors.stories.mdx b/libs/components/src/stories/colors.stories.mdx index bdcfc13c6b6..f358a68f592 100644 --- a/libs/components/src/stories/colors.stories.mdx +++ b/libs/components/src/stories/colors.stories.mdx @@ -21,6 +21,8 @@ export const Table = (args) => ( {Row("background")} {Row("background-alt")} {Row("background-alt2")} + {Row("background-alt3")} + {Row("background-alt4")} {Row("primary-300")} diff --git a/libs/components/src/tabs/tab-nav-bar/tab-link.component.ts b/libs/components/src/tabs/tab-nav-bar/tab-link.component.ts index 4b9da818242..cec2bf947b6 100644 --- a/libs/components/src/tabs/tab-nav-bar/tab-link.component.ts +++ b/libs/components/src/tabs/tab-nav-bar/tab-link.component.ts @@ -24,7 +24,7 @@ export class TabLinkComponent implements FocusableOption, AfterViewInit, OnDestr fragment: "ignored", }; - @Input() route: string; + @Input() route: string | any[]; @Input() disabled = false; @HostListener("keydown", ["$event"]) onKeyDown(event: KeyboardEvent) { diff --git a/libs/components/src/tw-theme.css b/libs/components/src/tw-theme.css index 7acdb02ebf7..f5d9febcebf 100644 --- a/libs/components/src/tw-theme.css +++ b/libs/components/src/tw-theme.css @@ -4,6 +4,8 @@ --color-background: 255 255 255; --color-background-alt: 251 251 251; --color-background-alt2: 23 92 219; + --color-background-alt3: 18 82 163; + --color-background-alt4: 13 60 119; --color-primary-300: 103 149 232; --color-primary-500: 23 93 220; @@ -45,6 +47,8 @@ --color-background: 31 36 46; --color-background-alt: 22 28 38; --color-background-alt2: 47 52 61; + --color-background-alt3: 47 52 61; + --color-background-alt4: 16 18 21; --color-primary-300: 23 93 220; --color-primary-500: 106 153 240; diff --git a/libs/components/tailwind.config.base.js b/libs/components/tailwind.config.base.js index 8b05c449c9e..a700a3377dd 100644 --- a/libs/components/tailwind.config.base.js +++ b/libs/components/tailwind.config.base.js @@ -56,6 +56,8 @@ module.exports = { DEFAULT: rgba("--color-background"), alt: rgba("--color-background-alt"), alt2: rgba("--color-background-alt2"), + alt3: rgba("--color-background-alt3"), + alt4: rgba("--color-background-alt4"), }, }, textColor: { @@ -83,6 +85,9 @@ module.exports = { "50vw": "50vw", "75vw": "75vw", }, + minWidth: { + 52: "13rem", + }, maxWidth: ({ theme }) => ({ ...theme("width"), "90vw": "90vw", diff --git a/tailwind.config.js b/tailwind.config.js index 3eadeab0c71..21012889938 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -4,6 +4,7 @@ const config = require("./libs/components/tailwind.config.base"); config.content = [ "./libs/components/src/**/*.{html,ts,mdx}", "./apps/web/src/**/*.{html,ts,mdx}", + "./bitwarden_license/bit-web/src/**/*.{html,ts,mdx}", "./.storybook/preview.js", ]; config.safelist = [ diff --git a/tsconfig.json b/tsconfig.json index d9189e23eb4..9bcd88c8802 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -18,7 +18,8 @@ "@bitwarden/common/*": ["./libs/common/src/*"], "@bitwarden/angular/*": ["./libs/angular/src/*"], "@bitwarden/node/*": ["./libs/node/src/*"], - "@bitwarden/components": ["./libs/components/src"] + "@bitwarden/components": ["./libs/components/src"], + "@bitwarden/web-vault/*": ["./apps/web/src/*"] }, "plugins": [ { @@ -26,6 +27,6 @@ } ] }, - "include": ["apps/web/src/**/*", "libs/*/src/**/*"], + "include": ["apps/web/src/**/*", "libs/*/src/**/*", "bitwarden_license/bit-web/src/**/*"], "exclude": ["apps/web/src/**/*.spec.ts", "libs/*/src/**/*.spec.ts"] } From fcdb0ecffe4b9346b2399c7cf59161fabe0c3d59 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 9 Dec 2022 11:35:15 +0100 Subject: [PATCH 6/9] Autosync the updated translations (#4205) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/browser/src/_locales/fi/messages.json | 2 +- apps/browser/src/_locales/ru/messages.json | 6 +- apps/browser/src/_locales/th/messages.json | 196 +++++++++--------- apps/browser/src/_locales/zh_CN/messages.json | 2 +- apps/browser/src/_locales/zh_TW/messages.json | 2 +- apps/browser/store/locales/fi/copy.resx | 2 +- 6 files changed, 105 insertions(+), 105 deletions(-) diff --git a/apps/browser/src/_locales/fi/messages.json b/apps/browser/src/_locales/fi/messages.json index e0dace731c5..bc362924d83 100644 --- a/apps/browser/src/_locales/fi/messages.json +++ b/apps/browser/src/_locales/fi/messages.json @@ -3,7 +3,7 @@ "message": "Bitwarden" }, "extName": { - "message": "Bitwarden - Free Password Manager", + "message": "Bitwarden – Ilmainen salasananhallinta", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { diff --git a/apps/browser/src/_locales/ru/messages.json b/apps/browser/src/_locales/ru/messages.json index 7b20d2151d3..39c7c700972 100644 --- a/apps/browser/src/_locales/ru/messages.json +++ b/apps/browser/src/_locales/ru/messages.json @@ -642,7 +642,7 @@ "description": "Light color" }, "solarizedDark": { - "message": "Солнечная темная", + "message": "Solarized dark", "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." }, "exportVault": { @@ -921,7 +921,7 @@ "message": "Коды подтверждения будут отправлены вам по электронной почте." }, "selfHostedEnvironment": { - "message": "Окружение собственного хостинга" + "message": "Окружение пользовательского хостинга" }, "selfHostedEnvironmentFooter": { "message": "Укажите URL Bitwarden на вашем сервере." @@ -1396,7 +1396,7 @@ "message": "Действие по тайм-ауту хранилища" }, "lock": { - "message": "Заблокировать", + "message": "Блокировка", "description": "Verb form: to make secure or inaccesible by" }, "trash": { diff --git a/apps/browser/src/_locales/th/messages.json b/apps/browser/src/_locales/th/messages.json index 52062854b6d..d13d2f63b80 100644 --- a/apps/browser/src/_locales/th/messages.json +++ b/apps/browser/src/_locales/th/messages.json @@ -53,13 +53,13 @@ "message": "แท็บ" }, "vault": { - "message": "Vault" + "message": "ตู้นิรภัย" }, "myVault": { "message": "My Vault" }, "allVaults": { - "message": "All vaults" + "message": "ตู้นิรภัยทั้งหมด" }, "tools": { "message": "เครื่องมือ" @@ -95,16 +95,16 @@ "message": "Generate Password (copied)" }, "copyElementIdentifier": { - "message": "Copy custom field name" + "message": "คัดลอกชื่อของช่องที่กำหนดเอง" }, "noMatchingLogins": { "message": "ไม่พบข้อมูลล็อกอินที่ตรงกัน" }, "unlockVaultMenu": { - "message": "Unlock your vault" + "message": "ปลดล็อกกตู้นิรภัยของคุณ" }, "loginToVaultMenu": { - "message": "Log in to your vault" + "message": "ลงชื่อเข้าใช้ตู้นิรภัยของคุณ" }, "autoFillInfo": { "message": "ไม่พบข้อมูลล็อกอินเพื่อใช้กรอกข้อมูลอัตโนมัติ สำหรับแท็บปัจจุบันของเบราว์เซอร์" @@ -153,7 +153,7 @@ "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, "yourAccountsFingerprint": { - "message": "Your account's fingerprint phrase", + "message": "ข้อความลายนิ้วมือของบัญชีของคุณ", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, "twoStepLogin": { @@ -212,7 +212,7 @@ "description": "Short for 'Password Generator'." }, "passGenInfo": { - "message": "Automatically generate strong, unique passwords for your logins." + "message": "สร้างรหัสผ่านที่รัดกุมและไม่ซ้ำใครโดยอัตโนมัติสำหรับการเข้าสู่ระบบของคุณ" }, "bitWebVault": { "message": "bitwarden Web Vault" @@ -291,7 +291,7 @@ "message": "รหัสผ่าน" }, "passphrase": { - "message": "Passphrase" + "message": "ข้อความรหัสผ่าน" }, "favorite": { "message": "รายการโปรด" @@ -333,10 +333,10 @@ "message": "Rate the Extension" }, "rateExtensionDesc": { - "message": "Please consider helping us out with a good review!" + "message": "โปรดพิจารณา ช่วยเราด้วยการตรวจสอบที่ดี!" }, "browserNotSupportClipboard": { - "message": "Your web browser does not support easy clipboard copying. Copy it manually instead." + "message": "เว็บเบราว์เซอร์ของคุณไม่รองรับการคัดลอกคลิปบอร์ดอย่างง่าย คัดลอกด้วยตนเองแทน" }, "verifyIdentity": { "message": "ยืนยันตัวตน" @@ -424,22 +424,22 @@ "message": "ที่อยู่อีเมลไม่ถูกต้อง" }, "masterPasswordRequired": { - "message": "Master password is required." + "message": "ต้องใช้รหัสผ่านหลัก" }, "confirmMasterPasswordRequired": { - "message": "Master password retype is required." + "message": "ต้องพิมพ์รหัสผ่านหลักอีกครั้ง" }, "masterPasswordMinlength": { - "message": "Master password must be at least 8 characters long." + "message": "รหัสผ่านหลักต้องมีความยาวอย่างน้อย 8 ตัวอักษร" }, "masterPassDoesntMatch": { - "message": "Master password confirmation does not match." + "message": "การยืนยันรหัสผ่านหลักไม่ตรงกัน" }, "newAccountCreated": { - "message": "Your new account has been created! You may now log in." + "message": "บัญชีใหม่ของคุณถูกสร้างขึ้นแล้ว! ตอนนี้คุณสามารถเข้าสู่ระบบ" }, "masterPassSent": { - "message": "We've sent you an email with your master password hint." + "message": "เราได้ส่งอีเมลพร้อมคำใบ้รหัสผ่านหลักของคุณออกไปแล้ว" }, "verificationCodeRequired": { "message": "ต้องระบุโค้ดยืนยัน" @@ -497,7 +497,7 @@ "message": "Edited Folder" }, "deleteFolderConfirmation": { - "message": "Are you sure you want to delete this folder?" + "message": "คุณแน่ใจหรือไม่ว่าต้องการลบโฟลเดอร์นี้" }, "deletedFolder": { "message": "ลบโฟลเดอร์แล้ว" @@ -506,7 +506,7 @@ "message": "Getting Started Tutorial" }, "gettingStartedTutorialVideo": { - "message": "Watch our getting started tutorial to learn how to get the most out of the browser extension." + "message": "ดูบทช่วยสอนการเริ่มต้นของเราเพื่อเรียนรู้วิธีใช้ประโยชน์สูงสุดจากส่วนขยายเบราว์เซอร์" }, "syncingComplete": { "message": "การซิงก์เสร็จสมบูรณ์" @@ -552,48 +552,48 @@ "message": "คุณต้องการเขียนทับรหัสผ่านปัจจุบันใช่หรือไม่?" }, "overwriteUsername": { - "message": "Overwrite username" + "message": "เขียนทับชื่อผู้ใช้" }, "overwriteUsernameConfirmation": { - "message": "Are you sure you want to overwrite the current username?" + "message": "คุณแน่ใจหรือไม่ว่าต้องการเขียนทับชื่อผู้ใช้ปัจจุบัน" }, "searchFolder": { "message": "ค้นหาในโพลเดอร์" }, "searchCollection": { - "message": "Search collection" + "message": "คอลเลกชันการค้นหา" }, "searchType": { - "message": "Search type" + "message": "ประเภทการค้นหา" }, "noneFolder": { "message": "No Folder", "description": "This is the folder for uncategorized items" }, "enableAddLoginNotification": { - "message": "Ask to add login" + "message": "ถามเพื่อให้เพิ่มการเข้าสู่ระบบ" }, "addLoginNotificationDesc": { "message": "The \"Add Login Notification\" automatically prompts you to save new logins to your vault whenever you log into them for the first time." }, "showCardsCurrentTab": { - "message": "Show cards on Tab page" + "message": "แสดงการ์ดบนหน้าแท็บ" }, "showCardsCurrentTabDesc": { - "message": "List card items on the Tab page for easy auto-fill." + "message": "บัตรรายการในหน้าแท็บเพื่อให้ป้อนอัตโนมัติได้ง่าย" }, "showIdentitiesCurrentTab": { - "message": "Show identities on Tab page" + "message": "แสดงตัวตนบนหน้าแท็บ" }, "showIdentitiesCurrentTabDesc": { - "message": "List identity items on the Tab page for easy auto-fill." + "message": "แสดงรายการข้อมูลประจำตัวในหน้าแท็บเพื่อให้ป้อนอัตโนมัติได้ง่าย" }, "clearClipboard": { "message": "ล้างคลิปบอร์ด", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." }, "clearClipboardDesc": { - "message": "Automatically clear copied values from your clipboard.", + "message": "ล้างค่าที่คัดลอกโดยอัตโนมัติจากคลิปบอร์ดของคุณ", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." }, "notificationAddDesc": { @@ -603,7 +603,7 @@ "message": "Yes, Save Now" }, "enableChangedPasswordNotification": { - "message": "Ask to update existing login" + "message": "ขอให้ปรับปรุงการเข้าสู่ระบบที่มีอยู่" }, "changedPasswordNotificationDesc": { "message": "Ask to update a login's password when a change is detected on a website." @@ -615,17 +615,17 @@ "message": "Yes, Update Now" }, "enableContextMenuItem": { - "message": "Show context menu options" + "message": "แสดงตัวเลือกเมนูบริบท" }, "contextMenuItemDesc": { - "message": "Use a secondary click to access password generation and matching logins for the website. " + "message": "ใช้การคลิกสำรองเพื่อเข้าถึงการสร้างรหัสผ่านและการเข้าสู่ระบบที่ตรงกันสำหรับเว็บไซต์ " }, "defaultUriMatchDetection": { - "message": "Default URI match detection", + "message": "การตรวจจับการจับคู่ URI เริ่มต้น", "description": "Default URI match detection for auto-fill." }, "defaultUriMatchDetectionDesc": { - "message": "Choose the default way that URI match detection is handled for logins when performing actions such as auto-fill." + "message": "เลือกวิธีเริ่มต้นในการจัดการการตรวจหาการจับคู่ URI สำหรับการเข้าสู่ระบบเมื่อดำเนินการต่างๆ เช่น การป้อนอัตโนมัติ" }, "theme": { "message": "ธีม" @@ -656,31 +656,31 @@ "description": "WARNING (should stay in capitalized letters if the language permits)" }, "confirmVaultExport": { - "message": "Confirm vault export" + "message": "ยืนยันการส่งออกตู้นิรภัย" }, "exportWarningDesc": { "message": "This export contains your vault data in an unencrypted format. You should not store or send the exported file over unsecure channels (such as email). Delete it immediately after you are done using it." }, "encExportKeyWarningDesc": { - "message": "This export encrypts your data using your account's encryption key. If you ever rotate your account's encryption key you should export again since you will not be able to decrypt this export file." + "message": "การส่งออกนี้เข้ารหัสข้อมูลของคุณโดยใช้คีย์เข้ารหัสของบัญชีของคุณ หากคุณเคยหมุนเวียนคีย์เข้ารหัสของบัญชี คุณควรส่งออกอีกครั้ง เนื่องจากคุณจะไม่สามารถถอดรหัสไฟล์ส่งออกนี้ได้" }, "encExportAccountWarningDesc": { - "message": "Account encryption keys are unique to each Bitwarden user account, so you can't import an encrypted export into a different account." + "message": "คีย์การเข้ารหัสบัญชีจะไม่ซ้ำกันสำหรับบัญชีผู้ใช้ Bitwarden แต่ละบัญชี ดังนั้นคุณจึงไม่สามารถนำเข้าการส่งออกที่เข้ารหัสไปยังบัญชีอื่นได้" }, "exportMasterPassword": { - "message": "Enter your master password to export your vault data." + "message": "ป้อนรหัสผ่านหลักของคุณเพื่อส่งออกข้อมูลตู้นิรภัยของคุณ" }, "shared": { "message": "แชร์แล้ว" }, "learnOrg": { - "message": "Learn about organizations" + "message": "เรียนรู้เกี่ยวกับองค์กร" }, "learnOrgConfirmation": { - "message": "Bitwarden allows you to share your vault items with others by using an organization. Would you like to visit the bitwarden.com website to learn more?" + "message": "Bitwarden อนุญาตให้คุณแชร์รายการตู้นิรภัยของคุณกับผู้อื่นโดยใช้องค์กร คุณต้องการเยี่ยมชมเว็บไซต์ bitwarden.com เพื่อเรียนรู้เพิ่มเติมหรือไม่?" }, "moveToOrganization": { - "message": "Move to organization" + "message": "ย้ายไปยังแบบองค์กร" }, "share": { "message": "แชร์" @@ -699,10 +699,10 @@ } }, "moveToOrgDesc": { - "message": "Choose an organization that you wish to move this item to. Moving to an organization transfers ownership of the item to that organization. You will no longer be the direct owner of this item once it has been moved." + "message": "เลือกองค์กรที่คุณต้องการย้ายรายการนี้ไป การย้ายไปยังองค์กรจะโอนความเป็นเจ้าของรายการไปยังองค์กรนั้น คุณจะไม่ได้เป็นเจ้าของโดยตรงของรายการนี้อีกต่อไปเมื่อมีการย้ายแล้ว" }, "learnMore": { - "message": "Learn more" + "message": "เรียนรู้เพิ่มเติม" }, "authenticatorKeyTotp": { "message": "Authenticator Key (TOTP)" @@ -747,7 +747,7 @@ "message": "Feature Unavailable" }, "updateKey": { - "message": "You cannot use this feature until you update your encryption key." + "message": "คุณไม่สามารถใช้คุณลักษณะนี้ได้จนกว่าคุณจะปรับปรุงคีย์การเข้ารหัสลับของคุณ" }, "premiumMembership": { "message": "Premium Membership" @@ -756,28 +756,28 @@ "message": "Manage Membership" }, "premiumManageAlert": { - "message": "You can manage your membership on the bitwarden.com web vault. Do you want to visit the website now?" + "message": "คุณสามารถจัดการการเป็นสมาชิกของคุณได้ที่ bitwarden.com web vault คุณต้องการเข้าชมเว็บไซต์ตอนนี้หรือไม่?" }, "premiumRefresh": { "message": "Refresh Membership" }, "premiumNotCurrentMember": { - "message": "You are not currently a Premium member." + "message": "คุณยังไม่ได้เป็นสมาชิกพรีเมียม" }, "premiumSignUpAndGet": { - "message": "Sign up for a Premium membership and get:" + "message": "สมัครสมาชิกพรีเมี่ยมและรับ:" }, "ppremiumSignUpStorage": { "message": "1 GB of encrypted file storage." }, "ppremiumSignUpTwoStep": { - "message": "Additional two-step login options such as YubiKey, FIDO U2F, and Duo." + "message": "ตัวเลือกการเข้าสู่ระบบแบบสองขั้นตอนเพิ่มเติม เช่น YubiKey, FIDO U2F และ Duo" }, "ppremiumSignUpReports": { - "message": "Password hygiene, account health, and data breach reports to keep your vault safe." + "message": "สุขอนามัยของรหัสผ่าน ความสมบูรณ์ของบัญชี และรายงานการละเมิดข้อมูลเพื่อให้ตู้นิรภัยของคุณปลอดภัย" }, "ppremiumSignUpTotp": { - "message": "TOTP verification code (2FA) generator for logins in your vault." + "message": "ตัวสร้างรหัสยืนยัน TOTP (2FA) สำหรับการเข้าสู่ระบบในตู้นิรภัยของคุณ" }, "ppremiumSignUpSupport": { "message": "Priority customer support." @@ -1055,7 +1055,7 @@ "message": "หมายเลข" }, "brand": { - "message": "Brand" + "message": "แบรนด์" }, "expirationMonth": { "message": "Expiration Month" @@ -1133,7 +1133,7 @@ "message": "Last Name" }, "fullName": { - "message": "Full name" + "message": "ชื่อเต็ม" }, "identityName": { "message": "Identity Name" @@ -1181,7 +1181,7 @@ "message": "ประเทศ" }, "type": { - "message": "Type" + "message": "ชนิด" }, "typeLogin": { "message": "ล็อกอิน" @@ -1205,35 +1205,35 @@ "message": "ย้อนกลับ" }, "collections": { - "message": "Collections" + "message": "คอลเลกชัน" }, "favorites": { "message": "รายการโปรด" }, "popOutNewWindow": { - "message": "Pop out to a new window" + "message": "เปิดหน้าต่างใหม่" }, "refresh": { - "message": "Refresh" + "message": "รีเฟรช" }, "cards": { - "message": "Cards" + "message": "บัตร" }, "identities": { - "message": "Identities" + "message": "ข้อมูลระบุตัวตน" }, "logins": { - "message": "Logins" + "message": "เข้าสู่ระบบ" }, "secureNotes": { "message": "Secure Notes" }, "clear": { - "message": "Clear", + "message": "ลบทิ้ง", "description": "To clear something out. example: To clear browser history." }, "checkPassword": { - "message": "Check if password has been exposed." + "message": "ตรวจสอบว่ารหัสผ่านถูกเปิดเผยหรือไม่" }, "passwordExposed": { "message": "This password has been exposed in data breaches. You should change it.", @@ -1245,28 +1245,28 @@ } }, "passwordSafe": { - "message": "This password was not found in any known data breaches. It should be safe to use." + "message": "ไม่พบรหัสผ่านนี้ในการละเมิดข้อมูลที่มี ควรใช้อย่างปลอดภัย" }, "baseDomain": { - "message": "Base domain", + "message": "โดเมนพื้นฐาน", "description": "Domain name. Ex. website.com" }, "domainName": { - "message": "Domain name", + "message": "ชื่อโดเมน", "description": "Domain name. Ex. website.com" }, "host": { - "message": "Host", + "message": "โฮสต์", "description": "A URL's host value. For example, the host of https://sub.domain.com:443 is 'sub.domain.com:443'." }, "exact": { - "message": "Exact" + "message": "ถูกต้อง" }, "startsWith": { - "message": "Starts with" + "message": "เริ่มต้นด้วย" }, "regEx": { - "message": "Regular expression", + "message": "นิพจน์ทั่วไป", "description": "A programming term, also known as 'RegEx'." }, "matchDetection": { @@ -1274,14 +1274,14 @@ "description": "URI match detection for auto-fill." }, "defaultMatchDetection": { - "message": "Default match detection", + "message": "การตรวจจับการจับคู่เริ่มต้น", "description": "Default URI match detection for auto-fill." }, "toggleOptions": { "message": "Toggle Options" }, "toggleCurrentUris": { - "message": "Toggle current URIs", + "message": "สลับ URI ปัจจุบัน", "description": "Toggle the display of the URIs of the currently open tabs in the browser." }, "currentUri": { @@ -1293,26 +1293,26 @@ "description": "An entity of multiple related people (ex. a team or business organization)." }, "types": { - "message": "Types" + "message": "ชนิด" }, "allItems": { "message": "รายการทั้งหมด" }, "noPasswordsInList": { - "message": "There are no passwords to list." + "message": "ไม่มีรหัสผ่านที่จะแสดง" }, "remove": { "message": "ลบ" }, "default": { - "message": "Default" + "message": "ค่าเริ่มต้น" }, "dateUpdated": { "message": "อัปเดตแล้ว", "description": "ex. Date this item was updated" }, "dateCreated": { - "message": "Created", + "message": "สร้างเมื่อ", "description": "ex. Date this item was created" }, "datePasswordUpdated": { @@ -1320,13 +1320,13 @@ "description": "ex. Date this password was updated" }, "neverLockWarning": { - "message": "Are you sure you want to use the \"Never\" option? Setting your lock options to \"Never\" stores your vault's encryption key on your device. If you use this option you should ensure that you keep your device properly protected." + "message": "คุณแน่ใจหรือไม่ว่าต้องการใช้ตัวเลือก \"ไม่เคย\" การตั้งค่าตัวเลือกการล็อกเป็น \"ไม่\" จะเก็บคีย์เข้ารหัสของห้องนิรภัยไว้ในอุปกรณ์ของคุณ หากคุณใช้ตัวเลือกนี้ คุณควรตรวจสอบให้แน่ใจว่าคุณปกป้องอุปกรณ์ของคุณอย่างเหมาะสม" }, "noOrganizationsList": { "message": "You do not belong to any organizations. Organizations allow you to securely share items with other users." }, "noCollectionsInList": { - "message": "There are no collections to list." + "message": "ไม่มีคอลเลกชันที่จะแสดง" }, "ownership": { "message": "เจ้าของ" @@ -1350,7 +1350,7 @@ "message": "Weak Master Password" }, "weakMasterPasswordDesc": { - "message": "The master password you have chosen is weak. You should use a strong master password (or a passphrase) to properly protect your Bitwarden account. Are you sure you want to use this master password?" + "message": "รหัสผ่านหลักที่คุณเลือกนั้นไม่รัดกุม คุณควรใช้รหัสผ่านหลักที่รัดกุม (หรือวลีรหัสผ่าน) เพื่อปกป้องบัญชี Bitwarden ของคุณอย่างเหมาะสม คุณแน่ใจหรือไม่ว่าต้องการใช้รหัสผ่านหลักนี้" }, "pin": { "message": "PIN", @@ -1369,31 +1369,31 @@ "message": "PIN ไม่ถูกต้อง" }, "unlockWithBiometrics": { - "message": "Unlock with biometrics" + "message": "ปลดล็อกด้วยไบโอเมตริก" }, "awaitDesktop": { "message": "Awaiting confirmation from desktop" }, "awaitDesktopDesc": { - "message": "Please confirm using biometrics in the Bitwarden desktop application to set up biometrics for browser." + "message": "โปรดยืนยันการใช้ไบโอเมตริกในแอปพลิเคชันเดสก์ท็อป Bitwarden เพื่อตั้งค่าไบโอเมตริกสำหรับเบราว์เซอร์" }, "lockWithMasterPassOnRestart": { - "message": "Lock with master password on browser restart" + "message": "ล็อคด้วยรหัสผ่านหลักเมื่อรีสตาร์ทเบราว์เซอร์" }, "selectOneCollection": { - "message": "You must select at least one collection." + "message": "คุณต้องเลือกอย่างน้อยหนึ่งคอลเลกชัน" }, "cloneItem": { - "message": "Clone item" + "message": "โคลนรายการ" }, "clone": { - "message": "Clone" + "message": "โคลน" }, "passwordGeneratorPolicyInEffect": { - "message": "One or more organization policies are affecting your generator settings." + "message": "นโยบายองค์กรอย่างน้อยหนึ่งนโยบายส่งผลต่อการตั้งค่าตัวสร้างของคุณ" }, "vaultTimeoutAction": { - "message": "Vault timeout action" + "message": "การดำเนินการหลังหมดเวลาล็อคตู้เซฟ" }, "lock": { "message": "ล็อก", @@ -1407,46 +1407,46 @@ "message": "ค้นหาในถังขยะ" }, "permanentlyDeleteItem": { - "message": "Permanently delete item" + "message": "ลบรายการอย่างถาวร" }, "permanentlyDeleteItemConfirmation": { - "message": "Are you sure you want to permanently delete this item?" + "message": "คุณแน่ใจหรือไม่ว่าต้องการลบรายการนี้อย่างถาวร?" }, "permanentlyDeletedItem": { - "message": "Item permanently deleted" + "message": "ลบรายการอย่างถาวรแล้ว" }, "restoreItem": { - "message": "Restore item" + "message": "กู้คืนรายการ" }, "restoreItemConfirmation": { - "message": "Are you sure you want to restore this item?" + "message": "คุณแน่ใจหรือไม่ว่าต้องการกู้คืนรายการนี้" }, "restoredItem": { - "message": "Item restored" + "message": "คืนค่ารายการแล้ว" }, "vaultTimeoutLogOutConfirmation": { - "message": "Logging out will remove all access to your vault and requires online authentication after the timeout period. Are you sure you want to use this setting?" + "message": "การออกจากระบบจะลบการเข้าถึงตู้นิรภัยของคุณทั้งหมด และต้องมีการตรวจสอบสิทธิ์ออนไลน์หลังจากหมดเวลา คุณแน่ใจหรือไม่ว่าต้องการใช้การตั้งค่านี้" }, "vaultTimeoutLogOutConfirmationTitle": { - "message": "Timeout action confirmation" + "message": "การยืนยันการดำเนินการหมดเวลา" }, "autoFillAndSave": { - "message": "Auto-fill and save" + "message": "กรอกอัตโนมัติและบันทึก" }, "autoFillSuccessAndSavedUri": { - "message": "Item auto-filled and URI saved" + "message": "เติมรายการอัตโนมัติและบันทึก URI แล้ว" }, "autoFillSuccess": { - "message": "Item auto-filled " + "message": "รายการเติมอัตโนมัติ " }, "setMasterPassword": { "message": "ตั้งรหัสผ่านหลัก" }, "masterPasswordPolicyInEffect": { - "message": "One or more organization policies require your master password to meet the following requirements:" + "message": "นโยบายองค์กรอย่างน้อยหนึ่งนโยบายกำหนดให้รหัสผ่านหลักของคุณเป็นไปตามข้อกำหนดต่อไปนี้:" }, "policyInEffectMinComplexity": { - "message": "Minimum complexity score of $SCORE$", + "message": "คะแนนความซับซ้อนขั้นต่ำ $SCORE$", "placeholders": { "score": { "content": "$1", @@ -1482,7 +1482,7 @@ } }, "masterPasswordPolicyRequirementsNotMet": { - "message": "Your new master password does not meet the policy requirements." + "message": "รหัสผ่านหลักใหม่ของคุณไม่เป็นไปตามข้อกำหนดของนโยบาย" }, "acceptPolicies": { "message": "By checking this box you agree to the following:" diff --git a/apps/browser/src/_locales/zh_CN/messages.json b/apps/browser/src/_locales/zh_CN/messages.json index b05d505d21d..b4e2bd0304f 100644 --- a/apps/browser/src/_locales/zh_CN/messages.json +++ b/apps/browser/src/_locales/zh_CN/messages.json @@ -2039,7 +2039,7 @@ "message": "不是你?" }, "newAroundHere": { - "message": "新建在这里?" + "message": "初来乍到吗?" }, "rememberEmail": { "message": "记住电子邮件地址" diff --git a/apps/browser/src/_locales/zh_TW/messages.json b/apps/browser/src/_locales/zh_TW/messages.json index a4ff34345c1..0fcb389a61f 100644 --- a/apps/browser/src/_locales/zh_TW/messages.json +++ b/apps/browser/src/_locales/zh_TW/messages.json @@ -2039,7 +2039,7 @@ "message": "不是您嗎?" }, "newAroundHere": { - "message": "New around here?" + "message": "第一次使用?" }, "rememberEmail": { "message": "記住電子郵件地址" diff --git a/apps/browser/store/locales/fi/copy.resx b/apps/browser/store/locales/fi/copy.resx index fb02d78fe42..dcf817e6b9d 100644 --- a/apps/browser/store/locales/fi/copy.resx +++ b/apps/browser/store/locales/fi/copy.resx @@ -118,7 +118,7 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Bitwarden – Free Password Manager + Bitwarden – Ilmainen salasananhallinta Turvallinen ja ilmainen salasanojen hallinta kaikille laitteillesi From 5207a855be23cacd5c9ccf9235f7eb2b9ebd654d Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Fri, 9 Dec 2022 11:38:03 +0100 Subject: [PATCH 7/9] [EC-827] feat: add logging to async actions (#4206) --- .../src/async-actions/bit-action.directive.ts | 11 +++++++++-- .../src/async-actions/bit-submit.directive.ts | 5 ++++- .../src/async-actions/standalone.stories.ts | 7 +++++++ 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/libs/components/src/async-actions/bit-action.directive.ts b/libs/components/src/async-actions/bit-action.directive.ts index 4fb28a4b1fb..0ea479f190b 100644 --- a/libs/components/src/async-actions/bit-action.directive.ts +++ b/libs/components/src/async-actions/bit-action.directive.ts @@ -1,6 +1,7 @@ import { Directive, HostListener, Input, OnDestroy, Optional } from "@angular/core"; import { BehaviorSubject, finalize, Subject, takeUntil, tap } from "rxjs"; +import { LogService } from "@bitwarden/common/abstractions/log.service"; import { ValidationService } from "@bitwarden/common/abstractions/validation.service"; import { ButtonLikeAbstraction } from "../shared/button-like.abstraction"; @@ -23,7 +24,8 @@ export class BitActionDirective implements OnDestroy { constructor( private buttonComponent: ButtonLikeAbstraction, - @Optional() private validationService?: ValidationService + @Optional() private validationService?: ValidationService, + @Optional() private logService?: LogService ) {} get loading() { @@ -44,7 +46,12 @@ export class BitActionDirective implements OnDestroy { this.loading = true; functionToObservable(this.handler) .pipe( - tap({ error: (err: unknown) => this.validationService?.showError(err) }), + tap({ + error: (err: unknown) => { + this.logService?.error(`Async action exception: ${err}`); + this.validationService?.showError(err); + }, + }), finalize(() => (this.loading = false)), takeUntil(this.destroy$) ) diff --git a/libs/components/src/async-actions/bit-submit.directive.ts b/libs/components/src/async-actions/bit-submit.directive.ts index 1fee5283bda..82b459c2d56 100644 --- a/libs/components/src/async-actions/bit-submit.directive.ts +++ b/libs/components/src/async-actions/bit-submit.directive.ts @@ -2,6 +2,7 @@ import { Directive, Input, OnDestroy, OnInit, Optional } from "@angular/core"; import { FormGroupDirective } from "@angular/forms"; import { BehaviorSubject, catchError, filter, of, Subject, switchMap, takeUntil } from "rxjs"; +import { LogService } from "@bitwarden/common/abstractions/log.service"; import { ValidationService } from "@bitwarden/common/abstractions/validation.service"; import { FunctionReturningAwaitable, functionToObservable } from "../utils/function-to-observable"; @@ -24,7 +25,8 @@ export class BitSubmitDirective implements OnInit, OnDestroy { constructor( private formGroupDirective: FormGroupDirective, - @Optional() validationService?: ValidationService + @Optional() validationService?: ValidationService, + @Optional() logService?: LogService ) { formGroupDirective.ngSubmit .pipe( @@ -39,6 +41,7 @@ export class BitSubmitDirective implements OnInit, OnDestroy { return awaitable.pipe( catchError((err: unknown) => { + logService?.error(`Async submit exception: ${err}`); validationService?.showError(err); return of(undefined); }) diff --git a/libs/components/src/async-actions/standalone.stories.ts b/libs/components/src/async-actions/standalone.stories.ts index cd0c6239b06..5a8edfaaac4 100644 --- a/libs/components/src/async-actions/standalone.stories.ts +++ b/libs/components/src/async-actions/standalone.stories.ts @@ -3,6 +3,7 @@ import { action } from "@storybook/addon-actions"; import { Meta, moduleMetadata, Story } from "@storybook/angular"; import { delay, of } from "rxjs"; +import { LogService } from "@bitwarden/common/abstractions/log.service"; import { ValidationService } from "@bitwarden/common/abstractions/validation.service"; import { ButtonModule } from "../button"; @@ -68,6 +69,12 @@ export default { showError: action("ValidationService.showError"), } as Partial, }, + { + provide: LogService, + useValue: { + error: action("LogService.error"), + } as Partial, + }, ], }), ], From 54a4b4690bed0e667d34492809af9721b4563e63 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 9 Dec 2022 11:52:55 +0100 Subject: [PATCH 8/9] Autosync the updated translations (#4204) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/web/src/locales/af/messages.json | 23 +- apps/web/src/locales/ar/messages.json | 143 +- apps/web/src/locales/az/messages.json | 15 + apps/web/src/locales/be/messages.json | 15 + apps/web/src/locales/bg/messages.json | 15 + apps/web/src/locales/bn/messages.json | 25 +- apps/web/src/locales/bs/messages.json | 25 +- apps/web/src/locales/ca/messages.json | 25 +- apps/web/src/locales/cs/messages.json | 19 +- apps/web/src/locales/da/messages.json | 139 +- apps/web/src/locales/de/messages.json | 15 + apps/web/src/locales/el/messages.json | 15 + apps/web/src/locales/en_GB/messages.json | 287 +- apps/web/src/locales/en_IN/messages.json | 15 + apps/web/src/locales/eo/messages.json | 15 + apps/web/src/locales/es/messages.json | 15 + apps/web/src/locales/et/messages.json | 15 + apps/web/src/locales/eu/messages.json | 15 + apps/web/src/locales/fi/messages.json | 219 +- apps/web/src/locales/fil/messages.json | 25 +- apps/web/src/locales/fr/messages.json | 19 +- apps/web/src/locales/he/messages.json | 17 +- apps/web/src/locales/hi/messages.json | 25 +- apps/web/src/locales/hr/messages.json | 33 +- apps/web/src/locales/hu/messages.json | 15 + apps/web/src/locales/id/messages.json | 15 + apps/web/src/locales/it/messages.json | 15 + apps/web/src/locales/ja/messages.json | 23 +- apps/web/src/locales/ka/messages.json | 25 +- apps/web/src/locales/km/messages.json | 25 +- apps/web/src/locales/kn/messages.json | 15 + apps/web/src/locales/ko/messages.json | 15 + apps/web/src/locales/lv/messages.json | 17 +- apps/web/src/locales/ml/messages.json | 17 +- apps/web/src/locales/nb/messages.json | 183 +- apps/web/src/locales/nl/messages.json | 15 + apps/web/src/locales/nn/messages.json | 25 +- apps/web/src/locales/pl/messages.json | 19 +- apps/web/src/locales/pt_BR/messages.json | 15 + apps/web/src/locales/pt_PT/messages.json | 17 +- apps/web/src/locales/ro/messages.json | 15 + apps/web/src/locales/ru/messages.json | 33 +- apps/web/src/locales/si/messages.json | 25 +- apps/web/src/locales/sk/messages.json | 15 + apps/web/src/locales/sl/messages.json | 25 +- apps/web/src/locales/sr/messages.json | 15 + apps/web/src/locales/sr_CS/messages.json | 25 +- apps/web/src/locales/sv/messages.json | 15 + apps/web/src/locales/th/messages.json | 5526 ++++++++++++++++++++++ apps/web/src/locales/tr/messages.json | 15 + apps/web/src/locales/uk/messages.json | 25 +- apps/web/src/locales/vi/messages.json | 25 +- apps/web/src/locales/zh_CN/messages.json | 37 +- apps/web/src/locales/zh_TW/messages.json | 55 +- 54 files changed, 6901 insertions(+), 580 deletions(-) create mode 100644 apps/web/src/locales/th/messages.json diff --git a/apps/web/src/locales/af/messages.json b/apps/web/src/locales/af/messages.json index 53b9730871e..a5397c7133b 100644 --- a/apps/web/src/locales/af/messages.json +++ b/apps/web/src/locales/af/messages.json @@ -2444,7 +2444,7 @@ "message": "Eienaar" }, "ownerDesc": { - "message": "The highest access user that can manage all aspects of your organization." + "message": "Manage all aspects of your organization, including billing and subscriptions" }, "clientOwnerDesc": { "message": "This user should be independent of the Provider. If the Provider is disassociated with the organization, this user will maintain ownership of the organization." @@ -2453,13 +2453,13 @@ "message": "Admin" }, "adminDesc": { - "message": "Admins can access and manage all items, collections and users in your organization." + "message": "Manage organization access, all collections, members, reporting, and security settings" }, "user": { "message": "Gebruiker" }, "userDesc": { - "message": "A regular user with access to assigned collections in your organization." + "message": "Access and add items to assigned collections" }, "manager": { "message": "Bestuurder" @@ -4117,7 +4117,22 @@ "message": "Pasgemaak" }, "customDesc": { - "message": "Allows more granular control of user permissions for advanced configurations." + "message": "Grant customized permissions to members" + }, + "customDescNonEnterpriseStart": { + "message": "Custom roles is an ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseLink": { + "message": "enterprise feature", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseEnd": { + "message": ". Contact our support team to upgrade your subscription", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customNonEnterpriseError": { + "message": "To enable custom permissions the organization must be on an Enterprise 2020 plan." }, "permissions": { "message": "Toestemmings" diff --git a/apps/web/src/locales/ar/messages.json b/apps/web/src/locales/ar/messages.json index 5cab9e1261c..116505c8573 100644 --- a/apps/web/src/locales/ar/messages.json +++ b/apps/web/src/locales/ar/messages.json @@ -122,16 +122,16 @@ "message": "كانون الأول" }, "title": { - "message": "Title" + "message": "اللقب" }, "mr": { - "message": "Mr" + "message": "السيد" }, "mrs": { - "message": "Mrs" + "message": "السيدة" }, "ms": { - "message": "Ms" + "message": "الآنسة" }, "dr": { "message": "د" @@ -152,7 +152,7 @@ "message": "حقل مخصص جديد" }, "value": { - "message": "Value" + "message": "القيمة" }, "dragToSort": { "message": "اسحب للفرز" @@ -164,7 +164,7 @@ "message": "مخفي" }, "cfTypeBoolean": { - "message": "Boolean" + "message": "قيمة منطقية" }, "cfTypeLinked": { "message": "Linked", @@ -174,7 +174,7 @@ "message": "إزالة" }, "unassigned": { - "message": "Unassigned" + "message": "غير معين" }, "noneFolder": { "message": "لا يوجد مجلد", @@ -233,7 +233,7 @@ "message": "Check if password has been exposed." }, "passwordExposed": { - "message": "This password has been exposed $VALUE$ time(s) in data breaches. You should change it.", + "message": "تم كشف كلمة المرور هذه {0} مرة (مرات) في عمليات اختراق البيانات. يجب عليك تغييرها.", "placeholders": { "value": { "content": "$1", @@ -242,7 +242,7 @@ } }, "passwordSafe": { - "message": "This password was not found in any known data breaches. It should be safe to use." + "message": "لم يتم العثور على كلمة المرور هذه في أي عمليات اختراق معروفة للبيانات. من المفترض أن تكون آمنة للاستخدام." }, "save": { "message": "حفظ" @@ -269,16 +269,16 @@ "message": "تعديل" }, "searchCollection": { - "message": "Search collection" + "message": "ابحث في المجموعة" }, "searchFolder": { - "message": "Search folder" + "message": "ابحث في المجلّد" }, "searchFavorites": { "message": "بحث في المفضلة" }, "searchType": { - "message": "Search type", + "message": "نوع البحث", "description": "Search item type" }, "searchVault": { @@ -321,7 +321,7 @@ "message": "المجلدات" }, "collections": { - "message": "Collections" + "message": "المجموعات" }, "firstName": { "message": "الاسم الأول" @@ -357,7 +357,7 @@ "message": "الدولة" }, "shared": { - "message": "Shared" + "message": "مشترك" }, "attachments": { "message": "المرفقات" @@ -375,7 +375,7 @@ "message": "عرض العنصر" }, "ex": { - "message": "ex.", + "message": "مثال.", "description": "Short abbreviation for 'example'." }, "other": { @@ -388,7 +388,7 @@ "message": "نقل إلى مؤسسة" }, "valueCopied": { - "message": "$VALUE$ copied", + "message": "تم نسخ $VALUE$", "description": "Value has been copied to the clipboard.", "placeholders": { "value": { @@ -428,7 +428,7 @@ "message": "خزنتي" }, "allVaults": { - "message": "All vaults" + "message": "جميع الخزنات" }, "vault": { "message": "الخزنة" @@ -479,7 +479,7 @@ "message": "الحجم الأقصى للملف هو 500 ميجابايت." }, "updateKey": { - "message": "You cannot use this feature until you update your encryption key." + "message": "لا يمكنك استخدام هذه المِيزة حتى تحديث مفتاح التشفير الخاص بك." }, "addedItem": { "message": "تمت إضافة العنصر" @@ -501,7 +501,7 @@ } }, "movedItemsToOrg": { - "message": "Selected items moved to $ORGNAME$", + "message": "تم نقل العناصر المحددة إلى $ORGNAME$", "placeholders": { "orgname": { "content": "$1", @@ -519,7 +519,7 @@ "message": "حذف المرفق" }, "deleteItemConfirmation": { - "message": "Do you really want to send to the trash?" + "message": "هل تريد فعلًا أن تحذف؟" }, "deletedItem": { "message": "تم حذف العنصر" @@ -531,13 +531,13 @@ "message": "تم نقل العناصر" }, "overwritePasswordConfirmation": { - "message": "Are you sure you want to overwrite the current password?" + "message": "هل أنت متأكد من أنك تريد الكتابة فوق كلمة المرور الحالية؟" }, "editedFolder": { "message": "تم تعديل المجلد" }, "addedFolder": { - "message": "Folder added" + "message": "تمت إضافة المجلد" }, "deleteFolderConfirmation": { "message": "هل أنت متأكد من حذف هذا المجلد؟" @@ -546,13 +546,13 @@ "message": "تم حذف المجلد" }, "loggedOut": { - "message": "Logged out" + "message": "تم تسجيل الخروج" }, "loginExpired": { - "message": "Your login session has expired." + "message": "انتهت صَلاحِيَة جَلسة الدخول." }, "logOutConfirmation": { - "message": "Are you sure you want to log out?" + "message": "هل أنت متأكد من أنك تريد تسجيل الخروج؟" }, "logOut": { "message": "تسجيل الخروج" @@ -567,31 +567,31 @@ "message": "لا" }, "loginOrCreateNewAccount": { - "message": "Log in or create a new account to access your secure vault." + "message": "قم بتسجيل الدخول أو أنشئ حساباً جديداً لتتمكن من الوصول إلى خزنتك السرية." }, "loginWithDevice": { - "message": "Log in with device" + "message": "تسجيل الدخول باستخدام جهاز" }, "loginWithDeviceEnabledInfo": { - "message": "Log in with device must be set up in the settings of the Bitwarden mobile app. Need another option?" + "message": "تسجيل الدخول باستخدام الجهاز يجب أن يكون مفعلاً في إعدادات تطبيق بيتواردن على هاتفك. هل تحتاج إلى خِيار آخر؟" }, "loginWithMasterPassword": { - "message": "Log in with master password" + "message": "تسجيل الدخول باستخدام كلمة المرور الرئيسية" }, "createAccount": { "message": "إنشاء حساب" }, "newAroundHere": { - "message": "New around here?" + "message": "هل أنت جديد هنا؟" }, "startTrial": { - "message": "Start trial" + "message": "ابدأ تجرِبة مجانية" }, "logIn": { "message": "تسجيل الدخول" }, "logInInitiated": { - "message": "Log in initiated" + "message": "بَدْء تسجيل الدخول" }, "submit": { "message": "قدِّم" @@ -603,7 +603,7 @@ "message": "اسمك" }, "yourNameDesc": { - "message": "What should we call you?" + "message": "ماذا تحب أن ندعوك؟" }, "masterPass": { "message": "كلمة المرور الرئيسية" @@ -645,10 +645,10 @@ "message": "عنوان البريد الإلكتروني غير صالح." }, "masterPasswordRequired": { - "message": "Master password is required." + "message": "كلمة المرور الرئيسية مطلوبة." }, "confirmMasterPasswordRequired": { - "message": "Master password retype is required." + "message": "مطلوب إعادة إدخال كلمة المرور الرئيسية." }, "masterPasswordMinlength": { "message": "Master password must be at least 8 characters long." @@ -700,19 +700,19 @@ "message": "قفل الآن" }, "noItemsInList": { - "message": "There are no items to list." + "message": "لا توجد عناصر لعرضها." }, "noCollectionsInList": { "message": "There are no collections to list." }, "noGroupsInList": { - "message": "There are no groups to list." + "message": "لا توجد أية مجموعات لعرضها." }, "noUsersInList": { - "message": "There are no users to list." + "message": "ليس هناك مستخدمون لعرضهم." }, "noEventsInList": { - "message": "There are no events to list." + "message": "لا توجد أية أحداث لعرضها." }, "newOrganization": { "message": "مؤسسة جديدة" @@ -721,7 +721,7 @@ "message": "أنت لا تنتمي إلى أي مؤسسة. تسمح لك المؤسسات بمشاركة العناصر بأمان مع مستخدمين آخرين." }, "notificationSentDevice": { - "message": "A notification has been sent to your device." + "message": "تم إرسال إشعار إلى جهازك." }, "versionNumber": { "message": "الإصدار $VERSION_NUMBER$", @@ -757,7 +757,7 @@ "message": "تذكرني" }, "sendVerificationCodeEmailAgain": { - "message": "Send verification code email again" + "message": "إرسال رمز التحقق إلى البريد الإلكتروني مرة أخرى" }, "useAnotherTwoStepMethod": { "message": "Use another two-step login method" @@ -2444,7 +2444,7 @@ "message": "Owner" }, "ownerDesc": { - "message": "The highest access user that can manage all aspects of your organization." + "message": "Manage all aspects of your organization, including billing and subscriptions" }, "clientOwnerDesc": { "message": "This user should be independent of the Provider. If the Provider is disassociated with the organization, this user will maintain ownership of the organization." @@ -2453,19 +2453,19 @@ "message": "Admin" }, "adminDesc": { - "message": "Admins can access and manage all items, collections and users in your organization." + "message": "Manage organization access, all collections, members, reporting, and security settings" }, "user": { "message": "User" }, "userDesc": { - "message": "A regular user with access to assigned collections in your organization." + "message": "Access and add items to assigned collections" }, "manager": { "message": "Manager" }, "managerDesc": { - "message": "Managers can access and manage assigned collections in your organization." + "message": "Create, delete, and manage access in assigned collections" }, "all": { "message": "All" @@ -2567,7 +2567,7 @@ } }, "viewAllLoginOptions": { - "message": "View all log in options" + "message": "عرض جميع خيارات تسجيل الدخول" }, "viewedItemId": { "message": "Viewed item $ID$.", @@ -2876,7 +2876,7 @@ "message": "Invalid date range." }, "errorOccurred": { - "message": "An error has occurred." + "message": "لقد حدث خطأ." }, "userAccess": { "message": "User access" @@ -2954,7 +2954,7 @@ "message": "Unable to verify your email. Try sending a new verification email." }, "emailVerificationRequired": { - "message": "Email verification required" + "message": "تأكيد البريد الإلكتروني مطلوب" }, "emailVerificationRequiredDesc": { "message": "You must verify your email to use this feature." @@ -2990,7 +2990,7 @@ } }, "rememberEmail": { - "message": "Remember email" + "message": "تذكرني" }, "recoverAccountTwoStepDesc": { "message": "If you cannot access your account through your normal two-step login methods, you can use your two-step login recovery code to turn off all two-step providers on your account." @@ -3426,10 +3426,10 @@ "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, "fingerprintMatchInfo": { - "message": "Please make sure your vault is unlocked and Fingerprint phrase matches the other device." + "message": "الرجاء التأكد من أن الخزنة الخاصة بك غير مقفلة وأن بصمة الإصبع تتطابق مع الجهاز الآخر." }, "fingerprintPhraseHeader": { - "message": "Fingerprint phrase" + "message": "بصمة الإصبع" }, "dontAskFingerprintAgain": { "message": "Never prompt to verify fingerprint phrases for invited users (not recommended)", @@ -3692,10 +3692,10 @@ "message": "Organization identifier" }, "ssoLogInWithOrgIdentifier": { - "message": "Log in using your organization's single sign-on portal. Please enter your organization's SSO identifier to begin." + "message": "قم بتسجيل الدخول بسرعة باستخدام بوابة تسجيل الدخول الأحادي لمؤسستك. الرجاء إدخال معرف الـSSO الخاص بمؤسستك للبدء." }, "enterpriseSingleSignOn": { - "message": "Enterprise single sign-on" + "message": "تسجيل الدخول الأُحادي للمؤسسات – SSO" }, "ssoHandOff": { "message": "You may now close this tab and continue in the extension." @@ -3713,10 +3713,10 @@ "message": "SSO validation failed" }, "ssoIdentifierRequired": { - "message": "Organization SSO identifier is required." + "message": "معرف الSSO للمنظمة مطلوب" }, "ssoIdentifier": { - "message": "SSO identifier" + "message": "معرف SSO" }, "ssoIdentifierHint": { "message": "Provide this ID to your members to login with SSO." @@ -4117,7 +4117,22 @@ "message": "Custom" }, "customDesc": { - "message": "Allows more granular control of user permissions for advanced configurations." + "message": "Grant customized permissions to members" + }, + "customDescNonEnterpriseStart": { + "message": "Custom roles is an ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseLink": { + "message": "enterprise feature", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseEnd": { + "message": ". Contact our support team to upgrade your subscription", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customNonEnterpriseError": { + "message": "To enable custom permissions the organization must be on an Enterprise 2020 plan." }, "permissions": { "message": "Permissions" @@ -4285,10 +4300,10 @@ "message": "The deletion date provided is not valid." }, "expirationDateAndTimeRequired": { - "message": "An expiration date and time are required." + "message": "مطلوب تاريخ ووقت انتهاء الصلاحية." }, "deletionDateAndTimeRequired": { - "message": "A deletion date and time are required." + "message": "مطلوب تاريخ ووقت الحذف." }, "dateParsingError": { "message": "There was an error saving your deletion and expiration dates." @@ -4441,7 +4456,7 @@ "message": "Resend invitations" }, "resendNotification": { - "message": "Resend notification" + "message": "إعادة إرسال الإشعار" }, "noSelectedUsersApplicable": { "message": "This action is not applicable to any of the selected users." @@ -5140,7 +5155,7 @@ "message": "1 field above needs your attention." }, "fieldRequiredError": { - "message": "$FIELDNAME$ is required.", + "message": "$FIELDNAME$ مطلوب.", "placeholders": { "fieldname": { "content": "$1", @@ -5404,10 +5419,10 @@ "description": "the text, 'SCIM', is an acronymn and should not be translated." }, "inputRequired": { - "message": "Input is required." + "message": "هذا الحقل مطلوب." }, "inputEmail": { - "message": "Input is not an email address." + "message": "القيمة المدخلة ليست عنوان بريد إلكتروني." }, "inputMinLength": { "message": "Input must be at least $COUNT$ characters long.", @@ -5455,10 +5470,10 @@ "message": "Number of users" }, "loggingInAs": { - "message": "Logging in as" + "message": "تسجيل الدخول كـ" }, "notYou": { - "message": "Not you?" + "message": "ليس حسابك؟" }, "multiSelectPlaceholder": { "message": "-- Type to Filter --" diff --git a/apps/web/src/locales/az/messages.json b/apps/web/src/locales/az/messages.json index 79e5b86cccb..016f0053154 100644 --- a/apps/web/src/locales/az/messages.json +++ b/apps/web/src/locales/az/messages.json @@ -4119,6 +4119,21 @@ "customDesc": { "message": "Qabaqcıl konfiqurasiya üçün istifadəçi icazələrinin daha təfsilatlı nəzarətinə icazə verər." }, + "customDescNonEnterpriseStart": { + "message": "Özəl rollar ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseLink": { + "message": "bir müəssisə özəlliyidir", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseEnd": { + "message": ". Abunəliyinizi yüksəltmək üçün dəstək komandamızla əlaqə saxlayın", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customNonEnterpriseError": { + "message": "Özəl icazələri fəallaşdırmaq üçün təşkilat, Enterprise 2020 planında olmalıdır." + }, "permissions": { "message": "İcazələr" }, diff --git a/apps/web/src/locales/be/messages.json b/apps/web/src/locales/be/messages.json index 32c2140430c..8071c8840f4 100644 --- a/apps/web/src/locales/be/messages.json +++ b/apps/web/src/locales/be/messages.json @@ -4119,6 +4119,21 @@ "customDesc": { "message": "Дазваляе больш паглыблена кантраляваць дазволы карыстальніка для дадатковых канфігурацый." }, + "customDescNonEnterpriseStart": { + "message": "Карыстальніцкія ролі з'яўляюцца ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseLink": { + "message": "функцыяй для прадпрыемства", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseEnd": { + "message": ". Звяжыцеся з нашай камандай падтрымкі для паляпшэння падпіскі", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customNonEnterpriseError": { + "message": "Каб уключыць дазволы карыстальніка, арганізацыя павінна мець тарыфны план Enterprise 2020." + }, "permissions": { "message": "Дазволы" }, diff --git a/apps/web/src/locales/bg/messages.json b/apps/web/src/locales/bg/messages.json index 645e47bbbc0..91e378d7e0a 100644 --- a/apps/web/src/locales/bg/messages.json +++ b/apps/web/src/locales/bg/messages.json @@ -4119,6 +4119,21 @@ "customDesc": { "message": "По-специфичен контрол на правата на потребителите при по-сложни варианти." }, + "customDescNonEnterpriseStart": { + "message": "Фукционалността „Персонализирани роли“ е ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseLink": { + "message": "за големи организации", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseEnd": { + "message": ". Свържете се с нашия екип по поддръжката, за да надградите абонамента си.", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customNonEnterpriseError": { + "message": "To enable custom permissions the organization must be on an Enterprise 2020 plan." + }, "permissions": { "message": "Права" }, diff --git a/apps/web/src/locales/bn/messages.json b/apps/web/src/locales/bn/messages.json index 09c39d19da8..a2fbddd5164 100644 --- a/apps/web/src/locales/bn/messages.json +++ b/apps/web/src/locales/bn/messages.json @@ -2444,7 +2444,7 @@ "message": "Owner" }, "ownerDesc": { - "message": "The highest access user that can manage all aspects of your organization." + "message": "Manage all aspects of your organization, including billing and subscriptions" }, "clientOwnerDesc": { "message": "This user should be independent of the Provider. If the Provider is disassociated with the organization, this user will maintain ownership of the organization." @@ -2453,19 +2453,19 @@ "message": "Admin" }, "adminDesc": { - "message": "Admins can access and manage all items, collections and users in your organization." + "message": "Manage organization access, all collections, members, reporting, and security settings" }, "user": { "message": "User" }, "userDesc": { - "message": "A regular user with access to assigned collections in your organization." + "message": "Access and add items to assigned collections" }, "manager": { "message": "Manager" }, "managerDesc": { - "message": "Managers can access and manage assigned collections in your organization." + "message": "Create, delete, and manage access in assigned collections" }, "all": { "message": "All" @@ -4117,7 +4117,22 @@ "message": "Custom" }, "customDesc": { - "message": "Allows more granular control of user permissions for advanced configurations." + "message": "Grant customized permissions to members" + }, + "customDescNonEnterpriseStart": { + "message": "Custom roles is an ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseLink": { + "message": "enterprise feature", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseEnd": { + "message": ". Contact our support team to upgrade your subscription", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customNonEnterpriseError": { + "message": "To enable custom permissions the organization must be on an Enterprise 2020 plan." }, "permissions": { "message": "Permissions" diff --git a/apps/web/src/locales/bs/messages.json b/apps/web/src/locales/bs/messages.json index 968990ebf34..1aa90576c34 100644 --- a/apps/web/src/locales/bs/messages.json +++ b/apps/web/src/locales/bs/messages.json @@ -2444,7 +2444,7 @@ "message": "Owner" }, "ownerDesc": { - "message": "The highest access user that can manage all aspects of your organization." + "message": "Manage all aspects of your organization, including billing and subscriptions" }, "clientOwnerDesc": { "message": "This user should be independent of the Provider. If the Provider is disassociated with the organization, this user will maintain ownership of the organization." @@ -2453,19 +2453,19 @@ "message": "Admin" }, "adminDesc": { - "message": "Admins can access and manage all items, collections and users in your organization." + "message": "Manage organization access, all collections, members, reporting, and security settings" }, "user": { "message": "User" }, "userDesc": { - "message": "A regular user with access to assigned collections in your organization." + "message": "Access and add items to assigned collections" }, "manager": { "message": "Manager" }, "managerDesc": { - "message": "Managers can access and manage assigned collections in your organization." + "message": "Create, delete, and manage access in assigned collections" }, "all": { "message": "All" @@ -4117,7 +4117,22 @@ "message": "Custom" }, "customDesc": { - "message": "Allows more granular control of user permissions for advanced configurations." + "message": "Grant customized permissions to members" + }, + "customDescNonEnterpriseStart": { + "message": "Custom roles is an ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseLink": { + "message": "enterprise feature", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseEnd": { + "message": ". Contact our support team to upgrade your subscription", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customNonEnterpriseError": { + "message": "To enable custom permissions the organization must be on an Enterprise 2020 plan." }, "permissions": { "message": "Permissions" diff --git a/apps/web/src/locales/ca/messages.json b/apps/web/src/locales/ca/messages.json index 4c18613ed17..d83bfd07c3f 100644 --- a/apps/web/src/locales/ca/messages.json +++ b/apps/web/src/locales/ca/messages.json @@ -2444,7 +2444,7 @@ "message": "Propietari" }, "ownerDesc": { - "message": "L'usuari d'accés més elevat que pot gestionar tots els aspectes de la vostra organització." + "message": "Gestioneu tots els aspectes de la vostra organització, incloses la facturació i les subscripcions" }, "clientOwnerDesc": { "message": "Aquest usuari ha de ser independent del proveïdor. Si el proveïdor està desvinculat de l'organització, aquest usuari mantindrà la propietat de l'organització." @@ -2453,19 +2453,19 @@ "message": "Administrador" }, "adminDesc": { - "message": "Els administradors poden accedir i gestionar tots els elements, col·leccions i usuaris de la vostra organització." + "message": "Gestioneu l'accés a l'organització, totes les col·leccions, els membres, els informes i la configuració de seguretat" }, "user": { "message": "Usuari" }, "userDesc": { - "message": "Un usuari habitual amb accés a les col·leccions assignades a la vostra organització." + "message": "Accediu i afegiu elements a les col·leccions assignades" }, "manager": { "message": "Gestor" }, "managerDesc": { - "message": "Els gestors poden accedir i gestionar les col·leccions assignades a la vostra organització." + "message": "Creeu, suprimiu i gestioneu l'accés a les col·leccions assignades" }, "all": { "message": "Tot" @@ -4117,7 +4117,22 @@ "message": "Personalitzat" }, "customDesc": { - "message": "Permet un control més granular dels permisos d'usuari per a configuracions avançades." + "message": "Concediu permisos personalitzats als membres" + }, + "customDescNonEnterpriseStart": { + "message": "Els rols personalitzats són una ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseLink": { + "message": "característica empresarial", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseEnd": { + "message": ". Contacta amb el nostre equip d'assistència per actualitzar la teua subscripció", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customNonEnterpriseError": { + "message": "Per habilitar els permisos personalitzats, l'organització ha de tenir un pla Enterprise 2020." }, "permissions": { "message": "Permisos" diff --git a/apps/web/src/locales/cs/messages.json b/apps/web/src/locales/cs/messages.json index cbdd04f18aa..73aed1345ff 100644 --- a/apps/web/src/locales/cs/messages.json +++ b/apps/web/src/locales/cs/messages.json @@ -2453,13 +2453,13 @@ "message": "Administrátor" }, "adminDesc": { - "message": "Administrátoři mohou prohlížet a spravovat všechny položky, sbírky a uživatele ve vaší organizaci." + "message": "Administrátoři mohou prohlížet a spravovat všechny položky, kolekce a uživatele ve vaší organizaci." }, "user": { "message": "Uživatel" }, "userDesc": { - "message": "Běžný uživatel s přístupem k přiřazeným kolekcím vaší organizace." + "message": "A regular user with access to your organization's collections." }, "manager": { "message": "Správce" @@ -4119,6 +4119,21 @@ "customDesc": { "message": "Umožňuje větší kontrolu nad uživatelských oprávnění pro pokročilé konfigurace." }, + "customDescNonEnterpriseStart": { + "message": "Custom roles is an ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseLink": { + "message": "enterprise feature", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseEnd": { + "message": ". Contact our support team to upgrade your subscription", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customNonEnterpriseError": { + "message": "To enable custom permissions the organization must be on an Enterprise 2020 plan." + }, "permissions": { "message": "Oprávnění" }, diff --git a/apps/web/src/locales/da/messages.json b/apps/web/src/locales/da/messages.json index 54b36fc945f..cc1fa59ddbd 100644 --- a/apps/web/src/locales/da/messages.json +++ b/apps/web/src/locales/da/messages.json @@ -10,7 +10,7 @@ } }, "whatTypeOfItem": { - "message": "Hvilken type element er dette?" + "message": "Hvilken emnetype er denne?" }, "name": { "message": "Navn" @@ -50,7 +50,7 @@ "message": "Tilpassede felter" }, "cardholderName": { - "message": "Kortholders navn" + "message": "Kortholdernavn" }, "number": { "message": "Nummer" @@ -125,16 +125,16 @@ "message": "Titel" }, "mr": { - "message": "Hr" + "message": "Hr." }, "mrs": { "message": "Fru" }, "ms": { - "message": "Frk" + "message": "Frk." }, "dr": { - "message": "Dr" + "message": "Dr." }, "expirationMonth": { "message": "Udløbsmåned" @@ -143,7 +143,7 @@ "message": "Udløbsår" }, "authenticatorKeyTotp": { - "message": "Autentificeringsnøgle (TOTP)" + "message": "Godkendelsesnøgle (TOTP)" }, "folder": { "message": "Mappe" @@ -187,7 +187,7 @@ "message": "Redigér mappe" }, "baseDomain": { - "message": "Grund-domæne", + "message": "Basisdomæne", "description": "Domain name. Example: website.com" }, "domainName": { @@ -213,7 +213,7 @@ "description": "URI match detection for auto-fill." }, "defaultMatchDetection": { - "message": "Standard matchmetode", + "message": "Standard matchdetektering", "description": "Default URI match detection for auto-fill." }, "never": { @@ -230,10 +230,10 @@ "message": "Generér adgangskode" }, "checkPassword": { - "message": "Undersøg om adgangskoden er blevet afsløret." + "message": "Tjek, om adgangskode er blevet kompromitteret." }, "passwordExposed": { - "message": "Denne adgangskode er blevet afsløret $VALUE$ gang(e) i datalæk. Du burde skifte den.", + "message": "Denne adgangskode er viderebragt $VALUE$ gang(e) i datalæk og bør straks skiftes.", "placeholders": { "value": { "content": "$1", @@ -414,7 +414,7 @@ "description": "Copy credit card number" }, "copySecurityCode": { - "message": "Kopiér kortverifikationskode", + "message": "Kopiér verifikationskode", "description": "Copy credit card security code (CVV)" }, "copyUri": { @@ -479,7 +479,7 @@ "message": "Maks. filstørrelse er 500 MB." }, "updateKey": { - "message": "Denne funktion kan ikke bruges, før du har opdateret din krypteringsnøgle." + "message": "Denne funktion kan ikke bruges, før du opdaterer din krypteringsnøgle." }, "addedItem": { "message": "Emne tilføjet" @@ -576,7 +576,7 @@ "message": "Log ind med enhed skal være opsat i indstillingerne i Bitwarden mobil-appen. Behov for en anden mulighed?" }, "loginWithMasterPassword": { - "message": "Log ind med hovedadgangskoden" + "message": "Log ind med hovedadgangskode" }, "createAccount": { "message": "Opret konto" @@ -612,31 +612,31 @@ "message": "Hovedadgangskoden er den adgangskode, du bruger, når du tilgår din boks. Det er meget vigtigt, at hovedadgangskoden ikke glemmes, da der ikke er nogen måde, hvorpå den kan genoprettes." }, "masterPassImportant": { - "message": "Hovedadgangskoder kan ikke gendannes, hvis du glemmer dem!" + "message": "Hovedadgangskoder kan ikke gendannes, hvis de glemmes!" }, "masterPassHintDesc": { - "message": "Et hovedadgangskode-tip kan bidrage til at komme i tanke om adgangskoden, hvis den glemmes." + "message": "Et hovedadgangskodetip kan bidrage til at komme i tanke om adgangskoden, hvis den glemmes." }, "reTypeMasterPass": { "message": "Angiv hovedadgangskoden igen" }, "masterPassHint": { - "message": "Hovedadgangskode-tip (valgfrit)" + "message": "Hovedadgangskodetip (valgfri)" }, "masterPassHintLabel": { - "message": "Hovedadgangskode-tip" + "message": "Hovedadgangskodetip" }, "settings": { "message": "Indstillinger" }, "passwordHint": { - "message": "Adgangskode-tip" + "message": "Adgangskodetip" }, "enterEmailToGetHint": { - "message": "Angiv din kontos e-mailadresse for at modtage hovedadgangskode-tip." + "message": "Angiv din kontos e-mailadresse for at modtage dit hovedadgangskodetip." }, "getMasterPasswordHint": { - "message": "Få hovedadgangskode-tip" + "message": "Få hovedadgangskodetip" }, "emailRequired": { "message": "E-mailadresse er påkrævet." @@ -663,7 +663,7 @@ "message": "Konto oprettet." }, "masterPassSent": { - "message": "En e-mail med dit hovedadgangskode-tip er sendt." + "message": "En e-mail med dit hovedadgangskodetip er sendt til dig." }, "unexpectedError": { "message": "En uventet fejl opstod." @@ -718,7 +718,7 @@ "message": "Ny organisation" }, "noOrganizationsList": { - "message": "Du tilhører ikke nogen organisation. Organisationer muliggør sikker deling af emner med andre brugere." + "message": "Du tilhører ingen organisationer. Organisationer muliggør sikker deling af emner med andre brugere." }, "notificationSentDevice": { "message": "En notifikation er sendt til din enhed." @@ -763,7 +763,7 @@ "message": "Brug en anden totrins-login metode" }, "insertYubiKey": { - "message": "Indsæt din YubiKey i computerens USB-port, tryk dernæst på dens knap." + "message": "Indsæt din YubiKey i computerens USB-port og tryk på dens knap." }, "insertU2f": { "message": "Indsæt din sikkerhedsnøgle i computerens USB-port. Har den en knap, tryk på den." @@ -1711,7 +1711,7 @@ } }, "goodNews": { - "message": "Gode nyheder", + "message": "Godt nyt", "description": "ex. Good News, No Breached Accounts Found!" }, "breachUsernameFound": { @@ -1740,10 +1740,10 @@ "message": "Berørte brugere" }, "breachOccurred": { - "message": "Læk forekom" + "message": "Læk forekommet" }, "breachReported": { - "message": "Læk rapporteret" + "message": "Læk anmeldt" }, "reportError": { "message": "Der opstod en fejl under forsøget på at indlæse rapporten. Prøv igen" @@ -1752,7 +1752,7 @@ "message": "Fakturering" }, "billingPlanLabel": { - "message": "Faktureringsabonnement" + "message": "Faktureringsplan" }, "paymentType": { "message": "Betalingstype" @@ -2336,7 +2336,7 @@ "message": "Politikker" }, "singleSignOn": { - "message": "Single Sign On" + "message": "Single Sign-On" }, "editPolicy": { "message": "Redigér politik" @@ -2366,7 +2366,7 @@ "message": "Når et medlems rettigheder tilbagekaldes, har vedkommende ikke længere adgang til organisationsdata. For hurtigt at genoprette medlemsadgang, så gå til fanen Tilbagekaldt." }, "removeUserConfirmationKeyConnector": { - "message": "Advarsel! Denne bruger behøver Key Connector til krypteringshåndtering. Fjernes brugeren fra din organisation, deaktiveres vedkommendes konto permanent. Denne handling kan ikke fortrydes. Vil du fortsætte?" + "message": "Advarsel! Denne bruger behøver Key Connector til krypteringshåndtering. Fjernes brugeren fra din organisation, deaktiveres vedkommendes konto permanent. Denne handling er irreversibel. Fortsæt alligevel?" }, "externalId": { "message": "Eksternt id" @@ -2438,13 +2438,13 @@ "message": "Bekræftet" }, "clientOwnerEmail": { - "message": "Klientejer e-mail" + "message": "Klientejers e-mail" }, "owner": { "message": "Ejer" }, "ownerDesc": { - "message": "Den bruger med højeste rettigheder, som kan håndtere alle aspekter af din organisation." + "message": "Håndtér alle aspekter af organisationen, herunder fakturering og abonnementer" }, "clientOwnerDesc": { "message": "Denne bruger bør være uafhængig af udbyderen. Hvis udbyderen frakobles organisationen, bevarer denne bruger ejerskabet af organisationen." @@ -2453,19 +2453,19 @@ "message": "Administrator" }, "adminDesc": { - "message": "Administratorer kan få adgang til og håndtere alle elementer, samlinger og brugere i din organisation." + "message": "Håndtér adgang, alle samlinger, medlemmer, rapportering og sikkerhedsindstillinger i organisationen" }, "user": { "message": "Bruger" }, "userDesc": { - "message": "En almindelig bruger med adgang til tildelte samlinger i din organisation." + "message": "Tilgå og føj emner til tildelte samlinger" }, "manager": { "message": "Manager" }, "managerDesc": { - "message": "Managere kan få adgang til og håndtere tildelte samlinger i din organisation." + "message": "Opret, slet og håndtér adgang i tildelte samlinger" }, "all": { "message": "Alle" @@ -2501,10 +2501,10 @@ "message": "Web-boks" }, "loggedIn": { - "message": "Logget ind." + "message": "Logget ind" }, "changedPassword": { - "message": "Ændrede konto kodeord." + "message": "Skift kontoadgangskode" }, "enabledUpdated2fa": { "message": "Totrins-login gemt" @@ -2522,7 +2522,7 @@ "message": "Loginforsøg mislykkedes med forkert totrins-login." }, "exportedVault": { - "message": "Eksporterede boks." + "message": "Boks eksporteret" }, "exportedOrganizationVault": { "message": "Eksporterede organisationsboks." @@ -2900,7 +2900,7 @@ "message": "Gensend e-mail" }, "hasBeenReinvited": { - "message": "$USER$ er blevet inviteret igen.", + "message": "$USER$ inviteret igen", "placeholders": { "user": { "content": "$1", @@ -4119,6 +4119,21 @@ "customDesc": { "message": "Tillader mere granulær kontrol af brugertilladelser for avancerede opsætninger." }, + "customDescNonEnterpriseStart": { + "message": "Tilpassede roller er en ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseLink": { + "message": "Enterprise-funktion", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseEnd": { + "message": ". Kontakt supportteamet for at opgradere abonnementet", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customNonEnterpriseError": { + "message": "Aktivering af tilpassede tilladelser kræver, at organisationen bruger en Enterprise 2020-abonnementstype." + }, "permissions": { "message": "Tilladelser" }, @@ -4306,7 +4321,7 @@ "message": "WebAuthn bekræftet Du kan lukke denne fane." }, "hintEqualsPassword": { - "message": "Dit kodeordstip må ikke være identisk med din adgangskode." + "message": "Adgangskodetip og adgangskode må ikke være identiske." }, "enrollPasswordReset": { "message": "Tilmeld Adgangskodenulstilling" @@ -5023,13 +5038,13 @@ "message": "Key Connector deaktiveret" }, "keyConnectorWarning": { - "message": "Når medlemmer begynder at bruge Key Connector, kan din organisation ikke vende tilbage til hovedadgangskode-dekryptering. Fortsæt kun, hvis du er fortrolig med at implementere og håndtere en nøgleserver." + "message": "Når medlemmer begynder at bruge Key Connector, kan en organisation ikke vende tilbage til hovedadgangskode-dekryptering. Fortsæt kun, hvis du er fortrolig med at implementere og håndtere en nøgleserver." }, "migratedKeyConnector": { "message": "Migreret til Key Connector" }, "paymentSponsored": { - "message": "Angiv en betalingsmetode der skal tilknyttes organisationen. Bare rolig, vi vil ikke opkræve dig noget, medmindre du vælger yderligere funktioner, eller dit sponsorat udløber." + "message": "Angiv en betalingsmetode der skal tilknyttes organisationen. Bare rolig, der sker ingen opkrævning, medmindre yderligere funktioner tilvælges, eller dit sponsorat udløber. " }, "orgCreatedSponsorshipInvalid": { "message": "Sponsortilbuddet er udløbet. Den oprettede organisation kan slettes for at undgå opkrævning ved udkøb af den 7-dages prøveperiode. Ellers luk denne prompt for at beholde organisationen og påtage dig faktureringsforpligtigelsen." @@ -5065,7 +5080,7 @@ "message": "Generering af faktureringssynk-token" }, "copyPasteBillingSync": { - "message": "Kopiér og indsæt dette token i indstillingerne Faktureringssynk i din selvhostede organisation." + "message": "Kopiér og indsæt dette token i indstillingerne Faktureringssynk i din selv-hostede organisation." }, "billingSyncCanAccess": { "message": "Din faktureringssynk-token kan tilgå og redigere denne organisations abonnementsindstillinger." @@ -5083,7 +5098,7 @@ "message": "Rotér token" }, "rotateBillingSyncTokenWarning": { - "message": "Fortsættes, vil faktureringssynk skulle genopsættes på din selvhostede server." + "message": "Fortsættes, vil faktureringssynk skulle genopsættes på din selv-hostede server." }, "rotateBillingSyncTokenTitle": { "message": "Rotering af faktureringssynk-tokenet vil ugyldiggøre det foregående token." @@ -5092,7 +5107,7 @@ "message": "Selvhosting" }, "selfHostingEnterpriseOrganizationSectionCopy": { - "message": "For at opsætte din organisation på egen server, så upload din licensfil. Opsæt faktureringssynk for at understøtte gratis Familier-abonnementstyper og avancerede faktureringsmuligheder for din selvhostede organisation." + "message": "For at opsætte din organisation på egen server, skal din licensfil uploads. Opsæt faktureringssynk for at understøtte gratis Familier-abonnementstyper og avancerede faktureringsmuligheder for din selv-hostede organisation." }, "billingSyncApiKeyRotated": { "message": "Token roteret." @@ -5101,7 +5116,7 @@ "message": "Faktureringssynk" }, "billingSyncDesc": { - "message": "Faktureringssynk giver gratis Familier-abonnementstyper til medlemmer samt avancerede faktureringsmuligheder ved at linke din selvhostede Bitwarden til Bitwarden-cloudserveren." + "message": "Faktureringssynk giver gratis Familier-abonnementstyper til medlemmer samt avancerede faktureringsmuligheder ved at linke din selv-hostede Bitwarden til Bitwarden-cloudserveren." }, "billingSyncKeyDesc": { "message": "Et faktureringssynk-token fra din cloud-organisations abonnementsindstillinger kræves for at udfylde denne formular." @@ -5212,7 +5227,7 @@ "message": "Organisationsvælger" }, "currentOrganization": { - "message": "Aktuel organisation", + "message": "Nuværende organisation", "description": "This is used by screen readers to indicate the organization that is currently being shown to the user." }, "accountSettings": { @@ -5222,7 +5237,7 @@ "message": "Generator" }, "whatWouldYouLikeToGenerate": { - "message": "Hvad vil du gerne generere?" + "message": "Hvad ønskes genereret?" }, "passwordType": { "message": "Adgangskodetype" @@ -5241,13 +5256,13 @@ "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" }, "plusAddressedEmailDesc": { - "message": "Brug din e-mailudbyders underadresseringsmuligheder." + "message": "Brug e-mailudbyderens underadresseringsmuligheder." }, "catchallEmail": { "message": "Fang-alle e-mail" }, "catchallEmailDesc": { - "message": "Brug dit domænes konfigurerede fang-alle-indbakke." + "message": "Brug den for domænet opsatte Fang-alle indbakke." }, "random": { "message": "Tilfældig", @@ -5260,7 +5275,7 @@ "message": "Tjeneste" }, "unknownCipher": { - "message": "Ukendt element. Du skal muligvis anmode om tilladelse for at få adgang til dette element." + "message": "Ukendt emne. Du skal muligvis anmode om tilladelse til at tilgå emnet." }, "cannotSponsorSelf": { "message": "Du kan ikke indløse for den aktive konto. Angiv en anden e-mail." @@ -5275,7 +5290,7 @@ } }, "awaitingSyncSingular": { - "message": "Token er roteret for $DAYS$ dag siden. Opdatér faktureringssynk i de selvhostede organisationsindstillinger.", + "message": "Token er roteret for $DAYS$ dag siden. Opdatér faktureringssynk under selv-hostet organisationsindstillingerne.", "placeholders": { "days": { "content": "$1", @@ -5284,7 +5299,7 @@ } }, "awaitingSyncPlural": { - "message": "Token er roteret for $DAYS$ dage siden. Opdatér faktureringssynk i de selvhostede organisationsindstillinger.", + "message": "Token er roteret for $DAYS$ dage siden. Opdatér faktureringssynk under selv-hostet organisationsindstillingerne.", "placeholders": { "days": { "content": "$1", @@ -5309,11 +5324,11 @@ } }, "billingContactProviderForAssistance": { - "message": "Kontakt venligst dem for yderligere assistance", + "message": "Kontakt dem for yderligere assistance", "description": "This text is displayed if an organization's billing is managed by a Provider. It tells the user to contact the Provider for assistance." }, "forwardedEmail": { - "message": "Videresendt E-mail Alias" + "message": "Videresendt e-mail alias" }, "forwardedEmailDesc": { "message": "Generér et e-mail alias med en ekstern viderestillingstjeneste." @@ -5332,13 +5347,13 @@ "message": "Aktivér enhedsbekræftelse" }, "deviceVerificationDesc": { - "message": "Når aktiveret, sendes bekræftelseskoder til din e-mailadresse ved indlogning fra en ikke genkendt enhed" + "message": "Bekræftelseskoder sendes til din e-mailadresse ved loginforsøg fra en ikke-genkendt enhed" }, "updatedDeviceVerification": { "message": "Opdateret enhedsbekræftelse" }, "areYouSureYouWantToEnableDeviceVerificationTheVerificationCodeEmailsWillArriveAtX": { - "message": "Er du sikker på, at du vil aktivere enhedsbekræftelse? Bekræftelseskoder e-mailes til: $EMAIL$", + "message": "Sikker på, at du vil aktivere enhedsbekræftelse? Bekræftelseskoder e-mailes til: $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -5354,7 +5369,7 @@ "description": "The text, 'SCIM', is an acronymn and should not be translated." }, "scimDescription": { - "message": "Opret automatisk brugere og grupper med din foretrukne identitetsudbyder via SCIM-provisionering", + "message": "Tildel automatisk brugere og grupper den foretrukne identitetsudbyder via SCIM-provisionering", "description": "the text, 'SCIM', is an acronymn and should not be translated." }, "scimEnabledCheckboxDesc": { @@ -5362,7 +5377,7 @@ "description": "the text, 'SCIM', is an acronymn and should not be translated." }, "scimEnabledCheckboxDescHelpText": { - "message": "Opsæt din foretrukne identitetsudbyder ved at konfigurere URL'en og SCIM API-nøglen", + "message": "Opsæt foretrukken identitetsudbyder ved at opsætte URL samt SCIM API-nøgle", "description": "the text, 'SCIM', is an acronymn and should not be translated." }, "scimApiKeyHelperText": { @@ -5377,7 +5392,7 @@ "description": "the text, 'SCIM' and 'API', are acronymns and should not be translated." }, "rotateScimKeyWarning": { - "message": "Er du sikker på, at du vil rotere SCIM API-nøglen? Den nuværende nøgle vil ikke længere fungere for eksisterende integrationer.", + "message": "Sikker på, at SCIM API-nøglen skal roteres? Nøglen vil i så fald ikke længere fungere for eksisterende integrationer.", "description": "the text, 'SCIM' and 'API', are acronymns and should not be translated." }, "rotateKey": { @@ -5396,11 +5411,11 @@ "description": "the text, 'SCIM' and 'URL', are acronymns and should not be translated." }, "scimApiKeyRotated": { - "message": "SCIM API-nøglen er blevet roteret", + "message": "SCIM API-nøglen er hermed roteret", "description": "the text, 'SCIM' and 'API', are acronymns and should not be translated." }, "scimSettingsSaved": { - "message": "SCIM-indstillingerne er blevet gemt", + "message": "SCIM-ændringer er hermed gemt", "description": "the text, 'SCIM', is an acronymn and should not be translated." }, "inputRequired": { @@ -5410,7 +5425,7 @@ "message": "Input er ikke en e-mailadresse." }, "inputMinLength": { - "message": "Input skal være mindst $COUNT$ tegn langt.", + "message": "Input skal udgøre min. $COUNT$ tegn.", "placeholders": { "count": { "content": "$1", diff --git a/apps/web/src/locales/de/messages.json b/apps/web/src/locales/de/messages.json index 73f2b544577..49103c101e2 100644 --- a/apps/web/src/locales/de/messages.json +++ b/apps/web/src/locales/de/messages.json @@ -4119,6 +4119,21 @@ "customDesc": { "message": "Feinere Kontrolle der Benutzer Berechtigungen für erweiterte Konfigurationen erlauben." }, + "customDescNonEnterpriseStart": { + "message": "Benutzerdefinierte Rollen sind eine ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseLink": { + "message": "Funktion für Unternehmen", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseEnd": { + "message": ". Kontaktiere unser Kundenserviceteam, um dein Abo hochzustufen", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customNonEnterpriseError": { + "message": "Um benutzerdefinierte Berechtigungen zu ermöglichen, muss sich die Organisation auf einem 2020er Unternehmensabo befinden." + }, "permissions": { "message": "Berechtigungen" }, diff --git a/apps/web/src/locales/el/messages.json b/apps/web/src/locales/el/messages.json index ee4f1426f01..2db57d3ba0c 100644 --- a/apps/web/src/locales/el/messages.json +++ b/apps/web/src/locales/el/messages.json @@ -4119,6 +4119,21 @@ "customDesc": { "message": "Επιτρέπει πιο κοκκώδη έλεγχο, των δικαιωμάτων χρήστη για προηγμένες ρυθμίσεις." }, + "customDescNonEnterpriseStart": { + "message": "Custom roles is an ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseLink": { + "message": "enterprise feature", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseEnd": { + "message": ". Contact our support team to upgrade your subscription", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customNonEnterpriseError": { + "message": "To enable custom permissions the organization must be on an Enterprise 2020 plan." + }, "permissions": { "message": "Άδειες" }, diff --git a/apps/web/src/locales/en_GB/messages.json b/apps/web/src/locales/en_GB/messages.json index 0a30129378a..bf6d6cca472 100644 --- a/apps/web/src/locales/en_GB/messages.json +++ b/apps/web/src/locales/en_GB/messages.json @@ -2444,7 +2444,7 @@ "message": "Owner" }, "ownerDesc": { - "message": "The highest access user that can manage all aspects of your organisation." + "message": "Manage all aspects of your organisation, including billing and subscriptions" }, "clientOwnerDesc": { "message": "This user should be independent of the Provider. If the Provider is disassociated with the organisation, this user will maintain ownership of the organisation." @@ -2453,19 +2453,19 @@ "message": "Admin" }, "adminDesc": { - "message": "Admins can access and manage all items, collections and users in your organisation." + "message": "Manage organisation access, all collections, members, reporting, and security settings" }, "user": { "message": "User" }, "userDesc": { - "message": "A regular user with access to assigned collections in your organisation." + "message": "Access and add items to assigned collections" }, "manager": { "message": "Manager" }, "managerDesc": { - "message": "Managers can access and manage assigned collections in your organisation." + "message": "Create, delete, and manage access in assigned collections" }, "all": { "message": "All" @@ -4117,7 +4117,22 @@ "message": "Custom" }, "customDesc": { - "message": "Allows more granular control of user permissions for advanced configurations." + "message": "Grant customised permissions to members" + }, + "customDescNonEnterpriseStart": { + "message": "Custom roles is an ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseLink": { + "message": "enterprise feature", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseEnd": { + "message": ". Contact our support team to upgrade your subscription", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customNonEnterpriseError": { + "message": "To enable custom permissions the organization must be on an Enterprise 2020 plan." }, "permissions": { "message": "Permissions" @@ -4553,28 +4568,28 @@ "message": "Join Provider" }, "joinProviderDesc": { - "message": "You've been invited to join the provider listed above. To accept the invitation, you need to log in or create a new Bitwarden account." + "message": "You've been invited to join the Provider listed above. To accept the invitation, you need to log in or create a new Bitwarden account." }, "providerInviteAcceptFailed": { - "message": "Unable to accept invitation. Ask a provider admin to send a new invitation." + "message": "Unable to accept invitation. Ask a Provider admin to send a new invitation." }, "providerInviteAcceptedDesc": { - "message": "You can access this provider once an administrator confirms your membership. We'll send you an email when that happens." + "message": "You can access this Provider once an administrator confirms your membership. We'll send you an email when that happens." }, "providerUsersNeedConfirmed": { - "message": "You have users that have accepted their invitation, but still need to be confirmed. Users will not have access to the provider until they are confirmed." + "message": "You have users that have accepted their invitation, but still need to be confirmed. Users will not have access to the Provider until they are confirmed." }, "provider": { "message": "Provider" }, "newClientOrganization": { - "message": "New Client Organisation" + "message": "New client organisation" }, "newClientOrganizationDesc": { - "message": "Create a new client organisation that will be associated with you as the provider. You will be able to access and manage this organisation." + "message": "Create a new client organisation that will be associated with you as the Provider. You will be able to access and manage this organisation." }, "addExistingOrganization": { - "message": "Add Existing Organisation" + "message": "Add existing organisation" }, "myProvider": { "message": "My Provider" @@ -4593,10 +4608,10 @@ } }, "organizationJoinedProvider": { - "message": "Organisation was successfully added to the provider" + "message": "Organisation was successfully added to the Provider" }, "accessingUsingProvider": { - "message": "Accessing organisation using provider $PROVIDER$", + "message": "Accessing organisation using Provider $PROVIDER$", "placeholders": { "provider": { "content": "$1", @@ -4605,13 +4620,13 @@ } }, "providerIsDisabled": { - "message": "Provider is disabled." + "message": "Provider suspended" }, "providerUpdated": { - "message": "Provider updated" + "message": "Provider saved" }, "yourProviderIs": { - "message": "Your provider is $PROVIDER$. They have administrative and billing privileges for your organisation.", + "message": "Your Provider is $PROVIDER$. They have administrative and billing privileges for your organisation.", "placeholders": { "provider": { "content": "$1", @@ -4620,7 +4635,7 @@ } }, "detachedOrganization": { - "message": "The organisation $ORGANIZATION$ has been detached from your provider.", + "message": "The organisation $ORGANIZATION$ has been detached from your Provider.", "placeholders": { "organization": { "content": "$1", @@ -4629,22 +4644,22 @@ } }, "detachOrganizationConfirmation": { - "message": "Are you sure you want to detach this organisation? The organisation will continue to exist but will no longer be managed by the provider." + "message": "Are you sure you want to detach this organisation? The organisation will continue to exist but will no longer be managed by the Provider." }, "add": { "message": "Add" }, "updatedMasterPassword": { - "message": "Updated Master Password" + "message": "Master password saved" }, "updateMasterPassword": { - "message": "Update Master Password" + "message": "Update master password" }, "updateMasterPasswordWarning": { - "message": "Your Master Password was recently changed by an administrator in your organisation. In order to access the vault, you must update your Master Password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." + "message": "Your master password was recently changed by an administrator in your organisation. In order to access the vault, you must update your master password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." }, "masterPasswordInvalidWarning": { - "message": "Your Master Password does not meet the policy requirements of this organisation. In order to join the organisation, you must update your Master Password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." + "message": "Your master password does not meet the policy requirements of this organisation. In order to join the organisation, you must update your master password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." }, "maximumVaultTimeout": { "message": "Vault timeout" @@ -4653,10 +4668,10 @@ "message": "Set a maximum vault timeout for members." }, "maximumVaultTimeoutLabel": { - "message": "Maximum Vault Timeout" + "message": "Maximum vault timeout" }, "invalidMaximumVaultTimeout": { - "message": "Invalid Maximum Vault Timeout." + "message": "Invalid maximum vault timeout." }, "hours": { "message": "Hours" @@ -4665,7 +4680,7 @@ "message": "Minutes" }, "vaultTimeoutPolicyInEffect": { - "message": "Your organisation policies are affecting your vault timeout. Maximum allowed Vault Timeout is $HOURS$ hour(s) and $MINUTES$ minute(s)", + "message": "Your organisation policies are affecting your vault timeout. Maximum allowed vault timeout is $HOURS$ hour(s) and $MINUTES$ minute(s)", "placeholders": { "hours": { "content": "$1", @@ -4678,7 +4693,7 @@ } }, "customVaultTimeout": { - "message": "Custom Vault Timeout" + "message": "Custom vault timeout" }, "vaultTimeoutToLarge": { "message": "Your vault timeout exceeds the restriction set by your organisation." @@ -4687,7 +4702,7 @@ "message": "Minimum custom timeout is 1 minute." }, "vaultTimeoutRangeError": { - "message": "Vault Timeout is not within allowed range." + "message": "Vault timeout is not within allowed range." }, "disablePersonalVaultExport": { "message": "Remove individual vault export" @@ -4696,31 +4711,31 @@ "message": "Do not allow members to export their individual vault data." }, "vaultExportDisabled": { - "message": "Vault Export Disabled" + "message": "Vault export removed" }, "personalVaultExportPolicyInEffect": { - "message": "One or more organisation policies prevents you from exporting your personal vault." + "message": "One or more organisation policies prevent you from exporting your individual vault." }, "selectType": { - "message": "Select SSO Type" + "message": "Select SSO type" }, "type": { "message": "Type" }, "openIdConnectConfig": { - "message": "OpenID Connect Configuration" + "message": "OpenID connect configuration" }, "samlSpConfig": { - "message": "SAML Service Provider Configuration" + "message": "SAML service provider configuration" }, "samlIdpConfig": { - "message": "SAML Identity Provider Configuration" + "message": "SAML identity provider configuration" }, "callbackPath": { - "message": "Callback Path" + "message": "Callback path" }, "signedOutCallbackPath": { - "message": "Signed Out Callback Path" + "message": "Signed out callback path" }, "authority": { "message": "Authority" @@ -4729,55 +4744,55 @@ "message": "Client ID" }, "clientSecret": { - "message": "Client Secret" + "message": "Client secret" }, "metadataAddress": { - "message": "Metadata Address" + "message": "Metadata address" }, "oidcRedirectBehavior": { - "message": "OIDC Redirect Behavior" + "message": "OIDC redirect behaviour" }, "getClaimsFromUserInfoEndpoint": { "message": "Get claims from user info endpoint" }, "additionalScopes": { - "message": "Custom Scopes" + "message": "Custom scopes" }, "additionalUserIdClaimTypes": { - "message": "Custom User ID Claim Types" + "message": "Custom user ID claim types" }, "additionalEmailClaimTypes": { - "message": "Email Claim Types" + "message": "Email claim types" }, "additionalNameClaimTypes": { - "message": "Custom Name Claim Types" + "message": "Custom name claim types" }, "acrValues": { - "message": "Requested Authentication Context Class Reference values" + "message": "Requested authentication context class reference values" }, "expectedReturnAcrValue": { - "message": "Expected \"acr\" Claim Value In Response" + "message": "Expected \"acr\" claim value in response" }, "spEntityId": { - "message": "SP Entity ID" + "message": "SP entity ID" }, "spMetadataUrl": { - "message": "SAML 2.0 Metadata URL" + "message": "SAML 2.0 metadata URL" }, "spAcsUrl": { - "message": "Assertion Consumer Service (ACS) URL" + "message": "Assertion consumer service (ACS) URL" }, "spNameIdFormat": { - "message": "Name ID Format" + "message": "Name ID format" }, "spOutboundSigningAlgorithm": { - "message": "Outbound Signing Algorithm" + "message": "Outbound signing algorithm" }, "spSigningBehavior": { - "message": "Signing Behavior" + "message": "Signing behavior" }, "spMinIncomingSigningAlgorithm": { - "message": "Minimum Incoming Signing Algorithm" + "message": "Minimum incoming signing algorithm" }, "spWantAssertionsSigned": { "message": "Expect signed assertions" @@ -4789,19 +4804,19 @@ "message": "Entity ID" }, "idpBindingType": { - "message": "Binding Type" + "message": "Binding type" }, "idpSingleSignOnServiceUrl": { - "message": "Single Sign On Service URL" + "message": "Single sign-on service URL" }, "idpSingleLogoutServiceUrl": { - "message": "Single Log Out Service URL" + "message": "Single log-out service URL" }, "idpX509PublicCert": { - "message": "X509 Public Certificate" + "message": "X509 public certificate" }, "idpOutboundSigningAlgorithm": { - "message": "Outbound Signing Algorithm" + "message": "Outbound signing algorithm" }, "idpAllowUnsolicitedAuthnResponse": { "message": "Allow unsolicited authentication response" @@ -4813,7 +4828,7 @@ "message": "Sign authentication requests" }, "ssoSettingsSaved": { - "message": "Single Sign-On configuration was saved." + "message": "Single sign-on configuration saved" }, "sponsoredFamilies": { "message": "Free Bitwarden Families" @@ -4858,10 +4873,10 @@ "message": "Accept offer for an existing organisation or create a new Families organisation." }, "setupSponsoredFamiliesLoginDesc": { - "message": "You've been offered a free Bitwarden Families Plan Organisation. To continue, you need to log in to the account that received the offer." + "message": "You've been offered a free Bitwarden Families plan organisation. To continue, you need to log in to the account that received the offer." }, "sponsoredFamiliesAcceptFailed": { - "message": "Unable to accept offer. Please resend the offer email from your enterprise account and try again." + "message": "Unable to accept offer. Please resend the offer email from your Enterprise account and try again." }, "sponsoredFamiliesAcceptFailedShort": { "message": "Unable to accept offer. $DESCRIPTION$", @@ -4882,7 +4897,7 @@ "message": "Redeemed" }, "redeemedAccount": { - "message": "Redeemed Account" + "message": "Account redeemed" }, "revokeAccount": { "message": "Revoke account $NAME$", @@ -4894,7 +4909,7 @@ } }, "resendEmailLabel": { - "message": "Resend Sponsorship email to $NAME$ sponsorship", + "message": "Resend sponsorship email to $NAME$ sponsorship", "placeholders": { "name": { "content": "$1", @@ -4903,31 +4918,31 @@ } }, "freeFamiliesPlan": { - "message": "Free Families Plan" + "message": "Free Families plan" }, "redeemNow": { - "message": "Redeem Now" + "message": "Redeem now" }, "recipient": { "message": "Recipient" }, "removeSponsorship": { - "message": "Remove Sponsorship" + "message": "Remove sponsorship" }, "removeSponsorshipConfirmation": { "message": "After removing a sponsorship, you will be responsible for this subscription and related invoices. Are you sure you want to continue?" }, "sponsorshipCreated": { - "message": "Sponsorship Created" + "message": "Sponsorship created" }, "emailSent": { - "message": "Email Sent" + "message": "Email sent" }, "revokeSponsorshipConfirmation": { "message": "After removing this account, the Families plan sponsorship will expire at the end of the billing period. You will not be able to redeem a new sponsorship offer until the existing one expires. Are you sure you want to continue?" }, "removeSponsorshipSuccess": { - "message": "Sponsorship Removed" + "message": "Sponsorship removed" }, "ssoKeyConnectorError": { "message": "Key Connector error: make sure Key Connector is available and working correctly." @@ -4939,13 +4954,13 @@ "message": "Send a verification code to your email" }, "sendCode": { - "message": "Send Code" + "message": "Send code" }, "codeSent": { - "message": "Code Sent" + "message": "Code sent" }, "verificationCode": { - "message": "Verification Code" + "message": "Verification code" }, "confirmIdentity": { "message": "Confirm your identity to continue." @@ -4966,13 +4981,13 @@ } }, "leaveOrganization": { - "message": "Leave Organisation" + "message": "Leave organisation" }, "removeMasterPassword": { - "message": "Remove Master Password" + "message": "Remove master password" }, "removedMasterPassword": { - "message": "Master password removed." + "message": "Master password removed" }, "allowSso": { "message": "Allow SSO authentication" @@ -4996,34 +5011,34 @@ "message": "The require SSO authentication and single organisation policies are required to set up Key Connector decryption." }, "memberDecryptionOption": { - "message": "Member Decryption Options" + "message": "Member decryption options" }, "memberDecryptionPassDesc": { - "message": "Once authenticated, members will decrypt vault data using their Master Passwords." + "message": "Once authenticated, members will decrypt vault data using their master passwords." }, "keyConnector": { "message": "Key Connector" }, "memberDecryptionKeyConnectorDesc": { - "message": "Connect Login with SSO to your self-hosted decryption key server. Using this option, members won’t need to use their Master Passwords to decrypt vault data. Contact Bitwarden Support for set up assistance." + "message": "Connect login with SSO to your self-hosted decryption key server. Using this option, members won’t need to use their master passwords to decrypt vault data. Contact Bitwarden Support for set up assistance." }, "keyConnectorPolicyRestriction": { - "message": "\"Login with SSO and Key Connector Decryption\" is enabled. This policy will only apply to Owners and Admins." + "message": "\"Login with SSO and Key Connector Decryption\" is activated. This policy will only apply to owners and admins." }, "enabledSso": { - "message": "Enabled SSO" + "message": "SSO turned on" }, "disabledSso": { - "message": "Disabled SSO" + "message": "SSO turned off" }, "enabledKeyConnector": { - "message": "Enabled Key Connector" + "message": "Key Connector activated" }, "disabledKeyConnector": { - "message": "Disabled Key Connector" + "message": "Key Connector deactivated" }, "keyConnectorWarning": { - "message": "Once members begin using Key Connector, your Organisation cannot revert to Master Password decryption. Proceed only if you are comfortable deploying and managing a key server." + "message": "Once members begin using Key Connector, your organisation cannot revert to master password decryption. Proceed only if you are comfortable deploying and managing a key server." }, "migratedKeyConnector": { "message": "Migrated to Key Connector" @@ -5035,13 +5050,13 @@ "message": "The sponsorship offer has expired. You may delete the organisation you created to avoid a charge at the end of your 7 day trial. Otherwise you may close this prompt to keep the organisation and assume billing responsibility." }, "newFamiliesOrganization": { - "message": "New Families Organisation" + "message": "New Families organisation" }, "acceptOffer": { - "message": "Accept Offer" + "message": "Accept offer" }, "sponsoringOrg": { - "message": "Sponsoring Organisation" + "message": "Sponsoring organisation" }, "keyConnectorTest": { "message": "Test" @@ -5059,55 +5074,55 @@ "message": "FREE with sponsorship" }, "viewBillingSyncToken": { - "message": "View Billing Sync Token" + "message": "View billing sync token" }, "generateBillingSyncToken": { - "message": "Generate Billing Sync Token" + "message": "Generate billing sync token" }, "copyPasteBillingSync": { - "message": "Copy and paste this token into the Billing Sync settings of your self-hosted organisation." + "message": "Copy and paste this token into the billing sync settings of your self-hosted organisation." }, "billingSyncCanAccess": { - "message": "Your Billing Sync token can access and edit this organisation's subscription settings." + "message": "Your billing sync token can access and edit this organisation's subscription settings." }, "manageBillingSync": { - "message": "Manage Billing Sync" + "message": "Manage billing sync" }, "setUpBillingSync": { - "message": "Set Up Billing Sync" + "message": "Set up billing sync" }, "generateToken": { - "message": "Generate Token" + "message": "Generate token" }, "rotateToken": { - "message": "Rotate Token" + "message": "Rotate token" }, "rotateBillingSyncTokenWarning": { "message": "If you proceed, you will need to re-setup billing sync on your self-hosted server." }, "rotateBillingSyncTokenTitle": { - "message": "Rotating the Billing Sync Token will invalidate the previous token." + "message": "Rotating the billing sync token will invalidate the previous token." }, "selfHostingTitle": { - "message": "Self-Hosting" + "message": "Self-hosting" }, "selfHostingEnterpriseOrganizationSectionCopy": { "message": "To set-up your organisation on your own server, you will need to upload your licence file. To support Free Families plans and advanced billing capabilities for your self-hosted organisation, you will need to set up billing sync." }, "billingSyncApiKeyRotated": { - "message": "Token rotated." + "message": "Token rotated" }, "billingSync": { - "message": "Billing Sync" + "message": "Billing sync" }, "billingSyncDesc": { - "message": "Billing Sync provides Free Families plans for members and advanced billing capabilities by linking your self-hosted Bitwarden to the Bitwarden cloud server." + "message": "Billing sync provides Free Families plans for members and advanced billing capabilities by linking your self-hosted Bitwarden to the Bitwarden cloud server." }, "billingSyncKeyDesc": { - "message": "A Billing Sync Token from your cloud organisation's subscription settings is required to complete this form." + "message": "A billing sync token from your cloud organisation's subscription settings is required to complete this form." }, "billingSyncKey": { - "message": "Billing Sync Token" + "message": "Billing sync token" }, "active": { "message": "Active" @@ -5116,13 +5131,13 @@ "message": "Inactive" }, "sentAwaitingSync": { - "message": "Sent (Awaiting Sync)" + "message": "Sent (awaiting sync)" }, "sent": { "message": "Sent" }, "requestRemoved": { - "message": "Removed (Awaiting Sync)" + "message": "Removed (awaiting sync)" }, "requested": { "message": "Requested" @@ -5155,7 +5170,7 @@ "message": "Required if Entity ID is not a URL." }, "openIdOptionalCustomizations": { - "message": "Optional Customisations" + "message": "Optional customisations" }, "openIdAuthorityRequired": { "message": "Required if Authority is not valid." @@ -5167,13 +5182,13 @@ "message": "Your session has timed out. Please go back and try logging in again." }, "exportingPersonalVaultTitle": { - "message": "Exporting Personal Vault" + "message": "Exporting individual vault" }, "exportingOrganizationVaultTitle": { - "message": "Exporting Organisation Vault" + "message": "Exporting organisation vault" }, "exportingPersonalVaultDescription": { - "message": "Only the personal vault items associated with $EMAIL$ will be exported. Organisation vault items will not be included.", + "message": "Only the individual vault items associated with $EMAIL$ will be exported. Organisation vault items will not be included.", "placeholders": { "email": { "content": "$1", @@ -5182,7 +5197,7 @@ } }, "exportingOrganizationVaultDescription": { - "message": "Only the organisation vault associated with $ORGANIZATION$ will be exported. Personal vault items and items from other organisations will not be included.", + "message": "Only the organisation vault associated with $ORGANIZATION$ will be exported. Individual vault items and items from other organisations will not be included.", "placeholders": { "organization": { "content": "$1", @@ -5191,10 +5206,10 @@ } }, "accessDenied": { - "message": "Access Denied. You do not have permission to view this page." + "message": "Access denied. You do not have permission to view this page." }, "masterPassword": { - "message": "Master Password" + "message": "Master password" }, "security": { "message": "Security" @@ -5203,10 +5218,10 @@ "message": "Keys" }, "billingHistory": { - "message": "Billing History" + "message": "Billing history" }, "backToReports": { - "message": "Back to Reports" + "message": "Back to reports" }, "organizationPicker": { "message": "Organisation picker" @@ -5216,7 +5231,7 @@ "description": "This is used by screen readers to indicate the organization that is currently being shown to the user." }, "accountSettings": { - "message": "Account Settings" + "message": "Account settings" }, "generator": { "message": "Generator" @@ -5225,26 +5240,26 @@ "message": "What would you like to generate?" }, "passwordType": { - "message": "Password Type" + "message": "Password type" }, "regenerateUsername": { - "message": "Regenerate Username" + "message": "Regenerate username" }, "generateUsername": { - "message": "Generate Username" + "message": "Generate username" }, "usernameType": { - "message": "Username Type" + "message": "Username type" }, "plusAddressedEmail": { - "message": "Plus Addressed Email", + "message": "Plus addressed email", "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" }, "plusAddressedEmailDesc": { "message": "Use your email provider's sub-addressing capabilities." }, "catchallEmail": { - "message": "Catch-all Email" + "message": "Catch-all email" }, "catchallEmailDesc": { "message": "Use your domain's configured catch-all inbox." @@ -5254,13 +5269,13 @@ "description": "Generates domain-based username using random letters" }, "randomWord": { - "message": "Random Word" + "message": "Random word" }, "service": { "message": "Service" }, "unknownCipher": { - "message": "Unknown Item, you may need to request permission to access this item." + "message": "Unknown item, you may need to request permission to access this item." }, "cannotSponsorSelf": { "message": "You cannot redeem for the active account. Enter a different email." @@ -5293,7 +5308,7 @@ } }, "lastSync": { - "message": "Last Sync", + "message": "Last sync", "Description": "Used as a prefix to indicate the last time a sync occured. Example \"Last sync 1968-11-16 00:00:00\"" }, "sponsorshipsSynced": { @@ -5313,7 +5328,7 @@ "description": "This text is displayed if an organization's billing is managed by a Provider. It tells the user to contact the Provider for assistance." }, "forwardedEmail": { - "message": "Forwarded Email Alias" + "message": "Forwarded email alias" }, "forwardedEmailDesc": { "message": "Generate an email alias with an external forwarding service." @@ -5323,22 +5338,22 @@ "description": "Part of a URL." }, "apiAccessToken": { - "message": "API Access Token" + "message": "API access token" }, "deviceVerification": { - "message": "Device Verification" + "message": "Device verification" }, "enableDeviceVerification": { - "message": "Enable Device Verification" + "message": "Turn on device verification" }, "deviceVerificationDesc": { - "message": "When enabled, verification codes are sent to your email address when logging in from an unrecognised device" + "message": "Verification codes are sent to your email address when logging in from an unrecognised device" }, "updatedDeviceVerification": { - "message": "Updated Device Verification" + "message": "Updated device verification" }, "areYouSureYouWantToEnableDeviceVerificationTheVerificationCodeEmailsWillArriveAtX": { - "message": "Are you sure you want to enable Device Verification? The verification code emails will arrive at: $EMAIL$", + "message": "Are you sure you want to turn on device verification? The verification code emails will arrive at: $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -5350,7 +5365,7 @@ "message": "Premium subscription required" }, "scim": { - "message": "SCIM Provisioning", + "message": "SCIM provisioning", "description": "The text, 'SCIM', is an acronymn and should not be translated." }, "scimDescription": { @@ -5369,11 +5384,11 @@ "message": "This API key has access to manage users within your organisation. It should be kept secret." }, "copyScimKey": { - "message": "Copy the SCIM API Key to your clipboard", + "message": "Copy the SCIM API key to your clipboard", "description": "the text, 'SCIM' and 'API', are acronymns and should not be translated." }, "rotateScimKey": { - "message": "Rotate the SCIM API Key", + "message": "Rotate the SCIM API key", "description": "the text, 'SCIM' and 'API', are acronymns and should not be translated." }, "rotateScimKeyWarning": { @@ -5381,10 +5396,10 @@ "description": "the text, 'SCIM' and 'API', are acronymns and should not be translated." }, "rotateKey": { - "message": "Rotate Key" + "message": "Rotate key" }, "scimApiKey": { - "message": "SCIM API Key", + "message": "SCIM API key", "description": "the text, 'SCIM' and 'API', are acronymns and should not be translated." }, "copyScimUrl": { @@ -5396,11 +5411,11 @@ "description": "the text, 'SCIM' and 'URL', are acronymns and should not be translated." }, "scimApiKeyRotated": { - "message": "The SCIM API Key has been successfully rotated", + "message": "SCIM API key successfully rotated", "description": "the text, 'SCIM' and 'API', are acronymns and should not be translated." }, "scimSettingsSaved": { - "message": "SCIM settings have been saved successfully", + "message": "SCIM settings saved", "description": "the text, 'SCIM', is an acronymn and should not be translated." }, "inputRequired": { diff --git a/apps/web/src/locales/en_IN/messages.json b/apps/web/src/locales/en_IN/messages.json index 04e039f3a7a..2206cacff20 100644 --- a/apps/web/src/locales/en_IN/messages.json +++ b/apps/web/src/locales/en_IN/messages.json @@ -4119,6 +4119,21 @@ "customDesc": { "message": "Allows more granular control of user permissions for advanced configurations." }, + "customDescNonEnterpriseStart": { + "message": "Custom roles is an ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseLink": { + "message": "enterprise feature", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseEnd": { + "message": ". Contact our support team to upgrade your subscription", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customNonEnterpriseError": { + "message": "To enable custom permissions the organization must be on an Enterprise 2020 plan." + }, "permissions": { "message": "Permissions" }, diff --git a/apps/web/src/locales/eo/messages.json b/apps/web/src/locales/eo/messages.json index 9b42192d80e..a0868d14932 100644 --- a/apps/web/src/locales/eo/messages.json +++ b/apps/web/src/locales/eo/messages.json @@ -4119,6 +4119,21 @@ "customDesc": { "message": "Permesas pli detalan kontrolon de uzaj permesoj por progresintaj agordoj." }, + "customDescNonEnterpriseStart": { + "message": "Custom roles is an ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseLink": { + "message": "enterprise feature", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseEnd": { + "message": ". Contact our support team to upgrade your subscription", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customNonEnterpriseError": { + "message": "To enable custom permissions the organization must be on an Enterprise 2020 plan." + }, "permissions": { "message": "Permesoj" }, diff --git a/apps/web/src/locales/es/messages.json b/apps/web/src/locales/es/messages.json index 63384e8f304..8a09ecdb64e 100644 --- a/apps/web/src/locales/es/messages.json +++ b/apps/web/src/locales/es/messages.json @@ -4119,6 +4119,21 @@ "customDesc": { "message": "Permite un control más granulado de los permisos de usuario para configuraciones avanzadas." }, + "customDescNonEnterpriseStart": { + "message": "Los roles personalizados son ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseLink": { + "message": "función de empresa", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseEnd": { + "message": ". Póngase en contacto con nuestro equipo de soporte para actualizar su suscripción", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customNonEnterpriseError": { + "message": "Para habilitar permisos personalizados, la organización debe estar en un plan de empresa 2020." + }, "permissions": { "message": "Permisos" }, diff --git a/apps/web/src/locales/et/messages.json b/apps/web/src/locales/et/messages.json index fe5b609fb6e..b4d47ba8505 100644 --- a/apps/web/src/locales/et/messages.json +++ b/apps/web/src/locales/et/messages.json @@ -4119,6 +4119,21 @@ "customDesc": { "message": "Pakub kasutajate haldamiseks ja õiguste määramiseks rohkem võimalusi." }, + "customDescNonEnterpriseStart": { + "message": "Custom roles is an ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseLink": { + "message": "enterprise feature", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseEnd": { + "message": ". Contact our support team to upgrade your subscription", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customNonEnterpriseError": { + "message": "To enable custom permissions the organization must be on an Enterprise 2020 plan." + }, "permissions": { "message": "Õigused" }, diff --git a/apps/web/src/locales/eu/messages.json b/apps/web/src/locales/eu/messages.json index 61d47324783..dd54e1f371e 100644 --- a/apps/web/src/locales/eu/messages.json +++ b/apps/web/src/locales/eu/messages.json @@ -4119,6 +4119,21 @@ "customDesc": { "message": "Ezarpen aurreratuetarako erabiltzaile baimenen kontrol aurreratuagoa ahalbidetzen du." }, + "customDescNonEnterpriseStart": { + "message": "Custom roles is an ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseLink": { + "message": "enterprise feature", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseEnd": { + "message": ". Contact our support team to upgrade your subscription", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customNonEnterpriseError": { + "message": "To enable custom permissions the organization must be on an Enterprise 2020 plan." + }, "permissions": { "message": "Baimenak" }, diff --git a/apps/web/src/locales/fi/messages.json b/apps/web/src/locales/fi/messages.json index d5652b452ca..fe6a6ef0e19 100644 --- a/apps/web/src/locales/fi/messages.json +++ b/apps/web/src/locales/fi/messages.json @@ -278,7 +278,7 @@ "message": "Hae suosikeista" }, "searchType": { - "message": "Hae tyypeistä", + "message": "Hae tyypistä", "description": "Search item type" }, "searchVault": { @@ -1109,10 +1109,10 @@ "message": "Mitätöi kaikki istunnot" }, "deauthorizeSessionsDesc": { - "message": "Oletko huolissasi, että tilisi on kirjautuneena muissa laitteissa? Jatka alla kirjataksesi ulos kaikki aiemmin käyttämäsi tietokoneet ja muut laitteet. Tämä turvallisuustoimenpide on suositeltava, jos olet aiemmin käyttänyt esimerkiksi julkista tietokonetta tai vahingossa tallentanut kirjautumisesi laitteeseen, joka ei ole sinun. Tämä mitätöi myös kaikki aiemmin muistetut kaksivaiheiset kirjautumiset." + "message": "Oletko huolissasi, että tilisi on kirjautuneena muissa laitteissa? Jatka alla kirjataksesi ulos kaikki aiemmin käyttämäsi tietokoneet ja muut laitteet. Tämä suojaustoimenpide on suositeltava, jos olet aiemmin käyttänyt esimerkiksi julkista tietokonetta tai vahingossa tallentanut salasanasi laitteelle, joka ei ole sinun. Tämä mitätöi myös kaikki aiemmin muistetut kaksivaiheiset kirjautumiset." }, "deauthorizeSessionsWarning": { - "message": "Jatkaminen uloskirjaa kaikki nykyiset istunnot pakottaen uudelleenkirjautumisen sekä kaksivaiheinen kirjautumisen. Muiden laitteiden aktiiviset istunnot saattavat toimia vielä tunnin ajan." + "message": "Jatkaminen uloskirjaa myös nykyisen istunnon pakottaen uudelleenkirjautumisen sekä kaksivaiheinen kirjautumisen, jos se on määritetty. Muiden laitteiden aktiiviset istunnot saattavat toimia vielä tunnin ajan." }, "sessionsDeauthorized": { "message": "Kaikki istunnot mitätöitiin" @@ -1124,7 +1124,7 @@ "message": "Organisaation holvi tyhjennetty." }, "vaultAccessedByProvider": { - "message": "Ylläpito on käyttänyt holvia." + "message": "Toimittaja on käyttänyt holvia." }, "purgeVaultDesc": { "message": "Jatka alla poistaaksesi kaikki holvisi kohteet ja kansiot. Kohteita, jotka on jaettu ja kuuluvat organisaatiolle, ei poisteta." @@ -1288,7 +1288,7 @@ "message": "Verkkotunnukset tallennettiin" }, "twoStepLogin": { - "message": "Kaksivaiheinen kirjautuminen (2FA)" + "message": "Kaksivaiheinen kirjautuminen" }, "twoStepLoginEnforcement": { "message": "Kaksivaiheisen kirjautumisen pakotus" @@ -1297,20 +1297,20 @@ "message": "Suojaa tilisi vaatimalla sisäänkirjautumiseen toinen todennusvaihe." }, "twoStepLoginOrganizationDescStart": { - "message": "Pakota Bitwardenin kaksivaiheiset kirjautumisvaihtoehdot jäsenille, jotka käyttävät ", + "message": "Pakota jäsenille Bitwardenin kaksivaiheisen kirjautumisen valinnat käytännöllä: ", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Enforce Bitwarden Two-step Login options for members by using the Two-step Login Policy.'" }, "twoStepLoginPolicy": { "message": "Kaksivaiheisen kirjautumisen käytäntö" }, "twoStepLoginOrganizationDuoDesc": { - "message": "Pakota kaksivaiheinen kirjautuminen Duon välityksellä alla olevilla vaihtoehdoilla." + "message": "Pakottaaksesi kaksivaiheisen kirjautumisen Duon välityksellä, käytä alla olevia valintoja." }, "twoStepLoginOrganizationSsoDesc": { "message": "Jos olet määrittänyt SSO:n tai aiot määrittää sen, on kaksivaiheinen kirjautuminen saatettu jo pakottaa identiteettitoimittajasi kautta." }, "twoStepLoginRecoveryWarning": { - "message": "Kaksivaiheisen kirjautumisen käyttöönotto voi lukita sinut pysyvästi ulos Bitwaren-tililtäsi. Palautuskoodi mahdollistaa pääsyn tilillesi myös silloin, kun et voi käyttää normaaleja kaksivaiheisen kirjautumisen todentajiasi (esim. kadotat todennuslaitteesi tai se varastetaan). Myöskään Bitwardenin tuki ei voi auttaa sinua, jos menetät pääsyn tillesi. Suosittelemme, että kirjoitat palautuskoodin muistiin tai tulostat sen ja säilytät sitä turvallisessa paikassa (esim. kassakaapissa tai pankin tallelokerossa)." + "message": "Kaksivaiheisen kirjautumisen käyttöönotto voi lukita sinut ulos Bitwarden-tililtäsi pysyvästi. Palautuskoodi mahdollistaa pääsyn tilillesi myös silloin, kun et voi käyttää normaaleja kaksivaiheisen kirjautumisen todentajiasi (esim. kadotat todennuslaitteesi tai se varastetaan). Bitwardenin asiakaspalvelukaan ei voi auttaa sinua, jos menetät pääsyn tillesi. Suosittelemme, että kirjoitat palautuskoodin muistiin tai tulostat sen ja säilytät sitä turvallisessa paikassa (esim. kassakaapissa tai pankin tallelokerossa)." }, "viewRecoveryCode": { "message": "Näytä palautuskoodi" @@ -1360,10 +1360,10 @@ "message": "Tämä kaksivaiheisen kirjautumisen todentaja on määritetty tilillesi." }, "twoStepLoginAuthDesc": { - "message": "Syötä pääsalasana muokataksesi kaksivaiheisen kirjautumisen asetuksia." + "message": "Syötä pääsalasanasi muokataksesi kaksivaiheisen kirjautumisen asetuksia." }, "twoStepAuthenticatorDesc": { - "message": "Seuraa näitä vaiheita ottaaksesi käyttöön kaksivaiheisen kirjautumisen todennussovelluksella:" + "message": "Määritä kaksivaiheinen kirjautuminen todennussovelluksella seuraamalla näitä vaiheita:" }, "twoStepAuthenticatorDownloadApp": { "message": "Lataa kaksivaiheisen kirjautumisen todennussovellus" @@ -1417,7 +1417,7 @@ "message": "Tallenna lomake." }, "twoFactorYubikeyWarning": { - "message": "Alustakohtaisten rajoitusten vuoksi YubiKey-todennuslaiteet eivät ole käytettävissä kaikissa Bitwarden-sovelluksissa. Sinun tulisi määrittää toinen kaksivaiheisen kirjautumisen todentaja, jotta pääset tilillesi myös silloin kun YubiKey-laitteen käyttö ei ole mahdollista. Tuetut alustat:" + "message": "Alustakohtaisten rajoitusten vuoksi YubiKey-todennuslaiteet eivät ole käytettävissä kaikissa Bitwarden-sovelluksissa. Sinun tulisi määrittää eri kaksivaiheisen kirjautumisen todentaja, jotta pääset tilillesi myös silloin kun YubiKey-laitteen käyttö ei ole mahdollista. Tuetut alustat:" }, "twoFactorYubikeySupportUsb": { "message": "Verkkoholvi, työpöytäsovellus, CLI ja kaikki selainlaajennukset laitteessa, jossa on YubiKey-todennuslaitteen käyttöön soveltuva USB-portti." @@ -1480,7 +1480,7 @@ "message": "API hostname" }, "twoFactorEmailDesc": { - "message": "Seuraa näitä vaiheita ottaaksesi käyttöön kaksivaiheisen kirjautumisen todennuksen sähköpostitse:" + "message": "Määritä kaksivaiheinen kirjautuminen sähköpostitse seuraamalla näitä vaiheita:" }, "twoFactorEmailEnterEmail": { "message": "Syötä sähköpostiosoite, johon haluat vastaanottaa todennuskoodit" @@ -1519,7 +1519,7 @@ "message": "Tallenna lomake." }, "twoFactorU2fWarning": { - "message": "Alustakohtaisten rajoitusten vuoksi FIDO U2F -todennuslaiteet eivät ole käytettävissä kaikissa Bitwarden-sovelluksissa. Sinun tulisi määrittää toinen kaksivaiheisen kirjautumisen todentaja, jotta pääset tilillesi myös silloin kun FIDO U2F -laitteen käyttö ei ole mahdollista. Tuetut alustat:" + "message": "Alustakohtaisten rajoitusten vuoksi FIDO U2F -todennuslaiteet eivät ole käytettävissä kaikissa Bitwarden-sovelluksissa. Sinun tulisi määrittää eri kaksivaiheisen kirjautumisen todentaja, jotta pääset tilillesi myös silloin kun FIDO U2F -laitteen käyttö ei ole mahdollista. Tuetut alustat:" }, "twoFactorU2fSupportWeb": { "message": "Verkkoholvi ja selainlaajennukset pöytäkoneissa/kannettavissa, joissa on U2F-tekniikkaa tukeva selain (Chrome, Opera, Vivaldi tai Firefox FIDO U2F käyttöön otettuna)." @@ -1534,7 +1534,7 @@ "message": "Todennuslaittetta luettaessa havaittiin ongelma. Yritä uudelleen." }, "twoFactorWebAuthnWarning": { - "message": "Alustakohtaisten rajoitusten vuoksi WebAuthn-todennuslaiteet eivät ole käytettävissä kaikissa Bitwarden-sovelluksissa. Sinun tulisi määrittää toinen kaksivaiheisen kirjautumisen todentaja, jotta pääset tilillesi myös silloin kun WebAuthn-laitteen käyttö ei ole mahdollista. Tuetut alustat:" + "message": "Alustakohtaisten rajoitusten vuoksi WebAuthn-todennuslaiteet eivät ole käytettävissä kaikissa Bitwarden-sovelluksissa. Sinun tulisi määrittää eri kaksivaiheisen kirjautumisen todentaja, jotta pääset tilillesi myös silloin kun WebAuthn-laitteen käyttö ei ole mahdollista. Tuetut alustat:" }, "twoFactorWebAuthnSupportWeb": { "message": "Verkkoholvi ja selainlaajennukset pöytäkoneissa/kannettavissa, joissa on WebAuthn-tekniikkaa tukeva selain (Chrome, Opera, Vivaldi tai Firefox FIDO U2F käyttöön otettuna)." @@ -1543,7 +1543,7 @@ "message": "Bitwardenin kaksivaiheisen kirjautumisen palautuskoodisi" }, "twoFactorRecoveryNoCode": { - "message": "Et ole vielä ottanut käyttöön yhtään kaksivaiheisen kirjautumisen todentajaa. Otettuasi todentajan käyttöön, löydät palautuskoodin täältä." + "message": "Et ole vielä ottanut käyttöön yhtään kaksivaiheisen kirjautumisen todentajaa. Otettuasi todentajan käyttöön, löydät palautuskoodisi täältä." }, "printCode": { "message": "Tulosta koodi", @@ -1591,7 +1591,7 @@ "message": "Löytyi kirjautumistietoja, joille ei ole määritetty kaksivaiheista kirjautumista" }, "inactive2faFoundDesc": { - "message": "Löysimme holvistasi $COUNT$ sivustoa, joita ei ehkä ole määritetty käyttämään kaksivaiheista kirjautumista (2fa.directory-sivuston mukaan). Suojataksesi nämä tilit paremmin, sinun tulisi ottaa kaksivaiheinen kirjautuminen käyttöön.", + "message": "Löysimme holvistasi $COUNT$ sivustoa, joita ei ehkä ole määritetty käyttämään kaksivaiheista kirjautumista (2fa.directory-sivuston mukaan). Suojataksesi nämä tilit paremmin, sinun tulisi määrittää niille kaksivaiheinen kirjautuminen.", "placeholders": { "count": { "content": "$1", @@ -1808,7 +1808,7 @@ "message": "Kaksivaiheisen kirjautumisen (2FA) TOTP-todennuskoodien generaattori holvisi kirjautumistiedoille." }, "premiumSignUpSupport": { - "message": "Ensisijainen asiakastuki." + "message": "Ensisijainen asiakaspalvelu." }, "premiumSignUpFuture": { "message": "Kaikki tulossa olevat Premium-ominaisuudet. Lisää tulossa pian!" @@ -1829,7 +1829,7 @@ "message": "Premium-käyttöoikeus" }, "premiumAccessDesc": { - "message": "Voit lisätä Premium-käyttöoikeuden kaikille organisaatiosi jäsenille $PRICE$/$INTERVAL$.", + "message": "Voit lisätä Premium-käyttöoikeuden kaikille organisaatiosi jäsenille hintaan $PRICE$/$INTERVAL$.", "placeholders": { "price": { "content": "$1", @@ -2252,7 +2252,7 @@ "message": "Pakota kaksivaiheinen kirjautuminen Duo Securityn kautta" }, "priorityCustomerSupport": { - "message": "Ensisijainen asiakastuki" + "message": "Ensisijainen asiakaspalvelu" }, "xDayFreeTrial": { "message": "$COUNT$ päivän ilmainen kokeilujakso, irtisano koska tahansa", @@ -2417,7 +2417,7 @@ } }, "userUsingTwoStep": { - "message": "Käyttäjä on ottanut kaksivaiheisen kirjautumisen käyttöön tilinsä suojaamiseksi." + "message": "Käyttäjä on suojannut tilinsä kaksivaiheisella kirjautumisella." }, "userAccessAllItems": { "message": "Käyttäjällä on käyttö- ja muokkausoikeus kaikkiin kohteisiin." @@ -2438,34 +2438,34 @@ "message": "Vahvistettu" }, "clientOwnerEmail": { - "message": "Asiakkaan omistajan sähköposti" + "message": "Asiakkaan omistajan sähköpostiosoite" }, "owner": { "message": "Omistaja" }, "ownerDesc": { - "message": "Korkeimman käyttöoikeustason käyttäjä, jolla on käyttö- ja hallintaoikeus koko organisaatioosi." + "message": "Hallitse organisaatiotasi kokonaisvaltaisesti, laskutus ja tilaukset mukaan lukien" }, "clientOwnerDesc": { - "message": "Tämän käyttäjän on oltava todentajariippumaton. Jos todentajan yhteys organisaatioon katkaistaan, säilyttää tämä käyttäjä organisaation omistajuuden." + "message": "Tämän käyttäjän on oltava toimittajariippumaton. Jos toimittajan yhteys organisaatioon katkaistaan, säilyttää tämä käyttäjä organisaation omistajuuden." }, "admin": { "message": "Ylläpitäjä" }, "adminDesc": { - "message": "Ylläpitäjillä on käyttö- ja hallintaoikeus kaikkiin organisaatiosi kohteisiin, kokoelmiin ja käyttäjiin." + "message": "Hallitse organisaation käyttöoikeuksia, kaikkia kokoelmia, jäseniä, raportointia ja suojausasetuksia" }, "user": { "message": "Käyttäjä" }, "userDesc": { - "message": "Tavallinen käyttäjä, jolla on organisaation valittujen kokoelmien käyttöoikeus." + "message": "Käytä ja lisää määritettyjen kokoelmien kohteita" }, "manager": { "message": "Valvoja" }, "managerDesc": { - "message": "Valvojilla on käyttö- ja määritysoikeus organisaatiosi kokoelmiin." + "message": "Luo, poista ja hallitse määritettyjen kokoelmien käyttöoikeuksia" }, "all": { "message": "Kaikki" @@ -2513,7 +2513,7 @@ "message": "Kaksivaiheinen kirjautuminen poistettiin käytöstä" }, "recovered2fa": { - "message": "Tili vapautettiin kaksivaiheisesta kirjautumisesta" + "message": "Tili vapautettiin kaksivaiheisesta kirjautumisesta." }, "failedLogin": { "message": "Sisäänkirjautumisyritys epäonnistui väärän salasanan vuoksi." @@ -2525,10 +2525,10 @@ "message": "Holvi vietiin" }, "exportedOrganizationVault": { - "message": "Organisaation holvi vietiin" + "message": "Organisaation holvi vietiin." }, "editedOrgSettings": { - "message": "Organisaation asetukset tallennettiin" + "message": "Organisaation asetuksia muokattiin." }, "createdItemId": { "message": "Kohde $ID$ luotu.", @@ -2567,7 +2567,7 @@ } }, "viewAllLoginOptions": { - "message": "Näytä kaikki kirjautumisvalinnat" + "message": "Näytä kaikki kirjautumisvaihtoehdot" }, "viewedItemId": { "message": "Kohdetta $ID$ tarkasteltu.", @@ -2993,13 +2993,13 @@ "message": "Muista sähköpostiosoite" }, "recoverAccountTwoStepDesc": { - "message": "Jos et pääse tilillesi käyttämilläsi kaksivaiheisen kirjautumisen todentajilla, voit kaksivaiheisen kirjautumisen palautuskoodilla poistaa kaikki tilillesi määritetyt todentajat käytöstä." + "message": "Jos et pääse tilillesi käyttämilläsi kaksivaiheisen kirjautumisen todentajilla, voit kaksivaiheisen kirjautumisen palautuskoodillasi poistaa kaikki tilillesi määritetyt todentajat käytöstä." }, "recoverAccountTwoStep": { "message": "Vapauta tili kaksivaiheisesta kirjautumisesta" }, "twoStepRecoverDisabled": { - "message": "Kaksivaiheinen kirjautuminen poistettiin käytöstä tililtäsi." + "message": "Kaksivaiheinen kirjautuminen poistettiin tililtäsi käytöstä." }, "learnMore": { "message": "Lue lisää" @@ -3137,7 +3137,7 @@ "message": "Syötä asennuksesi ID-tunnus" }, "limitSubscriptionDesc": { - "message": "Rajoita tilauksesi käyttäjäpaikkojen määrää. Kun määrä täyttyy, et voi kutsua uusia käyttäjiä." + "message": "Aseta tilaukseesi sisältyvä jäsenpaikkojen määrä. Määrän täytyttyä et voi kutsua uusia jäseniä." }, "maxSeatLimit": { "message": "Käyttäjäpaikkojen enimmäismäärä (valinnainen)", @@ -3158,7 +3158,7 @@ "message": "Tilausmuutokset aiheuttavat muutoksia laskutukseesi. Jos hiljattain kutsuttujen käyttäjien määrä ylittää tilauksesi käyttäjäpaikkojen enimmäismäärän, veloitetaan suhteutettu hinta uusista käyttäjistä välittömästi." }, "subscriptionUserSeats": { - "message": "Tilauksesi kattaa kaikkiaan $COUNT$ käyttäjää.", + "message": "Tilaukseesi sisältyy $COUNT$ jäsenpaikkaa.", "placeholders": { "count": { "content": "$1", @@ -3179,13 +3179,13 @@ "message": "Lisävalinnat" }, "additionalOptionsDesc": { - "message": "Jos tarvitset apua tilauksesi hallinnassa, ota yhteyttä asiakaspalveluun." + "message": "Jos tarvitset tilauksesi hallinnassa apua, ole yhteydessä asiakaspalveluun." }, "subscriptionUserSeatsUnlimitedAutoscale": { - "message": "Tilausmuutokset aiheuttavat muutoksia laskutukseesi. Jos hiljattain lisättyjen käyttäjien määrä ylittää tilauksesi käyttäjäpaikkojen enimmäismäärän, veloitetaan suhteutettu hinta uusista käyttäjistä välittömästi." + "message": "Tilausmuutokset aiheuttavat suhteutettuja laskutusmuutoksia. Jos hiljattain kutsuttujen jäsenten määrä ylittää tilauksesi jäsenpaikkojen enimmäismäärän, veloitetaan suhteutettu hinta uusista jäsenistä välittömästi." }, "subscriptionUserSeatsLimitedAutoscale": { - "message": "Tilausmuutokset johtavat suhteutettuihin muutoksiin laskutuksessasi. Jos hiljattain lisättyjen käyttäjien määrä ylittää tilauksesi käyttäjäpaikkojen enimmäismäärän, veloitetaan suhteutettu hinta uusista käyttäjistä välittömästi, kunnes $MAX$ paikan enimmäismäärä saavutetaan.", + "message": "Tilausmuutokset aiheuttavat suhteutettuja laskutusmuutoksia. Jos hiljattain kutsuttujen jäsenten määrä ylittää tilauksesi jäsenpaikkojen enimmäismäärän, veloitetaan suhteutettu hinta uusista jäsenistä välittömästi, kunnes $MAX$ paikan enimmäismäärä täyttyy.", "placeholders": { "max": { "content": "$1", @@ -3194,7 +3194,7 @@ } }, "subscriptionFreePlan": { - "message": "Voit kutsua enintään $COUNT$ käyttäjää päivittämättä tilaustasi.", + "message": "Voit kutsua enintään $COUNT$ jäsentä päivittämättä tilaustasi.", "placeholders": { "count": { "content": "$1", @@ -3203,7 +3203,7 @@ } }, "subscriptionFamiliesPlan": { - "message": "Voit kutsua enintään $COUNT$ käyttäjää päivittämättä tilaustasi. Ota yhteyttä tukeen päivittääksesi.", + "message": "Voit kutsua enintään $COUNT$ käyttäjää päivittämättä tilaustasi. Ole päivityksestä yhteydessä asiakaspalveluun.", "placeholders": { "count": { "content": "$1", @@ -3212,7 +3212,7 @@ } }, "subscriptionSponsoredFamiliesPlan": { - "message": "Tilauksesi kattaa kaikkiaan $COUNT$ käyttäjää. Tilauksesi on sponsoroitu ja se laskutetaan ulkoiselta organisaatiolta.", + "message": "Tilaukseesi sisältyy kaikkiaan $COUNT$ jäsentä. Tilauksesi on sponsoroitu ja se laskutetaan ulkoiselta organisaatiolta.", "placeholders": { "count": { "content": "$1", @@ -3221,7 +3221,7 @@ } }, "subscriptionMaxReached": { - "message": "Tilausmuutokset johtavat suhteutettuihin muutoksiin laskutuksessasi. Voit kutsua enintään $COUNT$ käyttäjää kasvattamatta tilauksesi käyttäjäpaikkojen määrää.", + "message": "Tilausmuutokset aiheuttavat suhteutettuja laskutusmuutoksia. Voit kutsua enintään $COUNT$ jäsentä kasvattamatta tilauksesi jäsenpaikkojen määrää.", "placeholders": { "count": { "content": "$1", @@ -3501,10 +3501,10 @@ "message": "Vaadi jäseniä määrittämään kaksivaiheinen kirjautuminen." }, "twoStepLoginPolicyWarning": { - "message": "Organisaation jäsenet, jotka eivät ole sen omistajia tai ylläpitäjiä, eivätkä ole ottaneet tileillään käyttöön kaksivaiheista kirjautumista, poistetaan organisaatiosta ja heille ilmoitetaan muutoksesta sähköpostitse." + "message": "Organisaation jäsenet, jotka eivät ole sen omistajia tai ylläpitäjiä, eivätkä ole määrittäneet tileilleen kaksivaiheista kirjautumista, poistetaan organisaatiosta ja heille ilmoitetaan muutoksesta sähköpostitse." }, "twoStepLoginPolicyUserWarning": { - "message": "Olet jäsenenä organisaatiossa, joka vaatii kaksivaiheisen kirjautumisen käyttöä käyttäjätililläsi. Jos poistat kaikki kaksivaiheisen kirjautumisen todentajat käytöstä, sinut poistetaan automaattisesti näistä organisaatioista." + "message": "Olet jäsen organisaatiossa, joka edellyttää käyttäjätililtäsi kaksivaiheista kirjautumista. Jos poistat kaikki kaksivaiheisen kirjautumisen todentajat käytöstä, sinut poistetaan näistä organisaatioista automaattisesti." }, "passwordGeneratorPolicyDesc": { "message": "Aseta salasanageneraattorin vaatimukset." @@ -3692,7 +3692,7 @@ "message": "Organisaation tunniste" }, "ssoLogInWithOrgIdentifier": { - "message": "Kirjaudu sisään käyttäen organisaatiosi kertakirjautumista (SSO). Syötä organisaatiosi tunniste aloittaaksesi." + "message": "Kirjaudu käyttäen organisaatiosi kertakirjautumisportaalia. Aloita syöttämällä organisaatiosi SSO-tunniste." }, "enterpriseSingleSignOn": { "message": "Yrityksen kertakirjautuminen (SSO)" @@ -3707,19 +3707,19 @@ "message": "Kertakirjautumisen (SSO) todennus SAML 2.0 ja OpenID Connect -todentajilla" }, "includeEnterprisePolicies": { - "message": "Yritykskäytännöt" + "message": "Yrityskäytännöt" }, "ssoValidationFailed": { - "message": "Kertakirjautumisen (SSO) todennus epäonnistui" + "message": "SSO-vahvistus epäonnistui" }, "ssoIdentifierRequired": { - "message": "Organisaation tunniste vaaditaan." + "message": "Organisaation SSO-tunniste vaaditaan." }, "ssoIdentifier": { "message": "SSO-tunniste" }, "ssoIdentifierHint": { - "message": "Ilmoita jäsenillesi tämä tunniste SSO-kirjautumista varten." + "message": "Ilmoita tämä tunniste jäsenillesi SSO-kirjautumista varten." }, "unlinkSso": { "message": "Poista kertakirjautumisen (SSO) liitos" @@ -4066,7 +4066,7 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "disableSendExemption": { - "message": "Käytäntöä ei pakoteta käyttöön niille organisaation käyttäjille, joilla on organisaatiokäytäntöjen hallintaoikeudet." + "message": "Tätä käytäntöä ei pakoteta niille organisaation jäsenille, joilla on organisaatiokäytäntöjen hallintaoikeus." }, "sendDisabled": { "message": "Send poistettiin", @@ -4085,7 +4085,7 @@ "description": "'Sends' is a plural noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendOptionsExemption": { - "message": "Käytäntöä ei pakoteta käyttöön niille organisaation käyttäjille, joilla on organisaatiokäytäntöjen hallintaoikeudet." + "message": "Tätä käytäntöä ei pakoteta niille organisaation jäsenille, joilla on organisaatiokäytäntöjen hallintaoikeus." }, "disableHideEmail": { "message": "Näytä jäsenen sähköpostiosoite aina vastaanottajien ohessa, kun Send luodaan tai sitä muokataan.", @@ -4117,7 +4117,22 @@ "message": "Mukautettu" }, "customDesc": { - "message": "Mahdollistaa käyttäjien oikeuksien tarkemman hallinnan edistyneemmissä käyttöympäristöissä." + "message": "Myönnä jäsenille mukautettuja käyttöoikeuksia" + }, + "customDescNonEnterpriseStart": { + "message": "Mukautetut roolit on ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseLink": { + "message": "yritysominaisuus", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseEnd": { + "message": ". Ole tilauspäivityksestä yhteydessä asiakaspalveluumme", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customNonEnterpriseError": { + "message": "Mukautettujen käyttöoikeuksien käyttöönottamiseksi organisaatiolla on oltava Yritykset 2020 -tilaus." }, "permissions": { "message": "Käyttöoikeudet" @@ -4333,7 +4348,7 @@ } }, "eventWithdrawPasswordReset": { - "message": "Käyttäjä $ID$ poistui salasanan palautusavusta.", + "message": "Käyttäjä $ID$ erosi salasanan palautusavusta.", "placeholders": { "id": { "content": "$1", @@ -4399,16 +4414,16 @@ "message": "Salli ylläpitäjien palauttaa jäsenten pääsalasanat." }, "resetPasswordPolicyWarning": { - "message": "Organisaation käyttäjien on liityttävä itse tai automaattisen liitoksen välityksellä ennen kuin ylläpitäjät voivat palauttaa heidän pääsalasanojaan." + "message": "Organisaation jäsenten on liityttävä itse tai automaattisen liitoksen välityksellä ennen kuin ylläpitäjät voivat palauttaa heidän pääsalasanojaan." }, "resetPasswordPolicyAutoEnroll": { "message": "Automaattinen liitos" }, "resetPasswordPolicyAutoEnrollDescription": { - "message": "Kaikki käyttäjät liitetään salasanan palautusapuun automaattisesti, kun heidän kutsunsa on hyväksytty, jonka jälkeen he eivät voi erota siitä." + "message": "Kaikki jäsenet liitetään salasanan palautusapuun automaattisesti kun heidän kutsunsa on hyväksytty, eivätkä he tämän jälkeen voi erota siitä." }, "resetPasswordPolicyAutoEnrollWarning": { - "message": "Vanhoja organisaation käyttäjiä ei liitetä salasanan palautusapuun takautuvasti, vaan heidän on itse liityttävä siihen ennen kuin ylläpitäjät voivat palauttaa heidän pääsalasanojaan." + "message": "Vanhoja organisaation jäseniä ei liitetä salasanan palautusapuun takautuvasti, vaan heidän on liityttävä siihen itse ennen kuin ylläpitäjät voivat palauttaa heidän pääsalasanojaan." }, "resetPasswordPolicyAutoEnrollCheckbox": { "message": "Liitä uudet käyttäjät automaattisesti" @@ -4513,19 +4528,19 @@ "message": "Myös \"Käyttäjien hallinta\" -oikeuden on oltava käytössä \"Salasanan palautusavun hallinta\" -oikeuden kanssa" }, "setupProvider": { - "message": "Todentajan määritys" + "message": "Toimittajan määritys" }, "setupProviderLoginDesc": { - "message": "Sinut on kutsuttu määrittämään uusi todentaja. Jatka kirjautumalla sisään tai luomalla uusi Bitwarden-tili." + "message": "Sinut on kutsuttu määrittämään uusi toimittaja. Jatka kirjautumalla sisään tai luomalla uusi Bitwarden-tili." }, "setupProviderDesc": { - "message": "Syötä alla pyydetyt tiedot viimeistelläksesi todentajan määrityksen. Jos sinulla on kysyttävää, ota yhteyttä asiakaspalveluun." + "message": "Syötä alla pyydetyt tiedot viimeistelläksesi todentajan määrityksen. Jos sinulla on kysyttävää, ole yhteydessä asiakaspalveluun." }, "providerName": { - "message": "Todentajan nimi" + "message": "Toimittajan nimi" }, "providerSetup": { - "message": "Todentaja on määritetty." + "message": "Toimittajan määritys onnistui" }, "clients": { "message": "Asiakkaat" @@ -4535,10 +4550,10 @@ "description": "This is used as a table header to describe which client application created an event log." }, "providerAdmin": { - "message": "Todentajan ylläpitäjä" + "message": "Toimittajan ylläpitäjä" }, "providerAdminDesc": { - "message": "Korkeimman käyttöoikeuden käyttäjä, joka voi hallinnoida todentajaasi kokonaisvaltaisesti sekä käyttää ja hallita asiakasorganisaatioita." + "message": "Korkeimman käyttöoikeuden jäsen, joka voi hallinnoida toimittajaasi kokonaisvaltaisesti sekä käyttää ja hallita asiakasorganisaatioita." }, "serviceUser": { "message": "Palvelun käyttäjä" @@ -4547,40 +4562,40 @@ "message": "Palvelun käyttäjät voivat käyttää ja hallita kaikkia asiakasorganisaatioita." }, "providerInviteUserDesc": { - "message": "Kutsu todentajallesi uusi käyttäjä syöttämällä alle hänen Bitwarden-tilinsä sähköpostiosoite. Jos hänellä ei vielä ole Bitwarden-tiliä, pyydetään häntä luomaan uusi tili." + "message": "Kutsu toimittajaasi uusi jäsen syöttämällä alle heidän Bitwarden-tilinsä sähköpostiosoite. Jos heillä ei vielä ole Bitwarden-tiliä, pyydetään heitä luomaan uusi tili." }, "joinProvider": { - "message": "Liity todentajaan" + "message": "Liity toimittajaan" }, "joinProviderDesc": { - "message": "Sinut on kutsuttu liittymään yllä mainittuun todentajaan. Hyväksyäksesi kutsun, sinun on kirjauduttava tilillesi tai luotava uusi Bitwarden-tili." + "message": "Sinut on kutsuttu liittymään yllä mainittuun toimittajaan. Hyväksyäksesi kutsun, sinun on kirjauduttava tilillesi tai luotava uusi Bitwarden-tili." }, "providerInviteAcceptFailed": { - "message": "Kutsua ei voitu hyväksyä. Pyydä todentajan ylläpitäjää lähettämään uusi kutsu." + "message": "Kutsua ei voitu hyväksyä. Pyydä toimittajan ylläpitäjää lähettämään uusi kutsu." }, "providerInviteAcceptedDesc": { - "message": "Pääset käyttämään todentajaa ylläpitäjän vahvistettua jäsentyytesi. Saat tästä ilmoituksen sähköpostitse." + "message": "Pääset käyttämään toimittajaa ylläpitäjän vahvistettua jäsentyytesi. Saat tästä ilmoituksen sähköpostitse." }, "providerUsersNeedConfirmed": { - "message": "Sinulla on kutsunsa hyväksyneitä käyttäjiä, jotka on vielä vahvistettava. Käyttäjät eivät voi käyttää todentajaa ennen vahvistusta." + "message": "Sinulla on kutsunsa hyväksyneitä jäseniä, jotka on vielä vahvistettava. Jäsenet eivät voi käyttää toimittajaa ennen vahvistusta." }, "provider": { - "message": "Todentaja" + "message": "Toimittaja" }, "newClientOrganization": { "message": "Uusi asiakasorganisaatio" }, "newClientOrganizationDesc": { - "message": "Luo uusi asiakasorganisaatio, joka liitetään sinuun todentajana. Voit käyttää ja hallinnoida tätä organisaatiota." + "message": "Luo uusi asiakasorganisaatio, joka liitetään sinuun toimittajana. Voit käyttää ja hallita tätä organisaatiota." }, "addExistingOrganization": { "message": "Lisää olemassa oleva organisaatio" }, "myProvider": { - "message": "Oma todentaja" + "message": "Oma toimittaja" }, "addOrganizationConfirmation": { - "message": "Haluatko varmasti lisätä organisaation $ORGANIZATION$ todentajan $PROVIDER$ asiakkaaksi?", + "message": "Haluatko varmasti lisätä organisaation $ORGANIZATION$ toimittajan $PROVIDER$ asiakkaaksi?", "placeholders": { "organization": { "content": "$1", @@ -4593,10 +4608,10 @@ } }, "organizationJoinedProvider": { - "message": "Organisaation lisäys todentajaan onnistui" + "message": "Organisaation lisäys toimittajaan onnistui" }, "accessingUsingProvider": { - "message": "Käytetään organisaatiota todentajalla $PROVIDER$", + "message": "Käytetään organisaatiota toimittajalla $PROVIDER$", "placeholders": { "provider": { "content": "$1", @@ -4605,13 +4620,13 @@ } }, "providerIsDisabled": { - "message": "Todentaja on jäädytetty" + "message": "Toimittaja on jäädytetty" }, "providerUpdated": { - "message": "Todentaja tallennettiin" + "message": "Toimittaja tallennettiin" }, "yourProviderIs": { - "message": "Todentajasi on $PROVIDER$. Heillä on hallinta- ja laskutusoikeudet organisaatioosi.", + "message": "Toimittajasi on $PROVIDER$. Heillä on organisaatiosi hallinta- ja laskutusoikeudet.", "placeholders": { "provider": { "content": "$1", @@ -4620,7 +4635,7 @@ } }, "detachedOrganization": { - "message": "Organisaatio $ORGANIZATION$ on irrotettu todentajastasi.", + "message": "Organisaatio $ORGANIZATION$ on irrotettu toimittajastasi.", "placeholders": { "organization": { "content": "$1", @@ -4629,7 +4644,7 @@ } }, "detachOrganizationConfirmation": { - "message": "Haluatko varmasti irrottaa organisaation? Organisaatio on edelleen olemassa, mutta todentaja ei enää hallinnoi sitä." + "message": "Haluatko varmasti irrottaa organisaation? Organisaatio on edelleen olemassa, mutta toimittaja ei enää hallinnoi sitä." }, "add": { "message": "Lisää" @@ -4711,16 +4726,16 @@ "message": "OpenID Connect -määritykset" }, "samlSpConfig": { - "message": "SAML-palveluntarjoajan määritykset" + "message": "SAML-palveluntarjoajan määritys" }, "samlIdpConfig": { - "message": "SAML-tunnistustietojen tarjoajan määritykset" + "message": "SAML-identiteettitoimittajan määritys" }, "callbackPath": { - "message": "Callback Path" + "message": "Callback path" }, "signedOutCallbackPath": { - "message": "Signed Out Callback Path" + "message": "Signed out callback path" }, "authority": { "message": "Authority" @@ -4729,55 +4744,55 @@ "message": "Client ID" }, "clientSecret": { - "message": "Client-salaisuus" + "message": "Client secret" }, "metadataAddress": { "message": "Metadata-osoite" }, "oidcRedirectBehavior": { - "message": "OIDC Redirect -käyttäytyminen" + "message": "OIDC redirect -käyttäytyminen" }, "getClaimsFromUserInfoEndpoint": { "message": "Hanki Claim-arvot käyttäjätietojen päätepisteestä" }, "additionalScopes": { - "message": "Mukautetut Scopes-arvot" + "message": "Mukautetut scopes-arvot" }, "additionalUserIdClaimTypes": { - "message": "Mukautetut käyttäjätunnuksen Claim-tyypit" + "message": "Mukautetut käyttäjätunnuksen claim-tyypit" }, "additionalEmailClaimTypes": { - "message": "Sähköpostin Claim-tyypit" + "message": "Sähköpostin claim-tyypit" }, "additionalNameClaimTypes": { - "message": "Mukautetut nimen Claim-tyypit" + "message": "Mukautetut nimen claim-tyypit" }, "acrValues": { - "message": "Pyydetyt Authentication Context Class -viitearvot" + "message": "Pyydetyt authentication context class -viitearvot" }, "expectedReturnAcrValue": { - "message": "Odotettu \"acr\" Claim-arvo vastauksessa" + "message": "Odotettu \"acr\" claim-arvo vastauksessa" }, "spEntityId": { - "message": "SP Entity ID" + "message": "SP entity ID" }, "spMetadataUrl": { - "message": "SAML 2.0 Metadata URL" + "message": "SAML 2.0 -metatietojen URL" }, "spAcsUrl": { - "message": "Assertion Consumer Service (ACS) URL" + "message": "Assertion consumer service (ACS) URL" }, "spNameIdFormat": { "message": "Name ID -muoto" }, "spOutboundSigningAlgorithm": { - "message": "Outbound Signing -algoritmi" + "message": "Outbound-allekirjoitusalgoritmi" }, "spSigningBehavior": { "message": "Allekirjoituskäyttäytyminen" }, "spMinIncomingSigningAlgorithm": { - "message": "Pienin saapuva allekirjoitusalgoritmi" + "message": "Pienin sallittu incoming-allekirjoitusalgoritmi" }, "spWantAssertionsSigned": { "message": "Odota Assertion-lähetysten allekirjoitusta" @@ -4801,7 +4816,7 @@ "message": "Julkinen X509 -varmenne" }, "idpOutboundSigningAlgorithm": { - "message": "Lähtevä allekirjoitusalgoritmi" + "message": "Outbound-allekirjoitusalgoritmi" }, "idpAllowUnsolicitedAuthnResponse": { "message": "Salli Unsolicited-todennusvastaus" @@ -4978,7 +4993,7 @@ "message": "Salli SSO-todennus" }, "allowSsoDesc": { - "message": "Määrityksen jälkeen asetukset tallennetaan ja käyttäjät voivat tunnistautua käyttäen tunnistustietojen tarjoajansa (Identity Provider) tietoja." + "message": "Määrityksen jälkeen asetukset tallennetaan ja käyttäjät voivat tunnistautua käyttäen identiteettitoimittajansa tunnistautumistietoja." }, "ssoPolicyHelpStart": { "message": "Käytä", @@ -5005,7 +5020,7 @@ "message": "Key Connector" }, "memberDecryptionKeyConnectorDesc": { - "message": "Liitä kertakirjautuminen (SSO) itse ylläpitämääsi salauksenpurkuavainpalvelimeen. Tätä valintaa käyttämällä jäsenten ei tarvitse käyttää pääsalasanojaan holvin salauksen purkuun. Ota yhtettä Bitwardenin tukeen saadaksesi apua määritykseen." + "message": "Liitä kertakirjautuminen (SSO) itse ylläpitämääsi salauksenpurun avainpalvelimeen. Tätä valintaa käyttämällä jäsenten ei tarvitse käyttää pääsalasanojaan holvin salauksen purkuun. Ole yhteydessä Bitwardenin asiakaspalveluun saadaksesi apua määritykseen." }, "keyConnectorPolicyRestriction": { "message": "Kertakirjautuminen (SSO) Key Connector -salauksenpurulla on käytössä. Käytäntö vaikuttaa vain omistajiin ja ylläpitäjiin." @@ -5092,7 +5107,7 @@ "message": "Itse ylläpidetty" }, "selfHostingEnterpriseOrganizationSectionCopy": { - "message": "Määrittääksesi organisaation omalle palvelimellesi, tulee lisenssitiedosto tallentaa sinne. Sinun on määritettävä laskutuksen synkronointi, jotta ilmaiset Perheille-tilaukset ja edistyneet laskutusominaisuudet ovat itse ylläpitämäsi organisaation käytettävissä." + "message": "Määrittääksesi organisaation omalle palvelimellesi, on lisenssitiedosto tallennettava sinne. Sinun on määritettävä laskutuksen synkronointi, jotta ilmaiset Perheille-tilaukset ja edistyneet laskutusominaisuudet ovat itse ylläpitämäsi organisaation käytettävissä." }, "billingSyncApiKeyRotated": { "message": "Tunniste uudistettiin" @@ -5354,7 +5369,7 @@ "description": "The text, 'SCIM', is an acronymn and should not be translated." }, "scimDescription": { - "message": "Provisioi käyttäjät ja ryhmät haluamasi tunnistustietojen tarjoajan (Identity Provider) kanssa SCIM-provisioinnilla.", + "message": "Provisioi käyttäjät ja ryhmät automaattisesti haluamasi identiteettitoimittajan kanssa SCIM-provisioinnilla", "description": "the text, 'SCIM', is an acronymn and should not be translated." }, "scimEnabledCheckboxDesc": { @@ -5362,7 +5377,7 @@ "description": "the text, 'SCIM', is an acronymn and should not be translated." }, "scimEnabledCheckboxDescHelpText": { - "message": "Määritä haluamasi tunnistustietojen tarjoaja (Identity Provider) määrittämällä URL-osoite ja SCIM API -avain.", + "message": "Määritä haluamasi identiteettitoimittaja määrittämällä sen URL-osoite ja SCIM API -avain", "description": "the text, 'SCIM', is an acronymn and should not be translated." }, "scimApiKeyHelperText": { diff --git a/apps/web/src/locales/fil/messages.json b/apps/web/src/locales/fil/messages.json index d8fd98cde24..97372e340d5 100644 --- a/apps/web/src/locales/fil/messages.json +++ b/apps/web/src/locales/fil/messages.json @@ -2444,7 +2444,7 @@ "message": "Owner" }, "ownerDesc": { - "message": "The highest access user that can manage all aspects of your organization." + "message": "Manage all aspects of your organization, including billing and subscriptions" }, "clientOwnerDesc": { "message": "This user should be independent of the Provider. If the Provider is disassociated with the organization, this user will maintain ownership of the organization." @@ -2453,19 +2453,19 @@ "message": "Admin" }, "adminDesc": { - "message": "Admins can access and manage all items, collections and users in your organization." + "message": "Manage organization access, all collections, members, reporting, and security settings" }, "user": { "message": "User" }, "userDesc": { - "message": "A regular user with access to assigned collections in your organization." + "message": "Access and add items to assigned collections" }, "manager": { "message": "Manager" }, "managerDesc": { - "message": "Managers can access and manage assigned collections in your organization." + "message": "Create, delete, and manage access in assigned collections" }, "all": { "message": "All" @@ -4117,7 +4117,22 @@ "message": "Custom" }, "customDesc": { - "message": "Allows more granular control of user permissions for advanced configurations." + "message": "Grant customized permissions to members" + }, + "customDescNonEnterpriseStart": { + "message": "Custom roles is an ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseLink": { + "message": "enterprise feature", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseEnd": { + "message": ". Contact our support team to upgrade your subscription", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customNonEnterpriseError": { + "message": "To enable custom permissions the organization must be on an Enterprise 2020 plan." }, "permissions": { "message": "Permissions" diff --git a/apps/web/src/locales/fr/messages.json b/apps/web/src/locales/fr/messages.json index adf3a7f5ce4..086de71b48a 100644 --- a/apps/web/src/locales/fr/messages.json +++ b/apps/web/src/locales/fr/messages.json @@ -2453,13 +2453,13 @@ "message": "Administrateur" }, "adminDesc": { - "message": "Les administrateurs peuvent voir et gérer tous les éléments, les collections et les utilisateurs de votre organisation." + "message": " Les administrateurs peuvent voir et gérer tous les éléments, les collections et les utilisateurs de votre organisation." }, "user": { "message": "Utilisateur" }, "userDesc": { - "message": "Un utilisateur standard avec accès aux collections qui lui sont assignées dans votre organisation." + "message": "Un utilisateur normal avec accès aux collections de votre organisation." }, "manager": { "message": "Gestionnaire" @@ -4119,6 +4119,21 @@ "customDesc": { "message": "Permet un contrôle plus précis des permissions utilisateur pour les configurations avancées." }, + "customDescNonEnterpriseStart": { + "message": "Rôles personnalisés est une ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseLink": { + "message": "fonctionnalité d'entreprise", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseEnd": { + "message": ". Contactez notre équipe de support pour mettre à jour votre abonnement", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customNonEnterpriseError": { + "message": "Pour activer les autorisations personnalisées, l'organisation doit être sur un plan Entreprise 2020." + }, "permissions": { "message": "Permissions" }, diff --git a/apps/web/src/locales/he/messages.json b/apps/web/src/locales/he/messages.json index 16f0329a121..4681c824c58 100644 --- a/apps/web/src/locales/he/messages.json +++ b/apps/web/src/locales/he/messages.json @@ -4117,7 +4117,22 @@ "message": "Custom" }, "customDesc": { - "message": "Allows more granular control of user permissions for advanced configurations." + "message": "Grant customized permissions to members" + }, + "customDescNonEnterpriseStart": { + "message": "Custom roles is an ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseLink": { + "message": "enterprise feature", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseEnd": { + "message": ". Contact our support team to upgrade your subscription", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customNonEnterpriseError": { + "message": "To enable custom permissions the organization must be on an Enterprise 2020 plan." }, "permissions": { "message": "Permissions" diff --git a/apps/web/src/locales/hi/messages.json b/apps/web/src/locales/hi/messages.json index 94e54e5c669..0b5b30a5c9f 100644 --- a/apps/web/src/locales/hi/messages.json +++ b/apps/web/src/locales/hi/messages.json @@ -2444,7 +2444,7 @@ "message": "Owner" }, "ownerDesc": { - "message": "The highest access user that can manage all aspects of your organization." + "message": "Manage all aspects of your organization, including billing and subscriptions" }, "clientOwnerDesc": { "message": "This user should be independent of the Provider. If the Provider is disassociated with the organization, this user will maintain ownership of the organization." @@ -2453,19 +2453,19 @@ "message": "Admin" }, "adminDesc": { - "message": "Admins can access and manage all items, collections and users in your organization." + "message": "Manage organization access, all collections, members, reporting, and security settings" }, "user": { "message": "User" }, "userDesc": { - "message": "A regular user with access to assigned collections in your organization." + "message": "Access and add items to assigned collections" }, "manager": { "message": "Manager" }, "managerDesc": { - "message": "Managers can access and manage assigned collections in your organization." + "message": "Create, delete, and manage access in assigned collections" }, "all": { "message": "All" @@ -4117,7 +4117,22 @@ "message": "Custom" }, "customDesc": { - "message": "Allows more granular control of user permissions for advanced configurations." + "message": "Grant customized permissions to members" + }, + "customDescNonEnterpriseStart": { + "message": "Custom roles is an ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseLink": { + "message": "enterprise feature", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseEnd": { + "message": ". Contact our support team to upgrade your subscription", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customNonEnterpriseError": { + "message": "To enable custom permissions the organization must be on an Enterprise 2020 plan." }, "permissions": { "message": "Permissions" diff --git a/apps/web/src/locales/hr/messages.json b/apps/web/src/locales/hr/messages.json index 1ddca8f5026..938e1eacc01 100644 --- a/apps/web/src/locales/hr/messages.json +++ b/apps/web/src/locales/hr/messages.json @@ -2444,7 +2444,7 @@ "message": "Vlasnik" }, "ownerDesc": { - "message": "Korisnik s najvišim pravima pristupa koji može upravljati svim mogućnostima tvoje organziacije." + "message": "Upravljaj svim mogućnostima tvoje organizacije uključujući naplatu i pretplate" }, "clientOwnerDesc": { "message": "Korisnik bi trebao biti neovisan o davatelju. Ako se davatelja razdvoji od organizacije, ovaj će korisnik zadržati vlasništvo nad organizacijom." @@ -2453,19 +2453,19 @@ "message": "Admin" }, "adminDesc": { - "message": "Admini mogu pristupiti i upravljati svim stavkama, zbirkama i korisnicima u organizaciji." + "message": "Upravljaj: pristupu organizaciji, svim zbirkama, korisnicima, izvještavanju i sigurnosnom postavkama" }, "user": { "message": "Korisnik" }, "userDesc": { - "message": "Obični korisnik s pristupom dodijeljenim zbirkama u tvojoj organizaciji." + "message": "Pristupi i dodaj stavke dodijeljenim zbirkama" }, "manager": { "message": "Upravitelj" }, "managerDesc": { - "message": "Upravitelji mogu pristupiti i upravljati dodijeljenim zbirkama u tvojoj organizaciji." + "message": "Stvori, obriši i upravljaj pristupom u dodijeljenim zbirkama" }, "all": { "message": "Sve" @@ -4117,7 +4117,22 @@ "message": "Prilagođeno" }, "customDesc": { - "message": "Omogućuje detaljnije upravljanje korisničkim dozvolama za napredne konfiguracije." + "message": "Dodijeli prilagođena dopuštenja članovima" + }, + "customDescNonEnterpriseStart": { + "message": "Custom roles is an ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseLink": { + "message": "enterprise feature", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseEnd": { + "message": ". Contact our support team to upgrade your subscription", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customNonEnterpriseError": { + "message": "To enable custom permissions the organization must be on an Enterprise 2020 plan." }, "permissions": { "message": "Dozvole" @@ -5155,16 +5170,16 @@ "message": "Required if Entity ID is not a URL." }, "openIdOptionalCustomizations": { - "message": "Optional customizations" + "message": "Izborne prilagodbe" }, "openIdAuthorityRequired": { - "message": "Required if Authority is not valid." + "message": "Obavezno ako ovlaštenje nije važeće." }, "separateMultipleWithComma": { "message": "Odvoji zarezom." }, "sessionTimeout": { - "message": "Your session has timed out. Please go back and try logging in again." + "message": "Tvoja sesija je istekla. Vrati se i pokušaj s ponovnom prijavom." }, "exportingPersonalVaultTitle": { "message": "Izvoz osobnog trezora" @@ -5173,7 +5188,7 @@ "message": "Izvoz organizacijskog trezora" }, "exportingPersonalVaultDescription": { - "message": "Only the individual vault items associated with $EMAIL$ will be exported. Organization vault items will not be included.", + "message": "Izvest će se samo stavke osobnog trezora povezanog s $EMAIL$. Stavke organizacijskog trezora neće biti uključene.", "placeholders": { "email": { "content": "$1", diff --git a/apps/web/src/locales/hu/messages.json b/apps/web/src/locales/hu/messages.json index 83afc204dd8..54422532e24 100644 --- a/apps/web/src/locales/hu/messages.json +++ b/apps/web/src/locales/hu/messages.json @@ -4119,6 +4119,21 @@ "customDesc": { "message": "A fejlett konfigurációk felhasználói engedélyeinek részletesebb ellenőrzését teszi lehetővé." }, + "customDescNonEnterpriseStart": { + "message": "Az egyedi szerepek egy ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseLink": { + "message": "vállalati funkció", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseEnd": { + "message": ". Vegyük fel a kapcsolatot a támogató csoporttal más előfizetésre áttéréshez.", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customNonEnterpriseError": { + "message": "Az egyedi jogosultságok engedélyezéséhez a szervezetnek Enterprise 2020 csomaggal kell rendelkeznie." + }, "permissions": { "message": "Jogosultságok" }, diff --git a/apps/web/src/locales/id/messages.json b/apps/web/src/locales/id/messages.json index 7b492b10fdb..22d111c1b6f 100644 --- a/apps/web/src/locales/id/messages.json +++ b/apps/web/src/locales/id/messages.json @@ -4119,6 +4119,21 @@ "customDesc": { "message": "Memungkinkan kontrol yang lebih terperinci atas izin pengguna untuk konfigurasi lanjutan." }, + "customDescNonEnterpriseStart": { + "message": "Custom roles is an ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseLink": { + "message": "enterprise feature", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseEnd": { + "message": ". Contact our support team to upgrade your subscription", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customNonEnterpriseError": { + "message": "To enable custom permissions the organization must be on an Enterprise 2020 plan." + }, "permissions": { "message": "Izin" }, diff --git a/apps/web/src/locales/it/messages.json b/apps/web/src/locales/it/messages.json index 7a2646497b1..610c81a1b00 100644 --- a/apps/web/src/locales/it/messages.json +++ b/apps/web/src/locales/it/messages.json @@ -4119,6 +4119,21 @@ "customDesc": { "message": "Consente un controllo più granulare delle autorizzazioni utente per le configurazioni avanzate." }, + "customDescNonEnterpriseStart": { + "message": "I ruoli personalizzati sono una ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseLink": { + "message": "feature enterprise", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseEnd": { + "message": ". Contatta il nostro team di supporto per aggiornare il tuo abbonamento", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customNonEnterpriseError": { + "message": "Per abilitare i permessi personalizzati, l'organizzazione deve essere su un piano Enterprise 2020." + }, "permissions": { "message": "Permessi" }, diff --git a/apps/web/src/locales/ja/messages.json b/apps/web/src/locales/ja/messages.json index 109d791065b..c5b048dfe1b 100644 --- a/apps/web/src/locales/ja/messages.json +++ b/apps/web/src/locales/ja/messages.json @@ -4119,6 +4119,21 @@ "customDesc": { "message": "ユーザー権限をより詳細に制御できます。" }, + "customDescNonEnterpriseStart": { + "message": "カスタムロールは", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseLink": { + "message": "企業向け機能", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseEnd": { + "message": "です。サブスクリプションのアップグレードについては、弊社サポートチームまでお問い合わせください。", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customNonEnterpriseError": { + "message": "カスタム権限を有効にするには、組織が Enterprise 2020 プランに登録済みである必要があります。" + }, "permissions": { "message": "権限" }, @@ -4723,19 +4738,19 @@ "message": "Signed out callback path" }, "authority": { - "message": "Authority" + "message": "権限" }, "clientId": { - "message": "Client ID" + "message": "クライアントID" }, "clientSecret": { "message": "Client secret" }, "metadataAddress": { - "message": "Metadata address" + "message": "メタデータアドレス" }, "oidcRedirectBehavior": { - "message": "OIDC redirect behavior" + "message": "OIDCリダイレクトの動作" }, "getClaimsFromUserInfoEndpoint": { "message": "Get claims from user info endpoint" diff --git a/apps/web/src/locales/ka/messages.json b/apps/web/src/locales/ka/messages.json index d126bb611d9..94cb80076d1 100644 --- a/apps/web/src/locales/ka/messages.json +++ b/apps/web/src/locales/ka/messages.json @@ -2444,7 +2444,7 @@ "message": "Owner" }, "ownerDesc": { - "message": "The highest access user that can manage all aspects of your organization." + "message": "Manage all aspects of your organization, including billing and subscriptions" }, "clientOwnerDesc": { "message": "This user should be independent of the Provider. If the Provider is disassociated with the organization, this user will maintain ownership of the organization." @@ -2453,19 +2453,19 @@ "message": "Admin" }, "adminDesc": { - "message": "Admins can access and manage all items, collections and users in your organization." + "message": "Manage organization access, all collections, members, reporting, and security settings" }, "user": { "message": "User" }, "userDesc": { - "message": "A regular user with access to assigned collections in your organization." + "message": "Access and add items to assigned collections" }, "manager": { "message": "Manager" }, "managerDesc": { - "message": "Managers can access and manage assigned collections in your organization." + "message": "Create, delete, and manage access in assigned collections" }, "all": { "message": "All" @@ -4117,7 +4117,22 @@ "message": "Custom" }, "customDesc": { - "message": "Allows more granular control of user permissions for advanced configurations." + "message": "Grant customized permissions to members" + }, + "customDescNonEnterpriseStart": { + "message": "Custom roles is an ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseLink": { + "message": "enterprise feature", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseEnd": { + "message": ". Contact our support team to upgrade your subscription", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customNonEnterpriseError": { + "message": "To enable custom permissions the organization must be on an Enterprise 2020 plan." }, "permissions": { "message": "Permissions" diff --git a/apps/web/src/locales/km/messages.json b/apps/web/src/locales/km/messages.json index d126bb611d9..94cb80076d1 100644 --- a/apps/web/src/locales/km/messages.json +++ b/apps/web/src/locales/km/messages.json @@ -2444,7 +2444,7 @@ "message": "Owner" }, "ownerDesc": { - "message": "The highest access user that can manage all aspects of your organization." + "message": "Manage all aspects of your organization, including billing and subscriptions" }, "clientOwnerDesc": { "message": "This user should be independent of the Provider. If the Provider is disassociated with the organization, this user will maintain ownership of the organization." @@ -2453,19 +2453,19 @@ "message": "Admin" }, "adminDesc": { - "message": "Admins can access and manage all items, collections and users in your organization." + "message": "Manage organization access, all collections, members, reporting, and security settings" }, "user": { "message": "User" }, "userDesc": { - "message": "A regular user with access to assigned collections in your organization." + "message": "Access and add items to assigned collections" }, "manager": { "message": "Manager" }, "managerDesc": { - "message": "Managers can access and manage assigned collections in your organization." + "message": "Create, delete, and manage access in assigned collections" }, "all": { "message": "All" @@ -4117,7 +4117,22 @@ "message": "Custom" }, "customDesc": { - "message": "Allows more granular control of user permissions for advanced configurations." + "message": "Grant customized permissions to members" + }, + "customDescNonEnterpriseStart": { + "message": "Custom roles is an ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseLink": { + "message": "enterprise feature", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseEnd": { + "message": ". Contact our support team to upgrade your subscription", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customNonEnterpriseError": { + "message": "To enable custom permissions the organization must be on an Enterprise 2020 plan." }, "permissions": { "message": "Permissions" diff --git a/apps/web/src/locales/kn/messages.json b/apps/web/src/locales/kn/messages.json index 49cf1fc3e21..a0a508798f5 100644 --- a/apps/web/src/locales/kn/messages.json +++ b/apps/web/src/locales/kn/messages.json @@ -4119,6 +4119,21 @@ "customDesc": { "message": "ಸುಧಾರಿತ ಸಂರಚನೆಗಳಿಗಾಗಿ ಬಳಕೆದಾರರ ಅನುಮತಿಗಳ ಹೆಚ್ಚಿನ ಹರಳಿನ ನಿಯಂತ್ರಣವನ್ನು ಅನುಮತಿಸುತ್ತದೆ." }, + "customDescNonEnterpriseStart": { + "message": "Custom roles is an ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseLink": { + "message": "enterprise feature", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseEnd": { + "message": ". Contact our support team to upgrade your subscription", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customNonEnterpriseError": { + "message": "To enable custom permissions the organization must be on an Enterprise 2020 plan." + }, "permissions": { "message": "ಅನುಮತಿಗಳು" }, diff --git a/apps/web/src/locales/ko/messages.json b/apps/web/src/locales/ko/messages.json index bdd0e189611..6bb89f3fade 100644 --- a/apps/web/src/locales/ko/messages.json +++ b/apps/web/src/locales/ko/messages.json @@ -4119,6 +4119,21 @@ "customDesc": { "message": "고급 설정을 위해 더욱 세분화된 사용자 권한 설정을 활성화합니다." }, + "customDescNonEnterpriseStart": { + "message": "Custom roles is an ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseLink": { + "message": "enterprise feature", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseEnd": { + "message": ". Contact our support team to upgrade your subscription", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customNonEnterpriseError": { + "message": "To enable custom permissions the organization must be on an Enterprise 2020 plan." + }, "permissions": { "message": "권한" }, diff --git a/apps/web/src/locales/lv/messages.json b/apps/web/src/locales/lv/messages.json index ab3d3013df9..4e7c99b0836 100644 --- a/apps/web/src/locales/lv/messages.json +++ b/apps/web/src/locales/lv/messages.json @@ -3063,7 +3063,7 @@ "description": "A billing plan/package. For example: Families, Teams, Enterprise, etc." }, "changeBillingPlan": { - "message": "Mainīt plānu", + "message": "Uzlabot plānu", "description": "A billing plan/package. For example: Families, Teams, Enterprise, etc." }, "changeBillingPlanUpgrade": { @@ -4119,6 +4119,21 @@ "customDesc": { "message": "Nodrošina izvērstāku lietotāju tiesību pārvaldību sarežģītākos uzstādījumos." }, + "customDescNonEnterpriseStart": { + "message": "Pielāgotas lomas ir ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseLink": { + "message": "uzņēmumu iespēja", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseEnd": { + "message": ". Lai uzlabotu abonementu, jāsazinās ar mūsu atbalsta komandu", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customNonEnterpriseError": { + "message": "Lai iespējotu pielāgotas atļaujas, apvienībai jābūt plānam Enterprise 2020." + }, "permissions": { "message": "Atļaujas" }, diff --git a/apps/web/src/locales/ml/messages.json b/apps/web/src/locales/ml/messages.json index 9fa5a2f3a2b..09c3b4265fe 100644 --- a/apps/web/src/locales/ml/messages.json +++ b/apps/web/src/locales/ml/messages.json @@ -4117,7 +4117,22 @@ "message": "Custom" }, "customDesc": { - "message": "Allows more granular control of user permissions for advanced configurations." + "message": "Grant customized permissions to members" + }, + "customDescNonEnterpriseStart": { + "message": "Custom roles is an ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseLink": { + "message": "enterprise feature", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseEnd": { + "message": ". Contact our support team to upgrade your subscription", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customNonEnterpriseError": { + "message": "To enable custom permissions the organization must be on an Enterprise 2020 plan." }, "permissions": { "message": "അനുമതികൾ" diff --git a/apps/web/src/locales/nb/messages.json b/apps/web/src/locales/nb/messages.json index 43c1c316b81..a7e44c49c60 100644 --- a/apps/web/src/locales/nb/messages.json +++ b/apps/web/src/locales/nb/messages.json @@ -10,7 +10,7 @@ } }, "whatTypeOfItem": { - "message": "Hvilken type gjenstand er dette?" + "message": "Hva slags element er dette?" }, "name": { "message": "Navn" @@ -174,7 +174,7 @@ "message": "Fjern" }, "unassigned": { - "message": "Utilegnet" + "message": "Utildelt" }, "noneFolder": { "message": "Ingen mappe", @@ -570,19 +570,19 @@ "message": "Logg på eller opprett en ny konto for å få tilgang til ditt sikre hvelv." }, "loginWithDevice": { - "message": "Log in with device" + "message": "Logg på med enhet" }, "loginWithDeviceEnabledInfo": { "message": "Log in with device must be set up in the settings of the Bitwarden mobile app. Need another option?" }, "loginWithMasterPassword": { - "message": "Log in with master password" + "message": "Logg på med hovedpassord" }, "createAccount": { "message": "Opprett en konto" }, "newAroundHere": { - "message": "New around here?" + "message": "Er du ny her?" }, "startTrial": { "message": "Start prøveperiode" @@ -591,7 +591,7 @@ "message": "Logg på" }, "logInInitiated": { - "message": "Log in initiated" + "message": "Pålogging startet" }, "submit": { "message": "Send inn" @@ -609,13 +609,13 @@ "message": "Hovedpassord" }, "masterPassDesc": { - "message": "Superpassordet er passordet du bruker for å få tilgang til hvelvet ditt. Det er veldig viktig at du aldri glemmer ditt superpassord. Det er ingen måter å få tilbake passordet på dersom du noensinne skulle klare å glemme det." + "message": "Hovedpassordet er passordet du bruker for å få tilgang til hvelvet ditt. Det er svært viktig at du aldri glemmer det. Det er ingen måter å gjenopprette hovedpassordet på dersom du glemmer det." }, "masterPassImportant": { "message": "Hovedpassord kan ikke gjenopprettes hvis du glemmer det!" }, "masterPassHintDesc": { - "message": "Et hint for superpassordet kan hjelpe deg med å huske på passordet dersom du skulle glemme det." + "message": "Et hint for hovedpassordet kan hjelpe deg med å huske på passordet dersom du skulle glemme det." }, "reTypeMasterPass": { "message": "Skriv inn hovedpassordet på nytt" @@ -633,10 +633,10 @@ "message": "Passordhint" }, "enterEmailToGetHint": { - "message": "Skriv inn din kontos E-postadresse for å motta hintet til ditt superpassord." + "message": "Skriv inn e-postadressen til kontoen din for å motta hintet til ditt hovedpassord." }, "getMasterPasswordHint": { - "message": "Få et hint om superpassordet" + "message": "Få et hint om hovedpassordet" }, "emailRequired": { "message": "E-postadressen er påkrevd." @@ -651,10 +651,10 @@ "message": "Skriv inn hovedpassordet på nytt." }, "masterPasswordMinlength": { - "message": "Master password must be at least 8 characters long." + "message": "Hovedpassordet må være ≥8 tegn lang." }, "masterPassDoesntMatch": { - "message": "Superpassord-bekreftelsen er ikke samsvarende." + "message": "Hovedpassord-bekreftelsen samsvarer ikke." }, "newAccountCreated": { "message": "Din nye konto har blitt opprettet! Du kan nå logge på." @@ -663,7 +663,7 @@ "message": "Kontoen ble opprettet." }, "masterPassSent": { - "message": "Vi har sendt deg en E-post med hintet til superpassordet." + "message": "Vi har sendt deg en e-post med hintet til hovedpassordet." }, "unexpectedError": { "message": "En uventet feil har oppstått." @@ -672,7 +672,7 @@ "message": "E-postadresse" }, "yourVaultIsLocked": { - "message": "Hvelvet ditt er låst. Kontroller superpassordet ditt for å fortsette." + "message": "Hvelvet ditt er låst. Kontroller hovedpassordet ditt for å fortsette." }, "unlock": { "message": "Lås opp" @@ -691,7 +691,7 @@ } }, "invalidMasterPassword": { - "message": "Ugyldig superpassord" + "message": "Ugyldig hovedpassord" }, "invalidFilePassword": { "message": "Invalid file password, please use the password you entered when you created the export file." @@ -700,7 +700,7 @@ "message": "Lås nå" }, "noItemsInList": { - "message": "Det er ingen gjenstander å liste opp." + "message": "Det er ingen elementer å vise." }, "noCollectionsInList": { "message": "Det er ingen samlinger å liste opp." @@ -721,7 +721,7 @@ "message": "Du tilhører ikke noen organisasjoner. Organisasjoner gjør det mulig for deg å trygt dele elementer med andre brukere." }, "notificationSentDevice": { - "message": "A notification has been sent to your device." + "message": "Et varsel har blitt sendt til enheten din." }, "versionNumber": { "message": "Versjon $VERSION_NUMBER$", @@ -847,7 +847,7 @@ "message": "Rediger samlingene som dette elementet blir delt med. Kun organisasjonsbrukere med tilgang til disse samlingene vil kunne se dette elementet." }, "deleteSelectedItemsDesc": { - "message": "Du har valgt $COUNT$ gjenstand(er) som skal slettes. Er du sikker på du vil slette alle disse gjenstandene?", + "message": "Du har valgt $COUNT$ elementer(er) for sletting. Er du sikker på du vil slette alle disse elementene?", "placeholders": { "count": { "content": "$1", @@ -918,16 +918,16 @@ "message": "This password will be used to export and import this file" }, "confirmMasterPassword": { - "message": "Confirm master password" + "message": "Bekreft hovedpassord" }, "confirmFormat": { - "message": "Confirm format" + "message": "Bekreft format" }, "filePassword": { - "message": "File password" + "message": "Filpassord" }, "confirmFilePassword": { - "message": "Confirm file password" + "message": "Bekreft filpassord" }, "accountBackupOptionDescription": { "message": "Use your account encryption key to encrypt the export and restrict import to only the current Bitwarden account." @@ -936,19 +936,19 @@ "message": "Set a password to encrypt the export and import it to any Bitwarden account using the password for decryption." }, "fileTypeHeading": { - "message": "File type" + "message": "Filtype" }, "accountBackup": { - "message": "Account backup" + "message": "Sikkerhetskopiering av konto" }, "passwordProtected": { - "message": "Password protected" + "message": "Passord beskyttet" }, "filePasswordAndConfirmFilePasswordDoNotMatch": { "message": "“File password” and “Confirm file password“ do not match." }, "confirmVaultImport": { - "message": "Confirm vault import" + "message": "Bekreft hvelvimportering" }, "confirmVaultImportDesc": { "message": "This file is password-protected. Please enter the file password to import data." @@ -1076,7 +1076,7 @@ "message": "KDF-iterasjoner" }, "kdfIterationsDesc": { - "message": "Flere KDF-gjentakelser kan hjelpe til med å beskytte superpassordet fra å bli tvunget inn i av en angriper. Vi anbefaler en verdi på $VALUE$ eller mer.", + "message": "Flere KDF-gjentakelser kan hjelpe til med å beskytte hovedpassordet fra å bli tvunget inn i av en angriper. Vi anbefaler en verdi på $VALUE$ eller mer.", "placeholders": { "value": { "content": "$1", @@ -1196,10 +1196,10 @@ "message": "Velg importfilen" }, "chooseFile": { - "message": "Choose File" + "message": "Velg fil" }, "noFileChosen": { - "message": "No file chosen" + "message": "Ingen fil er valgt" }, "orCopyPasteFileContents": { "message": "eller kopier og lim inn importfilens innhold" @@ -1360,7 +1360,7 @@ "message": "Denne 2-trinnsleverandøren er aktivert på din konto." }, "twoStepLoginAuthDesc": { - "message": "Skriv inn ditt superpassord for å endre på 2-trinnsinnloggingsinnstillingene." + "message": "Skriv inn ditt hovedpassord for å endre på 2-trinnsinnloggingsinnstillingene." }, "twoStepAuthenticatorDesc": { "message": "Følg disse trinnene for å sette opp 2-trinnsinnlogging med en autentiseringsapp:" @@ -1420,7 +1420,7 @@ "message": "På grunn av plattformbegrensninger, kan YubiKey-er ikke bli brukt på alle Bitwarden-apper. Du burde skru på en annen 2-trinnsinnloggingsleverandør, sånn at du kan få tilgang til din konto når YubiKeys ikke kan bli brukt. Støttede plattformer:" }, "twoFactorYubikeySupportUsb": { - "message": "Netthvelvet, datamaskinprogrammet, CLI, og alle nettleserutvidelser, på en enhet med en USB-port som støtter din YubiKey." + "message": "Netthvelvet, datamaskinprogrammet, ledetekst, og alle nettleserutvidelser, på en enhet med en USB-port som støtter din YubiKey." }, "twoFactorYubikeySupportMobile": { "message": "Mobilapper på en enhet med NFC-funksjoner, eller som har en USB-port som støtter din YubiKey." @@ -1570,7 +1570,7 @@ "message": "Usikrede nettsteder ble funnet" }, "unsecuredWebsitesFoundDesc": { - "message": "Vi fant $COUNT$ elementer i hvelvet ditt som benytter usikrede URIer. Du burde endre deres URI til å benytte https://, dersom det nettstedet tillater det.", + "message": "Vi fant $COUNT$ objekter i hvelvet ditt som benytter usikrede URI-er. Du burde endre deres URI-er til å benytte https://, dersom det nettstedet tillater det.", "placeholders": { "count": { "content": "$1", @@ -1579,7 +1579,7 @@ } }, "noUnsecuredWebsites": { - "message": "Ingen gjenstander i hvelvet ditt har usikrede URI-er." + "message": "Ingen elementer i hvelvet ditt har usikrede URI-er." }, "inactive2faReport": { "message": "Rapport om 2FA-nettsteder" @@ -1591,7 +1591,7 @@ "message": "Pålogginger som støtter 2FA ble funnet" }, "inactive2faFoundDesc": { - "message": "We found $COUNT$ website(s) in your vault that may not be configured with two-step login (according to 2fa.directory). To further protect these accounts, you should set up two-step login.", + "message": "Vi fant $COUNT$ nettsted(er) i hvelvet ditt som kanskje ikke har blitt satt opp med 2-trinnspålogging (i følge 2fa.directory). For å beskytte disse kontoene ytterligere, burde du sette opp 2-trinnspålogging.", "placeholders": { "count": { "content": "$1", @@ -1615,7 +1615,7 @@ "message": "Eksponerte passord ble funnet" }, "exposedPasswordsFoundDesc": { - "message": "Vi fant $COUNT$ gjenstander i hvelvet ditt som har passord som er eksponert i kjente databrudd. Du burde endre passordene deres til noe nytt.", + "message": "Vi fant $COUNT$ elementer i hvelvet ditt med passord eksponert i kjente datainnbrudd. Du bør endre passordet på dem.", "placeholders": { "count": { "content": "$1", @@ -1624,7 +1624,7 @@ } }, "noExposedPasswords": { - "message": "Ingen elementer i hvelvet ditt har passord som har vært eksponert i kjente databrudd." + "message": "Ingen objekter i hvelvet ditt har passord som har vært eksponert i kjente databrudd." }, "checkExposedPasswords": { "message": "Sjekk eksponerte passord" @@ -1648,7 +1648,7 @@ "message": "Svake passord ble funnet" }, "weakPasswordsFoundDesc": { - "message": "Vi fant $COUNT$ elementer i hvelvet ditt som har passord som ikke er sterke. Du burde endre dem slik at de har sterkere passord.", + "message": "Vi fant $COUNT$ objekter i hvelvet ditt som har passord som ikke er sterke. Du burde oppdatere dem til å bruke sterkere passord.", "placeholders": { "count": { "content": "$1", @@ -1657,7 +1657,7 @@ } }, "noWeakPasswords": { - "message": "Ingen elementer i hvelvet ditt har svake passord." + "message": "Ingen objekter i hvelvet ditt har svake passord." }, "reusedPasswordsReport": { "message": "Rapport om gjenbrukte passord" @@ -1696,7 +1696,7 @@ "message": "Kontoer som har blitt avslørt kan lekke personlig informasjon. Gjør kontoene tryggere ved å aktivere 2FA eller ved å lage et sterkere passord." }, "breachCheckUsernameEmail": { - "message": "Sjekk noen av brukernavnene eller E-postadressene som du bruker." + "message": "Sjekk de brukernavnene eller E-postadressene du bruker." }, "checkBreaches": { "message": "Sjekk databrudd" @@ -2378,7 +2378,7 @@ "message": "Tilgangsstyring" }, "groupAccessAllItems": { - "message": "Denne gruppen kan få tilgang til og modifisere alle gjenstander." + "message": "Denne gruppen kan få tilgang til og endre alle elementer." }, "groupAccessSelectedCollections": { "message": "Denne gruppen har bare tilgang til de valgte samlingene." @@ -2420,7 +2420,7 @@ "message": "Denne brukeren bruker 2-trinnsinnlogging til å beskytte kontoen sin." }, "userAccessAllItems": { - "message": "Denne brukeren kan få tilgang til og modifisere alle gjenstander." + "message": "Denne brukeren kan få tilgang til og endre alle elementer." }, "userAccessSelectedCollections": { "message": "Denne brukeren har bare tilgang til de valgte samlingene." @@ -2567,7 +2567,7 @@ } }, "viewAllLoginOptions": { - "message": "View all log in options" + "message": "Vis alle påloggingsalternativer" }, "viewedItemId": { "message": "Vist $ID$.", @@ -2945,7 +2945,7 @@ "message": "E-postadressen til kontoen din må verifiseres først." }, "checkInboxForVerification": { - "message": "Se etter i din E-postkontos innboks for en verifiseringslenke." + "message": "Se i din E-postinnboks for en verifiseringslenke." }, "emailVerified": { "message": "Din E-postadresse har blitt bekreftet." @@ -3017,7 +3017,7 @@ "message": "Min organisasjon" }, "organizationInfo": { - "message": "Organization info" + "message": "Organisasjonsinfo" }, "deleteOrganization": { "message": "Slett organisasjonen" @@ -3263,7 +3263,7 @@ "message": "Du bruker for øyeblikket et utdatert krypteringsoppsett." }, "updateEncryptionKeyDesc": { - "message": "Vi har byttet over til lengre krypteringsnøkler som sørger for bedre sikkerhet og tilgang til nyere funksjoner. Å oppdatere krypteringsnøkkelen din er raskt og enkelt. Bare skriv inn superpassordet ditt nedenfor. Denne oppdateringen vil etter hvert bli påbudt." + "message": "Vi har byttet over til lengre krypteringsnøkler som sørger for bedre sikkerhet og tilgang til nyere funksjoner. Å oppdatere krypteringsnøkkelen din er raskt og enkelt. Bare skriv inn hovedpassordet ditt nedenfor. Denne oppdateringen vil etter hvert bli påbudt." }, "updateEncryptionKeyWarning": { "message": "Etter å ha oppdatert krypteringsnøkkelen din, er du påkrevd å logge av og på på alle Bitwarden-appene og -programmene som du bruker for øyeblikket (deriblant mobilappen og nettleserutvidelsene). Å ikke logge av og på igjen (noe som vil laste ned din nye krypteringsnøkkel) kan føre til datakorrumpering. Vi vil forsøke å logge deg av automatisk, men det kan kanskje bli forsinket." @@ -3345,7 +3345,7 @@ "description": "ex. Date this item was updated" }, "dateCreated": { - "message": "Created", + "message": "Opprettet", "description": "ex. Date this item was created" }, "datePasswordUpdated": { @@ -3393,7 +3393,7 @@ "message": "Svakt hovedpassord" }, "weakMasterPasswordDesc": { - "message": "Superpassordet du har valgt er svakt. Du bør bruke et sterkt superpassord (eller en passordfrase) for å sikre Bitwarden-kontoen din på en forsvarlig måte. Er du sikker på at du vil bruke dette superpassordet?" + "message": "Hovedpassordet du har valgt er svakt. Du bør bruke et sterkt hovedpassord (eller en passordfrase) for å sikre Bitwarden-kontoen din på en forsvarlig måte. Er du sikker på at du vil bruke dette superpassordet?" }, "rotateAccountEncKey": { "message": "Oppdater også krypteringsnøkkelen til kontoen min" @@ -3429,7 +3429,7 @@ "message": "Please make sure your vault is unlocked and Fingerprint phrase matches the other device." }, "fingerprintPhraseHeader": { - "message": "Fingerprint phrase" + "message": "Fingeravtrykksfrase" }, "dontAskFingerprintAgain": { "message": "Ikke be om bekreftelse av fingeravtrykksfrase flere ganger", @@ -3716,7 +3716,7 @@ "message": "Organisasjonsidentifikator er påkrevd." }, "ssoIdentifier": { - "message": "SSO identifier" + "message": "SSO-identifikator" }, "ssoIdentifierHint": { "message": "Provide this ID to your members to login with SSO." @@ -4119,17 +4119,32 @@ "customDesc": { "message": "Tillater mer granatær kontroll med brukertillatelser for avanserte konfigurasjoner." }, + "customDescNonEnterpriseStart": { + "message": "Custom roles is an ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseLink": { + "message": "enterprise feature", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseEnd": { + "message": ". Contact our support team to upgrade your subscription", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customNonEnterpriseError": { + "message": "To enable custom permissions the organization must be on an Enterprise 2020 plan." + }, "permissions": { "message": "Rettigheter" }, "permission": { - "message": "Permission" + "message": "Tillatelse" }, "managerPermissions": { "message": "Manager Permissions" }, "adminPermissions": { - "message": "Admin Permissions" + "message": "Admin-tillatelser" }, "accessEventLogs": { "message": "Få tilgang til hendelseslogger" @@ -4435,13 +4450,13 @@ "message": "Superpassord bekreftelse" }, "passwordConfirmationDesc": { - "message": "Denne handlingen er beskyttet. For å fortsette, skriv inn superpassordet på nytt for å bekrefte identiteten din." + "message": "Denne handlingen er beskyttet. For å fortsette, skriv inn hovedpassordet på nytt for å bekrefte identiteten din." }, "reinviteSelected": { "message": "Send invitasjoner på nytt" }, "resendNotification": { - "message": "Resend notification" + "message": "Send varsel på nytt" }, "noSelectedUsersApplicable": { "message": "Denne handlingen er ikke relevant for noen av de valgte brukerne." @@ -4531,7 +4546,7 @@ "message": "Klienter" }, "client": { - "message": "Client", + "message": "Klient", "description": "This is used as a table header to describe which client application created an event log." }, "providerAdmin": { @@ -5326,16 +5341,16 @@ "message": "API tilgangstoken" }, "deviceVerification": { - "message": "Device verification" + "message": "Enhetsverifisering" }, "enableDeviceVerification": { - "message": "Turn on device verification" + "message": "Skru på enhetsverifisering" }, "deviceVerificationDesc": { "message": "Verification codes are sent to your email address when logging in from an unrecognized device" }, "updatedDeviceVerification": { - "message": "Updated device verification" + "message": "Oppdaterte enhetsverifiseringen" }, "areYouSureYouWantToEnableDeviceVerificationTheVerificationCodeEmailsWillArriveAtX": { "message": "Are you sure you want to turn on device verification? The verification code emails will arrive at: $EMAIL$", @@ -5347,7 +5362,7 @@ } }, "premiumSubcriptionRequired": { - "message": "Premium subscription required" + "message": "Krever Premium-abonnement" }, "scim": { "message": "SCIM provisioning", @@ -5358,7 +5373,7 @@ "description": "the text, 'SCIM', is an acronymn and should not be translated." }, "scimEnabledCheckboxDesc": { - "message": "Enable SCIM", + "message": "Skru på SCIM", "description": "the text, 'SCIM', is an acronymn and should not be translated." }, "scimEnabledCheckboxDescHelpText": { @@ -5392,7 +5407,7 @@ "description": "the text, 'SCIM' and 'URL', are acronymns and should not be translated." }, "scimUrl": { - "message": "SCIM URL", + "message": "SCIM-URL", "description": "the text, 'SCIM' and 'URL', are acronymns and should not be translated." }, "scimApiKeyRotated": { @@ -5404,13 +5419,13 @@ "description": "the text, 'SCIM', is an acronymn and should not be translated." }, "inputRequired": { - "message": "Input is required." + "message": "Inndata er påkrevd." }, "inputEmail": { - "message": "Input is not an email address." + "message": "Inndataen er ikke en E-postadresse." }, "inputMinLength": { - "message": "Input must be at least $COUNT$ characters long.", + "message": "Inndataen må være minst $COUNT$ tegn.", "placeholders": { "count": { "content": "$1", @@ -5419,7 +5434,7 @@ } }, "inputMaxLength": { - "message": "Input must not exceed $COUNT$ characters in length.", + "message": "Inndataen kan ikke ha mer enn $COUNT$ tegn.", "placeholders": { "count": { "content": "$1", @@ -5437,70 +5452,70 @@ } }, "turnOn": { - "message": "Turn on" + "message": "Slå på" }, "on": { - "message": "On" + "message": "På" }, "members": { - "message": "Members" + "message": "Medlemmer" }, "reporting": { - "message": "Reporting" + "message": "Rapportering" }, "cardBrandMir": { "message": "Mir" }, "numberOfUsers": { - "message": "Number of users" + "message": "Antall brukere" }, "loggingInAs": { - "message": "Logging in as" + "message": "Logger på som" }, "notYou": { - "message": "Not you?" + "message": "Ikke deg?" }, "multiSelectPlaceholder": { - "message": "-- Type to Filter --" + "message": "-- Skriv for å filtrere --" }, "multiSelectLoading": { "message": "Retrieving options..." }, "multiSelectNotFound": { - "message": "No items found" + "message": "Ingen oppføringer funnet" }, "multiSelectClearAll": { - "message": "Clear all" + "message": "Tøm alle" }, "from": { - "message": "From" + "message": "Fra" }, "to": { - "message": "To" + "message": "Til" }, "member": { - "message": "Member" + "message": "Medlem" }, "update": { - "message": "Update" + "message": "Oppdater" }, "role": { - "message": "Role" + "message": "Rolle" }, "canView": { - "message": "Can view" + "message": "Kan vise" }, "canViewExceptPass": { - "message": "Can view, except passwords" + "message": "Kan vise, bortsett fra passord" }, "canEdit": { - "message": "Can edit" + "message": "Kan redigere" }, "canEditExceptPass": { - "message": "Can edit, except passwords" + "message": "Kan redigere, bortsett fra passord" }, "group": { - "message": "Group" + "message": "Gruppe" }, "groupAccessAll": { "message": "This group can access and modify all items." diff --git a/apps/web/src/locales/nl/messages.json b/apps/web/src/locales/nl/messages.json index 596963deea8..42372af8a20 100644 --- a/apps/web/src/locales/nl/messages.json +++ b/apps/web/src/locales/nl/messages.json @@ -4119,6 +4119,21 @@ "customDesc": { "message": "Maakt een fijnmazige controle van gebruikersrechten voor geavanceerde configuraties mogelijk." }, + "customDescNonEnterpriseStart": { + "message": "Aangepaste rollen is een ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseLink": { + "message": "Enterprise feature", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseEnd": { + "message": ". Neem contact op met ons ondersteuningsteam voor het aanpassen van je abonnement", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customNonEnterpriseError": { + "message": "Voor het inschakelen van aangepaste rechten moet de organisatie een Enterprise 2020-abonnement hebben." + }, "permissions": { "message": "Rechten" }, diff --git a/apps/web/src/locales/nn/messages.json b/apps/web/src/locales/nn/messages.json index 9c862bb2119..fc5cfe963e3 100644 --- a/apps/web/src/locales/nn/messages.json +++ b/apps/web/src/locales/nn/messages.json @@ -2444,7 +2444,7 @@ "message": "Eigar" }, "ownerDesc": { - "message": "The highest access user that can manage all aspects of your organization." + "message": "Manage all aspects of your organization, including billing and subscriptions" }, "clientOwnerDesc": { "message": "This user should be independent of the Provider. If the Provider is disassociated with the organization, this user will maintain ownership of the organization." @@ -2453,19 +2453,19 @@ "message": "Admin" }, "adminDesc": { - "message": "Admins can access and manage all items, collections and users in your organization." + "message": "Manage organization access, all collections, members, reporting, and security settings" }, "user": { "message": "Brukar" }, "userDesc": { - "message": "A regular user with access to assigned collections in your organization." + "message": "Access and add items to assigned collections" }, "manager": { "message": "Manager" }, "managerDesc": { - "message": "Managers can access and manage assigned collections in your organization." + "message": "Create, delete, and manage access in assigned collections" }, "all": { "message": "All" @@ -4117,7 +4117,22 @@ "message": "Custom" }, "customDesc": { - "message": "Allows more granular control of user permissions for advanced configurations." + "message": "Grant customized permissions to members" + }, + "customDescNonEnterpriseStart": { + "message": "Custom roles is an ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseLink": { + "message": "enterprise feature", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseEnd": { + "message": ". Contact our support team to upgrade your subscription", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customNonEnterpriseError": { + "message": "To enable custom permissions the organization must be on an Enterprise 2020 plan." }, "permissions": { "message": "Permissions" diff --git a/apps/web/src/locales/pl/messages.json b/apps/web/src/locales/pl/messages.json index 1d3e755aa3c..3b2d637d1de 100644 --- a/apps/web/src/locales/pl/messages.json +++ b/apps/web/src/locales/pl/messages.json @@ -2444,7 +2444,7 @@ "message": "Właściciel" }, "ownerDesc": { - "message": "Użytkownik z najwyższym uprawnieniem, który może zarządzać wszystkimi ustawieniami organizacji." + "message": "Użytkownik z najszerszym dostępem, mogący zarządzać wszystkimi właściwościami organizacji." }, "clientOwnerDesc": { "message": "Ten użytkownik powinien być niezależny od dostawcy. Jeśli dostawca zostanie odłączony od organizacji, użytkownik ten zachowa własność organizacji." @@ -2453,7 +2453,7 @@ "message": "Administrator" }, "adminDesc": { - "message": "Administratorzy posiadają dostęp do wszystkich elementów, kolekcji i użytkowników w Twojej organizacji." + "message": "Administratorzy mają dostęp do wszystkich elementów, kolekcji i użytkowników w Twojej organizacji." }, "user": { "message": "Użytkownik" @@ -4119,6 +4119,21 @@ "customDesc": { "message": "Umożliwia zaawansowaną kontrolę uprawnień użytkownika." }, + "customDescNonEnterpriseStart": { + "message": "Role niestandardowe to ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseLink": { + "message": "funkcja dla przedsiębiorstw", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseEnd": { + "message": ". Skontaktuj się z naszym zespołem pomocy technicznej, aby ulepszyć subskrypcję", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customNonEnterpriseError": { + "message": "Aby włączyć uprawnienia niestandardowe, organizacja musi korzystać z planu Enterprise 2020." + }, "permissions": { "message": "Uprawnienia" }, diff --git a/apps/web/src/locales/pt_BR/messages.json b/apps/web/src/locales/pt_BR/messages.json index beb467202f5..6b33d1cf22f 100644 --- a/apps/web/src/locales/pt_BR/messages.json +++ b/apps/web/src/locales/pt_BR/messages.json @@ -4119,6 +4119,21 @@ "customDesc": { "message": "Permite controle mais granular das permissões de usuário para configurações avançadas." }, + "customDescNonEnterpriseStart": { + "message": "Custom roles is an ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseLink": { + "message": "enterprise feature", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseEnd": { + "message": ". Contact our support team to upgrade your subscription", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customNonEnterpriseError": { + "message": "To enable custom permissions the organization must be on an Enterprise 2020 plan." + }, "permissions": { "message": "Permissões" }, diff --git a/apps/web/src/locales/pt_PT/messages.json b/apps/web/src/locales/pt_PT/messages.json index e7724527aef..06a485a7be6 100644 --- a/apps/web/src/locales/pt_PT/messages.json +++ b/apps/web/src/locales/pt_PT/messages.json @@ -4117,7 +4117,22 @@ "message": "Personalizado" }, "customDesc": { - "message": "Allows more granular control of user permissions for advanced configurations." + "message": "Grant customized permissions to members" + }, + "customDescNonEnterpriseStart": { + "message": "Os papéis personalizados são uma ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseLink": { + "message": "funcionalidade empresarial", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseEnd": { + "message": ". Contacte a nossa equipa de suporte para atualizar a sua subscrição", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customNonEnterpriseError": { + "message": "Para ativar as permissões personalizadas, a organização tem de estar num plano Empresarial 2020." }, "permissions": { "message": "Permissões" diff --git a/apps/web/src/locales/ro/messages.json b/apps/web/src/locales/ro/messages.json index e48387a3715..e16c0deaa30 100644 --- a/apps/web/src/locales/ro/messages.json +++ b/apps/web/src/locales/ro/messages.json @@ -4119,6 +4119,21 @@ "customDesc": { "message": "Permite mai mult control granular al permisiunilor utilizatorilor pentru configurații avansate." }, + "customDescNonEnterpriseStart": { + "message": "Custom roles is an ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseLink": { + "message": "enterprise feature", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseEnd": { + "message": ". Contact our support team to upgrade your subscription", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customNonEnterpriseError": { + "message": "To enable custom permissions the organization must be on an Enterprise 2020 plan." + }, "permissions": { "message": "Permisiuni" }, diff --git a/apps/web/src/locales/ru/messages.json b/apps/web/src/locales/ru/messages.json index aad95307bda..0614b2b07bd 100644 --- a/apps/web/src/locales/ru/messages.json +++ b/apps/web/src/locales/ru/messages.json @@ -555,7 +555,7 @@ "message": "Вы действительно хотите выйти?" }, "logOut": { - "message": "Выход" + "message": "Выйти" }, "ok": { "message": "Ok" @@ -1022,7 +1022,7 @@ "message": "Изменить email" }, "changeEmailTwoFactorWarning": { - "message": "При продолжении будет изменен адрес email вашего аккаунта. Это не изменит адрес email для двухэтапной аутентификации. Его можно изменить в настройках двухэтапной аутентификации." + "message": "В результате будет изменен адрес электронной почты вашей учетной записи. Это не приведет к изменению адреса электронной почты, используемого для двухэтапной аутентификации. Этот адрес можно изменить в настройках двухэтапной аутентификации." }, "newEmail": { "message": "Новый email" @@ -1100,7 +1100,7 @@ "message": "Настройки ключа шифрования сохранены" }, "dangerZone": { - "message": "Зона опасности" + "message": "Зона риска" }, "dangerZoneDesc": { "message": "Осторожно, эти действия необратимы!" @@ -2444,7 +2444,7 @@ "message": "Владелец" }, "ownerDesc": { - "message": "Высший пользовательский уровень доступа, который позволяет управлять всеми аспектами вашей организации." + "message": "Управление всеми аспектами деятельности вашей организации, включая биллинг и подписки" }, "clientOwnerDesc": { "message": "Этот пользователь должен быть независимым от поставщика. Если поставщик будет отсоединен от организации, этот пользователь сохранит право собственности на организацию." @@ -2453,19 +2453,19 @@ "message": "Администратор" }, "adminDesc": { - "message": "Администраторы могут получать доступ и управлять всеми элементами, коллекциями и пользователями вашей организации." + "message": "Управление доступом к организации, всеми коллекциями, членами, отчетностью и настройками безопасности" }, "user": { "message": "Пользователь" }, "userDesc": { - "message": "Обычный пользователь с доступом к коллекциям вашей организации." + "message": "Доступ и добавление элементов в соответствующие коллекции" }, "manager": { "message": "Менеджер" }, "managerDesc": { - "message": "Менеджеры могут получать доступ и управлять назначенными коллекциями вашей организации." + "message": "Создание, удаление и управление доступом в соответствующих коллекциях" }, "all": { "message": "Все" @@ -3573,7 +3573,7 @@ "message": "Чтобы получить доступ к хранилищу после выхода из него требуется повторная авторизация." }, "lock": { - "message": "Заблокировать", + "message": "Блокировка", "description": "Verb form: to make secure or inaccesible by" }, "trash": { @@ -4117,7 +4117,22 @@ "message": "Пользовательский" }, "customDesc": { - "message": "Позволяет более гибко контролировать права пользователей для расширенных конфигураций." + "message": "Предоставление членам настраиваемых разрешений" + }, + "customDescNonEnterpriseStart": { + "message": "Пользовательские роли - ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseLink": { + "message": "это функционал для предприятий", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseEnd": { + "message": ". Свяжитесь с нашей службой поддержки для обновления подписки,", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customNonEnterpriseError": { + "message": "Для включения пользовательских разрешений организация должна быть на тарифном плане Enterprise 2020." }, "permissions": { "message": "Разрешения" diff --git a/apps/web/src/locales/si/messages.json b/apps/web/src/locales/si/messages.json index 904b1019020..22fa9b6bf2e 100644 --- a/apps/web/src/locales/si/messages.json +++ b/apps/web/src/locales/si/messages.json @@ -2444,7 +2444,7 @@ "message": "Owner" }, "ownerDesc": { - "message": "The highest access user that can manage all aspects of your organization." + "message": "Manage all aspects of your organization, including billing and subscriptions" }, "clientOwnerDesc": { "message": "This user should be independent of the Provider. If the Provider is disassociated with the organization, this user will maintain ownership of the organization." @@ -2453,19 +2453,19 @@ "message": "Admin" }, "adminDesc": { - "message": "Admins can access and manage all items, collections and users in your organization." + "message": "Manage organization access, all collections, members, reporting, and security settings" }, "user": { "message": "User" }, "userDesc": { - "message": "A regular user with access to assigned collections in your organization." + "message": "Access and add items to assigned collections" }, "manager": { "message": "Manager" }, "managerDesc": { - "message": "Managers can access and manage assigned collections in your organization." + "message": "Create, delete, and manage access in assigned collections" }, "all": { "message": "All" @@ -4117,7 +4117,22 @@ "message": "Custom" }, "customDesc": { - "message": "Allows more granular control of user permissions for advanced configurations." + "message": "Grant customized permissions to members" + }, + "customDescNonEnterpriseStart": { + "message": "Custom roles is an ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseLink": { + "message": "enterprise feature", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseEnd": { + "message": ". Contact our support team to upgrade your subscription", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customNonEnterpriseError": { + "message": "To enable custom permissions the organization must be on an Enterprise 2020 plan." }, "permissions": { "message": "Permissions" diff --git a/apps/web/src/locales/sk/messages.json b/apps/web/src/locales/sk/messages.json index 09c0781426d..dcdcab8ff02 100644 --- a/apps/web/src/locales/sk/messages.json +++ b/apps/web/src/locales/sk/messages.json @@ -4119,6 +4119,21 @@ "customDesc": { "message": "Umožňuje podrobnejšiu kontrolu nad povoleniami používateľov pre pokročilé konfigurácie." }, + "customDescNonEnterpriseStart": { + "message": "Custom roles is an ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseLink": { + "message": "enterprise feature", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseEnd": { + "message": ". Contact our support team to upgrade your subscription", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customNonEnterpriseError": { + "message": "To enable custom permissions the organization must be on an Enterprise 2020 plan." + }, "permissions": { "message": "Povolenia" }, diff --git a/apps/web/src/locales/sl/messages.json b/apps/web/src/locales/sl/messages.json index 8e2bf2592c5..4ebaf293e39 100644 --- a/apps/web/src/locales/sl/messages.json +++ b/apps/web/src/locales/sl/messages.json @@ -2444,7 +2444,7 @@ "message": "Owner" }, "ownerDesc": { - "message": "The highest access user that can manage all aspects of your organization." + "message": "Manage all aspects of your organization, including billing and subscriptions" }, "clientOwnerDesc": { "message": "This user should be independent of the Provider. If the Provider is disassociated with the organization, this user will maintain ownership of the organization." @@ -2453,19 +2453,19 @@ "message": "Admin" }, "adminDesc": { - "message": "Admins can access and manage all items, collections and users in your organization." + "message": "Manage organization access, all collections, members, reporting, and security settings" }, "user": { "message": "User" }, "userDesc": { - "message": "A regular user with access to assigned collections in your organization." + "message": "Access and add items to assigned collections" }, "manager": { "message": "Manager" }, "managerDesc": { - "message": "Managers can access and manage assigned collections in your organization." + "message": "Create, delete, and manage access in assigned collections" }, "all": { "message": "All" @@ -4117,7 +4117,22 @@ "message": "Custom" }, "customDesc": { - "message": "Allows more granular control of user permissions for advanced configurations." + "message": "Grant customized permissions to members" + }, + "customDescNonEnterpriseStart": { + "message": "Custom roles is an ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseLink": { + "message": "enterprise feature", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseEnd": { + "message": ". Contact our support team to upgrade your subscription", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customNonEnterpriseError": { + "message": "To enable custom permissions the organization must be on an Enterprise 2020 plan." }, "permissions": { "message": "Permissions" diff --git a/apps/web/src/locales/sr/messages.json b/apps/web/src/locales/sr/messages.json index 07c55259af2..9fe32ee7760 100644 --- a/apps/web/src/locales/sr/messages.json +++ b/apps/web/src/locales/sr/messages.json @@ -4119,6 +4119,21 @@ "customDesc": { "message": "Омогућава детаљнију контролу корисничких дозвола за напредне конфигурације." }, + "customDescNonEnterpriseStart": { + "message": "Custom roles is an ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseLink": { + "message": "enterprise feature", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseEnd": { + "message": ". Contact our support team to upgrade your subscription", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customNonEnterpriseError": { + "message": "To enable custom permissions the organization must be on an Enterprise 2020 plan." + }, "permissions": { "message": "Дозволе" }, diff --git a/apps/web/src/locales/sr_CS/messages.json b/apps/web/src/locales/sr_CS/messages.json index b0e0e899588..4afc702036b 100644 --- a/apps/web/src/locales/sr_CS/messages.json +++ b/apps/web/src/locales/sr_CS/messages.json @@ -2444,7 +2444,7 @@ "message": "Owner" }, "ownerDesc": { - "message": "The highest access user that can manage all aspects of your organization." + "message": "Manage all aspects of your organization, including billing and subscriptions" }, "clientOwnerDesc": { "message": "This user should be independent of the Provider. If the Provider is disassociated with the organization, this user will maintain ownership of the organization." @@ -2453,19 +2453,19 @@ "message": "Admin" }, "adminDesc": { - "message": "Admins can access and manage all items, collections and users in your organization." + "message": "Manage organization access, all collections, members, reporting, and security settings" }, "user": { "message": "User" }, "userDesc": { - "message": "A regular user with access to assigned collections in your organization." + "message": "Access and add items to assigned collections" }, "manager": { "message": "Manager" }, "managerDesc": { - "message": "Managers can access and manage assigned collections in your organization." + "message": "Create, delete, and manage access in assigned collections" }, "all": { "message": "All" @@ -4117,7 +4117,22 @@ "message": "Custom" }, "customDesc": { - "message": "Allows more granular control of user permissions for advanced configurations." + "message": "Grant customized permissions to members" + }, + "customDescNonEnterpriseStart": { + "message": "Custom roles is an ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseLink": { + "message": "enterprise feature", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseEnd": { + "message": ". Contact our support team to upgrade your subscription", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customNonEnterpriseError": { + "message": "To enable custom permissions the organization must be on an Enterprise 2020 plan." }, "permissions": { "message": "Permissions" diff --git a/apps/web/src/locales/sv/messages.json b/apps/web/src/locales/sv/messages.json index 74d273b2d21..4d3f950816a 100644 --- a/apps/web/src/locales/sv/messages.json +++ b/apps/web/src/locales/sv/messages.json @@ -4119,6 +4119,21 @@ "customDesc": { "message": "Tillåter mer detaljerad kontroll av användarbehörigheter för avancerade konfigurationer." }, + "customDescNonEnterpriseStart": { + "message": "Custom roles is an ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseLink": { + "message": "enterprise feature", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseEnd": { + "message": ". Contact our support team to upgrade your subscription", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customNonEnterpriseError": { + "message": "To enable custom permissions the organization must be on an Enterprise 2020 plan." + }, "permissions": { "message": "Behörigheter" }, diff --git a/apps/web/src/locales/th/messages.json b/apps/web/src/locales/th/messages.json new file mode 100644 index 00000000000..aad7df91670 --- /dev/null +++ b/apps/web/src/locales/th/messages.json @@ -0,0 +1,5526 @@ +{ + "pageTitle": { + "message": "$APP_NAME$ Web Vault", + "description": "The title of the website in the browser window.", + "placeholders": { + "app_name": { + "content": "$1", + "example": "Bitwarden" + } + } + }, + "whatTypeOfItem": { + "message": "What type of item is this?" + }, + "name": { + "message": "ชื่อ" + }, + "uri": { + "message": "URI" + }, + "uriPosition": { + "message": "URI $POSITION$", + "description": "A listing of URIs. Ex: URI 1, URI 2, URI 3, etc.", + "placeholders": { + "position": { + "content": "$1", + "example": "2" + } + } + }, + "newUri": { + "message": "เพิ่ม URI ใหม่" + }, + "username": { + "message": "ชื่อผู้ใช้" + }, + "password": { + "message": "รหัสผ่าน" + }, + "newPassword": { + "message": "รหัสผ่านใหม่" + }, + "passphrase": { + "message": "ข้อความรหัสผ่าน" + }, + "notes": { + "message": "หมายเหตุ" + }, + "customFields": { + "message": "ฟิลด์ที่กำหนดเอง" + }, + "cardholderName": { + "message": "ชื่อผู้ถือบัตร" + }, + "number": { + "message": "หมายเลข" + }, + "brand": { + "message": "ยี่ห้อ" + }, + "expiration": { + "message": "วันหมดอายุ" + }, + "securityCode": { + "message": "รหัสความปลอดภัย (CVV)" + }, + "identityName": { + "message": "Identity name" + }, + "company": { + "message": "บริษัท" + }, + "ssn": { + "message": "หมายเลขประกันสังคม" + }, + "passportNumber": { + "message": "หมายเลขหนังสือเดินทาง" + }, + "licenseNumber": { + "message": "เลขที่ใบอนุญาต" + }, + "email": { + "message": "อีเมล" + }, + "phone": { + "message": "โทรศัพท์" + }, + "january": { + "message": "มกราคม" + }, + "february": { + "message": "กุมภาพันธ์" + }, + "march": { + "message": "มีนาคม" + }, + "april": { + "message": "เมษายน" + }, + "may": { + "message": "พฤษภาคม" + }, + "june": { + "message": "มิถุนายน" + }, + "july": { + "message": "กรกฎาคม" + }, + "august": { + "message": "สิงหาคม" + }, + "september": { + "message": "กันยายน" + }, + "october": { + "message": "ตุลาคม" + }, + "november": { + "message": "พฤศจิกายน" + }, + "december": { + "message": "ธันวาคม" + }, + "title": { + "message": "คำนำหน้า" + }, + "mr": { + "message": "นาย" + }, + "mrs": { + "message": "นาง" + }, + "ms": { + "message": "นางสาว" + }, + "dr": { + "message": "ดร." + }, + "expirationMonth": { + "message": "เดือนที่หมดอายุ" + }, + "expirationYear": { + "message": "ปีที่หมดอายุ" + }, + "authenticatorKeyTotp": { + "message": "คีย์ Authenticator (TOTP)" + }, + "folder": { + "message": "โฟลเดอร์" + }, + "newCustomField": { + "message": "ฟิลด์ที่กำหนดเองใหม่" + }, + "value": { + "message": "ค่า" + }, + "dragToSort": { + "message": "ลากเพื่อเรียงลำดับ" + }, + "cfTypeText": { + "message": "ข้อความ" + }, + "cfTypeHidden": { + "message": "ซ่อน" + }, + "cfTypeBoolean": { + "message": "ค่าบูลีน" + }, + "cfTypeLinked": { + "message": "Linked", + "description": "This describes a field that is 'linked' (related) to another field." + }, + "remove": { + "message": "ลบ" + }, + "unassigned": { + "message": "ยกเลิกการกำหนดแล้ว" + }, + "noneFolder": { + "message": "ไม่มีโฟลเดอร์", + "description": "This is the folder for uncategorized items" + }, + "addFolder": { + "message": "เพิ่มโฟลเดอร์" + }, + "editFolder": { + "message": "แก้​ไข​โฟลเดอร์" + }, + "baseDomain": { + "message": "โดเมนพื้นฐาน", + "description": "Domain name. Example: website.com" + }, + "domainName": { + "message": "ชื่อโดเมน", + "description": "Domain name. Example: website.com" + }, + "host": { + "message": "โฮสต์", + "description": "A URL's host value. For example, the host of https://sub.domain.com:443 is 'sub.domain.com:443'." + }, + "exact": { + "message": "ถูกต้อง" + }, + "startsWith": { + "message": "เริ่มต้นด้วย" + }, + "regEx": { + "message": "นิพจน์ทั่วไป", + "description": "A programming term, also known as 'RegEx'." + }, + "matchDetection": { + "message": "ตรวจพบการจับคู่", + "description": "URI match detection for auto-fill." + }, + "defaultMatchDetection": { + "message": "การตรวจจับการจับคู่เริ่มต้น", + "description": "Default URI match detection for auto-fill." + }, + "never": { + "message": "ไม่เคย" + }, + "toggleVisibility": { + "message": "Toggle visibility" + }, + "toggleCollapse": { + "message": "Toggle collapse", + "description": "Toggling an expand/collapse state." + }, + "generatePassword": { + "message": "สร้างรหัสผ่าน" + }, + "checkPassword": { + "message": "ตรวจสอบว่ารหัสผ่านถูกเปิดเผยหรือไม่" + }, + "passwordExposed": { + "message": "รหัสผ่านนี้ถูกเปิดเผย {0} ครั้ง คุณควรเปลี่ยนรหัสผ่าน", + "placeholders": { + "value": { + "content": "$1", + "example": "2" + } + } + }, + "passwordSafe": { + "message": "ไม่พบรหัสผ่านนี้ในการละเมิดข้อมูลที่มี ควรใช้อย่างปลอดภัย" + }, + "save": { + "message": "บันทึก" + }, + "cancel": { + "message": "ยกเลิก" + }, + "canceled": { + "message": "ยกเลิกแล้ว" + }, + "close": { + "message": "ปิด" + }, + "delete": { + "message": "ลบ" + }, + "favorite": { + "message": "รายการโปรด" + }, + "unfavorite": { + "message": "ลบออกจากรายการโปรด" + }, + "edit": { + "message": "แก้ไข" + }, + "searchCollection": { + "message": "Search collection" + }, + "searchFolder": { + "message": "ค้นหา โฟลเดอร์" + }, + "searchFavorites": { + "message": "ค้นหารายการโปรด" + }, + "searchType": { + "message": "ประเภทการค้นหา", + "description": "Search item type" + }, + "searchVault": { + "message": "ค้นหาในตู้นิรภัย" + }, + "allItems": { + "message": "ไอเทมทั้งหมด" + }, + "favorites": { + "message": "รายการโปรด" + }, + "types": { + "message": "ชนิด" + }, + "typeLogin": { + "message": "เข้าสู่ระบบ" + }, + "typeCard": { + "message": "บัตร" + }, + "typeIdentity": { + "message": "ข้อมูลระบุตัวตน" + }, + "typeSecureNote": { + "message": "Secure note" + }, + "typeLoginPlural": { + "message": "เข้าสู่ระบบ" + }, + "typeCardPlural": { + "message": "บัตร" + }, + "typeIdentityPlural": { + "message": "ข้อมูลระบุตัวตน" + }, + "typeSecureNotePlural": { + "message": "Secure notes" + }, + "folders": { + "message": "โฟลเดอร์" + }, + "collections": { + "message": "คอลเลกชัน" + }, + "firstName": { + "message": "ชื่อจริง" + }, + "middleName": { + "message": "ชือกลาง" + }, + "lastName": { + "message": "นามสกุล" + }, + "fullName": { + "message": "ชื่อเต็ม" + }, + "address1": { + "message": "ที่อยู่ 1" + }, + "address2": { + "message": "ที่อยู่ 2" + }, + "address3": { + "message": "ที่อยู่ 3" + }, + "cityTown": { + "message": "เมือง" + }, + "stateProvince": { + "message": "รัฐ / จังหวัด" + }, + "zipPostalCode": { + "message": "รหัสไปรษณีย์" + }, + "country": { + "message": "ประเทศ" + }, + "shared": { + "message": "แชร์แล้ว" + }, + "attachments": { + "message": "ไฟล์แนบ" + }, + "select": { + "message": "เลือก" + }, + "addItem": { + "message": "เพิ่มรายการ" + }, + "editItem": { + "message": "แก้ไขรายการ" + }, + "viewItem": { + "message": "ดูรายการ" + }, + "ex": { + "message": "ex.", + "description": "Short abbreviation for 'example'." + }, + "other": { + "message": "อื่น ๆ" + }, + "share": { + "message": "แชร์" + }, + "moveToOrganization": { + "message": "ย้ายไปที่องค์กร" + }, + "valueCopied": { + "message": "$VALUE$ คัดลอกแล้ว", + "description": "Value has been copied to the clipboard.", + "placeholders": { + "value": { + "content": "$1", + "example": "Password" + } + } + }, + "copyValue": { + "message": "คัดลอกค่า", + "description": "Copy value to clipboard" + }, + "copyPassword": { + "message": "คัดลอกรหัสผ่าน", + "description": "Copy password to clipboard" + }, + "copyUsername": { + "message": "คัดลอกชื่อผู้ใช้", + "description": "Copy username to clipboard" + }, + "copyNumber": { + "message": "คัดลอกหมายเลข", + "description": "Copy credit card number" + }, + "copySecurityCode": { + "message": "คัดลอกรหัสรักษาความปลอดภัย", + "description": "Copy credit card security code (CVV)" + }, + "copyUri": { + "message": "คัดลอก URI", + "description": "Copy URI to clipboard" + }, + "me": { + "message": "ฉัน" + }, + "myVault": { + "message": "ตู้เซฟของฉัน" + }, + "allVaults": { + "message": "ตู้นิรภัยทั้งหมด" + }, + "vault": { + "message": "ตู้นิรภัย" + }, + "vaults": { + "message": "ตู้นิรภัย" + }, + "vaultItems": { + "message": "Vault items" + }, + "moveSelectedToOrg": { + "message": "Move selected to organization" + }, + "deleteSelected": { + "message": "ลบรายการที่เลือก" + }, + "moveSelected": { + "message": "ย้ายรายการที่เลือก" + }, + "selectAll": { + "message": "เลือกทั้งหมด" + }, + "unselectAll": { + "message": "ยกเลิกการเลือกทั้งหมด" + }, + "launch": { + "message": "เริ่ม" + }, + "newAttachment": { + "message": "เพิ่มไฟล์แนบใหม่" + }, + "deletedAttachment": { + "message": "ลบไฟล์แนบแล้ว" + }, + "deleteAttachmentConfirmation": { + "message": "คุณแน่ใจใช่ไหมว่าต้องการลบไฟล์แนบนี้" + }, + "attachmentSaved": { + "message": "Attachment saved" + }, + "file": { + "message": "ไฟล์" + }, + "selectFile": { + "message": "เลือกไฟล์" + }, + "maxFileSize": { + "message": "ขนาดไฟล์สูงสุด คือ 500 MB" + }, + "updateKey": { + "message": "คุณไม่สามารถใช้คุณลักษณะนี้ได้จนกว่าคุณจะปรับปรุงคีย์การเข้ารหัสลับของคุณ" + }, + "addedItem": { + "message": "Item added" + }, + "editedItem": { + "message": "Item saved" + }, + "movedItemToOrg": { + "message": "$ITEMNAME$ moved to $ORGNAME$", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + }, + "orgname": { + "content": "$2", + "example": "Company Name" + } + } + }, + "movedItemsToOrg": { + "message": "Selected items moved to $ORGNAME$", + "placeholders": { + "orgname": { + "content": "$1", + "example": "Company Name" + } + } + }, + "deleteItem": { + "message": "Delete item" + }, + "deleteFolder": { + "message": "Delete folder" + }, + "deleteAttachment": { + "message": "Delete attachment" + }, + "deleteItemConfirmation": { + "message": "Do you really want to send to the trash?" + }, + "deletedItem": { + "message": "Item sent to trash" + }, + "deletedItems": { + "message": "Items sent to trash" + }, + "movedItems": { + "message": "Items moved" + }, + "overwritePasswordConfirmation": { + "message": "Are you sure you want to overwrite the current password?" + }, + "editedFolder": { + "message": "Folder saved" + }, + "addedFolder": { + "message": "Folder added" + }, + "deleteFolderConfirmation": { + "message": "Are you sure you want to delete this folder?" + }, + "deletedFolder": { + "message": "Folder deleted" + }, + "loggedOut": { + "message": "Logged out" + }, + "loginExpired": { + "message": "Your login session has expired." + }, + "logOutConfirmation": { + "message": "Are you sure you want to log out?" + }, + "logOut": { + "message": "Log out" + }, + "ok": { + "message": "Ok" + }, + "yes": { + "message": "Yes" + }, + "no": { + "message": "No" + }, + "loginOrCreateNewAccount": { + "message": "Log in or create a new account to access your secure vault." + }, + "loginWithDevice": { + "message": "Log in with device" + }, + "loginWithDeviceEnabledInfo": { + "message": "Log in with device must be set up in the settings of the Bitwarden mobile app. Need another option?" + }, + "loginWithMasterPassword": { + "message": "Log in with master password" + }, + "createAccount": { + "message": "Create account" + }, + "newAroundHere": { + "message": "New around here?" + }, + "startTrial": { + "message": "Start trial" + }, + "logIn": { + "message": "Log in" + }, + "logInInitiated": { + "message": "Log in initiated" + }, + "submit": { + "message": "Submit" + }, + "emailAddressDesc": { + "message": "You'll use your email address to log in." + }, + "yourName": { + "message": "Your name" + }, + "yourNameDesc": { + "message": "What should we call you?" + }, + "masterPass": { + "message": "Master password" + }, + "masterPassDesc": { + "message": "รหัสผ่านหลัก คือ รหัสผ่านที่ใช้เข้าถึงตู้นิรภัยของคุณ สิ่งสำคัญมาก คือ คุณจะต้องไม่ลืมรหัสผ่านหลักโดยเด็ดขาด เพราะหากคุณลืมแล้วล่ะก็ จะไม่มีวิธีที่สามารถกู้รหัสผ่านของคุณได้เลย" + }, + "masterPassImportant": { + "message": "Master passwords cannot be recovered if you forget it!" + }, + "masterPassHintDesc": { + "message": "คำใบ้เกี่ยวกับรหัสผ่านหลักสามารถช่วยให้คุณนึกรหัสผ่านหลักออกได้หากลืม" + }, + "reTypeMasterPass": { + "message": "Re-type master password" + }, + "masterPassHint": { + "message": "คำใบ้รหัสผ่านหลัก (ไม่จำเป็น)" + }, + "masterPassHintLabel": { + "message": "รับคำใบ้เกี่ยวกับรหัสผ่านหลักของคุณ" + }, + "settings": { + "message": "การตั้งค่า" + }, + "passwordHint": { + "message": "คำใบ้รหัสผ่าน" + }, + "enterEmailToGetHint": { + "message": "กรอกอีเมลของบัญชีของคุณ เพื่อรับคำใบ้เกี่ยวกับรหัสผ่านหลักของคุณ" + }, + "getMasterPasswordHint": { + "message": "รับคำใบ้เกี่ยวกับรหัสผ่านหลักของคุณ" + }, + "emailRequired": { + "message": "จำเป็นต้องกรอกที่อยู่อีเมล" + }, + "invalidEmail": { + "message": "ที่อยู่อีเมลไม่ถูกต้อง" + }, + "masterPasswordRequired": { + "message": "ต้องระบุรหัสผ่านหลัก" + }, + "confirmMasterPasswordRequired": { + "message": "ต้องพิมพ์รหัสผ่านหลักอีกครั้ง" + }, + "masterPasswordMinlength": { + "message": "รหัสผ่านหลักต้องมีความยาวอย่างน้อย 8 ตัวอักษร" + }, + "masterPassDoesntMatch": { + "message": "การยืนยันรหัสผ่านหลักไม่ตรงกัน" + }, + "newAccountCreated": { + "message": "บัญชีใหม่ของคุณถูกสร้างขึ้นแล้ว! ตอนนี้คุณสามารถเข้าสู่ระบบได้" + }, + "trialAccountCreated": { + "message": "สร้างบัญชีสำเร็จแล้ว" + }, + "masterPassSent": { + "message": "เราได้ส่งอีเมลที่ มีคำใบ้รหัสผ่านหลักของคุณแล้ว" + }, + "unexpectedError": { + "message": "ข้อผิดพลาดที่ไม่คาดคิดเกิดขึ้น" + }, + "emailAddress": { + "message": "ที่อยู่อีเมล์" + }, + "yourVaultIsLocked": { + "message": "ตู้เซฟของคุณถูกล็อค ใส่รหัสผ่านหลักของคุณเพื่อดำเนินการต่อ" + }, + "unlock": { + "message": "ปลดล็อค" + }, + "loggedInAsEmailOn": { + "message": "ล็อกอินด้วย $EMAIL$ บน $HOSTNAME$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "hostname": { + "content": "$2", + "example": "bitwarden.com" + } + } + }, + "invalidMasterPassword": { + "message": "รหัสผ่านหลักไม่ถูกต้อง" + }, + "invalidFilePassword": { + "message": "Invalid file password, please use the password you entered when you created the export file." + }, + "lockNow": { + "message": "Lock now" + }, + "noItemsInList": { + "message": "ไม่มีรายการการสำหรับแสดง" + }, + "noCollectionsInList": { + "message": "ไม่มีคอลเลกชันที่จะแสดง" + }, + "noGroupsInList": { + "message": "ไม่มีกลุ่มสำหรับแสดง" + }, + "noUsersInList": { + "message": "ไม่มีผู้ใช้สำหรับแสดง" + }, + "noEventsInList": { + "message": "ไม่มีเหตุการณ์สำหรับแสดง" + }, + "newOrganization": { + "message": "เพิ่มองค์กรใหม่" + }, + "noOrganizationsList": { + "message": "You do not belong to any organizations. Organizations allow you to securely share items with other users." + }, + "notificationSentDevice": { + "message": "A notification has been sent to your device." + }, + "versionNumber": { + "message": "รุ่น $VERSION_NUM$", + "placeholders": { + "version_number": { + "content": "$1", + "example": "1.2.3" + } + } + }, + "enterVerificationCodeApp": { + "message": "ป้อนรหัสยืนยัน 6 หลักจากคุณแอป authenticator" + }, + "enterVerificationCodeEmail": { + "message": "ป้อนรหัสยืนยัน 6 หลักที่ส่งทางอีเมล $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "example@gmail.com" + } + } + }, + "verificationCodeEmailSent": { + "message": "ส่งรหัสยืนยันไปยังอีเมล $EMAIL$ แล้ว", + "placeholders": { + "email": { + "content": "$1", + "example": "example@gmail.com" + } + } + }, + "rememberMe": { + "message": "จำการเข้าระบบของฉันไว้" + }, + "sendVerificationCodeEmailAgain": { + "message": "ส่งรหัสยืนยันไปยังอีเมลอีกครั้ง" + }, + "useAnotherTwoStepMethod": { + "message": "ใช้วิธีลงชื่อเข้าใช้แบบสองขั้นตอนวิธีอื่น" + }, + "insertYubiKey": { + "message": "Insert your YubiKey into your computer's USB port, then touch its button." + }, + "insertU2f": { + "message": "Insert your security key into your computer's USB port. If it has a button, touch it." + }, + "loginUnavailable": { + "message": "Login unavailable" + }, + "noTwoStepProviders": { + "message": "This account has two-step login set up, however, none of the configured two-step providers are supported by this web browser." + }, + "noTwoStepProviders2": { + "message": "Please use a supported web browser (such as Chrome) and/or add additional providers that are better supported across web browsers (such as an authenticator app)." + }, + "twoStepOptions": { + "message": "ตัวเลือกการเข้าสู่ระบบแบบสองขั้นตอน" + }, + "recoveryCodeDesc": { + "message": "Lost access to all of your two-step login providers? Use your recovery code to turn off all two-step login providers from your account." + }, + "recoveryCodeTitle": { + "message": "รหัสกู้คืน" + }, + "authenticatorAppTitle": { + "message": "Authenticator App" + }, + "authenticatorAppDesc": { + "message": "Use an authenticator app (such as Authy or Google Authenticator) to generate time-based verification codes.", + "description": "'Authy' and 'Google Authenticator' are product names and should not be translated." + }, + "yubiKeyTitle": { + "message": "YubiKey OTP security key" + }, + "yubiKeyDesc": { + "message": "Use a YubiKey to access your account. Works with YubiKey 4 series, 5 series, and NEO devices." + }, + "duoDesc": { + "message": "Verify with Duo Security using the Duo Mobile app, SMS, phone call, or U2F security key.", + "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." + }, + "duoOrganizationDesc": { + "message": "Verify with Duo Security for your organization using the Duo Mobile app, SMS, phone call, or U2F security key.", + "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." + }, + "u2fDesc": { + "message": "Use any FIDO U2F compatible security key to access your account." + }, + "u2fTitle": { + "message": "FIDO U2F security key" + }, + "webAuthnTitle": { + "message": "FIDO2 WebAuthn" + }, + "webAuthnDesc": { + "message": "Use any WebAuthn compatible security key to access your account." + }, + "webAuthnMigrated": { + "message": "(Migrated from FIDO)" + }, + "emailTitle": { + "message": "อีเมล" + }, + "emailDesc": { + "message": "Verification codes will be emailed to you." + }, + "continue": { + "message": "ดำเนินการต่อ" + }, + "organization": { + "message": "องค์กร" + }, + "organizations": { + "message": "องค์กร" + }, + "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." + }, + "deleteSelectedItemsDesc": { + "message": "You have selected $COUNT$ item(s) to delete. Are you sure you want to delete all of these items?", + "placeholders": { + "count": { + "content": "$1", + "example": "150" + } + } + }, + "moveSelectedItemsDesc": { + "message": "Choose a folder that you would like to move the $COUNT$ selected item(s) to.", + "placeholders": { + "count": { + "content": "$1", + "example": "150" + } + } + }, + "moveSelectedItemsCountDesc": { + "message": "You have selected $COUNT$ item(s). $MOVEABLE_COUNT$ item(s) can be moved to an organization, $NONMOVEABLE_COUNT$ cannot.", + "placeholders": { + "count": { + "content": "$1", + "example": "10" + }, + "moveable_count": { + "content": "$2", + "example": "8" + }, + "nonmoveable_count": { + "content": "$3", + "example": "2" + } + } + }, + "verificationCodeTotp": { + "message": "Verification code (TOTP)" + }, + "copyVerificationCode": { + "message": "Copy verification code" + }, + "warning": { + "message": "Warning" + }, + "confirmVaultExport": { + "message": "Confirm vault export" + }, + "exportWarningDesc": { + "message": "This export contains your vault data in an unencrypted format. You should not store or send the exported file over unsecure channels (such as email). Delete it immediately after you are done using it." + }, + "encExportKeyWarningDesc": { + "message": "This export encrypts your data using your account's encryption key. If you ever rotate your account's encryption key you should export again since you will not be able to decrypt this export file." + }, + "encExportAccountWarningDesc": { + "message": "Account encryption keys are unique to each Bitwarden user account, so you can't import an encrypted export into a different account." + }, + "export": { + "message": "Export" + }, + "exportVault": { + "message": "Export vault" + }, + "fileFormat": { + "message": "File format" + }, + "fileEncryptedExportWarningDesc": { + "message": "This file export will be password protected and require the file password to decrypt." + }, + "exportPasswordDescription": { + "message": "This password will be used to export and import this file" + }, + "confirmMasterPassword": { + "message": "Confirm master password" + }, + "confirmFormat": { + "message": "Confirm format" + }, + "filePassword": { + "message": "File password" + }, + "confirmFilePassword": { + "message": "Confirm file password" + }, + "accountBackupOptionDescription": { + "message": "Use your account encryption key to encrypt the export and restrict import to only the current Bitwarden account." + }, + "passwordProtectedOptionDescription": { + "message": "Set a password to encrypt the export and import it to any Bitwarden account using the password for decryption." + }, + "fileTypeHeading": { + "message": "File type" + }, + "accountBackup": { + "message": "Account backup" + }, + "passwordProtected": { + "message": "Password protected" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "“File password” and “Confirm file password“ do not match." + }, + "confirmVaultImport": { + "message": "Confirm vault import" + }, + "confirmVaultImportDesc": { + "message": "This file is password-protected. Please enter the file password to import data." + }, + "exportSuccess": { + "message": "Vault data exported" + }, + "passwordGenerator": { + "message": "Password generator" + }, + "minComplexityScore": { + "message": "Minimum complexity score" + }, + "minNumbers": { + "message": "Minimum numbers" + }, + "minSpecial": { + "message": "Minimum special", + "description": "Minimum special characters" + }, + "ambiguous": { + "message": "Avoid ambiguous characters" + }, + "regeneratePassword": { + "message": "Regenerate password" + }, + "length": { + "message": "Length" + }, + "uppercase": { + "message": "Uppercase (A-Z)", + "description": "Include uppercase letters in the password generator." + }, + "lowercase": { + "message": "Lowercase (a-z)", + "description": "Include lowercase letters in the password generator." + }, + "numbers": { + "message": "Numbers (0-9)" + }, + "specialCharacters": { + "message": "Special characters (!@#$%^&*)" + }, + "numWords": { + "message": "Number of words" + }, + "wordSeparator": { + "message": "Word separator" + }, + "capitalize": { + "message": "Capitalize", + "description": "Make the first letter of a word uppercase." + }, + "includeNumber": { + "message": "Include number" + }, + "passwordHistory": { + "message": "Password history" + }, + "noPasswordsInList": { + "message": "There are no passwords to list." + }, + "clear": { + "message": "Clear", + "description": "To clear something out. Example: To clear browser history." + }, + "accountUpdated": { + "message": "Account saved" + }, + "changeEmail": { + "message": "Change email" + }, + "changeEmailTwoFactorWarning": { + "message": "Proceeding will change your account email address. It will not change the email address used for two-step login authentication. You can change this email address in the two-step login settings." + }, + "newEmail": { + "message": "New email" + }, + "code": { + "message": "Code" + }, + "changeEmailDesc": { + "message": "We have emailed a verification code to $EMAIL$. Please check your email for this code and enter it below to finalize the email address change.", + "placeholders": { + "email": { + "content": "$1", + "example": "john.smith@example.com" + } + } + }, + "loggedOutWarning": { + "message": "Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." + }, + "emailChanged": { + "message": "Email saved" + }, + "logBackIn": { + "message": "Please log back in." + }, + "logBackInOthersToo": { + "message": "Please log back in. If you are using other Bitwarden applications log out and back in to those as well." + }, + "changeMasterPassword": { + "message": "Change master password" + }, + "masterPasswordChanged": { + "message": "Master password saved" + }, + "currentMasterPass": { + "message": "Current master password" + }, + "newMasterPass": { + "message": "New master password" + }, + "confirmNewMasterPass": { + "message": "Confirm new master password" + }, + "encKeySettings": { + "message": "Encryption key settings" + }, + "kdfAlgorithm": { + "message": "KDF algorithm" + }, + "kdfIterations": { + "message": "KDF iterations" + }, + "kdfIterationsDesc": { + "message": "Higher KDF iterations can help protect your master password from being brute forced by an attacker. We recommend a value of $VALUE$ or more.", + "placeholders": { + "value": { + "content": "$1", + "example": "100,000" + } + } + }, + "kdfIterationsWarning": { + "message": "Setting your KDF iterations too high could result in poor performance when logging into (and unlocking) Bitwarden on devices with slower CPUs. We recommend that you increase the value in increments of $INCREMENT$ and then test all of your devices.", + "placeholders": { + "increment": { + "content": "$1", + "example": "50,000" + } + } + }, + "changeKdf": { + "message": "Change KDF" + }, + "encKeySettingsChanged": { + "message": "Encryption key settings saved" + }, + "dangerZone": { + "message": "Danger zone" + }, + "dangerZoneDesc": { + "message": "Careful, these actions are not reversible!" + }, + "deauthorizeSessions": { + "message": "Deauthorize sessions" + }, + "deauthorizeSessionsDesc": { + "message": "Concerned your account is logged in on another device? Proceed below to deauthorize all computers or devices that you have previously used. This security step is recommended if you previously used a public computer or accidentally saved your password on a device that isn't yours. This step will also clear all previously remembered two-step login sessions." + }, + "deauthorizeSessionsWarning": { + "message": "Proceeding will also log you out of your current session, requiring you to log back in. You will also be prompted for two-step login again, if set up. Active sessions on other devices may continue to remain active for up to one hour." + }, + "sessionsDeauthorized": { + "message": "All sessions deauthorized" + }, + "purgeVault": { + "message": "Purge vault" + }, + "purgedOrganizationVault": { + "message": "Purged organization vault." + }, + "vaultAccessedByProvider": { + "message": "Vault accessed by Provider." + }, + "purgeVaultDesc": { + "message": "Proceed below to delete all items and folders in your vault. Items that belong to an organization that you share with will not be deleted." + }, + "purgeOrgVaultDesc": { + "message": "Proceed below to delete all items in the organization's vault." + }, + "purgeVaultWarning": { + "message": "Purging your vault is permanent. It cannot be undone." + }, + "vaultPurged": { + "message": "Vault purged." + }, + "deleteAccount": { + "message": "Delete account" + }, + "deleteAccountDesc": { + "message": "Proceed below to delete your account and all vault data." + }, + "deleteAccountWarning": { + "message": "Deleting your account is permanent. It cannot be undone." + }, + "accountDeleted": { + "message": "Account deleted" + }, + "accountDeletedDesc": { + "message": "Your account has been closed and all associated data has been deleted." + }, + "myAccount": { + "message": "My account" + }, + "tools": { + "message": "Tools" + }, + "importData": { + "message": "Import data" + }, + "importError": { + "message": "Import error" + }, + "importErrorDesc": { + "message": "There was a problem with the data you tried to import. Please resolve the errors listed below in your source file and try again." + }, + "importSuccess": { + "message": "Data successfully imported" + }, + "importWarning": { + "message": "You are importing data to $ORGANIZATION$. Your data may be shared with members of this organization. Do you want to proceed?", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "importFormatError": { + "message": "Data is not formatted correctly. Please check your import file and try again." + }, + "importNothingError": { + "message": "Nothing was imported." + }, + "importEncKeyError": { + "message": "Error decrypting the exported file. Your encryption key does not match the encryption key used export the data." + }, + "selectFormat": { + "message": "Select the format of the import file" + }, + "selectImportFile": { + "message": "Select the import file" + }, + "chooseFile": { + "message": "Choose File" + }, + "noFileChosen": { + "message": "No file chosen" + }, + "orCopyPasteFileContents": { + "message": "or copy/paste the import file contents" + }, + "instructionsFor": { + "message": "$NAME$ Instructions", + "description": "The title for the import tool instructions.", + "placeholders": { + "name": { + "content": "$1", + "example": "LastPass (csv)" + } + } + }, + "options": { + "message": "Options" + }, + "preferences": { + "message": "Preferences" + }, + "preferencesDesc": { + "message": "Customize your web vault experience." + }, + "preferencesUpdated": { + "message": "Preferences saved" + }, + "language": { + "message": "Language" + }, + "languageDesc": { + "message": "Change the language used by the web vault." + }, + "enableFavicon": { + "message": "Show website icons" + }, + "faviconDesc": { + "message": "Show a recognizable image next to each login." + }, + "enableFullWidth": { + "message": "Display full width layout", + "description": "Allows scaling the web vault UI's width" + }, + "enableFullWidthDesc": { + "message": "Allow the web vault to expand the full width of the browser window." + }, + "default": { + "message": "Default" + }, + "domainRules": { + "message": "Domain rules" + }, + "domainRulesDesc": { + "message": "If you have the same login across multiple different website domains, you can mark the website as \"equivalent\". \"Global\" domains are ones already created for you by Bitwarden." + }, + "globalEqDomains": { + "message": "Global equivalent domains" + }, + "customEqDomains": { + "message": "Custom equivalent domains" + }, + "exclude": { + "message": "Exclude" + }, + "include": { + "message": "Include" + }, + "customize": { + "message": "Customize" + }, + "newCustomDomain": { + "message": "New custom domain" + }, + "newCustomDomainDesc": { + "message": "Enter a list of domains separated by commas. Only \"base\" domains are allowed. Do not enter subdomains. For example, enter \"google.com\" instead of \"www.google.com\". You can also enter \"androidapp://package.name\" to associate an android app with other website domains." + }, + "customDomainX": { + "message": "Custom domain $INDEX$", + "placeholders": { + "index": { + "content": "$1", + "example": "2" + } + } + }, + "domainsUpdated": { + "message": "Domains saved" + }, + "twoStepLogin": { + "message": "Two-step login" + }, + "twoStepLoginEnforcement": { + "message": "Two-step Login Enforcement" + }, + "twoStepLoginDesc": { + "message": "Secure your account by requiring an additional step when logging in." + }, + "twoStepLoginOrganizationDescStart": { + "message": "Enforce Bitwarden Two-step Login options for members by using the ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Enforce Bitwarden Two-step Login options for members by using the Two-step Login Policy.'" + }, + "twoStepLoginPolicy": { + "message": "Two-step Login Policy" + }, + "twoStepLoginOrganizationDuoDesc": { + "message": "To enforce Two-step Login through Duo, use the options below." + }, + "twoStepLoginOrganizationSsoDesc": { + "message": "If you have setup SSO or plan to, Two-step Login may already be enforced through your Identity Provider." + }, + "twoStepLoginRecoveryWarning": { + "message": "Setting up two-step login can permanently lock you out of your Bitwarden account. A recovery code allows you to access your account in the event that you can no longer use your normal two-step login provider (example: you lose your device). Bitwarden support will not be able to assist you if you lose access to your account. We recommend you write down or print the recovery code and keep it in a safe place." + }, + "viewRecoveryCode": { + "message": "View recovery code" + }, + "providers": { + "message": "Providers", + "description": "Two-step login providers such as YubiKey, Duo, Authenticator apps, Email, etc." + }, + "enable": { + "message": "Turn on" + }, + "enabled": { + "message": "Turned on" + }, + "restoreAccess": { + "message": "Restore access" + }, + "premium": { + "message": "Premium", + "description": "Premium membership" + }, + "premiumMembership": { + "message": "Premium membership" + }, + "premiumRequired": { + "message": "Premium required" + }, + "premiumRequiredDesc": { + "message": "A Premium membership is required to use this feature." + }, + "youHavePremiumAccess": { + "message": "You have Premium access" + }, + "alreadyPremiumFromOrg": { + "message": "You already have access to Premium features because of an organization you are a member of." + }, + "manage": { + "message": "Manage" + }, + "disable": { + "message": "Turn off" + }, + "revokeAccess": { + "message": "Revoke access" + }, + "twoStepLoginProviderEnabled": { + "message": "This two-step login provider is active on your account." + }, + "twoStepLoginAuthDesc": { + "message": "Enter your master password to modify two-step login settings." + }, + "twoStepAuthenticatorDesc": { + "message": "Follow these steps to set up two-step login with an authenticator app:" + }, + "twoStepAuthenticatorDownloadApp": { + "message": "Download a two-step authenticator app" + }, + "twoStepAuthenticatorNeedApp": { + "message": "Need a two-step authenticator app? Download one of the following" + }, + "iosDevices": { + "message": "iOS devices" + }, + "androidDevices": { + "message": "Android devices" + }, + "windowsDevices": { + "message": "Windows devices" + }, + "twoStepAuthenticatorAppsRecommended": { + "message": "These apps are recommended, however, other authenticator apps will also work." + }, + "twoStepAuthenticatorScanCode": { + "message": "Scan this QR code with your authenticator app" + }, + "key": { + "message": "Key" + }, + "twoStepAuthenticatorEnterCode": { + "message": "Enter the resulting 6 digit verification code from the app" + }, + "twoStepAuthenticatorReaddDesc": { + "message": "In case you need to add it to another device, below is the QR code (or key) required by your authenticator app." + }, + "twoStepDisableDesc": { + "message": "Are you sure you want to turn off this two-step login provider?" + }, + "twoStepDisabled": { + "message": "Two-step login provider turned off." + }, + "twoFactorYubikeyAdd": { + "message": "Add a new YubiKey to your account" + }, + "twoFactorYubikeyPlugIn": { + "message": "Plug the YubiKey into your computer's USB port." + }, + "twoFactorYubikeySelectKey": { + "message": "Select the first empty YubiKey input field below." + }, + "twoFactorYubikeyTouchButton": { + "message": "Touch the YubiKey's button." + }, + "twoFactorYubikeySaveForm": { + "message": "Save the form." + }, + "twoFactorYubikeyWarning": { + "message": "Due to platform limitations, YubiKeys cannot be used on all Bitwarden applications. You should set up another two-step login provider so that you can access your account when YubiKeys cannot be used. Supported platforms:" + }, + "twoFactorYubikeySupportUsb": { + "message": "Web vault, desktop application, CLI, and all browser extensions on a device with a USB port that can accept your YubiKey." + }, + "twoFactorYubikeySupportMobile": { + "message": "Mobile apps on a device with NFC capabilities or a data port that can accept your YubiKey." + }, + "yubikeyX": { + "message": "YubiKey $INDEX$", + "placeholders": { + "index": { + "content": "$1", + "example": "2" + } + } + }, + "u2fkeyX": { + "message": "U2F Key $INDEX$", + "placeholders": { + "index": { + "content": "$1", + "example": "2" + } + } + }, + "webAuthnkeyX": { + "message": "WebAuthn Key $INDEX$", + "placeholders": { + "index": { + "content": "$1", + "example": "2" + } + } + }, + "nfcSupport": { + "message": "NFC Support" + }, + "twoFactorYubikeySupportsNfc": { + "message": "One of my keys supports NFC." + }, + "twoFactorYubikeySupportsNfcDesc": { + "message": "If one of your YubiKeys supports NFC (such as a YubiKey NEO), you will be prompted on mobile devices whenever NFC availability is detected." + }, + "yubikeysUpdated": { + "message": "YubiKeys updated" + }, + "disableAllKeys": { + "message": "Deactivate all keys" + }, + "twoFactorDuoDesc": { + "message": "Enter the Bitwarden application information from your Duo Admin panel." + }, + "twoFactorDuoIntegrationKey": { + "message": "Integration key" + }, + "twoFactorDuoSecretKey": { + "message": "Secret key" + }, + "twoFactorDuoApiHostname": { + "message": "API hostname" + }, + "twoFactorEmailDesc": { + "message": "Follow these steps to set up two-step login with email:" + }, + "twoFactorEmailEnterEmail": { + "message": "Enter the email that you wish to receive verification codes" + }, + "twoFactorEmailEnterCode": { + "message": "Enter the resulting 6 digit verification code from the email" + }, + "sendEmail": { + "message": "Send email" + }, + "twoFactorU2fAdd": { + "message": "Add a FIDO U2F security key to your account" + }, + "removeU2fConfirmation": { + "message": "Are you sure you want to remove this security key?" + }, + "twoFactorWebAuthnAdd": { + "message": "Add a WebAuthn security key to your account" + }, + "readKey": { + "message": "Read key" + }, + "keyCompromised": { + "message": "Key is compromised." + }, + "twoFactorU2fGiveName": { + "message": "Give the security key a friendly name to identify it." + }, + "twoFactorU2fPlugInReadKey": { + "message": "Plug the security key into your computer's USB port and click the \"Read Key\" button." + }, + "twoFactorU2fTouchButton": { + "message": "If the security key has a button, touch it." + }, + "twoFactorU2fSaveForm": { + "message": "Save the form." + }, + "twoFactorU2fWarning": { + "message": "Due to platform limitations, FIDO U2F cannot be used on all Bitwarden applications. You should set up another two-step login provider so that you can access your account when FIDO U2F cannot be used. Supported platforms:" + }, + "twoFactorU2fSupportWeb": { + "message": "Web vault and browser extensions on a desktop/laptop with a U2F supported browser (Chrome, Opera, Vivaldi, or Firefox with FIDO U2F turned on)." + }, + "twoFactorU2fWaiting": { + "message": "Waiting for you to touch the button on your security key" + }, + "twoFactorU2fClickSave": { + "message": "Use the \"Save\" button below to activate this security key for two-step login." + }, + "twoFactorU2fProblemReadingTryAgain": { + "message": "There was a problem reading the security key. Try again." + }, + "twoFactorWebAuthnWarning": { + "message": "Due to platform limitations, WebAuthn cannot be used on all Bitwarden applications. You should set up another two-step login provider so that you can access your account when WebAuthn cannot be used. Supported platforms:" + }, + "twoFactorWebAuthnSupportWeb": { + "message": "Web vault and browser extensions on a desktop/laptop with a WebAuthn supported browser (Chrome, Opera, Vivaldi, or Firefox with FIDO U2F turned on)." + }, + "twoFactorRecoveryYourCode": { + "message": "Your Bitwarden two-step login recovery code" + }, + "twoFactorRecoveryNoCode": { + "message": "You have not set up any two-step login providers yet. After you have set up a two-step login provider you can check back here for your recovery code." + }, + "printCode": { + "message": "Print code", + "description": "Print 2FA recovery code" + }, + "reports": { + "message": "Reports" + }, + "reportsDesc": { + "message": "Identify and close security gaps in your online accounts by clicking the reports below.", + "description": "Vault health reports can be used to evaluate the security of your Bitwarden individual or organization vault." + }, + "orgsReportsDesc": { + "message": "Identify and close security gaps in your organization's accounts by clicking the reports below.", + "description": "Vault health reports can be used to evaluate the security of your Bitwarden individual or organization Vault." + }, + "unsecuredWebsitesReport": { + "message": "Unsecure websites" + }, + "unsecuredWebsitesReportDesc": { + "message": "URLs that start with http:// don’t use the best available encryption. Change the login URIs for these accounts to https:// for safer browsing." + }, + "unsecuredWebsitesFound": { + "message": "Unsecured websites found" + }, + "unsecuredWebsitesFoundDesc": { + "message": "We found $COUNT$ items in your vault with unsecured URIs. You should change their URI scheme to https:// if the website allows it.", + "placeholders": { + "count": { + "content": "$1", + "example": "8" + } + } + }, + "noUnsecuredWebsites": { + "message": "No items in your vault have unsecured URIs." + }, + "inactive2faReport": { + "message": "Inactive two-step login" + }, + "inactive2faReportDesc": { + "message": "Two-step login adds a layer of protection to your accounts. Set up two-step login using Bitwarden authenticator for these accounts or use an alternative method." + }, + "inactive2faFound": { + "message": "Logins without two-step login found" + }, + "inactive2faFoundDesc": { + "message": "We found $COUNT$ website(s) in your vault that may not be configured with two-step login (according to 2fa.directory). To further protect these accounts, you should set up two-step login.", + "placeholders": { + "count": { + "content": "$1", + "example": "8" + } + } + }, + "noInactive2fa": { + "message": "No websites were found in your vault with a missing two-step login configuration." + }, + "instructions": { + "message": "Instructions" + }, + "exposedPasswordsReport": { + "message": "Exposed passwords" + }, + "exposedPasswordsReportDesc": { + "message": "Passwords exposed in a data breach are easy targets for attackers. Change these passwords to prevent potential break-ins." + }, + "exposedPasswordsFound": { + "message": "Exposed passwords found" + }, + "exposedPasswordsFoundDesc": { + "message": "We found $COUNT$ items in your vault that have passwords that were exposed in known data breaches. You should change them to use a new password.", + "placeholders": { + "count": { + "content": "$1", + "example": "8" + } + } + }, + "noExposedPasswords": { + "message": "No items in your vault have passwords that have been exposed in known data breaches." + }, + "checkExposedPasswords": { + "message": "Check exposed passwords" + }, + "exposedXTimes": { + "message": "Exposed $COUNT$ time(s)", + "placeholders": { + "count": { + "content": "$1", + "example": "52" + } + } + }, + "weakPasswordsReport": { + "message": "Weak passwords" + }, + "weakPasswordsReportDesc": { + "message": "Weak passwords can be easily guessed by attackers. Change these passwords to strong ones using the password generator." + }, + "weakPasswordsFound": { + "message": "Weak passwords found" + }, + "weakPasswordsFoundDesc": { + "message": "We found $COUNT$ items in your vault with passwords that are not strong. You should update them to use stronger passwords.", + "placeholders": { + "count": { + "content": "$1", + "example": "8" + } + } + }, + "noWeakPasswords": { + "message": "No items in your vault have weak passwords." + }, + "reusedPasswordsReport": { + "message": "Reused passwords" + }, + "reusedPasswordsReportDesc": { + "message": "Reusing passwords makes it easier for attackers to break into multiple accounts. Change these passwords so that each is unique." + }, + "reusedPasswordsFound": { + "message": "Reused passwords found" + }, + "reusedPasswordsFoundDesc": { + "message": "We found $COUNT$ passwords that are being reused in your vault. You should change them to a unique value.", + "placeholders": { + "count": { + "content": "$1", + "example": "8" + } + } + }, + "noReusedPasswords": { + "message": "No logins in your vault have passwords that are being reused." + }, + "reusedXTimes": { + "message": "Reused $COUNT$ times", + "placeholders": { + "count": { + "content": "$1", + "example": "8" + } + } + }, + "dataBreachReport": { + "message": "Data breach" + }, + "breachDesc": { + "message": "Breached accounts can expose your personal information. Secure breached accounts by enabling 2FA or creating a stronger password." + }, + "breachCheckUsernameEmail": { + "message": "Check any usernames or email addresses that you use." + }, + "checkBreaches": { + "message": "Check breaches" + }, + "breachUsernameNotFound": { + "message": "$USERNAME$ was not found in any known data breaches.", + "placeholders": { + "username": { + "content": "$1", + "example": "user@example.com" + } + } + }, + "goodNews": { + "message": "Good news", + "description": "ex. Good News, No Breached Accounts Found!" + }, + "breachUsernameFound": { + "message": "$USERNAME$ was found in $COUNT$ different data breaches online.", + "placeholders": { + "username": { + "content": "$1", + "example": "user@example.com" + }, + "count": { + "content": "$2", + "example": "7" + } + } + }, + "breachFound": { + "message": "Breached accounts found" + }, + "compromisedData": { + "message": "Compromised data" + }, + "website": { + "message": "Website" + }, + "affectedUsers": { + "message": "Affected users" + }, + "breachOccurred": { + "message": "Breach occurred" + }, + "breachReported": { + "message": "Breach reported" + }, + "reportError": { + "message": "An error occurred trying to load the report. Try again" + }, + "billing": { + "message": "Billing" + }, + "billingPlanLabel": { + "message": "Billing plan" + }, + "paymentType": { + "message": "Payment type" + }, + "accountCredit": { + "message": "Account credit", + "description": "Financial term. In the case of Bitwarden, a positive balance means that you owe money, while a negative balance means that you have a credit (Bitwarden owes you money)." + }, + "accountBalance": { + "message": "Account balance", + "description": "Financial term. In the case of Bitwarden, a positive balance means that you owe money, while a negative balance means that you have a credit (Bitwarden owes you money)." + }, + "addCredit": { + "message": "Add credit", + "description": "Add more credit to your account's balance." + }, + "amount": { + "message": "Amount", + "description": "Dollar amount, or quantity." + }, + "creditDelayed": { + "message": "Added credit will appear on your account after the payment has been fully processed. Some payment methods are delayed and can take longer to process than others." + }, + "makeSureEnoughCredit": { + "message": "Please make sure that your account has enough credit available for this purchase. If your account does not have enough credit available, your default payment method on file will be used for the difference. You can add credit to your account from the Billing page." + }, + "creditAppliedDesc": { + "message": "Your account's credit can be used to make purchases. Any available credit will be automatically applied towards invoices generated for this account." + }, + "goPremium": { + "message": "Go Premium", + "description": "Another way of saying \"Get a Premium membership\"" + }, + "premiumUpdated": { + "message": "You've upgraded to Premium." + }, + "premiumUpgradeUnlockFeatures": { + "message": "Upgrade your account to a Premium membership and unlock some great additional features." + }, + "premiumSignUpStorage": { + "message": "1 GB encrypted storage for file attachments." + }, + "premiumSignUpTwoStep": { + "message": "Additional two-step login options such as YubiKey, FIDO U2F, and Duo." + }, + "premiumSignUpEmergency": { + "message": "Emergency access" + }, + "premiumSignUpReports": { + "message": "Password hygiene, account health, and data breach reports to keep your vault safe." + }, + "premiumSignUpTotp": { + "message": "TOTP verification code (2FA) generator for logins in your vault." + }, + "premiumSignUpSupport": { + "message": "Priority customer support." + }, + "premiumSignUpFuture": { + "message": "All future Premium features. More coming soon!" + }, + "premiumPrice": { + "message": "All for just $PRICE$ /year!", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + } + } + }, + "addons": { + "message": "Addons" + }, + "premiumAccess": { + "message": "Premium access" + }, + "premiumAccessDesc": { + "message": "You can add Premium access to all members of your organization for $PRICE$ /$INTERVAL$.", + "placeholders": { + "price": { + "content": "$1", + "example": "$3.33" + }, + "interval": { + "content": "$2", + "example": "'month' or 'year'" + } + } + }, + "additionalStorageGb": { + "message": "Additional storage (GB)" + }, + "additionalStorageGbDesc": { + "message": "# of additional GB" + }, + "additionalStorageIntervalDesc": { + "message": "Your plan comes with $SIZE$ of encrypted file storage. You can add additional storage for $PRICE$ per GB /$INTERVAL$.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + }, + "price": { + "content": "$2", + "example": "$4.00" + }, + "interval": { + "content": "$3", + "example": "'month' or 'year'" + } + } + }, + "summary": { + "message": "Summary" + }, + "total": { + "message": "Total" + }, + "year": { + "message": "year" + }, + "yr": { + "message": "yr" + }, + "month": { + "message": "month" + }, + "monthAbbr": { + "message": "mo.", + "description": "Short abbreviation for 'month'" + }, + "paymentChargedAnnually": { + "message": "Your payment method will be charged immediately and then on a recurring basis each year. You may cancel at any time." + }, + "paymentCharged": { + "message": "Your payment method will be charged immediately and then on a recurring basis each $INTERVAL$. You may cancel at any time.", + "placeholders": { + "interval": { + "content": "$1", + "example": "month or year" + } + } + }, + "paymentChargedWithTrial": { + "message": "Your plan comes with a free 7 day trial. Your payment method will not be charged until the trial has ended. You may cancel at any time." + }, + "paymentInformation": { + "message": "Payment information" + }, + "billingInformation": { + "message": "Billing information" + }, + "billingTrialSubLabel": { + "message": "Your payment method will not be charged during the 7 day free trial." + }, + "creditCard": { + "message": "Credit card" + }, + "paypalClickSubmit": { + "message": "Select the PayPal button to log into your PayPal account, then click the Submit button below to continue." + }, + "cancelSubscription": { + "message": "Cancel subscription" + }, + "subscriptionCanceled": { + "message": "The subscription has been canceled." + }, + "pendingCancellation": { + "message": "Pending cancellation" + }, + "subscriptionPendingCanceled": { + "message": "The subscription has been marked for cancellation at the end of the current billing period." + }, + "reinstateSubscription": { + "message": "Reinstate subscription" + }, + "reinstateConfirmation": { + "message": "Are you sure you want to remove the pending cancellation request and reinstate your subscription?" + }, + "reinstated": { + "message": "The subscription has been reinstated." + }, + "cancelConfirmation": { + "message": "Are you sure you want to cancel? You will lose access to all of this subscription's features at the end of this billing cycle." + }, + "canceledSubscription": { + "message": "Subscription canceled" + }, + "neverExpires": { + "message": "Never expires" + }, + "status": { + "message": "Status" + }, + "nextCharge": { + "message": "Next charge" + }, + "details": { + "message": "Details" + }, + "downloadLicense": { + "message": "Download license" + }, + "updateLicense": { + "message": "Update license" + }, + "updatedLicense": { + "message": "Updated license" + }, + "manageSubscription": { + "message": "Manage subscription" + }, + "storage": { + "message": "Storage" + }, + "addStorage": { + "message": "Add storage" + }, + "removeStorage": { + "message": "Remove storage" + }, + "subscriptionStorage": { + "message": "Your subscription has a total of $MAX_STORAGE$ GB of encrypted file storage. You are currently using $USED_STORAGE$.", + "placeholders": { + "max_storage": { + "content": "$1", + "example": "4" + }, + "used_storage": { + "content": "$2", + "example": "65 MB" + } + } + }, + "paymentMethod": { + "message": "Payment method" + }, + "noPaymentMethod": { + "message": "No payment method on file." + }, + "addPaymentMethod": { + "message": "Add payment method" + }, + "changePaymentMethod": { + "message": "Change payment method" + }, + "invoices": { + "message": "Invoices" + }, + "noInvoices": { + "message": "No invoices." + }, + "paid": { + "message": "Paid", + "description": "Past tense status of an invoice. ex. Paid or unpaid." + }, + "unpaid": { + "message": "Unpaid", + "description": "Past tense status of an invoice. ex. Paid or unpaid." + }, + "transactions": { + "message": "Transactions", + "description": "Payment/credit transactions." + }, + "noTransactions": { + "message": "No transactions." + }, + "chargeNoun": { + "message": "Charge", + "description": "Noun. A charge from a payment method." + }, + "refundNoun": { + "message": "Refund", + "description": "Noun. A refunded payment that was charged." + }, + "chargesStatement": { + "message": "Any charges will appear on your statement as $STATEMENT_NAME$.", + "placeholders": { + "statement_name": { + "content": "$1", + "example": "BITWARDEN" + } + } + }, + "gbStorageAdd": { + "message": "GB of storage to add" + }, + "gbStorageRemove": { + "message": "GB of storage to remove" + }, + "storageAddNote": { + "message": "Adding storage will result in adjustments to your billing totals and immediately charge your payment method on file. The first charge will be prorated for the remainder of the current billing cycle." + }, + "storageRemoveNote": { + "message": "Removing storage will result in adjustments to your billing totals that will be prorated as credits toward your next billing charge." + }, + "adjustedStorage": { + "message": "Adjusted $AMOUNT$ GB of storage.", + "placeholders": { + "amount": { + "content": "$1", + "example": "5" + } + } + }, + "contactSupport": { + "message": "Contact customer support" + }, + "updatedPaymentMethod": { + "message": "Updated payment method." + }, + "purchasePremium": { + "message": "Purchase Premium" + }, + "licenseFile": { + "message": "License file" + }, + "licenseFileDesc": { + "message": "Your license file will be named something like $FILE_NAME$", + "placeholders": { + "file_name": { + "content": "$1", + "example": "bitwarden_premium_license.json" + } + } + }, + "uploadLicenseFilePremium": { + "message": "To upgrade your account to a Premium membership you need to upload a valid license file." + }, + "uploadLicenseFileOrg": { + "message": "To create an on-premises hosted organization you need to upload a valid license file." + }, + "accountEmailMustBeVerified": { + "message": "Your account's email address must be verified." + }, + "newOrganizationDesc": { + "message": "Organizations allow you to share parts of your vault with others as well as manage related users for a specific entity such as a family, small team, or large company." + }, + "generalInformation": { + "message": "General information" + }, + "organizationName": { + "message": "Organization name" + }, + "accountOwnedBusiness": { + "message": "This account is owned by a business." + }, + "billingEmail": { + "message": "Billing email" + }, + "businessName": { + "message": "Business name" + }, + "chooseYourPlan": { + "message": "Choose your plan" + }, + "users": { + "message": "Users" + }, + "userSeats": { + "message": "User seats" + }, + "additionalUserSeats": { + "message": "Additional user seats" + }, + "userSeatsDesc": { + "message": "# of user seats" + }, + "userSeatsAdditionalDesc": { + "message": "Your plan comes with $BASE_SEATS$ user seats. You can add additional users for $SEAT_PRICE$ per user /month.", + "placeholders": { + "base_seats": { + "content": "$1", + "example": "5" + }, + "seat_price": { + "content": "$2", + "example": "$2.00" + } + } + }, + "userSeatsHowManyDesc": { + "message": "How many user seats do you need? You can also add additional seats later if needed." + }, + "planNameFree": { + "message": "Free", + "description": "Free as in 'free beer'." + }, + "planDescFree": { + "message": "For testing or personal users to share with $COUNT$ other user.", + "placeholders": { + "count": { + "content": "$1", + "example": "1" + } + } + }, + "planNameFamilies": { + "message": "Families" + }, + "planDescFamilies": { + "message": "For personal use, to share with family & friends." + }, + "planNameTeams": { + "message": "Teams" + }, + "planDescTeams": { + "message": "For businesses and other team organizations." + }, + "planNameEnterprise": { + "message": "Enterprise" + }, + "planDescEnterprise": { + "message": "For businesses and other large organizations." + }, + "freeForever": { + "message": "Free forever" + }, + "includesXUsers": { + "message": "includes $COUNT$ users", + "placeholders": { + "count": { + "content": "$1", + "example": "5" + } + } + }, + "additionalUsers": { + "message": "Additional users" + }, + "costPerUser": { + "message": "$COST$ per user", + "placeholders": { + "cost": { + "content": "$1", + "example": "$3" + } + } + }, + "limitedUsers": { + "message": "Limited to $COUNT$ users (including you)", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "limitedCollections": { + "message": "Limited to $COUNT$ collections", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "addShareLimitedUsers": { + "message": "Add and share with up to $COUNT$ users", + "placeholders": { + "count": { + "content": "$1", + "example": "5" + } + } + }, + "addShareUnlimitedUsers": { + "message": "Add and share with unlimited users" + }, + "createUnlimitedCollections": { + "message": "Create unlimited collections" + }, + "gbEncryptedFileStorage": { + "message": "$SIZE$ encrypted file storage", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, + "onPremHostingOptional": { + "message": "On-premise hosting (optional)" + }, + "usersGetPremium": { + "message": "Users get access to Premium features" + }, + "controlAccessWithGroups": { + "message": "Control user access with groups" + }, + "syncUsersFromDirectory": { + "message": "Sync your users and groups from a directory" + }, + "trackAuditLogs": { + "message": "Track user actions with audit logs" + }, + "enforce2faDuo": { + "message": "Enforce 2FA with Duo" + }, + "priorityCustomerSupport": { + "message": "Priority customer support" + }, + "xDayFreeTrial": { + "message": "$COUNT$ day free trial, cancel anytime", + "placeholders": { + "count": { + "content": "$1", + "example": "7" + } + } + }, + "trialThankYou": { + "message": "Thanks for signing up for Bitwarden for $PLAN$!", + "placeholders": { + "plan": { + "content": "$1", + "example": "Teams" + } + } + }, + "trialPaidInfoMessage": { + "message": "Your $PLAN$ 7 day free trial will be converted to a paid subscription after 7 days.", + "placeholders": { + "plan": { + "content": "$1", + "example": "Teams" + } + } + }, + "trialConfirmationEmail": { + "message": "We've sent a confirmation email to your team's billing email at " + }, + "monthly": { + "message": "Monthly" + }, + "annually": { + "message": "Annually" + }, + "annual": { + "message": "Annual" + }, + "basePrice": { + "message": "Base price" + }, + "organizationCreated": { + "message": "Organization created" + }, + "organizationReadyToGo": { + "message": "Your new organization is ready to go!" + }, + "organizationUpgraded": { + "message": "Organization upgraded" + }, + "leave": { + "message": "Leave" + }, + "leaveOrganizationConfirmation": { + "message": "Are you sure you want to leave this organization?" + }, + "leftOrganization": { + "message": "You left the organization" + }, + "defaultCollection": { + "message": "Default collection" + }, + "getHelp": { + "message": "Get help" + }, + "getApps": { + "message": "Get the apps" + }, + "loggedInAs": { + "message": "Logged in as" + }, + "eventLogs": { + "message": "Event logs" + }, + "people": { + "message": "People" + }, + "policies": { + "message": "Policies" + }, + "singleSignOn": { + "message": "Single sign-on" + }, + "editPolicy": { + "message": "Edit policy" + }, + "groups": { + "message": "Groups" + }, + "newGroup": { + "message": "New group" + }, + "addGroup": { + "message": "Add group" + }, + "editGroup": { + "message": "Edit group" + }, + "deleteGroupConfirmation": { + "message": "Are you sure you want to delete this group?" + }, + "removeUserConfirmation": { + "message": "Are you sure you want to remove this user?" + }, + "removeOrgUserConfirmation": { + "message": "When a member is removed, they no longer have access to organization data and this action is irreversible. To add the member back to the organization, they must be invited and onboarded again." + }, + "revokeUserConfirmation": { + "message": "When a member is revoked, they no longer have access to organization data. To quickly restore member access, go to the Revoked tab." + }, + "removeUserConfirmationKeyConnector": { + "message": "Warning! This user requires Key Connector to manage their encryption. Removing this user from your organization will permanently deactivate their account. This action cannot be undone. Do you want to proceed?" + }, + "externalId": { + "message": "External id" + }, + "externalIdDesc": { + "message": "The external id can be used as a reference or to link this resource to an external system such as a user directory." + }, + "accessControl": { + "message": "Access control" + }, + "groupAccessAllItems": { + "message": "This group can access and modify all items." + }, + "groupAccessSelectedCollections": { + "message": "This group can access only the selected collections." + }, + "readOnly": { + "message": "Read only" + }, + "newCollection": { + "message": "New collection" + }, + "addCollection": { + "message": "Add collection" + }, + "editCollection": { + "message": "Edit collection" + }, + "deleteCollectionConfirmation": { + "message": "Are you sure you want to delete this collection?" + }, + "editUser": { + "message": "Edit user" + }, + "inviteUser": { + "message": "Invite user" + }, + "inviteUserDesc": { + "message": "Invite a new user to your organization by entering their Bitwarden account email address below. If they do not have a Bitwarden account already, they will be prompted to create a new account." + }, + "inviteMultipleEmailDesc": { + "message": "You can invite up to $COUNT$ users at a time by comma separating a list of email addresses.", + "placeholders": { + "count": { + "content": "$1", + "example": "20" + } + } + }, + "userUsingTwoStep": { + "message": "This user is using two-step login to protect their account." + }, + "userAccessAllItems": { + "message": "This user can access and modify all items." + }, + "userAccessSelectedCollections": { + "message": "This user can access only the selected collections." + }, + "search": { + "message": "Search" + }, + "invited": { + "message": "Invited" + }, + "accepted": { + "message": "Accepted" + }, + "confirmed": { + "message": "Confirmed" + }, + "clientOwnerEmail": { + "message": "Client owner email" + }, + "owner": { + "message": "Owner" + }, + "ownerDesc": { + "message": "Manage all aspects of your organization, including billing and subscriptions" + }, + "clientOwnerDesc": { + "message": "This user should be independent of the Provider. If the Provider is disassociated with the organization, this user will maintain ownership of the organization." + }, + "admin": { + "message": "Admin" + }, + "adminDesc": { + "message": "Manage organization access, all collections, members, reporting, and security settings" + }, + "user": { + "message": "User" + }, + "userDesc": { + "message": "Access and add items to assigned collections" + }, + "manager": { + "message": "Manager" + }, + "managerDesc": { + "message": "Create, delete, and manage access in assigned collections" + }, + "all": { + "message": "All" + }, + "refresh": { + "message": "Refresh" + }, + "timestamp": { + "message": "Timestamp" + }, + "event": { + "message": "Event" + }, + "unknown": { + "message": "Unknown" + }, + "loadMore": { + "message": "Load more" + }, + "mobile": { + "message": "Mobile", + "description": "Mobile app" + }, + "extension": { + "message": "Extension", + "description": "Browser extension/addon" + }, + "desktop": { + "message": "Desktop", + "description": "Desktop app" + }, + "webVault": { + "message": "Web vault" + }, + "loggedIn": { + "message": "Logged in" + }, + "changedPassword": { + "message": "Changed account password" + }, + "enabledUpdated2fa": { + "message": "Two-step login saved" + }, + "disabled2fa": { + "message": "Two-step login turned off" + }, + "recovered2fa": { + "message": "Recovered account from two-step login." + }, + "failedLogin": { + "message": "Login attempt failed with incorrect password." + }, + "failedLogin2fa": { + "message": "Login attempt failed with incorrect two-step login." + }, + "exportedVault": { + "message": "Vault exported" + }, + "exportedOrganizationVault": { + "message": "Exported organization vault." + }, + "editedOrgSettings": { + "message": "Edited organization settings." + }, + "createdItemId": { + "message": "Created item $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "Google" + } + } + }, + "editedItemId": { + "message": "Edited item $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "Google" + } + } + }, + "deletedItemId": { + "message": "Sent item $ID$ to trash.", + "placeholders": { + "id": { + "content": "$1", + "example": "Google" + } + } + }, + "movedItemIdToOrg": { + "message": "Moved item $ID$ to an organization.", + "placeholders": { + "id": { + "content": "$1", + "example": "'Google'" + } + } + }, + "viewAllLoginOptions": { + "message": "View all log in options" + }, + "viewedItemId": { + "message": "Viewed item $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "Google" + } + } + }, + "viewedPasswordItemId": { + "message": "Viewed password for item $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "Google" + } + } + }, + "viewedHiddenFieldItemId": { + "message": "Viewed hidden field for item $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "Google" + } + } + }, + "viewedCardNumberItemId": { + "message": "Viewed Card Number for item $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "Unique ID" + } + } + }, + "viewedSecurityCodeItemId": { + "message": "Viewed security code for item $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "Google" + } + } + }, + "copiedPasswordItemId": { + "message": "Copied password for item $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "Google" + } + } + }, + "copiedHiddenFieldItemId": { + "message": "Copied hidden field for item $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "Google" + } + } + }, + "copiedSecurityCodeItemId": { + "message": "Copied security code for item $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "Google" + } + } + }, + "autofilledItemId": { + "message": "Auto-filled item $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "Google" + } + } + }, + "createdCollectionId": { + "message": "Created collection $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "Server Passwords" + } + } + }, + "editedCollectionId": { + "message": "Edited collection $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "Server Passwords" + } + } + }, + "deletedCollectionId": { + "message": "Deleted collection $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "Server Passwords" + } + } + }, + "editedPolicyId": { + "message": "Edited policy $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "Master Password" + } + } + }, + "createdGroupId": { + "message": "Created group $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "Developers" + } + } + }, + "editedGroupId": { + "message": "Edited group $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "Developers" + } + } + }, + "deletedGroupId": { + "message": "Deleted group $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "Developers" + } + } + }, + "removedUserId": { + "message": "Removed user $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "removeUserIdAccess": { + "message": "Remove $ID$ access", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "revokedUserId": { + "message": "Revoked organization access for $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "restoredUserId": { + "message": "Restored organization access for $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "revokeUserId": { + "message": "Revoke $ID$ access", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "createdAttachmentForItem": { + "message": "Created attachment for item $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "Google" + } + } + }, + "deletedAttachmentForItem": { + "message": "Deleted attachment for item $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "Google" + } + } + }, + "editedCollectionsForItem": { + "message": "Edited collections for item $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "Google" + } + } + }, + "invitedUserId": { + "message": "Invited user $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "confirmedUserId": { + "message": "Confirmed user $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "editedUserId": { + "message": "Edited user $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "editedGroupsForUser": { + "message": "Edited groups for user $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "unlinkedSsoUser": { + "message": "Unlinked SSO for user $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "createdOrganizationId": { + "message": "Created organization $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "Google" + } + } + }, + "addedOrganizationId": { + "message": "Added organization $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "Google" + } + } + }, + "removedOrganizationId": { + "message": "Removed organization $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "Google" + } + } + }, + "accessedClientVault": { + "message": "Accessed $ID$ organization vault.", + "placeholders": { + "id": { + "content": "$1", + "example": "Google" + } + } + }, + "device": { + "message": "Device" + }, + "view": { + "message": "View" + }, + "invalidDateRange": { + "message": "Invalid date range." + }, + "errorOccurred": { + "message": "An error has occurred." + }, + "userAccess": { + "message": "User access" + }, + "userType": { + "message": "User type" + }, + "groupAccess": { + "message": "Group access" + }, + "groupAccessUserDesc": { + "message": "Edit the groups that this user belongs to." + }, + "invitedUsers": { + "message": "User(s) invited" + }, + "resendInvitation": { + "message": "Resend invitation" + }, + "resendEmail": { + "message": "Resend email" + }, + "hasBeenReinvited": { + "message": "$USER$ reinvited", + "placeholders": { + "user": { + "content": "$1", + "example": "John Smith" + } + } + }, + "confirm": { + "message": "Confirm" + }, + "confirmUser": { + "message": "Confirm user" + }, + "hasBeenConfirmed": { + "message": "$USER$ confirmed.", + "placeholders": { + "user": { + "content": "$1", + "example": "John Smith" + } + } + }, + "confirmUsers": { + "message": "Confirm users" + }, + "usersNeedConfirmed": { + "message": "You have users that have accepted their invitation, but still need to be confirmed. Users will not have access to the organization until they are confirmed." + }, + "startDate": { + "message": "Start date" + }, + "endDate": { + "message": "End date" + }, + "verifyEmail": { + "message": "Verify email" + }, + "verifyEmailDesc": { + "message": "Verify your account's email address to unlock access to all features." + }, + "verifyEmailFirst": { + "message": "Your account's email address first must be verified." + }, + "checkInboxForVerification": { + "message": "Check your email inbox for a verification link." + }, + "emailVerified": { + "message": "Account email verified" + }, + "emailVerifiedFailed": { + "message": "Unable to verify your email. Try sending a new verification email." + }, + "emailVerificationRequired": { + "message": "Email verification required" + }, + "emailVerificationRequiredDesc": { + "message": "You must verify your email to use this feature." + }, + "updateBrowser": { + "message": "Update browser" + }, + "updateBrowserDesc": { + "message": "You are using an unsupported web browser. The web vault may not function properly." + }, + "joinOrganization": { + "message": "Join organization" + }, + "joinOrganizationDesc": { + "message": "You've been invited to join the organization listed above. To accept the invitation, you need to log in or create a new Bitwarden account." + }, + "inviteAccepted": { + "message": "Invitation accepted" + }, + "inviteAcceptedDesc": { + "message": "You can access this organization once an administrator confirms your membership. We'll send you an email when that happens." + }, + "inviteAcceptFailed": { + "message": "Unable to accept invitation. Ask an organization admin to send a new invitation." + }, + "inviteAcceptFailedShort": { + "message": "Unable to accept invitation. $DESCRIPTION$", + "placeholders": { + "description": { + "content": "$1", + "example": "You must set up 2FA on your user account before you can join this organization." + } + } + }, + "rememberEmail": { + "message": "Remember email" + }, + "recoverAccountTwoStepDesc": { + "message": "If you cannot access your account through your normal two-step login methods, you can use your two-step login recovery code to turn off all two-step providers on your account." + }, + "recoverAccountTwoStep": { + "message": "Recover account two-step login" + }, + "twoStepRecoverDisabled": { + "message": "Two-step login turned off on your account." + }, + "learnMore": { + "message": "Learn more" + }, + "deleteRecoverDesc": { + "message": "Enter your email address below to recover and delete your account." + }, + "deleteRecoverEmailSent": { + "message": "If your account exists, we've sent you an email with further instructions." + }, + "deleteRecoverConfirmDesc": { + "message": "You have requested to delete your Bitwarden account. Use the button below to confirm." + }, + "myOrganization": { + "message": "My organization" + }, + "organizationInfo": { + "message": "Organization info" + }, + "deleteOrganization": { + "message": "Delete organization" + }, + "deletingOrganizationContentWarning": { + "message": "Enter the master password to confirm deletion of $ORGANIZATION$ and all associated data. Vault data in $ORGANIZATION$ includes:", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "deletingOrganizationActiveUserAccountsWarning": { + "message": "User accounts will remain active after deletion but will no longer be associated to this organization." + }, + "deletingOrganizationIsPermanentWarning": { + "message": "Deleting $ORGANIZATION$ is permanent and irreversible.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "organizationDeleted": { + "message": "Organization deleted" + }, + "organizationDeletedDesc": { + "message": "The organization and all associated data has been deleted." + }, + "organizationUpdated": { + "message": "Organization saved" + }, + "taxInformation": { + "message": "Tax information" + }, + "taxInformationDesc": { + "message": "For customers within the US, ZIP code is required to satisfy sales tax requirements, for other countries you may optionally provide a tax identification number (VAT/GST) and/or address to appear on your invoices." + }, + "billingPlan": { + "message": "Plan", + "description": "A billing plan/package. For example: Families, Teams, Enterprise, etc." + }, + "changeBillingPlan": { + "message": "Upgrade plan", + "description": "A billing plan/package. For example: Families, Teams, Enterprise, etc." + }, + "changeBillingPlanUpgrade": { + "message": "Upgrade your account to another plan by providing the information below. Please ensure that you have an active payment method added to the account.", + "description": "A billing plan/package. For example: Families, Teams, Enterprise, etc." + }, + "invoiceNumber": { + "message": "Invoice #$NUMBER$", + "description": "ex. Invoice #79C66F0-0001", + "placeholders": { + "number": { + "content": "$1", + "example": "79C66F0-0001" + } + } + }, + "viewInvoice": { + "message": "View invoice" + }, + "downloadInvoice": { + "message": "Download invoice" + }, + "verifyBankAccount": { + "message": "Verify bank account" + }, + "verifyBankAccountDesc": { + "message": "We have made two micro-deposits to your bank account (it may take 1-2 business days to show up). Enter these amounts to verify the bank account." + }, + "verifyBankAccountInitialDesc": { + "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make two micro-deposits within the next 1-2 business days. Enter these amounts on the organization's billing page to verify the bank account." + }, + "verifyBankAccountFailureWarning": { + "message": "Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "verifiedBankAccount": { + "message": "Bank account verified" + }, + "bankAccount": { + "message": "Bank account" + }, + "amountX": { + "message": "Amount $COUNT$", + "description": "Used in bank account verification of micro-deposits. Amount, as in a currency amount. Ex. Amount 1 is $2.00, Amount 2 is $1.50", + "placeholders": { + "count": { + "content": "$1", + "example": "1" + } + } + }, + "routingNumber": { + "message": "Routing number", + "description": "Bank account routing number" + }, + "accountNumber": { + "message": "Account number" + }, + "accountHolderName": { + "message": "Account holder name" + }, + "bankAccountType": { + "message": "Account type" + }, + "bankAccountTypeCompany": { + "message": "Company (business)" + }, + "bankAccountTypeIndividual": { + "message": "Individual (personal)" + }, + "enterInstallationId": { + "message": "Enter your installation id" + }, + "limitSubscriptionDesc": { + "message": "Set a seat limit for your subscription. Once this limit is reached, you will not be able to invite new members." + }, + "maxSeatLimit": { + "message": "Seat Limit (optional)", + "description": "Upper limit of seats to allow through autoscaling" + }, + "maxSeatCost": { + "message": "Max potential seat cost" + }, + "addSeats": { + "message": "Add seats", + "description": "Seat = User Seat" + }, + "removeSeats": { + "message": "Remove seats", + "description": "Seat = User Seat" + }, + "subscriptionDesc": { + "message": "Adjustments to your subscription will result in prorated changes to your billing totals. If newly invited users exceed your subscription seats, you will immediately receive a prorated charge for the additional users." + }, + "subscriptionUserSeats": { + "message": "Your subscription allows for a total of $COUNT$ members.", + "placeholders": { + "count": { + "content": "$1", + "example": "50" + } + } + }, + "limitSubscription": { + "message": "Limit subscription (optional)" + }, + "subscriptionSeats": { + "message": "Subscription seats" + }, + "subscriptionUpdated": { + "message": "Subscription updated" + }, + "additionalOptions": { + "message": "Additional options" + }, + "additionalOptionsDesc": { + "message": "For additional help in managing your subscription, please contact Customer Support." + }, + "subscriptionUserSeatsUnlimitedAutoscale": { + "message": "Adjustments to your subscription will result in prorated changes to your billing totals. If newly invited members exceed your subscription seats, you will immediately receive a prorated charge for the additional members." + }, + "subscriptionUserSeatsLimitedAutoscale": { + "message": "Adjustments to your subscription will result in prorated changes to your billing totals. If newly invited members exceed your subscription seats, you will immediately receive a prorated charge for the additional members until your $MAX$ seat limit is reached.", + "placeholders": { + "max": { + "content": "$1", + "example": "50" + } + } + }, + "subscriptionFreePlan": { + "message": "You cannot invite more than $COUNT$ members without upgrading your plan.", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "subscriptionFamiliesPlan": { + "message": "You cannot invite more than $COUNT$ members without upgrading your plan. Please contact Customer Support to upgrade.", + "placeholders": { + "count": { + "content": "$1", + "example": "6" + } + } + }, + "subscriptionSponsoredFamiliesPlan": { + "message": "Your subscription allows for a total of $COUNT$ members. Your plan is sponsored and billed to an external organization.", + "placeholders": { + "count": { + "content": "$1", + "example": "6" + } + } + }, + "subscriptionMaxReached": { + "message": "Adjustments to your subscription will result in prorated changes to your billing totals. You cannot invite more than $COUNT$ members without increasing your subscription seats.", + "placeholders": { + "count": { + "content": "$1", + "example": "50" + } + } + }, + "seatsToAdd": { + "message": "Seats to add" + }, + "seatsToRemove": { + "message": "Seats to remove" + }, + "seatsAddNote": { + "message": "Adding user seats will result in adjustments to your billing totals and immediately charge your payment method on file. The first charge will be prorated for the remainder of the current billing cycle." + }, + "seatsRemoveNote": { + "message": "Removing user seats will result in adjustments to your billing totals that will be prorated as credits toward your next billing charge." + }, + "adjustedSeats": { + "message": "Adjusted $AMOUNT$ user seats.", + "placeholders": { + "amount": { + "content": "$1", + "example": "15" + } + } + }, + "keyUpdated": { + "message": "Key updated" + }, + "updateKeyTitle": { + "message": "Update key" + }, + "updateEncryptionKey": { + "message": "Update encryption key" + }, + "updateEncryptionKeyShortDesc": { + "message": "You are currently using an outdated encryption scheme." + }, + "updateEncryptionKeyDesc": { + "message": "We've moved to larger encryption keys that provide better security and access to newer features. Updating your encryption key is quick and easy. Just type your master password below. This update will eventually become mandatory." + }, + "updateEncryptionKeyWarning": { + "message": "After updating your encryption key, you are required to log out and back in to all Bitwarden applications that you are currently using (such as the mobile app or browser extensions). Failure to log out and back in (which downloads your new encryption key) may result in data corruption. We will attempt to log you out automatically, however, it may be delayed." + }, + "updateEncryptionKeyExportWarning": { + "message": "Any encrypted exports that you have saved will also become invalid." + }, + "subscription": { + "message": "Subscription" + }, + "loading": { + "message": "Loading" + }, + "upgrade": { + "message": "Upgrade" + }, + "upgradeOrganization": { + "message": "Upgrade organization" + }, + "upgradeOrganizationDesc": { + "message": "This feature is not available for free organizations. Switch to a paid plan to unlock more features." + }, + "createOrganizationStep1": { + "message": "Create organization: Step 1" + }, + "createOrganizationCreatePersonalAccount": { + "message": "Before creating your organization, you first need to create a free personal account." + }, + "refunded": { + "message": "Refunded" + }, + "nothingSelected": { + "message": "You have not selected anything." + }, + "acceptPolicies": { + "message": "By checking this box you agree to the following:" + }, + "acceptPoliciesRequired": { + "message": "Terms of Service and Privacy Policy have not been acknowledged." + }, + "termsOfService": { + "message": "Terms of Service" + }, + "privacyPolicy": { + "message": "Privacy Policy" + }, + "filters": { + "message": "Filters" + }, + "vaultTimeout": { + "message": "Vault timeout" + }, + "vaultTimeoutDesc": { + "message": "Choose when your vault will take the vault timeout action." + }, + "oneMinute": { + "message": "1 minute" + }, + "fiveMinutes": { + "message": "5 minutes" + }, + "fifteenMinutes": { + "message": "15 minutes" + }, + "thirtyMinutes": { + "message": "30 minutes" + }, + "oneHour": { + "message": "1 hour" + }, + "fourHours": { + "message": "4 hours" + }, + "onRefresh": { + "message": "On browser refresh" + }, + "dateUpdated": { + "message": "Updated", + "description": "ex. Date this item was updated" + }, + "dateCreated": { + "message": "Created", + "description": "ex. Date this item was created" + }, + "datePasswordUpdated": { + "message": "Password updated", + "description": "ex. Date this password was updated" + }, + "organizationIsDisabled": { + "message": "Organization suspended" + }, + "disabledOrganizationFilterError": { + "message": "Items in suspended organizations cannot be accessed. Contact your organization owner for assistance." + }, + "licenseIsExpired": { + "message": "License is expired." + }, + "updatedUsers": { + "message": "Updated users" + }, + "selected": { + "message": "Selected" + }, + "ownership": { + "message": "Ownership" + }, + "whoOwnsThisItem": { + "message": "Who owns this item?" + }, + "strong": { + "message": "Strong", + "description": "ex. A strong password. Scale: Very Weak -> Weak -> Good -> Strong" + }, + "good": { + "message": "Good", + "description": "ex. A good password. Scale: Very Weak -> Weak -> Good -> Strong" + }, + "weak": { + "message": "Weak", + "description": "ex. A weak password. Scale: Very Weak -> Weak -> Good -> Strong" + }, + "veryWeak": { + "message": "Very Weak", + "description": "ex. A very weak password. Scale: Very Weak -> Weak -> Good -> Strong" + }, + "weakMasterPassword": { + "message": "Weak master password" + }, + "weakMasterPasswordDesc": { + "message": "The master password you have chosen is weak. You should use a strong master password (or a passphrase) to properly protect your Bitwarden account. Are you sure you want to use this master password?" + }, + "rotateAccountEncKey": { + "message": "Also rotate my account's encryption key" + }, + "rotateEncKeyTitle": { + "message": "Rotate encryption key" + }, + "rotateEncKeyConfirmation": { + "message": "Are you sure you want to rotate your account's encryption key?" + }, + "attachmentsNeedFix": { + "message": "This item has old file attachments that need to be fixed." + }, + "attachmentFixDesc": { + "message": "This is an old file attachment the needs to be fixed. Click to learn more." + }, + "fix": { + "message": "Fix", + "description": "This is a verb. ex. 'Fix The Car'" + }, + "oldAttachmentsNeedFixDesc": { + "message": "There are old file attachments in your vault that need to be fixed before you can rotate your account's encryption key." + }, + "yourAccountsFingerprint": { + "message": "Your account's fingerprint phrase", + "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." + }, + "fingerprintEnsureIntegrityVerify": { + "message": "To ensure the integrity of your encryption keys, please verify the user's fingerprint phrase before continuing.", + "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." + }, + "fingerprintMatchInfo": { + "message": "Please make sure your vault is unlocked and Fingerprint phrase matches the other device." + }, + "fingerprintPhraseHeader": { + "message": "Fingerprint phrase" + }, + "dontAskFingerprintAgain": { + "message": "Never prompt to verify fingerprint phrases for invited users (not recommended)", + "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." + }, + "free": { + "message": "Free", + "description": "Free, as in 'Free beer'" + }, + "apiKey": { + "message": "API Key" + }, + "apiKeyDesc": { + "message": "Your API key can be used to authenticate to the Bitwarden public API." + }, + "apiKeyRotateDesc": { + "message": "Rotating the API key will invalidate the previous key. You can rotate your API key if you believe that the current key is no longer safe to use." + }, + "apiKeyWarning": { + "message": "Your API key has full access to the organization. It should be kept secret." + }, + "userApiKeyDesc": { + "message": "Your API key can be used to authenticate in the Bitwarden CLI." + }, + "userApiKeyWarning": { + "message": "Your API key is an alternative authentication mechanism. It should be kept secret." + }, + "oauth2ClientCredentials": { + "message": "OAuth 2.0 Client Credentials", + "description": "'OAuth 2.0' is a programming protocol. It should probably not be translated." + }, + "viewApiKey": { + "message": "View API key" + }, + "rotateApiKey": { + "message": "Rotate API key" + }, + "selectOneCollection": { + "message": "You must select at least one collection." + }, + "couldNotChargeCardPayInvoice": { + "message": "We were not able to charge your card. Please view and pay the unpaid invoice listed below." + }, + "inAppPurchase": { + "message": "In-app purchase" + }, + "cannotPerformInAppPurchase": { + "message": "You cannot perform this action while using an in-app purchase payment method." + }, + "manageSubscriptionFromStore": { + "message": "You must manage your subscription from the store where your in-app purchase was made." + }, + "minLength": { + "message": "Minimum length" + }, + "clone": { + "message": "Clone" + }, + "masterPassPolicyTitle": { + "message": "Master password requirements" + }, + "masterPassPolicyDesc": { + "message": "Set requirements for master password strength." + }, + "twoStepLoginPolicyTitle": { + "message": "Require two-step login" + }, + "twoStepLoginPolicyDesc": { + "message": "Require members to set up two-step login." + }, + "twoStepLoginPolicyWarning": { + "message": "Organization members who are not owners or admins and do not have two-step login setup for their account will be removed from the organization and will receive an email notifying them about the change." + }, + "twoStepLoginPolicyUserWarning": { + "message": "You are a member of an organization that requires two-step login to be setup on your user account. If you turn off all two-step login providers you will be automatically removed from these organizations." + }, + "passwordGeneratorPolicyDesc": { + "message": "Set requirements for password generator." + }, + "passwordGeneratorPolicyInEffect": { + "message": "One or more organization policies are affecting your generator settings." + }, + "masterPasswordPolicyInEffect": { + "message": "One or more organization policies require your master password to meet the following requirements:" + }, + "policyInEffectMinComplexity": { + "message": "Minimum complexity score of $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, + "policyInEffectMinLength": { + "message": "Minimum length of $LENGTH$", + "placeholders": { + "length": { + "content": "$1", + "example": "14" + } + } + }, + "policyInEffectUppercase": { + "message": "Contain one or more uppercase characters" + }, + "policyInEffectLowercase": { + "message": "Contain one or more lowercase characters" + }, + "policyInEffectNumbers": { + "message": "Contain one or more numbers" + }, + "policyInEffectSpecial": { + "message": "Contain one or more of the following special characters $CHARS$", + "placeholders": { + "chars": { + "content": "$1", + "example": "!@#$%^&*" + } + } + }, + "masterPasswordPolicyRequirementsNotMet": { + "message": "Your new master password does not meet the policy requirements." + }, + "minimumNumberOfWords": { + "message": "Minimum number of words" + }, + "defaultType": { + "message": "Default type" + }, + "userPreference": { + "message": "User preference" + }, + "vaultTimeoutAction": { + "message": "Vault timeout action" + }, + "vaultTimeoutActionLockDesc": { + "message": "Master password or other unlock method is required to access your vault again." + }, + "vaultTimeoutActionLogOutDesc": { + "message": "Re-authentication is required to access your vault again." + }, + "lock": { + "message": "Lock", + "description": "Verb form: to make secure or inaccesible by" + }, + "trash": { + "message": "Trash", + "description": "Noun: A special folder for holding deleted items that have not yet been permanently deleted" + }, + "searchTrash": { + "message": "Search trash" + }, + "permanentlyDelete": { + "message": "Permanently delete" + }, + "permanentlyDeleteSelected": { + "message": "Permanently delete selected" + }, + "permanentlyDeleteItem": { + "message": "Permanently delete item" + }, + "permanentlyDeleteItemConfirmation": { + "message": "Are you sure you want to permanently delete this item?" + }, + "permanentlyDeletedItem": { + "message": "Item permanently deleted" + }, + "permanentlyDeletedItems": { + "message": "Items permanently deleted" + }, + "permanentlyDeleteSelectedItemsDesc": { + "message": "You have selected $COUNT$ item(s) to permanently delete. Are you sure you want to permanently delete all of these items?", + "placeholders": { + "count": { + "content": "$1", + "example": "150" + } + } + }, + "permanentlyDeletedItemId": { + "message": "Item $ID$ permanently deleted", + "placeholders": { + "id": { + "content": "$1", + "example": "Google" + } + } + }, + "restore": { + "message": "Restore" + }, + "restoreSelected": { + "message": "Restore selected" + }, + "restoreItem": { + "message": "Restore item" + }, + "restoredItem": { + "message": "Item restored" + }, + "restoredItems": { + "message": "Items restored" + }, + "restoreItemConfirmation": { + "message": "Are you sure you want to restore this item?" + }, + "restoreItems": { + "message": "Restore items" + }, + "restoreSelectedItemsDesc": { + "message": "You have selected $COUNT$ item(s) to restore. Are you sure you want to restore all of these items?", + "placeholders": { + "count": { + "content": "$1", + "example": "150" + } + } + }, + "restoredItemId": { + "message": "Item $ID$ restored", + "placeholders": { + "id": { + "content": "$1", + "example": "Google" + } + } + }, + "vaultTimeoutLogOutConfirmation": { + "message": "Logging out will remove all access to your vault and requires online authentication after the timeout period. Are you sure you want to use this setting?" + }, + "vaultTimeoutLogOutConfirmationTitle": { + "message": "Timeout action confirmation" + }, + "hidePasswords": { + "message": "Hide passwords" + }, + "countryPostalCodeRequiredDesc": { + "message": "We require this information for calculating sales tax and financial reporting only." + }, + "includeVAT": { + "message": "Include VAT/GST Information (optional)" + }, + "taxIdNumber": { + "message": "VAT/GST Tax ID" + }, + "taxInfoUpdated": { + "message": "Tax information updated." + }, + "setMasterPassword": { + "message": "Set master password" + }, + "ssoCompleteRegistration": { + "message": "In order to complete logging in with SSO, please set a master password to access and protect your vault." + }, + "identifier": { + "message": "Identifier" + }, + "organizationIdentifier": { + "message": "Organization identifier" + }, + "ssoLogInWithOrgIdentifier": { + "message": "Log in using your organization's single sign-on portal. Please enter your organization's SSO identifier to begin." + }, + "enterpriseSingleSignOn": { + "message": "Enterprise single sign-on" + }, + "ssoHandOff": { + "message": "You may now close this tab and continue in the extension." + }, + "includeAllTeamsFeatures": { + "message": "All Teams features, plus:" + }, + "includeSsoAuthentication": { + "message": "SSO Authentication via SAML2.0 and OpenID Connect" + }, + "includeEnterprisePolicies": { + "message": "Enterprise policies" + }, + "ssoValidationFailed": { + "message": "SSO validation failed" + }, + "ssoIdentifierRequired": { + "message": "Organization SSO identifier is required." + }, + "ssoIdentifier": { + "message": "SSO identifier" + }, + "ssoIdentifierHint": { + "message": "Provide this ID to your members to login with SSO." + }, + "unlinkSso": { + "message": "Unlink SSO" + }, + "unlinkSsoConfirmation": { + "message": "Are you sure you want to unlink SSO for this organization?" + }, + "linkSso": { + "message": "Link SSO" + }, + "singleOrg": { + "message": "Single organization" + }, + "singleOrgDesc": { + "message": "Restrict members from joining other organizations." + }, + "singleOrgBlockCreateMessage": { + "message": "Your current organization has a policy that does not allow you to join more than one organization. Please contact your organization admins or sign up from a different Bitwarden account." + }, + "singleOrgPolicyWarning": { + "message": "Organization members who are not owners or admins and are already a member of another organization will be removed from your organization." + }, + "requireSso": { + "message": "Require single sign-on authentication" + }, + "requireSsoPolicyDesc": { + "message": "Require members to log in with the Enterprise single sign-on method." + }, + "prerequisite": { + "message": "Prerequisite" + }, + "requireSsoPolicyReq": { + "message": "The single organization Enterprise policy must be turned on before activating this policy." + }, + "requireSsoPolicyReqError": { + "message": "Single organization policy not set up." + }, + "requireSsoExemption": { + "message": "Organization owners and admins are exempt from this policy's enforcement." + }, + "sendTypeFile": { + "message": "File" + }, + "sendTypeText": { + "message": "Text" + }, + "createSend": { + "message": "New Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "editSend": { + "message": "Edit Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "createdSend": { + "message": "Send saved", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "editedSend": { + "message": "Send saved", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "deletedSend": { + "message": "Send deleted", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "deleteSend": { + "message": "Delete Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "deleteSendConfirmation": { + "message": "Are you sure you want to delete this Send?", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "whatTypeOfSend": { + "message": "What type of Send is this?", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "deletionDate": { + "message": "Deletion date" + }, + "deletionDateDesc": { + "message": "The Send will be permanently deleted on the specified date and time.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "expirationDate": { + "message": "Expiration date" + }, + "expirationDateDesc": { + "message": "If set, access to this Send will expire on the specified date and time.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "maxAccessCount": { + "message": "Maximum access count" + }, + "maxAccessCountDesc": { + "message": "If set, users will no longer be able to access this Send once the maximum access count is reached.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "currentAccessCount": { + "message": "Current access count" + }, + "sendPasswordDesc": { + "message": "Optionally require a password for users to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendNotesDesc": { + "message": "Private notes about this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "disabled": { + "message": "Disabled" + }, + "revoked": { + "message": "Revoked" + }, + "sendLink": { + "message": "Send link", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "copySendLink": { + "message": "Copy Send link", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "removePassword": { + "message": "Remove password" + }, + "removedPassword": { + "message": "Password removed" + }, + "removePasswordConfirmation": { + "message": "Are you sure you want to remove the password?" + }, + "hideEmail": { + "message": "Hide my email address from recipients." + }, + "disableThisSend": { + "message": "Deactivate this Send so that no one can access it.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "allSends": { + "message": "All Sends" + }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, + "pendingDeletion": { + "message": "Pending deletion" + }, + "expired": { + "message": "Expired" + }, + "searchSends": { + "message": "Search Sends", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendProtectedPassword": { + "message": "This Send is protected with a password. Please type the password below to continue.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendProtectedPasswordDontKnow": { + "message": "Don't know the password? Ask the sender for the password needed to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendHiddenByDefault": { + "message": "This Send is hidden by default. You can toggle its visibility using the button below.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "downloadFile": { + "message": "Download file" + }, + "sendAccessUnavailable": { + "message": "The Send you are trying to access does not exist or is no longer available.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "missingSendFile": { + "message": "The file associated with this Send could not be found.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "noSendsInList": { + "message": "There are no Sends to list.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "emergencyAccessDesc": { + "message": "Grant and manage emergency access for trusted contacts. Trusted contacts may request access to either View or Takeover your account in case of an emergency. Visit our help page for more information and details into how zero knowledge sharing works." + }, + "emergencyAccessOwnerWarning": { + "message": "You are an owner of one or more organizations. If you give takeover access to an emergency contact, they will be able to use all your permissions as owner after a takeover." + }, + "trustedEmergencyContacts": { + "message": "Trusted emergency contacts" + }, + "noTrustedContacts": { + "message": "You have not added any emergency contacts yet, invite a trusted contact to get started." + }, + "addEmergencyContact": { + "message": "Add emergency contact" + }, + "designatedEmergencyContacts": { + "message": "Designated as emergency contact" + }, + "noGrantedAccess": { + "message": "You have not been designated as an emergency contact for anyone yet." + }, + "inviteEmergencyContact": { + "message": "Invite emergency contact" + }, + "editEmergencyContact": { + "message": "Edit emergency contact" + }, + "inviteEmergencyContactDesc": { + "message": "Invite a new emergency contact by entering their Bitwarden account email address below. If they do not have a Bitwarden account already, they will be prompted to create a new account." + }, + "emergencyAccessRecoveryInitiated": { + "message": "Emergency access initiated" + }, + "emergencyAccessRecoveryApproved": { + "message": "Emergency access approved" + }, + "viewDesc": { + "message": "Can view all items in your own vault." + }, + "takeover": { + "message": "Takeover" + }, + "takeoverDesc": { + "message": "Can reset your account with a new master password." + }, + "waitTime": { + "message": "Wait time" + }, + "waitTimeDesc": { + "message": "Time required before automatically granting access." + }, + "oneDay": { + "message": "1 day" + }, + "days": { + "message": "$DAYS$ days", + "placeholders": { + "days": { + "content": "$1", + "example": "1" + } + } + }, + "invitedUser": { + "message": "Invited user." + }, + "acceptEmergencyAccess": { + "message": "You've been invited to become an emergency contact for the user listed above. To accept the invitation, you need to log in or create a new Bitwarden account." + }, + "emergencyInviteAcceptFailed": { + "message": "Unable to accept invitation. Ask the user to send a new invitation." + }, + "emergencyInviteAcceptFailedShort": { + "message": "Unable to accept invitation. $DESCRIPTION$", + "placeholders": { + "description": { + "content": "$1", + "example": "You must set up 2FA on your user account before you can join this organization." + } + } + }, + "emergencyInviteAcceptedDesc": { + "message": "You can access the emergency options for this user after your identity has been confirmed. We'll send you an email when that happens." + }, + "requestAccess": { + "message": "Request Access" + }, + "requestAccessConfirmation": { + "message": "Are you sure you want to request emergency access? You will be provided access after $WAITTIME$ day(s) or whenever the user manually approves the request.", + "placeholders": { + "waittime": { + "content": "$1", + "example": "1" + } + } + }, + "requestSent": { + "message": "Emergency access requested for $USER$. We'll notify you by email when it's possible to continue.", + "placeholders": { + "user": { + "content": "$1", + "example": "John Smith" + } + } + }, + "approve": { + "message": "Approve" + }, + "reject": { + "message": "Reject" + }, + "approveAccessConfirmation": { + "message": "Are you sure you want to approve emergency access? This will allow $USER$ to $ACTION$ your account.", + "placeholders": { + "user": { + "content": "$1", + "example": "John Smith" + }, + "action": { + "content": "$2", + "example": "View" + } + } + }, + "emergencyApproved": { + "message": "Emergency access approved" + }, + "emergencyRejected": { + "message": "Emergency access rejected" + }, + "passwordResetFor": { + "message": "Password reset for $USER$. You can now login using the new password.", + "placeholders": { + "user": { + "content": "$1", + "example": "John Smith" + } + } + }, + "personalOwnership": { + "message": "Remove individual vault" + }, + "personalOwnershipPolicyDesc": { + "message": "Require members to save items to an organization by removing the individual vault option." + }, + "personalOwnershipExemption": { + "message": "Organization owners and administrators are exempt from this policy's enforcement." + }, + "personalOwnershipSubmitError": { + "message": "Due to an Enterprise policy, you are restricted from saving items to your individual vault. Change the ownership option to an organization and choose from available collections." + }, + "disableSend": { + "message": "Remove Send" + }, + "disableSendPolicyDesc": { + "message": "Do not allow members to create or edit Sends.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "disableSendExemption": { + "message": "Organization members that can manage the organization's policies are exempt from this policy's enforcement." + }, + "sendDisabled": { + "message": "Send removed", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendDisabledWarning": { + "message": "Due to an Enterprise policy, you are only able to delete an existing Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendOptions": { + "message": "Send options", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendOptionsPolicyDesc": { + "message": "Set options for creating and editing Sends.", + "description": "'Sends' is a plural noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendOptionsExemption": { + "message": "Organization members that can manage the organization's policies are exempt from this policy's enforcement." + }, + "disableHideEmail": { + "message": "Always show member’s email address with recipients when creating or editing a Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendOptionsPolicyInEffect": { + "message": "The following organization policies are currently in effect:" + }, + "sendDisableHideEmailInEffect": { + "message": "Users are not allowed to hide their email address from recipients when creating or editing a Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "modifiedPolicyId": { + "message": "Modified policy $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "Master Password" + } + } + }, + "planPrice": { + "message": "Plan price" + }, + "estimatedTax": { + "message": "Estimated tax" + }, + "custom": { + "message": "Custom" + }, + "customDesc": { + "message": "Grant customized permissions to members" + }, + "customDescNonEnterpriseStart": { + "message": "Custom roles is an ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseLink": { + "message": "enterprise feature", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseEnd": { + "message": ". Contact our support team to upgrade your subscription", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customNonEnterpriseError": { + "message": "To enable custom permissions the organization must be on an Enterprise 2020 plan." + }, + "permissions": { + "message": "Permissions" + }, + "permission": { + "message": "Permission" + }, + "managerPermissions": { + "message": "Manager Permissions" + }, + "adminPermissions": { + "message": "Admin Permissions" + }, + "accessEventLogs": { + "message": "Access event logs" + }, + "accessImportExport": { + "message": "Access import/export" + }, + "accessReports": { + "message": "Access reports" + }, + "missingPermissions": { + "message": "You lack the necessary permissions to perform this action." + }, + "manageAllCollections": { + "message": "Manage all collections" + }, + "createNewCollections": { + "message": "Create new collections" + }, + "editAnyCollection": { + "message": "Edit any collection" + }, + "deleteAnyCollection": { + "message": "Delete any collection" + }, + "manageAssignedCollections": { + "message": "Manage assigned collections" + }, + "editAssignedCollections": { + "message": "Edit assigned collections" + }, + "deleteAssignedCollections": { + "message": "Delete assigned collections" + }, + "manageGroups": { + "message": "Manage groups" + }, + "managePolicies": { + "message": "Manage policies" + }, + "manageSso": { + "message": "Manage SSO" + }, + "manageUsers": { + "message": "Manage users" + }, + "manageResetPassword": { + "message": "Manage password reset" + }, + "disableRequiredError": { + "message": "You must manually turn the $POLICYNAME$ policy before this policy can be turned off.", + "placeholders": { + "policyName": { + "content": "$1", + "example": "Single Sign-On Authentication" + } + } + }, + "personalOwnershipPolicyInEffect": { + "message": "An organization policy is affecting your ownership options." + }, + "personalOwnershipPolicyInEffectImports": { + "message": "An organization policy has blocked importing items into your individual vault." + }, + "personalOwnershipCheckboxDesc": { + "message": "Remove individual ownership for organization users" + }, + "textHiddenByDefault": { + "message": "When accessing the Send, hide the text by default", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendNameDesc": { + "message": "A friendly name to describe this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendTextDesc": { + "message": "The text you want to Send." + }, + "sendFileDesc": { + "message": "The file you want to Send." + }, + "copySendLinkOnSave": { + "message": "Copy the link to share this Send to my clipboard upon save." + }, + "sendLinkLabel": { + "message": "Send link", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "send": { + "message": "Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendAccessTaglineProductDesc": { + "message": "Bitwarden Send transmits sensitive, temporary information to others easily and securely.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendAccessTaglineLearnMore": { + "message": "Learn more about", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read '**Learn more about** Bitwarden Send or sign up to try it today.'" + }, + "sendVaultCardProductDesc": { + "message": "Share text or files directly with anyone." + }, + "sendVaultCardLearnMore": { + "message": "Learn more", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read '**Learn more**, see how it works, or try it now. '" + }, + "sendVaultCardSee": { + "message": "see", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Learn more, **see** how it works, or try it now.'" + }, + "sendVaultCardHowItWorks": { + "message": "how it works", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Learn more, see **how it works**, or try it now.'" + }, + "sendVaultCardOr": { + "message": "or", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Learn more, see how it works, **or** try it now.'" + }, + "sendVaultCardTryItNow": { + "message": "try it now", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Learn more, see how it works, or **try it now**.'" + }, + "sendAccessTaglineOr": { + "message": "or", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Learn more about Bitwarden Send **or** sign up to try it today.'" + }, + "sendAccessTaglineSignUp": { + "message": "sign up", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Learn more about Bitwarden Send or **sign up** to try it today.'" + }, + "sendAccessTaglineTryToday": { + "message": "to try it today.", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Learn more about Bitwarden Send or sign up to **try it today.**'" + }, + "sendCreatorIdentifier": { + "message": "Bitwarden user $USER_IDENTIFIER$ shared the following with you", + "placeholders": { + "user_identifier": { + "content": "$1", + "example": "An email address" + } + } + }, + "viewSendHiddenEmailWarning": { + "message": "The Bitwarden user who created this Send has chosen to hide their email address. You should ensure you trust the source of this link before using or downloading its content.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "expirationDateIsInvalid": { + "message": "The expiration date provided is not valid." + }, + "deletionDateIsInvalid": { + "message": "The deletion date provided is not valid." + }, + "expirationDateAndTimeRequired": { + "message": "An expiration date and time are required." + }, + "deletionDateAndTimeRequired": { + "message": "A deletion date and time are required." + }, + "dateParsingError": { + "message": "There was an error saving your deletion and expiration dates." + }, + "webAuthnFallbackMsg": { + "message": "To verify your 2FA please click the button below." + }, + "webAuthnAuthenticate": { + "message": "Authenticate WebAuthn" + }, + "webAuthnNotSupported": { + "message": "WebAuthn is not supported in this browser." + }, + "webAuthnSuccess": { + "message": "WebAuthn verified successfully! You may close this tab." + }, + "hintEqualsPassword": { + "message": "Your password hint cannot be the same as your password." + }, + "enrollPasswordReset": { + "message": "Enroll in password reset" + }, + "enrolledPasswordReset": { + "message": "Enrolled in password reset" + }, + "withdrawPasswordReset": { + "message": "Withdraw from password reset" + }, + "enrollPasswordResetSuccess": { + "message": "Enrollment success!" + }, + "withdrawPasswordResetSuccess": { + "message": "Withdrawal success!" + }, + "eventEnrollPasswordReset": { + "message": "User $ID$ enrolled in password reset.", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "eventWithdrawPasswordReset": { + "message": "User $ID$ withdrew from password reset.", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "eventAdminPasswordReset": { + "message": "Master password reset for user $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "eventResetSsoLink": { + "message": "Reset SSO link for user $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "firstSsoLogin": { + "message": "$ID$ logged in using Sso for the first time", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "resetPassword": { + "message": "Reset password" + }, + "resetPasswordLoggedOutWarning": { + "message": "Proceeding will log $NAME$ out of their current session, requiring them to log back in. Active sessions on other devices may continue to remain active for up to one hour.", + "placeholders": { + "name": { + "content": "$1", + "example": "John Smith" + } + } + }, + "thisUser": { + "message": "this user" + }, + "resetPasswordMasterPasswordPolicyInEffect": { + "message": "One or more organization policies require the master password to meet the following requirements:" + }, + "resetPasswordSuccess": { + "message": "Password reset success!" + }, + "resetPasswordEnrollmentWarning": { + "message": "Enrollment will allow organization administrators to change your master password" + }, + "resetPasswordPolicy": { + "message": "Master password reset" + }, + "resetPasswordPolicyDescription": { + "message": "Allow admins to reset master passwords for members." + }, + "resetPasswordPolicyWarning": { + "message": "Members in the organization will need to self-enroll or be auto-enrolled before administrators can reset their master password." + }, + "resetPasswordPolicyAutoEnroll": { + "message": "Automatic enrollment" + }, + "resetPasswordPolicyAutoEnrollDescription": { + "message": "All members will be automatically enrolled in password reset once their invite is accepted and will not be allowed to withdraw." + }, + "resetPasswordPolicyAutoEnrollWarning": { + "message": "Members already in the organization will not be retroactively enrolled in password reset. They will need to self-enroll before administrators can reset their master password." + }, + "resetPasswordPolicyAutoEnrollCheckbox": { + "message": "Require new members to be enrolled automatically" + }, + "resetPasswordAutoEnrollInviteWarning": { + "message": "This organization has an Enterprise policy that will automatically enroll you in password reset. Enrollment will allow organization administrators to change your master password." + }, + "resetPasswordOrgKeysError": { + "message": "Organization keys response is null" + }, + "resetPasswordDetailsError": { + "message": "Reset password details response is null" + }, + "trashCleanupWarning": { + "message": "Items that have been in trash more than 30 days will be automatically deleted." + }, + "trashCleanupWarningSelfHosted": { + "message": "Items that have been in trash for a while will be automatically deleted." + }, + "passwordPrompt": { + "message": "Master password re-prompt" + }, + "passwordConfirmation": { + "message": "Master password confirmation" + }, + "passwordConfirmationDesc": { + "message": "This action is protected. To continue, please re-enter your master password to verify your identity." + }, + "reinviteSelected": { + "message": "Resend invitations" + }, + "resendNotification": { + "message": "Resend notification" + }, + "noSelectedUsersApplicable": { + "message": "This action is not applicable to any of the selected users." + }, + "removeUsersWarning": { + "message": "Are you sure you want to remove the following users? The process may take a few seconds to complete and cannot be interrupted or canceled." + }, + "removeOrgUsersConfirmation": { + "message": "When member(s) are removed, they no longer have access to organization data and this action is irreversible. To add the member back to the organization, they must be invited and onboarded again. The process may take a few seconds to complete and cannot be interrupted or canceled." + }, + "revokeUsersWarning": { + "message": "When member(s) are revoked, they no longer have access to organization data. To quickly restore member access, go to the Revoked tab. The process may take a few seconds to complete and cannot be interrupted or canceled." + }, + "theme": { + "message": "Theme" + }, + "themeDesc": { + "message": "Choose a theme for your web vault." + }, + "themeSystem": { + "message": "Use system theme" + }, + "themeDark": { + "message": "Dark" + }, + "themeLight": { + "message": "Light" + }, + "confirmSelected": { + "message": "Confirm selected" + }, + "bulkConfirmStatus": { + "message": "Bulk action status" + }, + "bulkConfirmMessage": { + "message": "Confirmed successfully" + }, + "bulkReinviteMessage": { + "message": "Reinvited successfully" + }, + "bulkRemovedMessage": { + "message": "Removed successfully" + }, + "bulkRevokedMessage": { + "message": "Revoked organization access successfully" + }, + "bulkRestoredMessage": { + "message": "Restored organization access successfully" + }, + "bulkFilteredMessage": { + "message": "Excluded, not applicable for this action" + }, + "fingerprint": { + "message": "Fingerprint" + }, + "removeUsers": { + "message": "Remove users" + }, + "revokeUsers": { + "message": "Revoke users" + }, + "restoreUsers": { + "message": "Restore users" + }, + "error": { + "message": "Error" + }, + "resetPasswordManageUsers": { + "message": "Manage users must also be granted with the manage password reset permission" + }, + "setupProvider": { + "message": "Provider setup" + }, + "setupProviderLoginDesc": { + "message": "You've been invited to setup a new Provider. To continue, you need to log in or create a new Bitwarden account." + }, + "setupProviderDesc": { + "message": "Please enter the details below to complete the Provider setup. Contact Customer Support if you have any questions." + }, + "providerName": { + "message": "Provider name" + }, + "providerSetup": { + "message": "Provider successfully set up" + }, + "clients": { + "message": "Clients" + }, + "client": { + "message": "Client", + "description": "This is used as a table header to describe which client application created an event log." + }, + "providerAdmin": { + "message": "Provider admin" + }, + "providerAdminDesc": { + "message": "The highest access user that can manage all aspects of your Provider as well as access and manage client organizations." + }, + "serviceUser": { + "message": "Service user" + }, + "serviceUserDesc": { + "message": "Service users can access and manage all client organizations." + }, + "providerInviteUserDesc": { + "message": "Invite a new user to your Provider by entering their Bitwarden account email address below. If they do not have a Bitwarden account already, they will be prompted to create a new account." + }, + "joinProvider": { + "message": "Join Provider" + }, + "joinProviderDesc": { + "message": "You've been invited to join the Provider listed above. To accept the invitation, you need to log in or create a new Bitwarden account." + }, + "providerInviteAcceptFailed": { + "message": "Unable to accept invitation. Ask a Provider admin to send a new invitation." + }, + "providerInviteAcceptedDesc": { + "message": "You can access this Provider once an administrator confirms your membership. We'll send you an email when that happens." + }, + "providerUsersNeedConfirmed": { + "message": "You have users that have accepted their invitation, but still need to be confirmed. Users will not have access to the Provider until they are confirmed." + }, + "provider": { + "message": "Provider" + }, + "newClientOrganization": { + "message": "New client organization" + }, + "newClientOrganizationDesc": { + "message": "Create a new client organization that will be associated with you as the Provider. You will be able to access and manage this organization." + }, + "addExistingOrganization": { + "message": "Add existing organization" + }, + "myProvider": { + "message": "My Provider" + }, + "addOrganizationConfirmation": { + "message": "Are you sure you want to add $ORGANIZATION$ as a client to $PROVIDER$?", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + }, + "provider": { + "content": "$2", + "example": "My Provider Name" + } + } + }, + "organizationJoinedProvider": { + "message": "Organization was successfully added to the Provider" + }, + "accessingUsingProvider": { + "message": "Accessing organization using Provider $PROVIDER$", + "placeholders": { + "provider": { + "content": "$1", + "example": "My Provider Name" + } + } + }, + "providerIsDisabled": { + "message": "Provider suspended" + }, + "providerUpdated": { + "message": "Provider saved" + }, + "yourProviderIs": { + "message": "Your Provider is $PROVIDER$. They have administrative and billing privileges for your organization.", + "placeholders": { + "provider": { + "content": "$1", + "example": "My Provider Name" + } + } + }, + "detachedOrganization": { + "message": "The organization $ORGANIZATION$ has been detached from your Provider.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "detachOrganizationConfirmation": { + "message": "Are you sure you want to detach this organization? The organization will continue to exist but will no longer be managed by the Provider." + }, + "add": { + "message": "Add" + }, + "updatedMasterPassword": { + "message": "Master password saved" + }, + "updateMasterPassword": { + "message": "Update master password" + }, + "updateMasterPasswordWarning": { + "message": "Your master password was recently changed by an administrator in your organization. In order to access the vault, you must update your master password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." + }, + "masterPasswordInvalidWarning": { + "message": "Your master password does not meet the policy requirements of this organization. In order to join the organization, you must update your master password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." + }, + "maximumVaultTimeout": { + "message": "Vault timeout" + }, + "maximumVaultTimeoutDesc": { + "message": "Set a maximum vault timeout for members." + }, + "maximumVaultTimeoutLabel": { + "message": "Maximum vault timeout" + }, + "invalidMaximumVaultTimeout": { + "message": "Invalid maximum vault timeout." + }, + "hours": { + "message": "Hours" + }, + "minutes": { + "message": "Minutes" + }, + "vaultTimeoutPolicyInEffect": { + "message": "Your organization policies are affecting your vault timeout. Maximum allowed vault timeout is $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "customVaultTimeout": { + "message": "Custom vault timeout" + }, + "vaultTimeoutToLarge": { + "message": "Your vault timeout exceeds the restriction set by your organization." + }, + "vaultCustomTimeoutMinimum": { + "message": "Minimum custom timeout is 1 minute." + }, + "vaultTimeoutRangeError": { + "message": "Vault timeout is not within allowed range." + }, + "disablePersonalVaultExport": { + "message": "Remove individual vault export" + }, + "disablePersonalVaultExportDesc": { + "message": "Do not allow members to export their individual vault data." + }, + "vaultExportDisabled": { + "message": "Vault export removed" + }, + "personalVaultExportPolicyInEffect": { + "message": "One or more organization policies prevents you from exporting your individual vault." + }, + "selectType": { + "message": "Select SSO type" + }, + "type": { + "message": "Type" + }, + "openIdConnectConfig": { + "message": "OpenID connect configuration" + }, + "samlSpConfig": { + "message": "SAML service provider configuration" + }, + "samlIdpConfig": { + "message": "SAML identity provider configuration" + }, + "callbackPath": { + "message": "Callback path" + }, + "signedOutCallbackPath": { + "message": "Signed out callback path" + }, + "authority": { + "message": "Authority" + }, + "clientId": { + "message": "Client ID" + }, + "clientSecret": { + "message": "Client secret" + }, + "metadataAddress": { + "message": "Metadata address" + }, + "oidcRedirectBehavior": { + "message": "OIDC redirect behavior" + }, + "getClaimsFromUserInfoEndpoint": { + "message": "Get claims from user info endpoint" + }, + "additionalScopes": { + "message": "Custom scopes" + }, + "additionalUserIdClaimTypes": { + "message": "Custom user ID claim types" + }, + "additionalEmailClaimTypes": { + "message": "Email claim types" + }, + "additionalNameClaimTypes": { + "message": "Custom name claim types" + }, + "acrValues": { + "message": "Requested authentication context class reference values" + }, + "expectedReturnAcrValue": { + "message": "Expected \"acr\" claim value in response" + }, + "spEntityId": { + "message": "SP entity ID" + }, + "spMetadataUrl": { + "message": "SAML 2.0 metadata URL" + }, + "spAcsUrl": { + "message": "Assertion consumer service (ACS) URL" + }, + "spNameIdFormat": { + "message": "Name ID format" + }, + "spOutboundSigningAlgorithm": { + "message": "Outbound signing algorithm" + }, + "spSigningBehavior": { + "message": "Signing behavior" + }, + "spMinIncomingSigningAlgorithm": { + "message": "Minimum incoming signing algorithm" + }, + "spWantAssertionsSigned": { + "message": "Expect signed assertions" + }, + "spValidateCertificates": { + "message": "Validate certificates" + }, + "idpEntityId": { + "message": "Entity ID" + }, + "idpBindingType": { + "message": "Binding type" + }, + "idpSingleSignOnServiceUrl": { + "message": "Single sign-on service URL" + }, + "idpSingleLogoutServiceUrl": { + "message": "Single log-out service URL" + }, + "idpX509PublicCert": { + "message": "X509 public certificate" + }, + "idpOutboundSigningAlgorithm": { + "message": "Outbound signing algorithm" + }, + "idpAllowUnsolicitedAuthnResponse": { + "message": "Allow unsolicited authentication response" + }, + "idpAllowOutboundLogoutRequests": { + "message": "Allow outbound logout requests" + }, + "idpSignAuthenticationRequests": { + "message": "Sign authentication requests" + }, + "ssoSettingsSaved": { + "message": "Single sign-on configuration saved" + }, + "sponsoredFamilies": { + "message": "Free Bitwarden Families" + }, + "sponsoredFamiliesEligible": { + "message": "You and your family are eligible for Free Bitwarden Families. Redeem with your personal email to keep your data secure even when you are not at work." + }, + "sponsoredFamiliesEligibleCard": { + "message": "Redeem your Free Bitwarden for Families plan today to keep your data secure even when you are not at work." + }, + "sponsoredFamiliesInclude": { + "message": "The Bitwarden for Families plan include" + }, + "sponsoredFamiliesPremiumAccess": { + "message": "Premium access for up to 6 users" + }, + "sponsoredFamiliesSharedCollections": { + "message": "Shared collections for Family secrets" + }, + "badToken": { + "message": "The link is no longer valid. Please have the sponsor resend the offer." + }, + "reclaimedFreePlan": { + "message": "Reclaimed free plan" + }, + "redeem": { + "message": "Redeem" + }, + "sponsoredFamiliesSelectOffer": { + "message": "Select the organization you would like sponsored" + }, + "familiesSponsoringOrgSelect": { + "message": "Which Free Families offer would you like to redeem?" + }, + "sponsoredFamiliesEmail": { + "message": "Enter your personal email to redeem Bitwarden Families" + }, + "sponsoredFamiliesLeaveCopy": { + "message": "If you remove an offer or are removed from the sponsoring organization, your Families sponsorship will expire at the next renewal date." + }, + "acceptBitwardenFamiliesHelp": { + "message": "Accept offer for an existing organization or create a new Families organization." + }, + "setupSponsoredFamiliesLoginDesc": { + "message": "You've been offered a free Bitwarden Families plan organization. To continue, you need to log in to the account that received the offer." + }, + "sponsoredFamiliesAcceptFailed": { + "message": "Unable to accept offer. Please resend the offer email from your Enterprise account and try again." + }, + "sponsoredFamiliesAcceptFailedShort": { + "message": "Unable to accept offer. $DESCRIPTION$", + "placeholders": { + "description": { + "content": "$1", + "example": "You must have at least one existing Families organization." + } + } + }, + "sponsoredFamiliesOffer": { + "message": "Accept Free Bitwarden Families" + }, + "sponsoredFamiliesOfferRedeemed": { + "message": "Free Bitwarden Families offer successfully redeemed" + }, + "redeemed": { + "message": "Redeemed" + }, + "redeemedAccount": { + "message": "Account redeemed" + }, + "revokeAccount": { + "message": "Revoke account $NAME$", + "placeholders": { + "name": { + "content": "$1", + "example": "My Sponsorship Name" + } + } + }, + "resendEmailLabel": { + "message": "Resend sponsorship email to $NAME$ sponsorship", + "placeholders": { + "name": { + "content": "$1", + "example": "My Sponsorship Name" + } + } + }, + "freeFamiliesPlan": { + "message": "Free Families plan" + }, + "redeemNow": { + "message": "Redeem now" + }, + "recipient": { + "message": "Recipient" + }, + "removeSponsorship": { + "message": "Remove sponsorship" + }, + "removeSponsorshipConfirmation": { + "message": "After removing a sponsorship, you will be responsible for this subscription and related invoices. Are you sure you want to continue?" + }, + "sponsorshipCreated": { + "message": "Sponsorship created" + }, + "emailSent": { + "message": "Email sent" + }, + "revokeSponsorshipConfirmation": { + "message": "After removing this account, the Families plan sponsorship will expire at the end of the billing period. You will not be able to redeem a new sponsorship offer until the existing one expires. Are you sure you want to continue?" + }, + "removeSponsorshipSuccess": { + "message": "Sponsorship removed" + }, + "ssoKeyConnectorError": { + "message": "Key Connector error: make sure Key Connector is available and working correctly." + }, + "keyConnectorUrl": { + "message": "Key Connector URL" + }, + "sendVerificationCode": { + "message": "Send a verification code to your email" + }, + "sendCode": { + "message": "Send code" + }, + "codeSent": { + "message": "Code sent" + }, + "verificationCode": { + "message": "Verification code" + }, + "confirmIdentity": { + "message": "Confirm your identity to continue." + }, + "verificationCodeRequired": { + "message": "Verification code is required." + }, + "invalidVerificationCode": { + "message": "Invalid verification code" + }, + "convertOrganizationEncryptionDesc": { + "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "leaveOrganization": { + "message": "Leave organization" + }, + "removeMasterPassword": { + "message": "Remove master password" + }, + "removedMasterPassword": { + "message": "Master password removed" + }, + "allowSso": { + "message": "Allow SSO authentication" + }, + "allowSsoDesc": { + "message": "Once set up, your configuration will be saved and members will be able to authenticate using their Identity Provider credentials." + }, + "ssoPolicyHelpStart": { + "message": "Use the", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Use the require single-sign-on authentication policy to require all members to log in with SSO.'" + }, + "ssoPolicyHelpLink": { + "message": "require single-sign-on authentication policy", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Use the require single-sign-on authentication policy to require all members to log in with SSO.'" + }, + "ssoPolicyHelpEnd": { + "message": "to require all members to log in with SSO.", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Use the require single-sign-on authentication policy to require all members to log in with SSO.'" + }, + "ssoPolicyHelpKeyConnector": { + "message": "The require SSO authentication and single organization policies are required to set up Key Connector decryption." + }, + "memberDecryptionOption": { + "message": "Member decryption options" + }, + "memberDecryptionPassDesc": { + "message": "Once authenticated, members will decrypt vault data using their master passwords." + }, + "keyConnector": { + "message": "Key Connector" + }, + "memberDecryptionKeyConnectorDesc": { + "message": "Connect login with SSO to your self-hosted decryption key server. Using this option, members won’t need to use their master passwords to decrypt vault data. Contact Bitwarden Support for set up assistance." + }, + "keyConnectorPolicyRestriction": { + "message": "\"Login with SSO and Key Connector Decryption\" is activated. This policy will only apply to owners and admins." + }, + "enabledSso": { + "message": "SSO turned on" + }, + "disabledSso": { + "message": "SSO turned on" + }, + "enabledKeyConnector": { + "message": "Key Connector activated" + }, + "disabledKeyConnector": { + "message": "Key Connector deactivated" + }, + "keyConnectorWarning": { + "message": "Once members begin using Key Connector, your organization cannot revert to master password decryption. Proceed only if you are comfortable deploying and managing a key server." + }, + "migratedKeyConnector": { + "message": "Migrated to Key Connector" + }, + "paymentSponsored": { + "message": "Please provide a payment method to associate with the organization. Don't worry, we won't charge you anything unless you select additional features or your sponsorship expires. " + }, + "orgCreatedSponsorshipInvalid": { + "message": "The sponsorship offer has expired. You may delete the organization you created to avoid a charge at the end of your 7 day trial. Otherwise you may close this prompt to keep the organization and assume billing responsibility." + }, + "newFamiliesOrganization": { + "message": "New Families organization" + }, + "acceptOffer": { + "message": "Accept offer" + }, + "sponsoringOrg": { + "message": "Sponsoring organization" + }, + "keyConnectorTest": { + "message": "Test" + }, + "keyConnectorTestSuccess": { + "message": "Success! Key Connector reached." + }, + "keyConnectorTestFail": { + "message": "Cannot reach Key Connector. Check URL." + }, + "sponsorshipTokenHasExpired": { + "message": "The sponsorship offer has expired." + }, + "freeWithSponsorship": { + "message": "FREE with sponsorship" + }, + "viewBillingSyncToken": { + "message": "View billing sync token" + }, + "generateBillingSyncToken": { + "message": "Generate billing sync token" + }, + "copyPasteBillingSync": { + "message": "Copy and paste this token into the billing sync settings of your self-hosted organization." + }, + "billingSyncCanAccess": { + "message": "Your billing sync token can access and edit this organization's subscription settings." + }, + "manageBillingSync": { + "message": "Manage billing sync" + }, + "setUpBillingSync": { + "message": "Set up billing sync" + }, + "generateToken": { + "message": "Generate token" + }, + "rotateToken": { + "message": "Rotate token" + }, + "rotateBillingSyncTokenWarning": { + "message": "If you proceed, you will need to re-setup billing sync on your self-hosted server." + }, + "rotateBillingSyncTokenTitle": { + "message": "Rotating the billing sync token will invalidate the previous token." + }, + "selfHostingTitle": { + "message": "Self-hosting" + }, + "selfHostingEnterpriseOrganizationSectionCopy": { + "message": "To set-up your organization on your own server, you will need to upload your license file. To support Free Families plans and advanced billing capabilities for your self-hosted organization, you will need to set up billing sync." + }, + "billingSyncApiKeyRotated": { + "message": "Token rotated" + }, + "billingSync": { + "message": "Billing sync" + }, + "billingSyncDesc": { + "message": "Billing sync provides Free Families plans for members and advanced billing capabilities by linking your self-hosted Bitwarden to the Bitwarden cloud server." + }, + "billingSyncKeyDesc": { + "message": "A billing sync token from your cloud organization's subscription settings is required to complete this form." + }, + "billingSyncKey": { + "message": "Billing sync token" + }, + "active": { + "message": "Active" + }, + "inactive": { + "message": "Inactive" + }, + "sentAwaitingSync": { + "message": "Sent (awaiting sync)" + }, + "sent": { + "message": "Sent" + }, + "requestRemoved": { + "message": "Removed (awaiting sync)" + }, + "requested": { + "message": "Requested" + }, + "formErrorSummaryPlural": { + "message": "$COUNT$ fields above need your attention.", + "placeholders": { + "count": { + "content": "$1", + "example": "5" + } + } + }, + "formErrorSummarySingle": { + "message": "1 field above needs your attention." + }, + "fieldRequiredError": { + "message": "$FIELDNAME$ is required.", + "placeholders": { + "fieldname": { + "content": "$1", + "example": "Full name" + } + } + }, + "required": { + "message": "required" + }, + "idpSingleSignOnServiceUrlRequired": { + "message": "Required if Entity ID is not a URL." + }, + "openIdOptionalCustomizations": { + "message": "Optional customizations" + }, + "openIdAuthorityRequired": { + "message": "Required if Authority is not valid." + }, + "separateMultipleWithComma": { + "message": "Separate multiple with a comma." + }, + "sessionTimeout": { + "message": "Your session has timed out. Please go back and try logging in again." + }, + "exportingPersonalVaultTitle": { + "message": "Exporting individual vault" + }, + "exportingOrganizationVaultTitle": { + "message": "Exporting organization vault" + }, + "exportingPersonalVaultDescription": { + "message": "Only the individual vault items associated with $EMAIL$ will be exported. Organization vault items will not be included.", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "exportingOrganizationVaultDescription": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Individual vault items and items from other organizations will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "accessDenied": { + "message": "Access denied. You do not have permission to view this page." + }, + "masterPassword": { + "message": "Master password" + }, + "security": { + "message": "Security" + }, + "keys": { + "message": "Keys" + }, + "billingHistory": { + "message": "Billing history" + }, + "backToReports": { + "message": "Back to reports" + }, + "organizationPicker": { + "message": "Organization picker" + }, + "currentOrganization": { + "message": "Current organization", + "description": "This is used by screen readers to indicate the organization that is currently being shown to the user." + }, + "accountSettings": { + "message": "Account settings" + }, + "generator": { + "message": "Generator" + }, + "whatWouldYouLikeToGenerate": { + "message": "What would you like to generate?" + }, + "passwordType": { + "message": "Password type" + }, + "regenerateUsername": { + "message": "Regenerate username" + }, + "generateUsername": { + "message": "Generate username" + }, + "usernameType": { + "message": "Username type" + }, + "plusAddressedEmail": { + "message": "Plus addressed email", + "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" + }, + "plusAddressedEmailDesc": { + "message": "Use your email provider's sub-addressing capabilities." + }, + "catchallEmail": { + "message": "Catch-all email" + }, + "catchallEmailDesc": { + "message": "Use your domain's configured catch-all inbox." + }, + "random": { + "message": "Random", + "description": "Generates domain-based username using random letters" + }, + "randomWord": { + "message": "Random word" + }, + "service": { + "message": "Service" + }, + "unknownCipher": { + "message": "Unknown item, you may need to request permission to access this item." + }, + "cannotSponsorSelf": { + "message": "You cannot redeem for the active account. Enter a different email." + }, + "revokeWhenExpired": { + "message": "Expires $DATE$", + "placeholders": { + "date": { + "content": "$1", + "example": "12/31/2020" + } + } + }, + "awaitingSyncSingular": { + "message": "Token rotated $DAYS$ day ago. Update the billing sync token in your self-hosted organization settings.", + "placeholders": { + "days": { + "content": "$1", + "example": "1" + } + } + }, + "awaitingSyncPlural": { + "message": "Token rotated $DAYS$ days ago. Update the billing sync token in your self-hosted organization settings.", + "placeholders": { + "days": { + "content": "$1", + "example": "1" + } + } + }, + "lastSync": { + "message": "Last sync", + "Description": "Used as a prefix to indicate the last time a sync occured. Example \"Last sync 1968-11-16 00:00:00\"" + }, + "sponsorshipsSynced": { + "message": "Self-hosted sponsorships synced." + }, + "billingManagedByProvider": { + "message": "Managed by $PROVIDER$", + "placeholders": { + "provider": { + "content": "$1", + "example": "Managed Services Company" + } + } + }, + "billingContactProviderForAssistance": { + "message": "Please reach out to them for further assistance", + "description": "This text is displayed if an organization's billing is managed by a Provider. It tells the user to contact the Provider for assistance." + }, + "forwardedEmail": { + "message": "Forwarded email alias" + }, + "forwardedEmailDesc": { + "message": "Generate an email alias with an external forwarding service." + }, + "hostname": { + "message": "Hostname", + "description": "Part of a URL." + }, + "apiAccessToken": { + "message": "API access token" + }, + "deviceVerification": { + "message": "Device verification" + }, + "enableDeviceVerification": { + "message": "Turn on device verification" + }, + "deviceVerificationDesc": { + "message": "Verification codes are sent to your email address when logging in from an unrecognized device" + }, + "updatedDeviceVerification": { + "message": "Updated device verification" + }, + "areYouSureYouWantToEnableDeviceVerificationTheVerificationCodeEmailsWillArriveAtX": { + "message": "Are you sure you want to turn on device verification? The verification code emails will arrive at: $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "My Email" + } + } + }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, + "scim": { + "message": "SCIM provisioning", + "description": "The text, 'SCIM', is an acronymn and should not be translated." + }, + "scimDescription": { + "message": "Automatically provision users and groups with your preferred identity provider via SCIM provisioning", + "description": "the text, 'SCIM', is an acronymn and should not be translated." + }, + "scimEnabledCheckboxDesc": { + "message": "Enable SCIM", + "description": "the text, 'SCIM', is an acronymn and should not be translated." + }, + "scimEnabledCheckboxDescHelpText": { + "message": "Set up your preferred identity provider by configuring the URL and SCIM API Key", + "description": "the text, 'SCIM', is an acronymn and should not be translated." + }, + "scimApiKeyHelperText": { + "message": "This API key has access to manage users within your organization. It should be kept secret." + }, + "copyScimKey": { + "message": "Copy the SCIM API key to your clipboard", + "description": "the text, 'SCIM' and 'API', are acronymns and should not be translated." + }, + "rotateScimKey": { + "message": "Rotate the SCIM API key", + "description": "the text, 'SCIM' and 'API', are acronymns and should not be translated." + }, + "rotateScimKeyWarning": { + "message": "Are you sure you want to rotate the SCIM API Key? The current key will no longer work for any existing integrations.", + "description": "the text, 'SCIM' and 'API', are acronymns and should not be translated." + }, + "rotateKey": { + "message": "Rotate key" + }, + "scimApiKey": { + "message": "SCIM API key", + "description": "the text, 'SCIM' and 'API', are acronymns and should not be translated." + }, + "copyScimUrl": { + "message": "Copy the SCIM endpoint URL to your clipboard", + "description": "the text, 'SCIM' and 'URL', are acronymns and should not be translated." + }, + "scimUrl": { + "message": "SCIM URL", + "description": "the text, 'SCIM' and 'URL', are acronymns and should not be translated." + }, + "scimApiKeyRotated": { + "message": "SCIM API key successfully rotated", + "description": "the text, 'SCIM' and 'API', are acronymns and should not be translated." + }, + "scimSettingsSaved": { + "message": "SCIM settings saved", + "description": "the text, 'SCIM', is an acronymn and should not be translated." + }, + "inputRequired": { + "message": "Input is required." + }, + "inputEmail": { + "message": "Input is not an email address." + }, + "inputMinLength": { + "message": "Input must be at least $COUNT$ characters long.", + "placeholders": { + "count": { + "content": "$1", + "example": "8" + } + } + }, + "inputMaxLength": { + "message": "Input must not exceed $COUNT$ characters in length.", + "placeholders": { + "count": { + "content": "$1", + "example": "20" + } + } + }, + "fieldsNeedAttention": { + "message": "$COUNT$ field(s) above need your attention.", + "placeholders": { + "count": { + "content": "$1", + "example": "4" + } + } + }, + "turnOn": { + "message": "Turn on" + }, + "on": { + "message": "On" + }, + "members": { + "message": "Members" + }, + "reporting": { + "message": "Reporting" + }, + "cardBrandMir": { + "message": "Mir" + }, + "numberOfUsers": { + "message": "Number of users" + }, + "loggingInAs": { + "message": "Logging in as" + }, + "notYou": { + "message": "Not you?" + }, + "multiSelectPlaceholder": { + "message": "-- Type to Filter --" + }, + "multiSelectLoading": { + "message": "Retrieving options..." + }, + "multiSelectNotFound": { + "message": "No items found" + }, + "multiSelectClearAll": { + "message": "Clear all" + }, + "from": { + "message": "From" + }, + "to": { + "message": "To" + }, + "member": { + "message": "Member" + }, + "update": { + "message": "Update" + }, + "role": { + "message": "Role" + }, + "canView": { + "message": "Can view" + }, + "canViewExceptPass": { + "message": "Can view, except passwords" + }, + "canEdit": { + "message": "Can edit" + }, + "canEditExceptPass": { + "message": "Can edit, except passwords" + }, + "group": { + "message": "Group" + }, + "groupAccessAll": { + "message": "This group can access and modify all items." + }, + "memberAccessAll": { + "message": "This member can access and modify all items." + } +} diff --git a/apps/web/src/locales/tr/messages.json b/apps/web/src/locales/tr/messages.json index 1de248d2c5c..5ca4db18053 100644 --- a/apps/web/src/locales/tr/messages.json +++ b/apps/web/src/locales/tr/messages.json @@ -4119,6 +4119,21 @@ "customDesc": { "message": "Gelişmiş yapılandırma için kullanıcı izinleri üzerinde daha detaylı kontrol sağlar." }, + "customDescNonEnterpriseStart": { + "message": "Custom roles is an ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseLink": { + "message": "enterprise feature", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseEnd": { + "message": ". Contact our support team to upgrade your subscription", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customNonEnterpriseError": { + "message": "To enable custom permissions the organization must be on an Enterprise 2020 plan." + }, "permissions": { "message": "İzinler" }, diff --git a/apps/web/src/locales/uk/messages.json b/apps/web/src/locales/uk/messages.json index babbe3cd7d3..e4c4ba24b64 100644 --- a/apps/web/src/locales/uk/messages.json +++ b/apps/web/src/locales/uk/messages.json @@ -2444,7 +2444,7 @@ "message": "Власник" }, "ownerDesc": { - "message": "Користувач з найвищими привілеями, який може керувати всіма налаштуваннями організації." + "message": "Керування всіма налаштуваннями організації, як-от платежі й передплати" }, "clientOwnerDesc": { "message": "Цей користувач має бути незалежним від постачальника. Якщо постачальник не пов'язаний з організацією, цей користувач підтримуватиме право власності організації." @@ -2453,19 +2453,19 @@ "message": "Адміністратор" }, "adminDesc": { - "message": "Адміністратори мають доступ і можливість керування всіма записами, збірками та користувачами вашої організації." + "message": "Керування доступом до організації, всіма збірками, учасниками, звітами та налаштуваннями безпеки" }, "user": { "message": "Користувач" }, "userDesc": { - "message": "Звичайний користувач з доступом до пов'язаних збірок вашої організації." + "message": "Доступ та додавання елементів до призначених збірок" }, "manager": { "message": "Менеджер" }, "managerDesc": { - "message": "Менеджери мають доступ і можуть керувати пов'язаними збірками вашої організації." + "message": "Створення, видалення та керування доступом у призначених збірках" }, "all": { "message": "Усі" @@ -4117,7 +4117,22 @@ "message": "Спеціальний" }, "customDesc": { - "message": "Дозволяє детальніший контроль дозволів користувача для розширеної конфігурації." + "message": "Надання користувацьких дозволів учасникам" + }, + "customDescNonEnterpriseStart": { + "message": "Користувацькі ролі є ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseLink": { + "message": "корпоративною функцією", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseEnd": { + "message": ". Зверніться до своєї команди підтримки або оновіть передплату", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customNonEnterpriseError": { + "message": "Щоб увімкнути користувацькі дозволи, організація повинна мати тарифний план Enterprise 2020." }, "permissions": { "message": "Дозволи" diff --git a/apps/web/src/locales/vi/messages.json b/apps/web/src/locales/vi/messages.json index ad826f0152e..74370c028cd 100644 --- a/apps/web/src/locales/vi/messages.json +++ b/apps/web/src/locales/vi/messages.json @@ -2444,7 +2444,7 @@ "message": "Owner" }, "ownerDesc": { - "message": "The highest access user that can manage all aspects of your organization." + "message": "Manage all aspects of your organization, including billing and subscriptions" }, "clientOwnerDesc": { "message": "This user should be independent of the Provider. If the Provider is disassociated with the organization, this user will maintain ownership of the organization." @@ -2453,19 +2453,19 @@ "message": "Admin" }, "adminDesc": { - "message": "Admins can access and manage all items, collections and users in your organization." + "message": "Manage organization access, all collections, members, reporting, and security settings" }, "user": { "message": "User" }, "userDesc": { - "message": "A regular user with access to assigned collections in your organization." + "message": "Access and add items to assigned collections" }, "manager": { "message": "Manager" }, "managerDesc": { - "message": "Managers can access and manage assigned collections in your organization." + "message": "Create, delete, and manage access in assigned collections" }, "all": { "message": "All" @@ -4117,7 +4117,22 @@ "message": "Custom" }, "customDesc": { - "message": "Allows more granular control of user permissions for advanced configurations." + "message": "Grant customized permissions to members" + }, + "customDescNonEnterpriseStart": { + "message": "Custom roles is an ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseLink": { + "message": "enterprise feature", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseEnd": { + "message": ". Contact our support team to upgrade your subscription", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customNonEnterpriseError": { + "message": "To enable custom permissions the organization must be on an Enterprise 2020 plan." }, "permissions": { "message": "Permissions" diff --git a/apps/web/src/locales/zh_CN/messages.json b/apps/web/src/locales/zh_CN/messages.json index fcc48dbb4c8..8dd6dfe2521 100644 --- a/apps/web/src/locales/zh_CN/messages.json +++ b/apps/web/src/locales/zh_CN/messages.json @@ -573,7 +573,7 @@ "message": "使用设备登录" }, "loginWithDeviceEnabledInfo": { - "message": "设备登录必须在 Bitwarden 移动应用程序的设置中启用。需要其他选项吗?" + "message": "必须在 Bitwarden 移动应用程序的设置中启用设备登录。需要其他选项吗?" }, "loginWithMasterPassword": { "message": "使用主密码登录" @@ -582,7 +582,7 @@ "message": "创建账户" }, "newAroundHere": { - "message": "New around here?" + "message": "初来乍到吗?" }, "startTrial": { "message": "开始试用" @@ -1291,23 +1291,23 @@ "message": "两步登录" }, "twoStepLoginEnforcement": { - "message": "强制两步登录" + "message": "两步登录实施" }, "twoStepLoginDesc": { "message": "在登录时要求使用额外的步骤来保护您的账户。" }, "twoStepLoginOrganizationDescStart": { - "message": "要强制成员使用 Bitwarden 两步登录选项,请使用 ", + "message": "要为成员实施 Bitwarden 两步登录选项,请使用 ", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Enforce Bitwarden Two-step Login options for members by using the Two-step Login Policy.'" }, "twoStepLoginPolicy": { "message": "两步登录策略" }, "twoStepLoginOrganizationDuoDesc": { - "message": "要强制 Duo 方式的两步登录,请使用下面的选项。" + "message": "要实施 Duo 方式的两步登录,请使用下面的选项。" }, "twoStepLoginOrganizationSsoDesc": { - "message": "如果您已设置或计划设置 SSO,两步登录可能已经通过您的身份提供程序强制执行了。" + "message": "如果您已设置或计划设置 SSO,两步登录可能已经通过您的身份提供程序实施了。" }, "twoStepLoginRecoveryWarning": { "message": "启用两步登录可能会将您永久锁定在 Bitwarden 账户之外。如果您无法使用常规的两步登录提供程序(例如您丢失了设备),则可以使用恢复代码访问您的账户。如果您失去对您账户的访问,Bitwarden 支持也无法帮助您。我们建议您记下或打印恢复代码,并将其妥善保管。" @@ -2444,7 +2444,7 @@ "message": "所有者" }, "ownerDesc": { - "message": "可以管理组织的所有方面的最高权限用户。" + "message": "管理您组织的所有方面,包括计费和订阅" }, "clientOwnerDesc": { "message": "此用户应独立于提供商。如果提供商与组织断开关联,该用户将保留该组织的所有权。" @@ -2453,19 +2453,19 @@ "message": "管理员" }, "adminDesc": { - "message": " 管理员可以访问和管理组织中的所有项目、集合和用户。" + "message": "管理组织访问权限,所有集合,成员,报告以及安全设置" }, "user": { "message": "用户" }, "userDesc": { - "message": "具有对组织所分配的集合有访问权限的普通用户。" + "message": "访问并将项目添加到已分配的集合" }, "manager": { "message": "经理" }, "managerDesc": { - "message": "经理可以访问和管理组织所分配的集合。" + "message": "在已分配的集合中创建、删除和管理访问权限" }, "all": { "message": "全部" @@ -4117,7 +4117,22 @@ "message": "自定义" }, "customDesc": { - "message": "高级配置允许对用户权限进行更多的粒度控制。" + "message": "授予成员自定义权限" + }, + "customDescNonEnterpriseStart": { + "message": "自定义角色是 ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseLink": { + "message": "企业功能", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseEnd": { + "message": "。请联系我们的支持团队升级您的订阅", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customNonEnterpriseError": { + "message": "要启用自定义权限,该组织必须处于 2020 企业计划中。" }, "permissions": { "message": "权限" diff --git a/apps/web/src/locales/zh_TW/messages.json b/apps/web/src/locales/zh_TW/messages.json index 53fd79f313e..aac3c6302c2 100644 --- a/apps/web/src/locales/zh_TW/messages.json +++ b/apps/web/src/locales/zh_TW/messages.json @@ -177,7 +177,7 @@ "message": "未指派" }, "noneFolder": { - "message": "默認資料夾", + "message": "預設資料夾", "description": "This is the folder for uncategorized items" }, "addFolder": { @@ -1291,7 +1291,7 @@ "message": "兩步驟登入" }, "twoStepLoginEnforcement": { - "message": "強制兩步驟驗證" + "message": "兩步驟驗證執行" }, "twoStepLoginDesc": { "message": "在登入時執行額外的步驟來保護您的帳戶。" @@ -1301,7 +1301,7 @@ "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Enforce Bitwarden Two-step Login options for members by using the Two-step Login Policy.'" }, "twoStepLoginPolicy": { - "message": "兩步驟登入政策" + "message": "兩步驟登入原則" }, "twoStepLoginOrganizationDuoDesc": { "message": "要以 Duo 進行兩步驟登入,請使用以下的選項。" @@ -1829,7 +1829,7 @@ "message": "進階存取" }, "premiumAccessDesc": { - "message": "您可以為您的組織的所有使用者添加進階存取,只需 $PRICE$ /$INTERVAL$.", + "message": "您可以為您的組織的所有成員添加進階存取,只需 $PRICE$ /$INTERVAL$.", "placeholders": { "price": { "content": "$1", @@ -2972,7 +2972,7 @@ "message": "您已被邀請加入以上組織。若想接受邀請,您需要登入或建立新的 Bitwarden 帳戶。" }, "inviteAccepted": { - "message": "已接受邀請" + "message": "邀請已接受" }, "inviteAcceptedDesc": { "message": "管理員確認您的成員身分後,您便可以存取此組織。到時我們會向您傳送電子郵件通知。" @@ -3137,7 +3137,7 @@ "message": "輸入您的安裝 ID" }, "limitSubscriptionDesc": { - "message": "為您的訂閲設定席位限制。達到此限制後,您將無法邀請新的使用者。" + "message": "為您的訂閲設定席位限制。達到此限制後,您將無法邀請新的成員。" }, "maxSeatLimit": { "message": "席位限制(選用)", @@ -3158,7 +3158,7 @@ "message": "調整訂閱將會依比例變更您的計費總額。若新邀請的使用者超過您的訂閱席位,您將立即收到依比例收取的額外使用者費用。" }, "subscriptionUserSeats": { - "message": "您的訂閱當中包含最多 $COUNT$ 位使用者。", + "message": "您的訂閱當中包含最多 $COUNT$ 位成員。", "placeholders": { "count": { "content": "$1", @@ -3182,10 +3182,10 @@ "message": "如需更多管理訂閱協助,請聯絡客戶支援。" }, "subscriptionUserSeatsUnlimitedAutoscale": { - "message": "調整訂閱將會依比例變更您的計費總額。若新邀請的使用者超過您的訂閱席位,您將立即收到依比例收取的額外使用者費用。" + "message": "調整訂閱將會依比例變更您的計費總額。若新邀請的成員超過您的訂閱席位,您將立即收到依比例收取的額外使用者費用。" }, "subscriptionUserSeatsLimitedAutoscale": { - "message": "調整訂閱將會依比例變更您的計費總額。若新邀請的使用者超過您的訂閱席位,您將立即收到依比例收取的額外使用者費用,直到您達到 $MAX$ 席位限制。", + "message": "調整訂閱將會依比例變更您的計費總額。若新邀請的成員超過您的訂閱席位,您將立即收到依比例收取的額外使用者費用,直到您達到 $MAX$ 席位限制。", "placeholders": { "max": { "content": "$1", @@ -3194,7 +3194,7 @@ } }, "subscriptionFreePlan": { - "message": "除非升級方案,否則您最多只能邀請 $COUNT$ 位使用者。", + "message": "除非升級方案,否則您最多只能邀請 $COUNT$ 位成員。", "placeholders": { "count": { "content": "$1", @@ -3203,7 +3203,7 @@ } }, "subscriptionFamiliesPlan": { - "message": "除非升級方案,否則您最多只能邀請 $COUNT$ 位使用者。請聯絡客戶支援進行升級。", + "message": "除非升級方案,否則您最多只能邀請 $COUNT$ 位成員。請聯絡客戶支援進行升級。", "placeholders": { "count": { "content": "$1", @@ -3212,7 +3212,7 @@ } }, "subscriptionSponsoredFamiliesPlan": { - "message": "您的訂閱允許總共 $COUNT$ 位使用者。您的方案由外部組織贊助和支付費用。", + "message": "您的訂閱允許總共 $COUNT$ 位成員。您的方案由外部組織贊助和支付費用。", "placeholders": { "count": { "content": "$1", @@ -3221,7 +3221,7 @@ } }, "subscriptionMaxReached": { - "message": "調整訂閱將會依比例變更您的計費總額。在不增加訂閱席位的情況下,您不能邀請超過 $COUNT$ 位使用者。", + "message": "調整訂閱將會依比例變更您的計費總額。在不增加訂閱席位的情況下,您不能邀請超過 $COUNT$ 位成員。", "placeholders": { "count": { "content": "$1", @@ -3719,7 +3719,7 @@ "message": "SSO 識別" }, "ssoIdentifierHint": { - "message": "將該 ID 提供給您的成員,以便使用 SSO 登入" + "message": "將該 ID 提供給您的成員,以便使用 SSO 登入。" }, "unlinkSso": { "message": "取消連結 SSO" @@ -4066,7 +4066,7 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "disableSendExemption": { - "message": "可以管理組織原則的組織使用者豁免此原則的執行。" + "message": "可以管理組織原則的組織成員豁免此原則的執行。" }, "sendDisabled": { "message": "Send 已停用", @@ -4085,7 +4085,7 @@ "description": "'Sends' is a plural noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendOptionsExemption": { - "message": "可以管理組織原則的組織使用者豁免此原則的執行。" + "message": "可以管理組織原則的組織成員豁免此原則的執行。" }, "disableHideEmail": { "message": "建立或編輯 Send 時,始終對收件人顯示成員的電子郵件地址。", @@ -4119,6 +4119,21 @@ "customDesc": { "message": "進階設定允許更精細地控制使用者權限。" }, + "customDescNonEnterpriseStart": { + "message": "Custom roles is an ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseLink": { + "message": "enterprise feature", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseEnd": { + "message": ". Contact our support team to upgrade your subscription", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customNonEnterpriseError": { + "message": "To enable custom permissions the organization must be on an Enterprise 2020 plan." + }, "permissions": { "message": "權限" }, @@ -4399,19 +4414,19 @@ "message": "允許管理者重設成員的主密碼。" }, "resetPasswordPolicyWarning": { - "message": "組織使用者需要先自行註冊或被自動註冊後,管理者才能重設他們的主密碼。" + "message": "組織成員需要先自行註冊或被自動註冊後,管理者才能重設他們的主密碼。" }, "resetPasswordPolicyAutoEnroll": { "message": "自動註冊" }, "resetPasswordPolicyAutoEnrollDescription": { - "message": "所有接受邀請的使用者,將會被自動註冊密碼重設,並且不允許撤銷。" + "message": "所有接受邀請的成員,將會被自動註冊密碼重設,並且不允許撤銷。" }, "resetPasswordPolicyAutoEnrollWarning": { - "message": "已經在組織中的使用者將不會被註冊密碼重設。他們需要先自行註冊後管理者才能重設他們的主密碼。" + "message": "已經在組織中的成員將不會被註冊密碼重設。他們需要先自行註冊後管理者才能重設他們的主密碼。" }, "resetPasswordPolicyAutoEnrollCheckbox": { - "message": "為新使用者啟用自動註冊" + "message": "為新成員啟用自動註冊" }, "resetPasswordAutoEnrollInviteWarning": { "message": "此組織有一個可以為您自動註冊密碼重設的企業原則。註冊後將允許組織管理員變更您的主密碼。" From bdfc2b0839bdf1838e0bf606d197ad1eb142a0ce Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Mon, 12 Dec 2022 15:16:15 +0100 Subject: [PATCH 9/9] fix: make checkbox story documentation easier (#4208) --- .../components/src/checkbox/checkbox.stories.ts | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/libs/components/src/checkbox/checkbox.stories.ts b/libs/components/src/checkbox/checkbox.stories.ts index c7bb4bf80e2..a384dd2ce01 100644 --- a/libs/components/src/checkbox/checkbox.stories.ts +++ b/libs/components/src/checkbox/checkbox.stories.ts @@ -43,7 +43,6 @@ class ExampleComponent { export default { title: "Component Library/Form/Checkbox", - component: ExampleComponent, decorators: [ moduleMetadata({ declarations: [ExampleComponent], @@ -68,10 +67,6 @@ export default { url: "https://www.figma.com/file/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=3930%3A16850&t=xXPx6GJYsJfuMQPE-4", }, }, - args: { - checked: false, - disabled: false, - }, } as Meta; const DefaultTemplate: Story = (args: ExampleComponent) => ({ @@ -80,6 +75,17 @@ const DefaultTemplate: Story = (args: ExampleComponent) => ({ }); export const Default = DefaultTemplate.bind({}); +Default.parameters = { + docs: { + source: { + code: template, + }, + }, +}; +Default.args = { + checked: false, + disabled: false, +}; const CustomTemplate: Story = (args) => ({ props: args, @@ -100,5 +106,6 @@ const CustomTemplate: Story = (args) => ({ `, }); +CustomTemplate.args = {}; export const Custom = CustomTemplate.bind({});