diff --git a/src/Helper.ts b/src/Helper.ts index 16e93d0..4ef868a 100644 --- a/src/Helper.ts +++ b/src/Helper.ts @@ -1,10 +1,11 @@ -import {configurationDefaults} from "./configurationDefaults"; +import {getConfigurationDefaults} from "./configurationDefaults"; import {HassEntities, HassEntity} from "home-assistant-js-websocket"; import deepmerge from "deepmerge"; import {EntityRegistryEntry} from "./types/homeassistant/data/entity_registry"; import {DeviceRegistryEntry} from "./types/homeassistant/data/device_registry"; import {AreaRegistryEntry} from "./types/homeassistant/data/area_registry"; import {generic} from "./types/strategy/generic"; +import setupCustomLocalize from "./localize"; import StrategyArea = generic.StrategyArea; /** @@ -68,6 +69,7 @@ class Helper { * @private */ static #debug: boolean; + static customLocalize: Function; /** * Class constructor. @@ -140,8 +142,12 @@ class Helper { */ static async initialize(info: generic.DashBoardInfo): Promise { // Initialize properties. - this.#hassStates = info.hass.states; + this.customLocalize = setupCustomLocalize(info.hass); + + const configurationDefaults = getConfigurationDefaults(this.customLocalize) this.#strategyOptions = deepmerge(configurationDefaults, info.config?.strategy?.options ?? {}); + + this.#hassStates = info.hass.states; this.#debug = this.#strategyOptions.debug; try { diff --git a/src/configurationDefaults.ts b/src/configurationDefaults.ts index fe33928..bc6400c 100644 --- a/src/configurationDefaults.ts +++ b/src/configurationDefaults.ts @@ -4,151 +4,153 @@ import StrategyDefaults = generic.StrategyDefaults; /** * Default configuration for the mushroom strategy. */ -export const configurationDefaults: StrategyDefaults = { - areas: { - undisclosed: { - area_id: "undisclosed", - floor_id: null, - name: "Undisclosed", - picture: null, - icon: "mdi:floor-plan", - labels: [], - aliases: [], - hidden: false, +export const getConfigurationDefaults = (localize: Function): StrategyDefaults => { + return { + areas: { + undisclosed: { + area_id: "undisclosed", + floor_id: null, + name: "Undisclosed", + picture: null, + icon: "mdi:floor-plan", + labels: [], + aliases: [], + hidden: false, + } + }, + debug: false, + domains: { + _: { + hide_config_entities: false, + }, + default: { + title: localize("generic.miscellaneous"), + showControls: false, + hidden: false, + }, + light: { + title: localize("light.lights"), + showControls: true, + iconOn: "mdi:lightbulb", + iconOff: "mdi:lightbulb-off", + onService: "light.turn_on", + offService: "light.turn_off", + hidden: false, + }, + fan: { + title: localize("fan.fans"), + showControls: true, + iconOn: "mdi:fan", + iconOff: "mdi:fan-off", + onService: "fan.turn_on", + offService: "fan.turn_off", + hidden: false, + }, + cover: { + title: localize("cover.covers"), + showControls: true, + iconOn: "mdi:arrow-up", + iconOff: "mdi:arrow-down", + onService: "cover.open_cover", + offService: "cover.close_cover", + hidden: false, + }, + switch: { + title: localize("switch.switches"), + showControls: true, + iconOn: "mdi:power-plug", + iconOff: "mdi:power-plug-off", + onService: "switch.turn_on", + offService: "switch.turn_off", + hidden: false, + }, + camera: { + title: localize("camera.cameras"), + showControls: false, + hidden: false, + }, + lock: { + title: localize("lock.locks"), + showControls: false, + hidden: false, + }, + climate: { + title: localize("climate.climates"), + showControls: false, + hidden: false, + }, + media_player: { + title: localize("media_player.media_players"), + showControls: false, + hidden: false, + }, + sensor: { + title: localize("sensor.sensors"), + showControls: false, + hidden: false, + }, + binary_sensor: { + title: `${localize("sensor.binary")} ` + localize("sensor.sensors"), + showControls: false, + hidden: false, + }, + number: { + title: localize("generic.numbers"), + showControls: false, + hidden: false, + }, + vacuum: { + title: localize("vacuum.vacuums"), + showControls: true, + hidden: false, + }, + select: { + title: localize("select.selects"), + showControls: false, + hidden: false, + }, + input_select: { + title: localize("input_select.input_selects"), + showControls: false, + hidden: false, + }, + }, + home_view: { + hidden: [], + }, + views: { + home: { + order: 1, + hidden: false, + }, + light: { + order: 2, + hidden: false, + }, + fan: { + order: 3, + hidden: false, + }, + cover: { + order: 4, + hidden: false, + }, + switch: { + order: 5, + hidden: false, + }, + climate: { + order: 6, + hidden: false, + }, + camera: { + order: 7, + hidden: false, + }, + vacuum: { + order: 8, + hidden: false, + }, } - }, - debug: false, - domains: { - _: { - hide_config_entities: false, - }, - default: { - title: "Miscellaneous", - showControls: false, - hidden: false, - }, - light: { - title: "Lights", - showControls: true, - iconOn: "mdi:lightbulb", - iconOff: "mdi:lightbulb-off", - onService: "light.turn_on", - offService: "light.turn_off", - hidden: false, - }, - fan: { - title: "Fans", - showControls: true, - iconOn: "mdi:fan", - iconOff: "mdi:fan-off", - onService: "fan.turn_on", - offService: "fan.turn_off", - hidden: false, - }, - cover: { - title: "Covers", - showControls: true, - iconOn: "mdi:arrow-up", - iconOff: "mdi:arrow-down", - onService: "cover.open_cover", - offService: "cover.close_cover", - hidden: false, - }, - switch: { - title: "Switches", - showControls: true, - iconOn: "mdi:power-plug", - iconOff: "mdi:power-plug-off", - onService: "switch.turn_on", - offService: "switch.turn_off", - hidden: false, - }, - camera: { - title: "Cameras", - showControls: false, - hidden: false, - }, - lock: { - title: "Locks", - showControls: false, - hidden: false, - }, - climate: { - title: "Climates", - showControls: false, - hidden: false, - }, - media_player: { - title: "Media Players", - showControls: false, - hidden: false, - }, - sensor: { - title: "Sensors", - showControls: false, - hidden: false, - }, - binary_sensor: { - title: "Binary Sensors", - showControls: false, - hidden: false, - }, - number: { - title: "Numbers", - showControls: false, - hidden: false, - }, - vacuum: { - title: "Vacuums", - showControls: true, - hidden: false, - }, - select: { - title: "Selects", - showControls: false, - hidden: false, - }, - input_select: { - title: "Input Selects", - showControls: false, - hidden: false, - }, - }, - home_view: { - hidden: [], - }, - views: { - home: { - order: 1, - hidden: false, - }, - light: { - order: 2, - hidden: false, - }, - fan: { - order: 3, - hidden: false, - }, - cover: { - order: 4, - hidden: false, - }, - switch: { - order: 5, - hidden: false, - }, - climate: { - order: 6, - hidden: false, - }, - camera: { - order: 7, - hidden: false, - }, - vacuum: { - order: 8, - hidden: false, - }, - } + }; }; diff --git a/src/localize.ts b/src/localize.ts new file mode 100644 index 0000000..99e59e2 --- /dev/null +++ b/src/localize.ts @@ -0,0 +1,56 @@ +import {HomeAssistant} from "./types/homeassistant/types"; +import * as en from "./translations/en.json"; +import * as nl from "./translations/nl.json"; + +/* Registry of currently supported languages */ +const languages: Record = { + en, + nl, +}; + +/* The fallback language if the user-defined language isn't defined */ +const DEFAULT_LANG = "en"; + +/** + * Get a string by keyword and language. + * + * @param {string} key The keyword to look for in object notation (E.g. generic.home). + * @param {string} lang The language to get the string from (E.g. en). + * + * @return {string | undefined} The requested string or undefined if the keyword doesn't exist/on error. + */ +function getTranslatedString(key: string, lang: string): string | undefined { + try { + return key + .split(".") + .reduce( + (o, i) => (o as Record)[i], + languages[lang] + ) as string; + } catch (_) { + + return undefined; + } +} + +/** + * Set up the localization. + * + * It reads the user-defined language with a fall-back to english and returns a function to get strings from + * language-files by keyword. + * + * If the keyword is undefined, or on error, the keyword itself is returned. + * + * @param {HomeAssistant} hass The Home Assistant object. + * @return {(key: string) => string} The function to call for translating strings. + */ +export default function setupCustomLocalize(hass?: HomeAssistant): (key: string) => string { + return function (key: string) { + const lang = hass?.locale.language ?? DEFAULT_LANG; + + let translated = getTranslatedString(key, lang); + if (!translated) translated = getTranslatedString(key, DEFAULT_LANG); + + return translated ?? key; + }; +} diff --git a/src/translations/en.json b/src/translations/en.json new file mode 100644 index 0000000..038d8ab --- /dev/null +++ b/src/translations/en.json @@ -0,0 +1,61 @@ +{ + "camera": { + "all_cameras": "All Cameras", + "cameras": "Cameras" + }, + "climate": { + "all_climates": "All Climates", + "climates": "Climates" + }, + "covers": { + "all_covers": "All Covers", + "covers": "Covers" + }, + "fan": { + "all_fans": "All Fans", + "fans": "Fans" + }, + "generic": { + "all": "All", + "areas": "Areas", + "busy": "Busy", + "good_afternoon": "Good afternoon", + "good_evening": "Good evening", + "good_morning": "Good morning", + "hello": "Hello", + "home": "Home", + "miscellaneous": "Miscellaneous", + "numbers": "Numbers", + "off": "Off", + "on": "On", + "open": "Open" + }, + "input_select": { + "input_selects": "Input Selects" + }, + "light": { + "all_lights": "All Lights", + "lights": "Lights" + }, + "lock": { + "locks": "Locks" + }, + "media_player": { + "media_players": "Mediaplayers" + }, + "select": { + "selects": "Selects" + }, + "sensor": { + "binary": "Binary", + "sensors": "Sensors" + }, + "switch": { + "all_switches": "All Switches", + "switches": "Switches" + }, + "vacuum": { + "all_vacuums": "All Vacuums", + "vacuums": "Vacuums" + } +} diff --git a/src/translations/nl.json b/src/translations/nl.json new file mode 100644 index 0000000..5d17f5f --- /dev/null +++ b/src/translations/nl.json @@ -0,0 +1,61 @@ +{ + "camera": { + "all_cameras": "Alle Cameras", + "cameras": "Cameras" + }, + "climate": { + "all_climates": "Alle Klimaatregelingen", + "climates": "Klimaatregelingen" + }, + "covers": { + "all_covers": "Alle Bedekkingen", + "covers": "Bedekkingen" + }, + "fan": { + "all_fans": "Alle Ventilatoren", + "fans": "Ventilatoren" + }, + "generic": { + "all": "Alle", + "areas": "Ruimtes", + "busy": "Bezig", + "good_afternoon": "Goede middag", + "good_evening": "Goede avond", + "good_morning": "Goede morgen", + "hello": "Hallo", + "home": "Start", + "miscellaneous": "Overige", + "numbers": "Nummers", + "off": "Uit", + "on": "Aan", + "open": "Open" + }, + "input_select": { + "input_selects": "Lijsten" + }, + "light": { + "all_lights": "Alle Lampen", + "lights": "Lampen" + }, + "lock": { + "locks": "Sloten" + }, + "media_player": { + "media_players": "Mediaspelers" + }, + "select": { + "selects": "Statuslijsten" + }, + "sensor": { + "binary": "Binaire", + "sensors": "Sensoren" + }, + "switch": { + "all_switches": "Alle Schakelaars", + "switches": "Schakelaars" + }, + "vacuum": { + "all_vacuums": "Alle Afzuiging", + "vacuums": "Afzuiging" + } +} diff --git a/src/views/CameraView.ts b/src/views/CameraView.ts index 949052f..03c83a0 100644 --- a/src/views/CameraView.ts +++ b/src/views/CameraView.ts @@ -30,7 +30,7 @@ class CameraView extends AbstractView { * @private */ #defaultConfig: views.ViewConfig = { - title: "Cameras", + title: Helper.customLocalize("camera.cameras"), path: "cameras", icon: "mdi:cctv", subview: false, @@ -46,8 +46,10 @@ class CameraView extends AbstractView { * @private */ #viewControllerCardConfig: cards.ControllerCardOptions = { - title: "All Cameras", - subtitle: Helper.getCountTemplate(CameraView.#domain, "ne", "off") + " cameras on", + title: Helper.customLocalize("camera.all_cameras"), + subtitle: + `${Helper.getCountTemplate(CameraView.#domain, "ne", "off")} ${Helper.customLocalize("camera.cameras")} ` + + Helper.customLocalize("generic.busy"), }; /** diff --git a/src/views/ClimateView.ts b/src/views/ClimateView.ts index 1e0a476..e1bf5d0 100644 --- a/src/views/ClimateView.ts +++ b/src/views/ClimateView.ts @@ -30,7 +30,7 @@ class ClimateView extends AbstractView { * @private */ #defaultConfig: views.ViewConfig = { - title: "Climates", + title: Helper.customLocalize("climate.climates"), path: "climates", icon: "mdi:thermostat", subview: false, @@ -46,8 +46,10 @@ class ClimateView extends AbstractView { * @private */ #viewControllerCardConfig: cards.ControllerCardOptions = { - title: "All Climates", - subtitle: Helper.getCountTemplate(ClimateView.#domain, "ne", "off") + " climates on", + title: Helper.customLocalize("climate.all_climates"), + subtitle: + `${Helper.getCountTemplate(ClimateView.#domain, "ne", "off")} ${Helper.customLocalize("climate.climates")} ` + + Helper.customLocalize("generic.busy"), }; /** diff --git a/src/views/CoverView.ts b/src/views/CoverView.ts index 23c1f1a..4722e46 100644 --- a/src/views/CoverView.ts +++ b/src/views/CoverView.ts @@ -30,7 +30,7 @@ class CoverView extends AbstractView { * @private */ #defaultConfig: views.ViewConfig = { - title: "Covers", + title: Helper.customLocalize("cover.covers"), path: "covers", icon: "mdi:window-open", subview: false, @@ -49,8 +49,10 @@ class CoverView extends AbstractView { * @private */ #viewControllerCardConfig: cards.ControllerCardOptions = { - title: "All Covers", - subtitle: Helper.getCountTemplate(CoverView.#domain, "eq", "open") + " covers open", + title: Helper.customLocalize("cover.all_covers"), + subtitle: + `${Helper.getCountTemplate(CoverView.#domain, "eq", "open")} ${Helper.customLocalize("cover.covers")} ` + + Helper.customLocalize("generic.open"), }; /** diff --git a/src/views/FanView.ts b/src/views/FanView.ts index e1699b0..09b9aab 100644 --- a/src/views/FanView.ts +++ b/src/views/FanView.ts @@ -30,7 +30,7 @@ class FanView extends AbstractView { * @private */ #defaultConfig: views.ViewConfig = { - title: "Fans", + title: Helper.customLocalize("fan.fans"), path: "fans", icon: "mdi:fan", subview: false, @@ -49,8 +49,10 @@ class FanView extends AbstractView { * @private */ #viewControllerCardConfig: cards.ControllerCardOptions = { - title: "All Fans", - subtitle: Helper.getCountTemplate(FanView.#domain, "eq", "on") + " fans on", + title: Helper.customLocalize("fan.all_fans"), + subtitle: + `${Helper.getCountTemplate(FanView.#domain, "eq", "on")} ${Helper.customLocalize("fan.fans")} ` + + Helper.customLocalize("generic.on"), }; /** diff --git a/src/views/HomeView.ts b/src/views/HomeView.ts index 54b53f9..6b9dd45 100644 --- a/src/views/HomeView.ts +++ b/src/views/HomeView.ts @@ -27,7 +27,7 @@ class HomeView extends AbstractView { * @private */ #defaultConfig: views.ViewConfig = { - title: "Home", + title: Helper.customLocalize("generic.home"), icon: "mdi:home-assistant", path: "home", subview: false, @@ -77,24 +77,28 @@ class HomeView extends AbstractView { } if (!Helper.strategyOptions.home_view.hidden.includes("greeting")) { - homeViewCards.push({ - type: "custom:mushroom-template-card", - primary: "{% set time = now().hour %} {% if (time >= 18) %} Good Evening, {{user}}! {% elif (time >= 12) %} Good Afternoon, {{user}}! {% elif (time >= 5) %} Good Morning, {{user}}! {% else %} Hello, {{user}}! {% endif %}", - icon: "mdi:hand-wave", - icon_color: "orange", - tap_action: { - action: "none", - } as ActionConfig, - double_tap_action: { - action: "none", - } as ActionConfig, - hold_action: { - action: "none", - } as ActionConfig, - } as TemplateCardConfig); + const greeting = + homeViewCards.push({ + type: "custom:mushroom-template-card", + primary: + `{% set time = now().hour %} {% if (time >= 18) %} ${Helper.customLocalize("generic.good_evening")},{{user}}! + {% elif (time >= 12) %} ${Helper.customLocalize("generic.good_afternoon")}, {{user}}! + {% elif (time >= 5) %} ${Helper.customLocalize("generic.good_morning")}, {{user}}! + {% else %} ${Helper.customLocalize("generic.hello")}, {{user}}! {% endif %}`, + icon: "mdi:hand-wave", + icon_color: "orange", + tap_action: { + action: "none", + } as ActionConfig, + double_tap_action: { + action: "none", + } as ActionConfig, + hold_action: { + action: "none", + } as ActionConfig, + } as TemplateCardConfig); } - // Add quick access cards. if (options.quick_access_cards) { homeViewCards.push(...options.quick_access_cards); @@ -225,7 +229,7 @@ class HomeView extends AbstractView { if (!Helper.strategyOptions.home_view.hidden.includes("areasTitle")) { groupedCards.push({ type: "custom:mushroom-title-card", - title: "Areas", + title: Helper.customLocalize("generic.areas"), }, ); } diff --git a/src/views/LightView.ts b/src/views/LightView.ts index 75a2b15..7f5cab6 100644 --- a/src/views/LightView.ts +++ b/src/views/LightView.ts @@ -30,7 +30,7 @@ class LightView extends AbstractView { * @private */ #defaultConfig: views.ViewConfig = { - title: "Lights", + title: Helper.customLocalize("light.lights"), path: "lights", icon: "mdi:lightbulb-group", subview: false, @@ -49,8 +49,10 @@ class LightView extends AbstractView { * @private */ #viewControllerCardConfig: cards.ControllerCardOptions = { - title: "All Lights", - subtitle: Helper.getCountTemplate(LightView.#domain, "eq", "on") + " lights on", + title: Helper.customLocalize("light.all_lights"), + subtitle: + `${Helper.getCountTemplate(LightView.#domain, "eq", "on")} ${Helper.customLocalize("light.lights")} ` + + Helper.customLocalize("generic.on"), }; /** diff --git a/src/views/SwitchView.ts b/src/views/SwitchView.ts index 8244dab..88d9622 100644 --- a/src/views/SwitchView.ts +++ b/src/views/SwitchView.ts @@ -30,7 +30,7 @@ class SwitchView extends AbstractView { * @private */ #defaultConfig: views.ViewConfig = { - title: "Switches", + title: Helper.customLocalize("switch.switches"), path: "switches", icon: "mdi:dip-switch", subview: false, @@ -49,8 +49,10 @@ class SwitchView extends AbstractView { * @private */ #viewControllerCardConfig: cards.ControllerCardOptions = { - title: "All Switches", - subtitle: Helper.getCountTemplate(SwitchView.#domain, "eq", "on") + " switches on", + title: Helper.customLocalize("switch.all_switches"), + subtitle: + `${Helper.getCountTemplate(SwitchView.#domain, "eq", "on")} ${Helper.customLocalize("switch.switches")} ` + + Helper.customLocalize("generic.on"), }; /** diff --git a/src/views/VacuumView.ts b/src/views/VacuumView.ts index 55b4ae1..b2885a5 100644 --- a/src/views/VacuumView.ts +++ b/src/views/VacuumView.ts @@ -30,7 +30,7 @@ class VacuumView extends AbstractView { * @private */ #defaultConfig: views.ViewConfig = { - title: "Vacuums", + title: Helper.customLocalize("vacuum.vacuums"), path: "vacuums", icon: "mdi:robot-vacuum", subview: false, @@ -49,8 +49,10 @@ class VacuumView extends AbstractView { * @private */ #viewControllerCardConfig: cards.ControllerCardOptions = { - title: "All Vacuums", - subtitle: Helper.getCountTemplate(VacuumView.#domain, "ne", "off") + " vacuums on", + title: Helper.customLocalize("vacuum.all_vacuums"), + subtitle: + `${Helper.getCountTemplate(VacuumView.#domain, "ne", "off")} ${Helper.customLocalize("vacuum.vacuums")} ` + + Helper.customLocalize("generic.busy"), }; /** diff --git a/tsconfig.json b/tsconfig.json index 45286fb..551c253 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -29,7 +29,9 @@ "strictNullChecks": true, /* Completeness */ - "skipLibCheck": true + "skipLibCheck": true, + + "resolveJsonModule": true, }, "ts-node": { "compilerOptions": {