diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index 8e52b1ba00..0e4c0da245 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -2877,6 +2877,20 @@ "generateEmail": { "message": "Generate email" }, + "generatorBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$", + "description": "Explains spin box minimum and maximum values to the user", + "placeholders": { + "min": { + "content": "$1", + "example": "8" + }, + "max": { + "content": "$2", + "example": "128" + } + } + }, "usernameType": { "message": "Username type" }, diff --git a/apps/desktop/src/locales/en/messages.json b/apps/desktop/src/locales/en/messages.json index d24616edab..414f254e7a 100644 --- a/apps/desktop/src/locales/en/messages.json +++ b/apps/desktop/src/locales/en/messages.json @@ -2393,6 +2393,20 @@ "generateEmail": { "message": "Generate email" }, + "generatorBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$", + "description": "Explains spin box minimum and maximum values to the user", + "placeholders": { + "min": { + "content": "$1", + "example": "8" + }, + "max": { + "content": "$2", + "example": "128" + } + } + }, "usernameType": { "message": "Username type" }, diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index f25f03c281..eead4fd80d 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -6412,6 +6412,20 @@ "generateEmail": { "message": "Generate email" }, + "generatorBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$", + "description": "Explains spin box minimum and maximum values to the user", + "placeholders": { + "min": { + "content": "$1", + "example": "8" + }, + "max": { + "content": "$2", + "example": "128" + } + } + }, "usernameType": { "message": "Username type" }, diff --git a/libs/tools/generator/components/src/passphrase-settings.component.html b/libs/tools/generator/components/src/passphrase-settings.component.html index 25e9684e86..d089de7a07 100644 --- a/libs/tools/generator/components/src/passphrase-settings.component.html +++ b/libs/tools/generator/components/src/passphrase-settings.component.html @@ -8,6 +8,7 @@ {{ "numWords" | i18n }} + {{ numWordsBoundariesHint$ | async }} diff --git a/libs/tools/generator/components/src/passphrase-settings.component.ts b/libs/tools/generator/components/src/passphrase-settings.component.ts index 4c171e0c20..d65e897f4e 100644 --- a/libs/tools/generator/components/src/passphrase-settings.component.ts +++ b/libs/tools/generator/components/src/passphrase-settings.component.ts @@ -1,9 +1,10 @@ import { coerceBooleanProperty } from "@angular/cdk/coercion"; import { OnInit, Input, Output, EventEmitter, Component, OnDestroy } from "@angular/core"; import { FormBuilder } from "@angular/forms"; -import { BehaviorSubject, skip, takeUntil, Subject } from "rxjs"; +import { BehaviorSubject, skip, takeUntil, Subject, ReplaySubject } from "rxjs"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { UserId } from "@bitwarden/common/types/guid"; import { Generators, @@ -29,11 +30,13 @@ export class PassphraseSettingsComponent implements OnInit, OnDestroy { /** Instantiates the component * @param accountService queries user availability * @param generatorService settings and policy logic + * @param i18nService localize hints * @param formBuilder reactive form controls */ constructor( private formBuilder: FormBuilder, private generatorService: CredentialGeneratorService, + private i18nService: I18nService, private accountService: AccountService, ) {} @@ -97,6 +100,13 @@ export class PassphraseSettingsComponent implements OnInit, OnDestroy { this.toggleEnabled(Controls.capitalize, !constraints.capitalize?.readonly); this.toggleEnabled(Controls.includeNumber, !constraints.includeNumber?.readonly); + + const boundariesHint = this.i18nService.t( + "generatorBoundariesHint", + constraints.numWords.min, + constraints.numWords.max, + ); + this.numWordsBoundariesHint.next(boundariesHint); }); // now that outputs are set up, connect inputs @@ -106,6 +116,11 @@ export class PassphraseSettingsComponent implements OnInit, OnDestroy { /** display binding for enterprise policy notice */ protected policyInEffect: boolean; + private numWordsBoundariesHint = new ReplaySubject(1); + + /** display binding for min/max constraints of `numWords` */ + protected numWordsBoundariesHint$ = this.numWordsBoundariesHint.asObservable(); + private toggleEnabled(setting: keyof typeof Controls, enabled: boolean) { if (enabled) { this.settings.get(setting).enable({ emitEvent: false }); diff --git a/libs/tools/generator/components/src/password-settings.component.html b/libs/tools/generator/components/src/password-settings.component.html index 5e1d1941e7..aa12a3247c 100644 --- a/libs/tools/generator/components/src/password-settings.component.html +++ b/libs/tools/generator/components/src/password-settings.component.html @@ -8,6 +8,7 @@ {{ "length" | i18n }} + {{ lengthBoundariesHint$ | async }} diff --git a/libs/tools/generator/components/src/password-settings.component.ts b/libs/tools/generator/components/src/password-settings.component.ts index 7832818d67..6e9d106b71 100644 --- a/libs/tools/generator/components/src/password-settings.component.ts +++ b/libs/tools/generator/components/src/password-settings.component.ts @@ -1,9 +1,10 @@ import { coerceBooleanProperty } from "@angular/cdk/coercion"; import { OnInit, Input, Output, EventEmitter, Component, OnDestroy } from "@angular/core"; import { FormBuilder } from "@angular/forms"; -import { BehaviorSubject, takeUntil, Subject, map, filter, tap, skip } from "rxjs"; +import { BehaviorSubject, takeUntil, Subject, map, filter, tap, skip, ReplaySubject } from "rxjs"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { UserId } from "@bitwarden/common/types/guid"; import { Generators, @@ -33,11 +34,13 @@ export class PasswordSettingsComponent implements OnInit, OnDestroy { /** Instantiates the component * @param accountService queries user availability * @param generatorService settings and policy logic + * @param i18nService localize hints * @param formBuilder reactive form controls */ constructor( private formBuilder: FormBuilder, private generatorService: CredentialGeneratorService, + private i18nService: I18nService, private accountService: AccountService, ) {} @@ -147,6 +150,13 @@ export class PasswordSettingsComponent implements OnInit, OnDestroy { for (const [control, enabled] of toggles) { this.toggleEnabled(control, enabled); } + + const boundariesHint = this.i18nService.t( + "generatorBoundariesHint", + constraints.length.min, + constraints.length.max, + ); + this.lengthBoundariesHint.next(boundariesHint); }); // cascade selections between checkboxes and spinboxes @@ -208,6 +218,11 @@ export class PasswordSettingsComponent implements OnInit, OnDestroy { /** display binding for enterprise policy notice */ protected policyInEffect: boolean; + private lengthBoundariesHint = new ReplaySubject(1); + + /** display binding for min/max constraints of `length` */ + protected lengthBoundariesHint$ = this.lengthBoundariesHint.asObservable(); + private toggleEnabled(setting: keyof typeof Controls, enabled: boolean) { if (enabled) { this.settings.get(setting).enable({ emitEvent: false });