Add title and description translation support to expandable form (#21745)

* Add title and description translation support to expandable form

* Fix type

* handle translations in sections

* Rename prefix to path + refactor

* Fix section name and description

---------

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
This commit is contained in:
Paul Bottein 2024-08-28 15:49:38 +02:00 committed by GitHub
parent c556742ff4
commit 19f057a51b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 137 additions and 53 deletions

View File

@ -21,13 +21,45 @@ export class HaFormExpendable extends LitElement implements HaFormElement {
@property({ attribute: false }) public computeLabel?: ( @property({ attribute: false }) public computeLabel?: (
schema: HaFormSchema, schema: HaFormSchema,
data?: HaFormDataContainer data?: HaFormDataContainer,
options?: { path?: string[] }
) => string; ) => string;
@property({ attribute: false }) public computeHelper?: ( @property({ attribute: false }) public computeHelper?: (
schema: HaFormSchema schema: HaFormSchema,
options?: { path?: string[] }
) => string; ) => string;
private _renderDescription() {
const description = this.computeHelper?.(this.schema);
return description ? html`<p>${description}</p>` : nothing;
}
private _computeLabel = (
schema: HaFormSchema,
data?: HaFormDataContainer,
options?: { path?: string[] }
) => {
if (!this.computeLabel) return this.computeLabel;
return this.computeLabel(schema, data, {
...options,
path: [...(options?.path || []), this.schema.name],
});
};
private _computeHelper = (
schema: HaFormSchema,
options?: { path?: string[] }
) => {
if (!this.computeHelper) return this.computeHelper;
return this.computeHelper(schema, {
...options,
path: [...(options?.path || []), this.schema.name],
});
};
protected render() { protected render() {
return html` return html`
<ha-expansion-panel outlined .expanded=${Boolean(this.schema.expanded)}> <ha-expansion-panel outlined .expanded=${Boolean(this.schema.expanded)}>
@ -43,16 +75,17 @@ export class HaFormExpendable extends LitElement implements HaFormElement {
<ha-svg-icon .path=${this.schema.iconPath}></ha-svg-icon> <ha-svg-icon .path=${this.schema.iconPath}></ha-svg-icon>
` `
: nothing} : nothing}
${this.schema.title} ${this.schema.title || this.computeLabel?.(this.schema)}
</div> </div>
<div class="content"> <div class="content">
${this._renderDescription()}
<ha-form <ha-form
.hass=${this.hass} .hass=${this.hass}
.data=${this.data} .data=${this.data}
.schema=${this.schema.schema} .schema=${this.schema.schema}
.disabled=${this.disabled} .disabled=${this.disabled}
.computeLabel=${this.computeLabel} .computeLabel=${this._computeLabel}
.computeHelper=${this.computeHelper} .computeHelper=${this._computeHelper}
></ha-form> ></ha-form>
</div> </div>
</ha-expansion-panel> </ha-expansion-panel>
@ -71,6 +104,9 @@ export class HaFormExpendable extends LitElement implements HaFormElement {
.content { .content {
padding: 12px; padding: 12px;
} }
.content p {
margin: 0 0 24px;
}
ha-expansion-panel { ha-expansion-panel {
display: block; display: block;
--expansion-panel-content-padding: 0; --expansion-panel-content-padding: 0;

View File

@ -31,7 +31,7 @@ const LOAD_ELEMENTS = {
}; };
const getValue = (obj, item) => const getValue = (obj, item) =>
obj ? (!item.name ? obj : obj[item.name]) : null; obj ? (!item.name || item.flatten ? obj : obj[item.name]) : null;
const getError = (obj, item) => (obj && item.name ? obj[item.name] : null); const getError = (obj, item) => (obj && item.name ? obj[item.name] : null);
@ -73,10 +73,6 @@ export class HaForm extends LitElement implements HaFormElement {
schema: any schema: any
) => string | undefined; ) => string | undefined;
@property({ attribute: false }) public localizeValue?: (
key: string
) => string;
protected getFormProperties(): Record<string, any> { protected getFormProperties(): Record<string, any> {
return {}; return {};
} }
@ -149,7 +145,6 @@ export class HaForm extends LitElement implements HaFormElement {
.disabled=${item.disabled || this.disabled || false} .disabled=${item.disabled || this.disabled || false}
.placeholder=${item.required ? "" : item.default} .placeholder=${item.required ? "" : item.default}
.helper=${this._computeHelper(item)} .helper=${this._computeHelper(item)}
.localizeValue=${this.localizeValue}
.required=${item.required || false} .required=${item.required || false}
.context=${this._generateContext(item)} .context=${this._generateContext(item)}
></ha-selector>` ></ha-selector>`
@ -204,9 +199,10 @@ export class HaForm extends LitElement implements HaFormElement {
if (ev.target === this) return; if (ev.target === this) return;
const newValue = !schema.name const newValue =
? ev.detail.value !schema.name || ("flatten" in schema && schema.flatten)
: { [schema.name]: ev.detail.value }; ? ev.detail.value
: { [schema.name]: ev.detail.value };
this.data = { this.data = {
...this.data, ...this.data,

View File

@ -31,15 +31,15 @@ export interface HaFormBaseSchema {
export interface HaFormGridSchema extends HaFormBaseSchema { export interface HaFormGridSchema extends HaFormBaseSchema {
type: "grid"; type: "grid";
name: string; flatten?: boolean;
column_min_width?: string; column_min_width?: string;
schema: readonly HaFormSchema[]; schema: readonly HaFormSchema[];
} }
export interface HaFormExpandableSchema extends HaFormBaseSchema { export interface HaFormExpandableSchema extends HaFormBaseSchema {
type: "expandable"; type: "expandable";
name: ""; flatten?: boolean;
title: string; title?: string;
icon?: string; icon?: string;
iconPath?: string; iconPath?: string;
expanded?: boolean; expanded?: boolean;
@ -100,7 +100,7 @@ export type SchemaUnion<
SchemaArray extends readonly HaFormSchema[], SchemaArray extends readonly HaFormSchema[],
Schema = SchemaArray[number], Schema = SchemaArray[number],
> = Schema extends HaFormGridSchema | HaFormExpandableSchema > = Schema extends HaFormGridSchema | HaFormExpandableSchema
? SchemaUnion<Schema["schema"]> ? SchemaUnion<Schema["schema"]> | Schema
: Schema; : Schema;
export interface HaFormDataContainer { export interface HaFormDataContainer {

View File

@ -162,8 +162,14 @@ export class HaLocationSelector extends LitElement {
private _computeLabel = ( private _computeLabel = (
entry: SchemaUnion<ReturnType<typeof this._schema>> entry: SchemaUnion<ReturnType<typeof this._schema>>
): string => ): string => {
this.hass.localize(`ui.components.selectors.location.${entry.name}`); if (entry.name) {
return this.hass.localize(
`ui.components.selectors.location.${entry.name}`
);
}
return "";
};
static styles = css` static styles = css`
ha-locations-editor { ha-locations-editor {

View File

@ -76,19 +76,36 @@ export const showConfigFlowDialog = (
: ""; : "";
}, },
renderShowFormStepFieldLabel(hass, step, field) { renderShowFormStepFieldLabel(hass, step, field, options) {
if (field.type === "expandable") {
return hass.localize(
`component.${step.handler}.config.step.${step.step_id}.sections.${field.name}.name`
);
}
const prefix = options?.path?.[0] ? `sections.${options.path[0]}` : "";
return ( return (
hass.localize( hass.localize(
`component.${step.handler}.config.step.${step.step_id}.data.${field.name}` `component.${step.handler}.config.step.${step.step_id}.${prefix}data.${field.name}`
) || field.name ) || field.name
); );
}, },
renderShowFormStepFieldHelper(hass, step, field) { renderShowFormStepFieldHelper(hass, step, field, options) {
if (field.type === "expandable") {
return hass.localize(
`component.${step.translation_domain || step.handler}.config.step.${step.step_id}.sections.${field.name}.description`
);
}
const prefix = options?.path?.[0] ? `sections.${options.path[0]}.` : "";
const description = hass.localize( const description = hass.localize(
`component.${step.translation_domain || step.handler}.config.step.${step.step_id}.data_description.${field.name}`, `component.${step.translation_domain || step.handler}.config.step.${step.step_id}.${prefix}data_description.${field.name}`,
step.description_placeholders step.description_placeholders
); );
return description return description
? html`<ha-markdown breaks .content=${description}></ha-markdown>` ? html`<ha-markdown breaks .content=${description}></ha-markdown>`
: ""; : "";

View File

@ -49,13 +49,15 @@ export interface FlowConfig {
renderShowFormStepFieldLabel( renderShowFormStepFieldLabel(
hass: HomeAssistant, hass: HomeAssistant,
step: DataEntryFlowStepForm, step: DataEntryFlowStepForm,
field: HaFormSchema field: HaFormSchema,
options: { path?: string[]; [key: string]: any }
): string; ): string;
renderShowFormStepFieldHelper( renderShowFormStepFieldHelper(
hass: HomeAssistant, hass: HomeAssistant,
step: DataEntryFlowStepForm, step: DataEntryFlowStepForm,
field: HaFormSchema field: HaFormSchema,
options: { path?: string[]; [key: string]: any }
): TemplateResult | string; ): TemplateResult | string;
renderShowFormStepFieldError( renderShowFormStepFieldError(

View File

@ -93,17 +93,33 @@ export const showOptionsFlowDialog = (
: ""; : "";
}, },
renderShowFormStepFieldLabel(hass, step, field) { renderShowFormStepFieldLabel(hass, step, field, options) {
if (field.type === "expandable") {
return hass.localize(
`component.${configEntry.domain}.options.step.${step.step_id}.sections.${field.name}.name`
);
}
const prefix = options?.path?.[0] ? `sections.${options.path[0]}.` : "";
return ( return (
hass.localize( hass.localize(
`component.${configEntry.domain}.options.step.${step.step_id}.data.${field.name}` `component.${configEntry.domain}.options.step.${step.step_id}.${prefix}data.${field.name}`
) || field.name ) || field.name
); );
}, },
renderShowFormStepFieldHelper(hass, step, field) { renderShowFormStepFieldHelper(hass, step, field, options) {
if (field.type === "expandable") {
return hass.localize(
`component.${step.translation_domain || configEntry.domain}.options.step.${step.step_id}.sections.${field.name}.description`
);
}
const prefix = options?.path?.[0] ? `sections.${options.path[0]}.` : "";
const description = hass.localize( const description = hass.localize(
`component.${step.translation_domain || configEntry.domain}.options.step.${step.step_id}.data_description.${field.name}`, `component.${step.translation_domain || configEntry.domain}.options.step.${step.step_id}.${prefix}data_description.${field.name}`,
step.description_placeholders step.description_placeholders
); );
return description return description

View File

@ -225,11 +225,24 @@ class StepFlowForm extends LitElement {
this._stepData = ev.detail.value; this._stepData = ev.detail.value;
} }
private _labelCallback = (field: HaFormSchema): string => private _labelCallback = (field: HaFormSchema, _data, options): string =>
this.flowConfig.renderShowFormStepFieldLabel(this.hass, this.step, field); this.flowConfig.renderShowFormStepFieldLabel(
this.hass,
this.step,
field,
options
);
private _helperCallback = (field: HaFormSchema): string | TemplateResult => private _helperCallback = (
this.flowConfig.renderShowFormStepFieldHelper(this.hass, this.step, field); field: HaFormSchema,
options
): string | TemplateResult =>
this.flowConfig.renderShowFormStepFieldHelper(
this.hass,
this.step,
field,
options
);
private _errorCallback = (error: string) => private _errorCallback = (error: string) =>
this.flowConfig.renderShowFormStepFieldError(this.hass, this.step, error); this.flowConfig.renderShowFormStepFieldError(this.hass, this.step, error);

View File

@ -96,20 +96,20 @@ export const showRepairsFlowDialog = (
: ""; : "";
}, },
renderShowFormStepFieldLabel(hass, step, field) { renderShowFormStepFieldLabel(hass, step, field, options) {
return hass.localize( return hass.localize(
`component.${issue.domain}.issues.${ `component.${issue.domain}.issues.${
issue.translation_key || issue.issue_id issue.translation_key || issue.issue_id
}.fix_flow.step.${step.step_id}.data.${field.name}`, }.fix_flow.step.${step.step_id}.${options?.prefix ? `section.${options.prefix[0]}.` : ""}data.${field.name}`,
step.description_placeholders step.description_placeholders
); );
}, },
renderShowFormStepFieldHelper(hass, step, field) { renderShowFormStepFieldHelper(hass, step, field, options) {
const description = hass.localize( const description = hass.localize(
`component.${issue.domain}.issues.${ `component.${issue.domain}.issues.${
issue.translation_key || issue.issue_id issue.translation_key || issue.issue_id
}.fix_flow.step.${step.step_id}.data_description.${field.name}`, }.fix_flow.step.${step.step_id}.${options?.prefix ? `section.${options.prefix[0]}.` : ""}data_description.${field.name}`,
step.description_placeholders step.description_placeholders
); );
return description return description

View File

@ -74,10 +74,10 @@ export class HuiEntityBadgeEditor
[ [
{ name: "entity", selector: { entity: {} } }, { name: "entity", selector: { entity: {} } },
{ {
name: "", name: "appearance",
type: "expandable", type: "expandable",
flatten: true,
iconPath: mdiPalette, iconPath: mdiPalette,
title: localize(`ui.panel.lovelace.editor.badge.entity.appearance`),
schema: [ schema: [
{ {
name: "", name: "",
@ -153,9 +153,9 @@ export class HuiEntityBadgeEditor
], ],
}, },
{ {
name: "", name: "interactions",
type: "expandable", type: "expandable",
title: localize(`ui.panel.lovelace.editor.badge.entity.interactions`), flatten: true,
iconPath: mdiGestureTap, iconPath: mdiGestureTap,
schema: [ schema: [
{ {
@ -238,6 +238,8 @@ export class HuiEntityBadgeEditor
case "state_content": case "state_content":
case "show_entity_picture": case "show_entity_picture":
case "displayed_elements": case "displayed_elements":
case "appearance":
case "interactions":
return this.hass!.localize( return this.hass!.localize(
`ui.panel.lovelace.editor.badge.entity.${schema.name}` `ui.panel.lovelace.editor.badge.entity.${schema.name}`
); );

View File

@ -14,7 +14,6 @@ import {
union, union,
} from "superstruct"; } from "superstruct";
import { HASSDomEvent, fireEvent } from "../../../../common/dom/fire_event"; import { HASSDomEvent, fireEvent } from "../../../../common/dom/fire_event";
import { LocalizeFunc } from "../../../../common/translations/localize";
import "../../../../components/ha-form/ha-form"; import "../../../../components/ha-form/ha-form";
import type { import type {
HaFormSchema, HaFormSchema,
@ -69,18 +68,14 @@ export class HuiTileCardEditor
} }
private _schema = memoizeOne( private _schema = memoizeOne(
( (entityId: string | undefined, hideState: boolean) =>
localize: LocalizeFunc,
entityId: string | undefined,
hideState: boolean
) =>
[ [
{ name: "entity", selector: { entity: {} } }, { name: "entity", selector: { entity: {} } },
{ {
name: "", name: "appearance",
flatten: true,
type: "expandable", type: "expandable",
iconPath: mdiPalette, iconPath: mdiPalette,
title: localize(`ui.panel.lovelace.editor.card.tile.appearance`),
schema: [ schema: [
{ {
name: "", name: "",
@ -136,9 +131,9 @@ export class HuiTileCardEditor
], ],
}, },
{ {
name: "", name: "interactions",
type: "expandable", type: "expandable",
title: localize(`ui.panel.lovelace.editor.card.tile.interactions`), flatten: true,
iconPath: mdiGestureTap, iconPath: mdiGestureTap,
schema: [ schema: [
{ {
@ -178,7 +173,6 @@ export class HuiTileCardEditor
: undefined; : undefined;
const schema = this._schema( const schema = this._schema(
this.hass!.localize,
this._config.entity, this._config.entity,
this._config.hide_state ?? false this._config.hide_state ?? false
); );
@ -306,6 +300,8 @@ export class HuiTileCardEditor
case "vertical": case "vertical":
case "hide_state": case "hide_state":
case "state_content": case "state_content":
case "appearance":
case "interactions":
return this.hass!.localize( return this.hass!.localize(
`ui.panel.lovelace.editor.card.tile.${schema.name}` `ui.panel.lovelace.editor.card.tile.${schema.name}`
); );