Property Editors: Fix localization of user-provided labels (closes #20974) (#21045)

* fix: uses localization string() to localize user-provided labels

* fix: localizes placeholder as well

* Refinements to the Toggle input

The localizations can happen in the `config` setter,
then they don't need to re-get the localization each re-render.

Added a `when` directive to show/hide the label `<span>` tag.

Removed `_currentLabel` as unused.

* Refinements to the Textbox input

The localizations can happen in the `config` setter,
then they don't need to re-get the localization each re-render.

Refactored the `uui-input` attributes/properties.

* Refinements to the Number input

The localizations can happen in the `config` setter,
then they don't need to re-get the localization each re-render.

Refactored the `uui-input` attributes/properties.

* Update src/Umbraco.Web.UI.Client/src/packages/core/components/input-toggle/input-toggle.element.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update src/Umbraco.Web.UI.Client/src/packages/property-editors/text-box/property-editor-ui-text-box.element.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update src/Umbraco.Web.UI.Client/src/packages/property-editors/number/property-editor-ui-number.element.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Updates based on Copilot feedback

---------

Co-authored-by: leekelleher <leekelleher@gmail.com>
Co-authored-by: Lee Kelleher <leekelleher@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Jacob Overgaard
2025-12-04 10:20:33 +01:00
committed by GitHub
parent 455e7027a0
commit d8c03c426e
4 changed files with 81 additions and 67 deletions

View File

@@ -1,8 +1,8 @@
import { html, customElement, property, state } from '@umbraco-cms/backoffice/external/lit';
import { customElement, html, property, when } from '@umbraco-cms/backoffice/external/lit';
import { UmbChangeEvent } from '@umbraco-cms/backoffice/event';
import { UmbFormControlMixin } from '@umbraco-cms/backoffice/validation';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import type { UUIBooleanInputEvent } from '@umbraco-cms/backoffice/external/uui';
import { UmbFormControlMixin } from '@umbraco-cms/backoffice/validation';
@customElement('umb-input-toggle')
export class UmbInputToggleElement extends UmbFormControlMixin(UmbLitElement, '') {
@@ -46,9 +46,6 @@ export class UmbInputToggleElement extends UmbFormControlMixin(UmbLitElement, ''
@property({ type: Boolean, reflect: true })
readonly = false;
@state()
private _currentLabel?: string;
protected override firstUpdated(): void {
this.addFormControlElement(this.shadowRoot!.querySelector('uui-toggle')!);
}
@@ -60,16 +57,18 @@ export class UmbInputToggleElement extends UmbFormControlMixin(UmbLitElement, ''
}
override render() {
const label = this.showLabels ? (this.checked ? this.labelOn : this.labelOff) : '';
return html`<uui-toggle
.checked=${this.#checked}
.label=${this.ariaLabel}
?required=${this.required}
.requiredMessage=${this.requiredMessage}
@change=${this.#onChange}
?readonly=${this.readonly}
><span>${label}</span>
</uui-toggle>`;
const label = this.checked ? this.labelOn : this.labelOff;
return html`
<uui-toggle
.checked=${this.#checked}
.label=${this.ariaLabel ?? label ?? ''}
.requiredMessage=${this.requiredMessage}
?readonly=${this.readonly}
?required=${this.required}
@change=${this.#onChange}>
${when(this.showLabels, () => html`<span>${label}</span>`)}
</uui-toggle>
`;
}
}

View File

@@ -1,12 +1,12 @@
import { css, customElement, html, ifDefined, property, state } from '@umbraco-cms/backoffice/external/lit';
import { UMB_VALIDATION_EMPTY_LOCALIZATION_KEY, UmbFormControlMixin } from '@umbraco-cms/backoffice/validation';
import { UmbChangeEvent } from '@umbraco-cms/backoffice/event';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import { UMB_PROPERTY_CONTEXT } from '@umbraco-cms/backoffice/property';
import { UMB_VALIDATION_EMPTY_LOCALIZATION_KEY, UmbFormControlMixin } from '@umbraco-cms/backoffice/validation';
import type {
UmbPropertyEditorConfigCollection,
UmbPropertyEditorUiElement,
} from '@umbraco-cms/backoffice/property-editor';
import { UmbChangeEvent } from '@umbraco-cms/backoffice/event';
@customElement('umb-property-editor-ui-number')
export class UmbPropertyEditorUINumberElement
@@ -51,7 +51,7 @@ export class UmbPropertyEditorUINumberElement
this._min = this.#parseNumber(config.getValueByAlias('min'));
this._max = this.#parseNumber(config.getValueByAlias('max'));
this._step = this.#parseNumber(config.getValueByAlias('step'));
this._placeholder = config.getValueByAlias('placeholder');
this._placeholder = this.localize.string(config.getValueByAlias<string>('placeholder') ?? '');
}
constructor() {
@@ -110,12 +110,12 @@ export class UmbPropertyEditorUINumberElement
min=${ifDefined(this._min)}
max=${ifDefined(this._max)}
step=${ifDefined(this._step)}
placeholder=${ifDefined(this._placeholder)}
value=${this.value?.toString() ?? ''}
@change=${this.#onChange}
?required=${this.mandatory}
.placeholder=${this._placeholder ?? ''}
.requiredMessage=${this.mandatoryMessage}
?readonly=${this.readonly}>
?required=${this.mandatory}
?readonly=${this.readonly}
@change=${this.#onChange}>
</uui-input>
`;
}

View File

@@ -1,15 +1,16 @@
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import { css, html, customElement, state, ifDefined, property } from '@umbraco-cms/backoffice/external/lit';
import { css, customElement, html, property, state } from '@umbraco-cms/backoffice/external/lit';
import { UmbChangeEvent } from '@umbraco-cms/backoffice/event';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import { UMB_VALIDATION_EMPTY_LOCALIZATION_KEY, UmbFormControlMixin } from '@umbraco-cms/backoffice/validation';
import type {
InputMode as UUIInputMode,
InputType as UUIInputType,
UUIInputElement,
} from '@umbraco-cms/backoffice/external/uui';
import type {
UmbPropertyEditorUiElement,
UmbPropertyEditorConfigCollection,
} from '@umbraco-cms/backoffice/property-editor';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import type { UUIInputElement } from '@umbraco-cms/backoffice/external/uui';
import { UMB_VALIDATION_EMPTY_LOCALIZATION_KEY, UmbFormControlMixin } from '@umbraco-cms/backoffice/validation';
import { UmbChangeEvent } from '@umbraco-cms/backoffice/event';
type UuiInputTypeType = typeof UUIInputElement.prototype.type;
@customElement('umb-property-editor-ui-text-box')
export class UmbPropertyEditorUITextBoxElement
@@ -41,13 +42,15 @@ export class UmbPropertyEditorUITextBoxElement
@property({ type: String })
name?: string;
#defaultType: UuiInputTypeType = 'text';
#defaultType: UUIInputType = 'text';
#defaultInputMode: UUIInputMode = 'text';
@state()
private _type: UuiInputTypeType = this.#defaultType;
private _type: UUIInputType = this.#defaultType;
@state()
private _inputMode?: string;
private _inputMode: UUIInputMode = this.#defaultInputMode;
@state()
private _maxChars?: number;
@@ -56,10 +59,12 @@ export class UmbPropertyEditorUITextBoxElement
private _placeholder?: string;
public set config(config: UmbPropertyEditorConfigCollection | undefined) {
this._type = config?.getValueByAlias<UuiInputTypeType>('inputType') ?? this.#defaultType;
this._inputMode = config?.getValueByAlias('inputMode');
this._maxChars = config?.getValueByAlias('maxChars');
this._placeholder = config?.getValueByAlias('placeholder');
if (!config) return;
this._type = config.getValueByAlias<UUIInputType>('inputType') ?? this.#defaultType;
this._inputMode = config.getValueByAlias<UUIInputMode>('inputMode') || this.#defaultInputMode;
this._maxChars = this.#parseNumber(config.getValueByAlias('maxChars'));
this._placeholder = this.localize.string(config.getValueByAlias<string>('placeholder') ?? '');
}
protected override firstUpdated(): void {
@@ -70,6 +75,15 @@ export class UmbPropertyEditorUITextBoxElement
return this.shadowRoot?.querySelector<UUIInputElement>('uui-input')?.focus();
}
#parseNumber(input: unknown): number | undefined {
const num = Number(input);
return Number.isFinite(num) ? num : undefined;
}
#getMaxLengthMessage(max: number, current: number) {
return this.localize.term('textbox_characters_exceed', max, current);
}
#onInput(e: InputEvent) {
const newValue = (e.target as HTMLInputElement).value;
if (newValue === this.value) return;
@@ -78,25 +92,24 @@ export class UmbPropertyEditorUITextBoxElement
}
override render() {
return html`<uui-input
.label=${this.localize.term('general_fieldFor', [this.name])}
.value=${this.value ?? ''}
.type=${this._type}
placeholder=${ifDefined(this._placeholder)}
inputMode=${ifDefined(this._inputMode)}
maxlength=${ifDefined(this._maxChars)}
@input=${this.#onInput}
?required=${this.mandatory}
.requiredMessage=${this.mandatoryMessage}
.maxlengthMessage=${() => {
const exceeded = (this.value?.length ?? 0) - (this._maxChars ?? 0);
return this.localize.term('textbox_characters_exceed', this._maxChars, exceeded);
}}
?readonly=${this.readonly}></uui-input>`;
return html`
<uui-input
.inputMode=${this._inputMode}
.label=${this.localize.term('general_fieldFor', [this.name])}
.maxlength=${this._maxChars}
.maxlengthMessage=${this.#getMaxLengthMessage.bind(this)}
.placeholder=${this._placeholder ?? ''}
.requiredMessage=${this.mandatoryMessage}
.type=${this._type}
.value=${this.value ?? ''}
?readonly=${this.readonly}
?required=${this.mandatory}
@input=${this.#onInput}>
</uui-input>
`;
}
static override styles = [
UmbTextStyles,
css`
uui-input {
width: 100%;

View File

@@ -1,13 +1,13 @@
import type { UmbTogglePropertyEditorUiValue } from './types.js';
import { UmbChangeEvent } from '@umbraco-cms/backoffice/event';
import type { UmbInputToggleElement } from '@umbraco-cms/backoffice/components';
import { customElement, html, property, state } from '@umbraco-cms/backoffice/external/lit';
import { UmbChangeEvent } from '@umbraco-cms/backoffice/event';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import { UmbFormControlMixin, UMB_VALIDATION_FALSE_LOCALIZATION_KEY } from '@umbraco-cms/backoffice/validation';
import type { UmbInputToggleElement } from '@umbraco-cms/backoffice/components';
import type {
UmbPropertyEditorConfigCollection,
UmbPropertyEditorUiElement,
} from '@umbraco-cms/backoffice/property-editor';
import { UMB_VALIDATION_FALSE_LOCALIZATION_KEY, UmbFormControlMixin } from '@umbraco-cms/backoffice/validation';
@customElement('umb-property-editor-ui-toggle')
export class UmbPropertyEditorUIToggleElement
@@ -48,10 +48,14 @@ export class UmbPropertyEditorUIToggleElement
public set config(config: UmbPropertyEditorConfigCollection | undefined) {
if (!config) return;
this._labelOff = config.getValueByAlias('labelOff');
this._labelOn = config.getValueByAlias('labelOn');
this._showLabels = Boolean(config.getValueByAlias('showLabels'));
this._ariaLabel = config.getValueByAlias('ariaLabel');
this._labelOn = this.localize.string(config.getValueByAlias<string>('labelOn') ?? '');
this._labelOff = this.localize.string(config.getValueByAlias<string>('labelOff') ?? '');
this._ariaLabel =
this.localize.string(config.getValueByAlias<string>('ariaLabel')) ||
this.localize.term('general_toggleFor', [this.name]);
}
protected override firstUpdated(): void {
@@ -67,17 +71,15 @@ export class UmbPropertyEditorUIToggleElement
override render() {
return html`
<umb-input-toggle
.ariaLabel=${this._ariaLabel
? this.localize.string(this._ariaLabel)
: this.localize.term('general_toggleFor', [this.name])}
.ariaLabel=${this._ariaLabel ?? null}
.labelOn=${this._labelOn}
.labelOff=${this._labelOff}
?checked=${this.value}
?showLabels=${this._showLabels}
?required=${this.mandatory}
.requiredMessage=${this.mandatoryMessage}
@change=${this.#onChange}
?readonly=${this.readonly}>
.showLabels=${this._showLabels}
?checked=${this.value}
?readonly=${this.readonly}
?required=${this.mandatory}
@change=${this.#onChange}>
</umb-input-toggle>
`;
}