Files
frontend/src/data/script_i18n.ts
karwosts cff7ed9b05 Fix for undefined description_placeholders (#28395)
Another fix for undefined description_placeholders
2025-12-07 09:21:08 +02:00

499 lines
15 KiB
TypeScript

import { ensureArray } from "../common/array/ensure-array";
import { formatNumericDuration } from "../common/datetime/format_duration";
import secondsToDuration from "../common/datetime/seconds_to_duration";
import { computeDeviceNameDisplay } from "../common/entity/compute_device_name";
import { computeStateName } from "../common/entity/compute_state_name";
import { formatListWithAnds } from "../common/string/format-list";
import { isTemplate } from "../common/string/has-template";
import type { HomeAssistant } from "../types";
import type { Condition } from "./automation";
import { describeCondition } from "./automation_i18n";
import { localizeDeviceAutomationAction } from "./device_automation";
import type { EntityRegistryEntry } from "./entity_registry";
import {
computeEntityRegistryName,
entityRegistryById,
} from "./entity_registry";
import type { FloorRegistryEntry } from "./floor_registry";
import { domainToName } from "./integration";
import type { LabelRegistryEntry } from "./label_registry";
import type {
ActionType,
ActionTypes,
ChooseAction,
DelayAction,
DeviceAction,
EventAction,
IfAction,
ParallelAction,
RepeatAction,
SequenceAction,
SetConversationResponseAction,
StopAction,
VariablesAction,
WaitForTriggerAction,
} from "./script";
import { getActionType } from "./script";
const actionTranslationBaseKey =
"ui.panel.config.automation.editor.actions.type";
export const describeAction = <T extends ActionType>(
hass: HomeAssistant,
entityRegistry: EntityRegistryEntry[],
labelRegistry: LabelRegistryEntry[],
floorRegistry: Record<string, FloorRegistryEntry>,
action: ActionTypes[T],
actionType?: T,
ignoreAlias = false
): string => {
try {
const description = tryDescribeAction(
hass,
entityRegistry,
labelRegistry,
floorRegistry,
action,
actionType,
ignoreAlias
);
if (typeof description !== "string") {
throw new Error(String(description));
}
return description;
} catch (error: any) {
// eslint-disable-next-line no-console
console.error(error);
let msg = "Error in describing action";
if (error.message) {
msg += ": " + error.message;
}
return msg;
}
};
const tryDescribeAction = <T extends ActionType>(
hass: HomeAssistant,
entityRegistry: EntityRegistryEntry[],
labelRegistry: LabelRegistryEntry[],
floorRegistry: Record<string, FloorRegistryEntry>,
action: ActionTypes[T],
actionType?: T,
ignoreAlias = false
): string => {
if (action.alias && !ignoreAlias) {
return action.alias;
}
if (!actionType) {
actionType = getActionType(action) as T;
}
if (actionType === "service") {
const config = action as ActionTypes["service"];
const targets: string[] = [];
const targetOrData = config.target || config.data;
if (typeof targetOrData === "string" && isTemplate(targetOrData)) {
targets.push(
hass.localize(
`${actionTranslationBaseKey}.service.description.target_template`,
{ name: "target" }
)
);
} else if (targetOrData) {
for (const [key, name] of Object.entries({
area_id: "areas",
device_id: "devices",
entity_id: "entities",
floor_id: "floors",
label_id: "labels",
})) {
if (!(key in targetOrData)) {
continue;
}
const keyConf: string[] = ensureArray(targetOrData[key]) || [];
for (const targetThing of keyConf) {
if (isTemplate(targetThing)) {
targets.push(
hass.localize(
`${actionTranslationBaseKey}.service.description.target_template`,
{ name }
)
);
break;
} else if (key === "entity_id") {
if (targetThing.includes(".")) {
const state = hass.states[targetThing];
if (state) {
targets.push(computeStateName(state));
} else {
targets.push(targetThing);
}
} else {
const entityReg = entityRegistryById(entityRegistry)[targetThing];
if (entityReg) {
targets.push(
computeEntityRegistryName(hass, entityReg) || targetThing
);
} else if (targetThing === "all") {
targets.push(
hass.localize(
`${actionTranslationBaseKey}.service.description.target_every_entity`
)
);
} else {
targets.push(
hass.localize(
`${actionTranslationBaseKey}.service.description.target_unknown_entity`
)
);
}
}
} else if (key === "device_id") {
const device = hass.devices[targetThing];
if (device) {
targets.push(computeDeviceNameDisplay(device, hass));
} else {
targets.push(
hass.localize(
`${actionTranslationBaseKey}.service.description.target_unknown_device`
)
);
}
} else if (key === "area_id") {
const area = hass.areas[targetThing];
if (area?.name) {
targets.push(area.name);
} else {
targets.push(
hass.localize(
`${actionTranslationBaseKey}.service.description.target_unknown_area`
)
);
}
} else if (key === "floor_id") {
const floor = floorRegistry[targetThing] ?? undefined;
if (floor?.name) {
targets.push(floor.name);
} else {
targets.push(
hass.localize(
`${actionTranslationBaseKey}.service.description.target_unknown_floor`
)
);
}
} else if (key === "label_id") {
const label = labelRegistry.find(
(lbl) => lbl.label_id === targetThing
);
if (label?.name) {
targets.push(label.name);
} else {
targets.push(
hass.localize(
`${actionTranslationBaseKey}.service.description.target_unknown_label`
)
);
}
} else {
targets.push(targetThing);
}
}
}
}
if (
config.service_template ||
(config.action && isTemplate(config.action))
) {
return hass.localize(
targets.length
? `${actionTranslationBaseKey}.service.description.service_based_on_template`
: `${actionTranslationBaseKey}.service.description.service_based_on_template_no_targets`,
{
targets: formatListWithAnds(hass.locale, targets),
}
);
}
if (config.action) {
const [domain, serviceName] = config.action.split(".", 2);
const descriptionPlaceholders =
hass.services[domain]?.[serviceName]?.description_placeholders;
const service =
hass.localize(
`component.${domain}.services.${serviceName}.name`,
descriptionPlaceholders
) || hass.services[domain]?.[serviceName]?.name;
if (config.metadata) {
return hass.localize(
targets.length
? `${actionTranslationBaseKey}.service.description.service_name`
: `${actionTranslationBaseKey}.service.description.service_name_no_targets`,
{
domain: domainToName(hass.localize, domain),
name: service || config.action,
targets: formatListWithAnds(hass.locale, targets),
}
);
}
return hass.localize(
targets.length
? `${actionTranslationBaseKey}.service.description.service_based_on_name`
: `${actionTranslationBaseKey}.service.description.service_based_on_name_no_targets`,
{
name: service
? `${domainToName(hass.localize, domain)}: ${service}`
: config.action,
targets: formatListWithAnds(hass.locale, targets),
}
);
}
return hass.localize(
`${actionTranslationBaseKey}.service.description.service`
);
}
if (actionType === "delay") {
const config = action as DelayAction;
let duration: string;
if (typeof config.delay === "number") {
duration = hass.localize(
`${actionTranslationBaseKey}.delay.description.duration_string`,
{
string: secondsToDuration(config.delay)!,
}
);
} else if (typeof config.delay === "string") {
duration = isTemplate(config.delay)
? hass.localize(
`${actionTranslationBaseKey}.delay.description.duration_template`
)
: hass.localize(
`${actionTranslationBaseKey}.delay.description.duration_string`,
{
string:
config.delay ||
hass.localize(
`${actionTranslationBaseKey}.delay.description.duration_unknown`
),
}
);
} else if (config.delay) {
duration = hass.localize(
`${actionTranslationBaseKey}.delay.description.duration_string`,
{
string: formatNumericDuration(hass.locale, config.delay),
}
);
} else {
duration = hass.localize(
`${actionTranslationBaseKey}.delay.description.duration_string`,
{
string: hass.localize(
`${actionTranslationBaseKey}.delay.description.duration_unknown`
),
}
);
}
return hass.localize(`${actionTranslationBaseKey}.delay.description.full`, {
duration: duration,
});
}
if (actionType === "wait_for_trigger") {
const config = action as WaitForTriggerAction;
const triggers = ensureArray(config.wait_for_trigger);
if (!triggers || triggers.length === 0) {
return hass.localize(
`${actionTranslationBaseKey}.wait_for_trigger.description.wait_for_a_trigger`
);
}
return hass.localize(
`${actionTranslationBaseKey}.wait_for_trigger.description.wait_for_triggers`,
{ count: triggers.length }
);
}
if (actionType === "variables") {
const config = action as VariablesAction;
return hass.localize(
`${actionTranslationBaseKey}.variables.description.full`,
{
names: formatListWithAnds(hass.locale, Object.keys(config.variables)),
}
);
}
if (actionType === "fire_event") {
const config = action as EventAction;
if (isTemplate(config.event)) {
return hass.localize(
`${actionTranslationBaseKey}.event.description.full`,
{
name: hass.localize(
`${actionTranslationBaseKey}.event.description.template`
),
}
);
}
return hass.localize(`${actionTranslationBaseKey}.event.description.full`, {
name: config.event,
});
}
if (actionType === "wait_template") {
return hass.localize(
`${actionTranslationBaseKey}.wait_template.description.full`
);
}
if (actionType === "stop") {
const config = action as StopAction;
return hass.localize(`${actionTranslationBaseKey}.stop.description.full`, {
hasReason: config.stop !== undefined ? "true" : "false",
reason: config.stop,
});
}
if (actionType === "if") {
const config = action as IfAction;
if (config.else !== undefined) {
return hass.localize(
`${actionTranslationBaseKey}.if.description.if_else`
);
}
return hass.localize(`${actionTranslationBaseKey}.if.description.if`);
}
if (actionType === "choose") {
const config = action as ChooseAction;
if (config.choose) {
const numActions =
ensureArray(config.choose).length + (config.default ? 1 : 0);
return hass.localize(
`${actionTranslationBaseKey}.choose.description.full`,
{ number: numActions }
);
}
return hass.localize(
`${actionTranslationBaseKey}.choose.description.no_action`
);
}
if (actionType === "repeat") {
const config = action as RepeatAction;
let chosenAction = "";
if ("count" in config.repeat) {
const count = config.repeat.count;
chosenAction = hass.localize(
`${actionTranslationBaseKey}.repeat.description.count`,
{ count: count }
);
} else if ("while" in config.repeat) {
const conditions = ensureArray(config.repeat.while);
chosenAction = hass.localize(
`${actionTranslationBaseKey}.repeat.description.while_count`,
{ count: conditions.length }
);
} else if ("until" in config.repeat) {
const conditions = ensureArray(config.repeat.until);
chosenAction = hass.localize(
`${actionTranslationBaseKey}.repeat.description.until_count`,
{ count: conditions.length }
);
} else if ("for_each" in config.repeat) {
const items = ensureArray(config.repeat.for_each).map((item) =>
JSON.stringify(item)
);
chosenAction = hass.localize(
`${actionTranslationBaseKey}.repeat.description.for_each`,
{ items: formatListWithAnds(hass.locale, items) }
);
}
return hass.localize(
`${actionTranslationBaseKey}.repeat.description.full`,
{ chosenAction: chosenAction }
);
}
if (actionType === "check_condition") {
return hass.localize(
`${actionTranslationBaseKey}.check_condition.description.full`,
{
condition: describeCondition(action as Condition, hass, entityRegistry),
}
);
}
if (actionType === "device_action") {
const config = action as DeviceAction;
if (!config.device_id) {
return hass.localize(
`${actionTranslationBaseKey}.device_id.description.no_device`
);
}
const localized = localizeDeviceAutomationAction(
hass,
entityRegistry,
config
);
if (localized) {
return localized;
}
const stateObj = hass.states[config.entity_id];
if (config.type) {
return `${config.type} ${
stateObj ? computeStateName(stateObj) : config.entity_id
}`;
}
return hass.localize(
`${actionTranslationBaseKey}.device_id.description.perform_device_action`,
{
device: stateObj ? computeStateName(stateObj) : config.entity_id,
}
);
}
if (actionType === "sequence") {
const config = action as SequenceAction;
const numActions = ensureArray(config.sequence).length;
return hass.localize(
`${actionTranslationBaseKey}.sequence.description.full`,
{ number: numActions }
);
}
if (actionType === "parallel") {
const config = action as ParallelAction;
const numActions = ensureArray(config.parallel).length;
return hass.localize(
`${actionTranslationBaseKey}.parallel.description.full`,
{ number: numActions }
);
}
if (actionType === "set_conversation_response") {
const config = action as SetConversationResponseAction;
if (isTemplate(config.set_conversation_response)) {
return hass.localize(
`${actionTranslationBaseKey}.set_conversation_response.description.template`
);
}
return hass.localize(
`${actionTranslationBaseKey}.set_conversation_response.description.full`,
{ response: config.set_conversation_response }
);
}
return actionType;
};