From 33b1b2635af8b3fc6793e3d88df465997f9dda78 Mon Sep 17 00:00:00 2001 From: Simon Lamon <32477463+silamon@users.noreply.github.com> Date: Mon, 25 Dec 2023 15:39:24 +0000 Subject: [PATCH 1/4] Implement Time Trigger Pattern --- src/data/automation_i18n.ts | 125 ++++++++++++++++++++++++------------ src/translations/en.json | 22 +++++-- 2 files changed, 100 insertions(+), 47 deletions(-) diff --git a/src/data/automation_i18n.ts b/src/data/automation_i18n.ts index aec2965cdf10..fec4b5a281a7 100644 --- a/src/data/automation_i18n.ts +++ b/src/data/automation_i18n.ts @@ -23,6 +23,7 @@ import { formatListWithAnds, formatListWithOrs, } from "../common/string/format-list"; +import { LocalizeFunc } from "../common/translations/localize"; const triggerTranslationBaseKey = "ui.panel.config.automation.editor.triggers.type"; @@ -64,22 +65,10 @@ const localizeTimeString = ( } }; -const ordinalSuffix = (n: number) => { - n %= 100; - if ([11, 12, 13].includes(n)) { - return "th"; - } - if (n % 10 === 1) { - return "st"; - } - if (n % 10 === 2) { - return "nd"; - } - if (n % 10 === 3) { - return "rd"; - } - return "th"; -}; +const ordinalSuffix = (n: number, localize: LocalizeFunc) => + localize(`${triggerTranslationBaseKey}.time_pattern.description.ordinal`, { + part: n, + }); export const describeTrigger = ( trigger: Trigger, @@ -401,14 +390,37 @@ const tryDescribeTrigger = ( // Time Pattern Trigger if (trigger.platform === "time_pattern") { if (!trigger.seconds && !trigger.minutes && !trigger.hours) { - return "When a time pattern matches"; + return hass.localize( + `${triggerTranslationBaseKey}.time_pattern.description.initial` + ); } - let result = "Trigger "; + + const invalidParts: Array<"seconds" | "minutes" | "hours"> = []; + + let secondsChoice: "every" | "every_interval" | "on_the_xth" | "other" = + "other"; + let minutesChoice: + | "every" + | "every_interval" + | "on_the_xth" + | "other" + | "has_seconds" = "other"; + let hoursChoice: + | "every" + | "every_interval" + | "on_the_xth" + | "other" + | "has_seconds_or_minutes" = "other"; + + let seconds = 0; + let minutes = 0; + let hours = 0; + if (trigger.seconds !== undefined) { const seconds_all = trigger.seconds === "*"; const seconds_interval = typeof trigger.seconds === "string" && trigger.seconds.startsWith("/"); - const seconds = seconds_all + seconds = seconds_all ? 0 : typeof trigger.seconds === "number" ? trigger.seconds @@ -422,22 +434,22 @@ const tryDescribeTrigger = ( seconds < 0 || (seconds_interval && seconds === 0) ) { - return "Invalid Time Pattern Seconds"; + invalidParts.push("seconds"); } if (seconds_all || (seconds_interval && seconds === 1)) { - result += "every second of "; + secondsChoice = "every"; } else if (seconds_interval) { - result += `every ${seconds} seconds of `; + secondsChoice = "every_interval"; } else { - result += `on the ${seconds}${ordinalSuffix(seconds)} second of `; + secondsChoice = "on_the_xth"; } } if (trigger.minutes !== undefined) { const minutes_all = trigger.minutes === "*"; const minutes_interval = typeof trigger.minutes === "string" && trigger.minutes.startsWith("/"); - const minutes = minutes_all + minutes = minutes_all ? 0 : typeof trigger.minutes === "number" ? trigger.minutes @@ -451,30 +463,30 @@ const tryDescribeTrigger = ( minutes < 0 || (minutes_interval && minutes === 0) ) { - return "Invalid Time Pattern Minutes"; + invalidParts.push("minutes"); } if (minutes_all || (minutes_interval && minutes === 1)) { - result += "every minute of "; + minutesChoice = "every"; } else if (minutes_interval) { - result += `every ${minutes} minutes of `; + minutesChoice = "every_interval"; } else { - result += `${ - trigger.seconds !== undefined ? "" : "on" - } the ${minutes}${ordinalSuffix(minutes)} minute of `; + minutesChoice = + trigger.seconds !== undefined ? "has_seconds" : "on_the_xth"; } } else if (trigger.seconds !== undefined) { if (trigger.hours !== undefined) { - result += `the 0${ordinalSuffix(0)} minute of `; + minutes = 0; + minutesChoice = "on_the_xth"; } else { - result += "every minute of "; + minutesChoice = "every"; } } if (trigger.hours !== undefined) { const hours_all = trigger.hours === "*"; const hours_interval = typeof trigger.hours === "string" && trigger.hours.startsWith("/"); - const hours = hours_all + hours = hours_all ? 0 : typeof trigger.hours === "number" ? trigger.hours @@ -488,24 +500,53 @@ const tryDescribeTrigger = ( hours < 0 || (hours_interval && hours === 0) ) { - return "Invalid Time Pattern Hours"; + invalidParts.push("hours"); } if (hours_all || (hours_interval && hours === 1)) { - result += "every hour"; + hoursChoice = "every"; } else if (hours_interval) { - result += `every ${hours} hours`; + hoursChoice = "every_interval"; } else { - result += `${ + hoursChoice = trigger.seconds !== undefined || trigger.minutes !== undefined - ? "" - : "on" - } the ${hours}${ordinalSuffix(hours)} hour`; + ? "has_seconds_or_minutes" + : "on_the_xth"; } } else { - result += "every hour"; + hoursChoice = "every"; } - return result; + + if (invalidParts.length !== 0) { + return hass.localize( + `${triggerTranslationBaseKey}.time_pattern.description.invalid`, + { + parts: formatListWithAnds( + hass.locale, + invalidParts.map((invalidPart) => + hass.localize( + `${triggerTranslationBaseKey}.time_pattern.${invalidPart}` + ) + ) + ), + } + ); + } + + return hass.localize( + `${triggerTranslationBaseKey}.time_pattern.description.full`, + { + secondsChoice: secondsChoice, + minutesChoice: minutesChoice, + hoursChoice: hoursChoice, + seconds: seconds, + minutes: minutes, + hours: hours, + secondsWithOrdinal: ordinalSuffix(seconds, hass.localize), + minutesWithOrdinal: ordinalSuffix(minutes, hass.localize), + hoursWithOrdinal: ordinalSuffix(hours, hass.localize), + } + ); } // Zone Trigger diff --git a/src/translations/en.json b/src/translations/en.json index 28bde2f44d9d..4448af24132f 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -2491,7 +2491,9 @@ "label": "Time and location", "description": "When someone enters or leaves a zone, or at a specific time." }, - "other": { "label": "Other" } + "other": { + "label": "Other" + } }, "type": { "calendar": { @@ -2662,7 +2664,11 @@ "minutes": "Minutes", "seconds": "Seconds", "description": { - "picker": "Periodically, every defined interval of time." + "picker": "Periodically, every defined interval of time.", + "initial": "When a time pattern matches", + "invalid": "Invalid Time Pattern for {parts}", + "full": "Trigger {secondsChoice, select, \n every {every second of }\n every_interval {every {seconds} seconds of } on_the_xth {on the {secondsWithOrdinal} second of } other {} } {minutesChoice, select, \n every {every minute of }\n every_interval {every {minutes} minutes of } has_seconds {the {minutesWithOrdinal} minute of } on_the_xth {on the {minutesWithOrdinal} minute of } other {} } {hoursChoice, select, \n every {every hour}\n every_interval {every {hours} hours} has_seconds_or_minutes {the {hoursWithOrdinal} hour} on_the_xth {on the {hoursWithOrdinal} hour} other {} \n}", + "ordinal": "{part, selectordinal, \none {#st}\ntwo {#nd}\nfew {#rd}\nother {#th}\n}" } }, "webhook": { @@ -2727,7 +2733,9 @@ "label": "Time and location", "description": "If someone is in a zone or if the current time is before or after a specified time." }, - "other": { "label": "Other" }, + "other": { + "label": "Other" + }, "building_blocks": { "label": "Building blocks", "description": "Build more complex conditions." @@ -2891,8 +2899,12 @@ "type_select": "Action type", "continue_on_error": "Continue on error", "groups": { - "helpers": { "label": "Helpers" }, - "other": { "label": "Other" }, + "helpers": { + "label": "Helpers" + }, + "other": { + "label": "Other" + }, "building_blocks": { "label": "Building blocks", "description": "Build more complex sequences of actions." From 80c6fd9c5976ac07c217335049138452130ebd13 Mon Sep 17 00:00:00 2001 From: Simon Lamon <32477463+silamon@users.noreply.github.com> Date: Thu, 28 Dec 2023 18:06:22 +0000 Subject: [PATCH 2/4] Ordinal suffix --- src/data/automation_i18n.ts | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/src/data/automation_i18n.ts b/src/data/automation_i18n.ts index fec4b5a281a7..27def9df520f 100644 --- a/src/data/automation_i18n.ts +++ b/src/data/automation_i18n.ts @@ -23,7 +23,6 @@ import { formatListWithAnds, formatListWithOrs, } from "../common/string/format-list"; -import { LocalizeFunc } from "../common/translations/localize"; const triggerTranslationBaseKey = "ui.panel.config.automation.editor.triggers.type"; @@ -65,11 +64,6 @@ const localizeTimeString = ( } }; -const ordinalSuffix = (n: number, localize: LocalizeFunc) => - localize(`${triggerTranslationBaseKey}.time_pattern.description.ordinal`, { - part: n, - }); - export const describeTrigger = ( trigger: Trigger, hass: HomeAssistant, @@ -542,9 +536,24 @@ const tryDescribeTrigger = ( seconds: seconds, minutes: minutes, hours: hours, - secondsWithOrdinal: ordinalSuffix(seconds, hass.localize), - minutesWithOrdinal: ordinalSuffix(minutes, hass.localize), - hoursWithOrdinal: ordinalSuffix(hours, hass.localize), + secondsWithOrdinal: hass.localize( + `${triggerTranslationBaseKey}.time_pattern.description.ordinal`, + { + part: seconds, + } + ), + minutesWithOrdinal: hass.localize( + `${triggerTranslationBaseKey}.time_pattern.description.ordinal`, + { + part: minutes, + } + ), + hoursWithOrdinal: hass.localize( + `${triggerTranslationBaseKey}.time_pattern.description.ordinal`, + { + part: hours, + } + ), } ); } From 53c58ba4c45e6ab9a38d96c6b41825953f326282 Mon Sep 17 00:00:00 2001 From: Simon Lamon <32477463+silamon@users.noreply.github.com> Date: Thu, 28 Dec 2023 18:12:06 +0000 Subject: [PATCH 3/4] formatting --- src/translations/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/translations/en.json b/src/translations/en.json index 4448af24132f..a24ad701397e 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -2667,7 +2667,7 @@ "picker": "Periodically, every defined interval of time.", "initial": "When a time pattern matches", "invalid": "Invalid Time Pattern for {parts}", - "full": "Trigger {secondsChoice, select, \n every {every second of }\n every_interval {every {seconds} seconds of } on_the_xth {on the {secondsWithOrdinal} second of } other {} } {minutesChoice, select, \n every {every minute of }\n every_interval {every {minutes} minutes of } has_seconds {the {minutesWithOrdinal} minute of } on_the_xth {on the {minutesWithOrdinal} minute of } other {} } {hoursChoice, select, \n every {every hour}\n every_interval {every {hours} hours} has_seconds_or_minutes {the {hoursWithOrdinal} hour} on_the_xth {on the {hoursWithOrdinal} hour} other {} \n}", + "full": "Trigger {secondsChoice, select, \n every {every second of }\n every_interval {every {seconds} seconds of }\n on_the_xth {on the {secondsWithOrdinal} second of }\n other {}\n} {minutesChoice, select, \n every {every minute of }\n every_interval {every {minutes} minutes of }\n has_seconds {the {minutesWithOrdinal} minute of }\n on_the_xth {on the {minutesWithOrdinal} minute of }\n other {}\n} {hoursChoice, select, \n every {every hour}\n every_interval {every {hours} hours}\n has_seconds_or_minutes {the {hoursWithOrdinal} hour}\n on_the_xth {on the {hoursWithOrdinal} hour}\n other {}\n}", "ordinal": "{part, selectordinal, \none {#st}\ntwo {#nd}\nfew {#rd}\nother {#th}\n}" } }, From fa3195fcd16084ed4730719ef9ab5f08ba969c98 Mon Sep 17 00:00:00 2001 From: Simon Lamon <32477463+silamon@users.noreply.github.com> Date: Thu, 28 Dec 2023 19:13:30 +0000 Subject: [PATCH 4/4] Fix incorrect month choice --- src/data/automation_i18n.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/data/automation_i18n.ts b/src/data/automation_i18n.ts index 27def9df520f..d7f186a0dc3b 100644 --- a/src/data/automation_i18n.ts +++ b/src/data/automation_i18n.ts @@ -471,7 +471,7 @@ const tryDescribeTrigger = ( } else if (trigger.seconds !== undefined) { if (trigger.hours !== undefined) { minutes = 0; - minutesChoice = "on_the_xth"; + minutesChoice = "has_seconds"; } else { minutesChoice = "every"; }