From 851c1772e53fdbedde53ca2420b4f6c90211db26 Mon Sep 17 00:00:00 2001 From: Max Gramser Date: Sat, 19 Sep 2026 14:40:27 +0200 Subject: [PATCH] Take Unavailable and the Activate, Run and Press words from Home Assistant's frontend too Co-Authored-By: Claude Opus 5 --- components/smart_display/media_card.h | 2 +- components/smart_display/runtime_tiles.h | 12 +- components/smart_display/screen_text_keys.h | 388 ++++++++++---------- components/smart_display/tile_controls.h | 6 +- screen_manager/translations/de.json | 6 + screen_manager/translations/en-GB.json | 6 + screen_manager/translations/en.json | 12 +- screen_manager/translations/es.json | 6 + screen_manager/translations/fr.json | 6 + screen_manager/translations/it.json | 6 + screen_manager/translations/nl.json | 12 +- screen_manager/translations/pl.json | 6 + screen_manager/translations/pt.json | 6 + tests/screen_text_en.h | 12 +- tools/i18n.py | 24 ++ 15 files changed, 288 insertions(+), 222 deletions(-) diff --git a/components/smart_display/media_card.h b/components/smart_display/media_card.h index 61007e1..d927a11 100644 --- a/components/smart_display/media_card.h +++ b/components/smart_display/media_card.h @@ -149,7 +149,7 @@ inline const char *idle_text(const std::string &state) { if (state == "off") return screen_text::tr(screen_text::txt::ha_off); if (state == "standby") return screen_text::tr(screen_text::txt::ha_media_standby); if (state == "buffering") return screen_text::tr(screen_text::txt::media_loading); - if (state == "unavailable") return screen_text::tr(screen_text::txt::tile_unavailable); + if (state == "unavailable") return screen_text::tr(screen_text::txt::ha_unavailable); return screen_text::tr(screen_text::txt::media_not_playing); } inline bool playing(const std::string &state) { return state == "playing" || state == "buffering"; } diff --git a/components/smart_display/runtime_tiles.h b/components/smart_display/runtime_tiles.h index 1bcb6c8..79b5109 100644 --- a/components/smart_display/runtime_tiles.h +++ b/components/smart_display/runtime_tiles.h @@ -920,7 +920,7 @@ inline std::string detail_state(const Tile &t){ if(t.state=="returning")return tr(txt::ha_vacuum_returning); if(t.state=="idle")return tr(txt::ha_vacuum_idle); if(t.state=="error")return tr(txt::vacuum_check_robot); - if(!t.available())return tr(txt::tile_unavailable); + if(!t.available())return tr(txt::ha_unavailable); // Home Assistant's word where the screen has none of its own (firmware 0.2.58+). if(!t.extra().state_word.empty())return t.extra().state_word; return t.unit.empty()?t.state:screen_text::localize(t.state); @@ -1131,7 +1131,7 @@ inline void render_weather_detail(const Tile &t,bool large,int width,int height, detail_text(now,degrees(t.current),temp_x,cy+(hero-big_h)/2,temp_w,big,LV_TEXT_ALIGN_LEFT,ink); int text_x=temp_x+temp_w+(large?4:2),text_w=width-2*pad-card_pad-text_x; int lines_h=text_h+small_h+(large?2:0); - detail_text(now,t.available()?weather_text(t.state):tr(txt::tile_unavailable),text_x,cy+(hero-lines_h)/2,text_w,detail_font,LV_TEXT_ALIGN_LEFT,ink); + detail_text(now,t.available()?weather_text(t.state):tr(txt::ha_unavailable),text_x,cy+(hero-lines_h)/2,text_w,detail_font,LV_TEXT_ALIGN_LEFT,ink); std::string details; if(std::isfinite(weather.feels))details=fill(txt::weather_feels_like,"n",(int)std::lround(weather.feels)); if(std::isfinite(t.humidity)){snprintf(b,sizeof(b),"%d%%",(int)std::lround(t.humidity));details+=(details.empty()?"":" · ")+std::string(b);} @@ -1373,7 +1373,7 @@ inline constexpr uint32_t COVER_ACCENT = theme::ha::PURPLE; inline uint32_t cover_track(){return theme::tint(COVER_ACCENT,37);} inline uint32_t cover_slats(){return theme::tint(COVER_ACCENT,80);} inline lv_obj_t *cover_values[2]{}; -inline std::string cover_status_line(const Tile &t){return t.available()?tile_controls::cover_card_status(t):tr(txt::tile_unavailable);} +inline std::string cover_status_line(const Tile &t){return t.available()?tile_controls::cover_card_status(t):tr(txt::ha_unavailable);} inline void cover_slider_event(lv_event_t *e){ auto *slider=lv_event_get_target_obj(e);auto code=lv_event_get_code(e); const bool tilt=(uintptr_t)lv_event_get_user_data(e)==1; @@ -1570,7 +1570,7 @@ inline bool history_fits(const Tile &t){ // Home Assistant's word for the state now: from this entity's history words, else the card's own. The history can // still be the previous card's while this one waits for its answer. inline std::string history_words(const Tile &t){ - if(!t.available())return tr(txt::tile_unavailable); + if(!t.available())return tr(txt::ha_unavailable); if(history.entity==t.entity)for(const auto &pair:history.words)if(pair.first==t.state)return pair.second; return detail_state(t); } @@ -1809,7 +1809,7 @@ inline void render_history_detail(const Tile &t,bool large,int width,int height, if(line){ int decimals=0;const auto dot=t.state.find('.');if(dot!=std::string::npos)decimals=static_cast(std::min(4,t.state.size()-dot-1)); if(c.ready)decimals=h.decimals; - c.value_text=!t.available()?std::string(tr(txt::tile_unavailable)):numeric?history_view::number(current,decimals,t.unit):t.state; + c.value_text=!t.available()?std::string(tr(txt::ha_unavailable)):numeric?history_view::number(current,decimals,t.unit):t.state; if(c.ready){ // The value now counts too: it can lie beyond the history (the running hour is not in the statistics yet). if(h.has_high){c.high=h.high;c.high_at=h.high_at;} @@ -3117,7 +3117,7 @@ inline void render_slot(size_t slot) { // Nothing in Home Assistant stands behind the settings card, so it says the same with the link down. if (t.is_settings()) value = tr(txt::tile_tap_to_open); else if (t.is_page()) value = fill(txt::tile_page, "n", t.page_target()); - else if (!fresh() || !t.available()) value = tr(txt::tile_unavailable); + else if (!fresh() || !t.available()) value = tr(txt::ha_unavailable); else if (t.refused_at && esphome::millis() - t.refused_at < 4000) value = tr(txt::tile_refused); else if (d == "light" && t.state == "on" && tile_controls::effect_running(t.extra().effect)) value = t.extra().effect; else if (d == "light" && t.state == "on" && std::isfinite(t.brightness)) value = std::to_string(static_cast(std::lround(std::clamp(t.brightness, 0.0f, 255.0f) * 100 / 255))) + " %"; diff --git a/components/smart_display/screen_text_keys.h b/components/smart_display/screen_text_keys.h index 77f5c35..34a6c53 100644 --- a/components/smart_display/screen_text_keys.h +++ b/components/smart_display/screen_text_keys.h @@ -73,199 +73,199 @@ constexpr uint16_t settings_hours = 108; constexpr uint16_t settings_hours_minutes = 109; constexpr uint16_t tile_tap_to_open = 110; constexpr uint16_t tile_page = 111; -constexpr uint16_t tile_unavailable = 112; -constexpr uint16_t tile_refused = 113; -constexpr uint16_t tile_command_sent = 114; -constexpr uint16_t tile_confirmed = 115; -constexpr uint16_t alert_notification = 116; -constexpr uint16_t alert_ok = 117; -constexpr uint16_t light_color = 118; -constexpr uint16_t light_color_temperature = 119; -constexpr uint16_t light_brightness = 120; -constexpr uint16_t light_waiting = 121; -constexpr uint16_t effects_effect = 122; -constexpr uint16_t effects_nothing = 123; -constexpr uint16_t effects_loading = 124; -constexpr uint16_t effects_not_connected = 125; -constexpr uint16_t climate_target = 126; -constexpr uint16_t climate_mode = 127; -constexpr uint16_t climate_fan = 128; -constexpr uint16_t climate_swing = 129; -constexpr uint16_t climate_now = 130; -constexpr uint16_t vacuum_check_robot = 131; -constexpr uint16_t vacuum_auto_suction = 132; -constexpr uint16_t vacuum_robot_chooses = 133; -constexpr uint16_t vacuum_per_room = 134; -constexpr uint16_t vacuum_speed_quiet = 135; -constexpr uint16_t vacuum_speed_balanced = 136; -constexpr uint16_t vacuum_speed_turbo = 137; -constexpr uint16_t vacuum_speed_max = 138; -constexpr uint16_t vacuum_pause_cleaning = 139; -constexpr uint16_t vacuum_pause = 140; -constexpr uint16_t vacuum_resume = 141; -constexpr uint16_t vacuum_start_cleaning = 142; -constexpr uint16_t vacuum_clean = 143; -constexpr uint16_t vacuum_dock = 144; -constexpr uint16_t cover_position = 145; -constexpr uint16_t cover_tilt = 146; -constexpr uint16_t cover_tilt_value = 147; -constexpr uint16_t weather_feels_like = 148; -constexpr uint16_t weather_no_forecast = 149; -constexpr uint16_t weather_coming_days = 150; -constexpr uint16_t history_no_data = 151; -constexpr uint16_t history_average = 152; -constexpr uint16_t history_average_of = 153; -constexpr uint16_t history_now = 154; -constexpr uint16_t history_range_hour = 155; -constexpr uint16_t history_range_day = 156; -constexpr uint16_t history_range_week = 157; -constexpr uint16_t history_high = 158; -constexpr uint16_t history_low = 159; -constexpr uint16_t history_times = 160; -constexpr uint16_t history_loading = 161; -constexpr uint16_t history_empty = 162; -constexpr uint16_t history_unavailable = 163; -constexpr uint16_t media_muted = 164; -constexpr uint16_t media_not_playing = 165; -constexpr uint16_t media_loading = 166; -constexpr uint16_t timer_running = 167; -constexpr uint16_t timer_paused = 168; -constexpr uint16_t timer_stopped = 169; -constexpr uint16_t timer_pause = 170; -constexpr uint16_t timer_start = 171; -constexpr uint16_t timer_cancel = 172; -constexpr uint16_t timer_paused_left = 173; -constexpr uint16_t sun_name = 174; -constexpr uint16_t sun_sunrise = 175; -constexpr uint16_t sun_sunset = 176; -constexpr uint16_t sun_rise = 177; -constexpr uint16_t sun_set = 178; -constexpr uint16_t script_running = 179; -constexpr uint16_t script_never_run = 180; -constexpr uint16_t script_last_time = 181; -constexpr uint16_t script_yesterday_time = 182; -constexpr uint16_t script_last_date = 183; -constexpr uint16_t camera_no_image = 184; -constexpr uint16_t camera_loading = 185; -constexpr uint16_t camera_live = 186; -constexpr uint16_t camera_recording = 187; -constexpr uint16_t camera_tap_to_view = 188; -constexpr uint16_t camera_no_image_yet = 189; -constexpr uint16_t button_activate = 190; -constexpr uint16_t button_run = 191; -constexpr uint16_t button_press = 192; -constexpr uint16_t calibration_title = 193; -constexpr uint16_t calibration_touch_check = 194; -constexpr uint16_t calibration_hold = 195; -constexpr uint16_t calibration_crosshair = 196; -constexpr uint16_t calibration_gently = 197; -constexpr uint16_t calibration_same = 198; -constexpr uint16_t calibration_rejected = 199; -constexpr uint16_t calibration_save_failed = 200; -constexpr uint16_t ha_on = 201; -constexpr uint16_t ha_off = 202; -constexpr uint16_t ha_climate_off = 203; -constexpr uint16_t ha_climate_heat = 204; -constexpr uint16_t ha_climate_cool = 205; -constexpr uint16_t ha_climate_heat_cool = 206; -constexpr uint16_t ha_climate_auto = 207; -constexpr uint16_t ha_climate_dry = 208; -constexpr uint16_t ha_climate_fan_only = 209; -constexpr uint16_t ha_hvac_action_heating = 210; -constexpr uint16_t ha_hvac_action_cooling = 211; -constexpr uint16_t ha_hvac_action_idle = 212; -constexpr uint16_t ha_hvac_action_off = 213; -constexpr uint16_t ha_hvac_action_drying = 214; -constexpr uint16_t ha_hvac_action_fan = 215; -constexpr uint16_t ha_hvac_action_preheating = 216; -constexpr uint16_t ha_hvac_action_defrosting = 217; -constexpr uint16_t ha_cover_open = 218; -constexpr uint16_t ha_cover_closed = 219; -constexpr uint16_t ha_cover_opening = 220; -constexpr uint16_t ha_cover_closing = 221; -constexpr uint16_t ha_media_playing = 222; -constexpr uint16_t ha_media_paused = 223; -constexpr uint16_t ha_media_idle = 224; -constexpr uint16_t ha_media_standby = 225; -constexpr uint16_t ha_person_home = 226; -constexpr uint16_t ha_person_not_home = 227; -constexpr uint16_t ha_sun_above_horizon = 228; -constexpr uint16_t ha_sun_below_horizon = 229; -constexpr uint16_t ha_vacuum_docked = 230; -constexpr uint16_t ha_vacuum_cleaning = 231; -constexpr uint16_t ha_vacuum_paused = 232; -constexpr uint16_t ha_vacuum_returning = 233; -constexpr uint16_t ha_vacuum_idle = 234; -constexpr uint16_t ha_weather_sunny = 235; -constexpr uint16_t ha_weather_clear_night = 236; -constexpr uint16_t ha_weather_cloudy = 237; -constexpr uint16_t ha_weather_partlycloudy = 238; -constexpr uint16_t ha_weather_rainy = 239; -constexpr uint16_t ha_weather_pouring = 240; -constexpr uint16_t ha_weather_snowy = 241; -constexpr uint16_t ha_weather_snowy_rainy = 242; -constexpr uint16_t ha_weather_fog = 243; -constexpr uint16_t ha_weather_hail = 244; -constexpr uint16_t ha_weather_lightning = 245; -constexpr uint16_t ha_weather_lightning_rainy = 246; -constexpr uint16_t ha_weather_windy = 247; -constexpr uint16_t ha_weather_exceptional = 248; -constexpr uint16_t ha_binary_battery_on = 249; -constexpr uint16_t ha_binary_battery_off = 250; -constexpr uint16_t ha_binary_battery_charging_on = 251; -constexpr uint16_t ha_binary_battery_charging_off = 252; -constexpr uint16_t ha_binary_carbon_monoxide_on = 253; -constexpr uint16_t ha_binary_carbon_monoxide_off = 254; -constexpr uint16_t ha_binary_cold_on = 255; -constexpr uint16_t ha_binary_cold_off = 256; -constexpr uint16_t ha_binary_connectivity_on = 257; -constexpr uint16_t ha_binary_connectivity_off = 258; -constexpr uint16_t ha_binary_door_on = 259; -constexpr uint16_t ha_binary_door_off = 260; -constexpr uint16_t ha_binary_garage_door_on = 261; -constexpr uint16_t ha_binary_garage_door_off = 262; -constexpr uint16_t ha_binary_gas_on = 263; -constexpr uint16_t ha_binary_gas_off = 264; -constexpr uint16_t ha_binary_heat_on = 265; -constexpr uint16_t ha_binary_heat_off = 266; -constexpr uint16_t ha_binary_light_on = 267; -constexpr uint16_t ha_binary_light_off = 268; -constexpr uint16_t ha_binary_lock_on = 269; -constexpr uint16_t ha_binary_lock_off = 270; -constexpr uint16_t ha_binary_moisture_on = 271; -constexpr uint16_t ha_binary_moisture_off = 272; -constexpr uint16_t ha_binary_motion_on = 273; -constexpr uint16_t ha_binary_motion_off = 274; -constexpr uint16_t ha_binary_moving_on = 275; -constexpr uint16_t ha_binary_moving_off = 276; -constexpr uint16_t ha_binary_occupancy_on = 277; -constexpr uint16_t ha_binary_occupancy_off = 278; -constexpr uint16_t ha_binary_opening_on = 279; -constexpr uint16_t ha_binary_opening_off = 280; -constexpr uint16_t ha_binary_plug_on = 281; -constexpr uint16_t ha_binary_plug_off = 282; -constexpr uint16_t ha_binary_power_on = 283; -constexpr uint16_t ha_binary_power_off = 284; -constexpr uint16_t ha_binary_presence_on = 285; -constexpr uint16_t ha_binary_presence_off = 286; -constexpr uint16_t ha_binary_problem_on = 287; -constexpr uint16_t ha_binary_problem_off = 288; -constexpr uint16_t ha_binary_running_on = 289; -constexpr uint16_t ha_binary_running_off = 290; -constexpr uint16_t ha_binary_safety_on = 291; -constexpr uint16_t ha_binary_safety_off = 292; -constexpr uint16_t ha_binary_smoke_on = 293; -constexpr uint16_t ha_binary_smoke_off = 294; -constexpr uint16_t ha_binary_sound_on = 295; -constexpr uint16_t ha_binary_sound_off = 296; -constexpr uint16_t ha_binary_tamper_on = 297; -constexpr uint16_t ha_binary_tamper_off = 298; -constexpr uint16_t ha_binary_update_on = 299; -constexpr uint16_t ha_binary_update_off = 300; -constexpr uint16_t ha_binary_vibration_on = 301; -constexpr uint16_t ha_binary_vibration_off = 302; -constexpr uint16_t ha_binary_window_on = 303; -constexpr uint16_t ha_binary_window_off = 304; +constexpr uint16_t tile_refused = 112; +constexpr uint16_t tile_command_sent = 113; +constexpr uint16_t tile_confirmed = 114; +constexpr uint16_t alert_notification = 115; +constexpr uint16_t alert_ok = 116; +constexpr uint16_t light_color = 117; +constexpr uint16_t light_color_temperature = 118; +constexpr uint16_t light_brightness = 119; +constexpr uint16_t light_waiting = 120; +constexpr uint16_t effects_effect = 121; +constexpr uint16_t effects_nothing = 122; +constexpr uint16_t effects_loading = 123; +constexpr uint16_t effects_not_connected = 124; +constexpr uint16_t climate_target = 125; +constexpr uint16_t climate_mode = 126; +constexpr uint16_t climate_fan = 127; +constexpr uint16_t climate_swing = 128; +constexpr uint16_t climate_now = 129; +constexpr uint16_t vacuum_check_robot = 130; +constexpr uint16_t vacuum_auto_suction = 131; +constexpr uint16_t vacuum_robot_chooses = 132; +constexpr uint16_t vacuum_per_room = 133; +constexpr uint16_t vacuum_speed_quiet = 134; +constexpr uint16_t vacuum_speed_balanced = 135; +constexpr uint16_t vacuum_speed_turbo = 136; +constexpr uint16_t vacuum_speed_max = 137; +constexpr uint16_t vacuum_pause_cleaning = 138; +constexpr uint16_t vacuum_pause = 139; +constexpr uint16_t vacuum_resume = 140; +constexpr uint16_t vacuum_start_cleaning = 141; +constexpr uint16_t vacuum_clean = 142; +constexpr uint16_t vacuum_dock = 143; +constexpr uint16_t cover_position = 144; +constexpr uint16_t cover_tilt = 145; +constexpr uint16_t cover_tilt_value = 146; +constexpr uint16_t weather_feels_like = 147; +constexpr uint16_t weather_no_forecast = 148; +constexpr uint16_t weather_coming_days = 149; +constexpr uint16_t history_no_data = 150; +constexpr uint16_t history_average = 151; +constexpr uint16_t history_average_of = 152; +constexpr uint16_t history_now = 153; +constexpr uint16_t history_range_hour = 154; +constexpr uint16_t history_range_day = 155; +constexpr uint16_t history_range_week = 156; +constexpr uint16_t history_high = 157; +constexpr uint16_t history_low = 158; +constexpr uint16_t history_times = 159; +constexpr uint16_t history_loading = 160; +constexpr uint16_t history_empty = 161; +constexpr uint16_t history_unavailable = 162; +constexpr uint16_t media_muted = 163; +constexpr uint16_t media_not_playing = 164; +constexpr uint16_t media_loading = 165; +constexpr uint16_t timer_running = 166; +constexpr uint16_t timer_paused = 167; +constexpr uint16_t timer_stopped = 168; +constexpr uint16_t timer_pause = 169; +constexpr uint16_t timer_start = 170; +constexpr uint16_t timer_cancel = 171; +constexpr uint16_t timer_paused_left = 172; +constexpr uint16_t sun_name = 173; +constexpr uint16_t sun_sunrise = 174; +constexpr uint16_t sun_sunset = 175; +constexpr uint16_t sun_rise = 176; +constexpr uint16_t sun_set = 177; +constexpr uint16_t script_running = 178; +constexpr uint16_t script_never_run = 179; +constexpr uint16_t script_last_time = 180; +constexpr uint16_t script_yesterday_time = 181; +constexpr uint16_t script_last_date = 182; +constexpr uint16_t camera_no_image = 183; +constexpr uint16_t camera_loading = 184; +constexpr uint16_t camera_live = 185; +constexpr uint16_t camera_recording = 186; +constexpr uint16_t camera_tap_to_view = 187; +constexpr uint16_t camera_no_image_yet = 188; +constexpr uint16_t calibration_title = 189; +constexpr uint16_t calibration_touch_check = 190; +constexpr uint16_t calibration_hold = 191; +constexpr uint16_t calibration_crosshair = 192; +constexpr uint16_t calibration_gently = 193; +constexpr uint16_t calibration_same = 194; +constexpr uint16_t calibration_rejected = 195; +constexpr uint16_t calibration_save_failed = 196; +constexpr uint16_t ha_on = 197; +constexpr uint16_t ha_off = 198; +constexpr uint16_t ha_unavailable = 199; +constexpr uint16_t ha_climate_off = 200; +constexpr uint16_t ha_climate_heat = 201; +constexpr uint16_t ha_climate_cool = 202; +constexpr uint16_t ha_climate_heat_cool = 203; +constexpr uint16_t ha_climate_auto = 204; +constexpr uint16_t ha_climate_dry = 205; +constexpr uint16_t ha_climate_fan_only = 206; +constexpr uint16_t ha_hvac_action_heating = 207; +constexpr uint16_t ha_hvac_action_cooling = 208; +constexpr uint16_t ha_hvac_action_idle = 209; +constexpr uint16_t ha_hvac_action_off = 210; +constexpr uint16_t ha_hvac_action_drying = 211; +constexpr uint16_t ha_hvac_action_fan = 212; +constexpr uint16_t ha_hvac_action_preheating = 213; +constexpr uint16_t ha_hvac_action_defrosting = 214; +constexpr uint16_t ha_cover_open = 215; +constexpr uint16_t ha_cover_closed = 216; +constexpr uint16_t ha_cover_opening = 217; +constexpr uint16_t ha_cover_closing = 218; +constexpr uint16_t ha_media_playing = 219; +constexpr uint16_t ha_media_paused = 220; +constexpr uint16_t ha_media_idle = 221; +constexpr uint16_t ha_media_standby = 222; +constexpr uint16_t ha_person_home = 223; +constexpr uint16_t ha_person_not_home = 224; +constexpr uint16_t ha_sun_above_horizon = 225; +constexpr uint16_t ha_sun_below_horizon = 226; +constexpr uint16_t ha_vacuum_docked = 227; +constexpr uint16_t ha_vacuum_cleaning = 228; +constexpr uint16_t ha_vacuum_paused = 229; +constexpr uint16_t ha_vacuum_returning = 230; +constexpr uint16_t ha_vacuum_idle = 231; +constexpr uint16_t ha_weather_sunny = 232; +constexpr uint16_t ha_weather_clear_night = 233; +constexpr uint16_t ha_weather_cloudy = 234; +constexpr uint16_t ha_weather_partlycloudy = 235; +constexpr uint16_t ha_weather_rainy = 236; +constexpr uint16_t ha_weather_pouring = 237; +constexpr uint16_t ha_weather_snowy = 238; +constexpr uint16_t ha_weather_snowy_rainy = 239; +constexpr uint16_t ha_weather_fog = 240; +constexpr uint16_t ha_weather_hail = 241; +constexpr uint16_t ha_weather_lightning = 242; +constexpr uint16_t ha_weather_lightning_rainy = 243; +constexpr uint16_t ha_weather_windy = 244; +constexpr uint16_t ha_weather_exceptional = 245; +constexpr uint16_t ha_binary_battery_on = 246; +constexpr uint16_t ha_binary_battery_off = 247; +constexpr uint16_t ha_binary_battery_charging_on = 248; +constexpr uint16_t ha_binary_battery_charging_off = 249; +constexpr uint16_t ha_binary_carbon_monoxide_on = 250; +constexpr uint16_t ha_binary_carbon_monoxide_off = 251; +constexpr uint16_t ha_binary_cold_on = 252; +constexpr uint16_t ha_binary_cold_off = 253; +constexpr uint16_t ha_binary_connectivity_on = 254; +constexpr uint16_t ha_binary_connectivity_off = 255; +constexpr uint16_t ha_binary_door_on = 256; +constexpr uint16_t ha_binary_door_off = 257; +constexpr uint16_t ha_binary_garage_door_on = 258; +constexpr uint16_t ha_binary_garage_door_off = 259; +constexpr uint16_t ha_binary_gas_on = 260; +constexpr uint16_t ha_binary_gas_off = 261; +constexpr uint16_t ha_binary_heat_on = 262; +constexpr uint16_t ha_binary_heat_off = 263; +constexpr uint16_t ha_binary_light_on = 264; +constexpr uint16_t ha_binary_light_off = 265; +constexpr uint16_t ha_binary_lock_on = 266; +constexpr uint16_t ha_binary_lock_off = 267; +constexpr uint16_t ha_binary_moisture_on = 268; +constexpr uint16_t ha_binary_moisture_off = 269; +constexpr uint16_t ha_binary_motion_on = 270; +constexpr uint16_t ha_binary_motion_off = 271; +constexpr uint16_t ha_binary_moving_on = 272; +constexpr uint16_t ha_binary_moving_off = 273; +constexpr uint16_t ha_binary_occupancy_on = 274; +constexpr uint16_t ha_binary_occupancy_off = 275; +constexpr uint16_t ha_binary_opening_on = 276; +constexpr uint16_t ha_binary_opening_off = 277; +constexpr uint16_t ha_binary_plug_on = 278; +constexpr uint16_t ha_binary_plug_off = 279; +constexpr uint16_t ha_binary_power_on = 280; +constexpr uint16_t ha_binary_power_off = 281; +constexpr uint16_t ha_binary_presence_on = 282; +constexpr uint16_t ha_binary_presence_off = 283; +constexpr uint16_t ha_binary_problem_on = 284; +constexpr uint16_t ha_binary_problem_off = 285; +constexpr uint16_t ha_binary_running_on = 286; +constexpr uint16_t ha_binary_running_off = 287; +constexpr uint16_t ha_binary_safety_on = 288; +constexpr uint16_t ha_binary_safety_off = 289; +constexpr uint16_t ha_binary_smoke_on = 290; +constexpr uint16_t ha_binary_smoke_off = 291; +constexpr uint16_t ha_binary_sound_on = 292; +constexpr uint16_t ha_binary_sound_off = 293; +constexpr uint16_t ha_binary_tamper_on = 294; +constexpr uint16_t ha_binary_tamper_off = 295; +constexpr uint16_t ha_binary_update_on = 296; +constexpr uint16_t ha_binary_update_off = 297; +constexpr uint16_t ha_binary_vibration_on = 298; +constexpr uint16_t ha_binary_vibration_off = 299; +constexpr uint16_t ha_binary_window_on = 300; +constexpr uint16_t ha_binary_window_off = 301; +constexpr uint16_t ha_button_activate = 302; +constexpr uint16_t ha_button_run = 303; +constexpr uint16_t ha_button_press = 304; constexpr uint16_t date_weekdays = 17; constexpr uint16_t date_weekdays_count = 7; constexpr uint16_t date_weekdays_short = 24; @@ -278,5 +278,5 @@ constexpr uint16_t date_months_short = 50; constexpr uint16_t date_months_short_count = 12; } // namespace txt constexpr uint16_t KEY_COUNT = 305; -constexpr uint32_t KEYS_HASH = 0x8971C0EBu; +constexpr uint32_t KEYS_HASH = 0xC6B1A3EDu; } // namespace screen_text diff --git a/components/smart_display/tile_controls.h b/components/smart_display/tile_controls.h index 04e028b..72a21e1 100644 --- a/components/smart_display/tile_controls.h +++ b/components/smart_display/tile_controls.h @@ -348,9 +348,9 @@ inline std::string neighbour_option(const Tile &t, int direction) { return options[(current + direction + count) % count]; } inline const char *run_label(const std::string &domain) { - if (domain == "scene") return screen_text::tr(screen_text::txt::button_activate); - if (domain == "script") return screen_text::tr(screen_text::txt::button_run); - return screen_text::tr(screen_text::txt::button_press); + if (domain == "scene") return screen_text::tr(screen_text::txt::ha_button_activate); + if (domain == "script") return screen_text::tr(screen_text::txt::ha_button_run); + return screen_text::tr(screen_text::txt::ha_button_press); } // The Home Assistant action behind a key. STEP_* are handled locally (debounced) and return nothing. inline Action key_action(const Tile &t, int command, const std::string &arg = "") { diff --git a/screen_manager/translations/de.json b/screen_manager/translations/de.json index ceeef42..d2898ed 100644 --- a/screen_manager/translations/de.json +++ b/screen_manager/translations/de.json @@ -200,6 +200,12 @@ "vibration_off": "Frei", "window_on": "Geöffnet", "window_off": "Geschlossen" + }, + "unavailable": "Nicht verfügbar", + "button": { + "activate": "Aktivieren", + "run": "Ausführen", + "press": "Drücken" } } }, diff --git a/screen_manager/translations/en-GB.json b/screen_manager/translations/en-GB.json index cff2711..3a3df8b 100644 --- a/screen_manager/translations/en-GB.json +++ b/screen_manager/translations/en-GB.json @@ -134,6 +134,12 @@ "vibration_off": "Clear", "window_on": "Open", "window_off": "Closed" + }, + "unavailable": "Unavailable", + "button": { + "activate": "Activate", + "run": "Run", + "press": "Press" } } } diff --git a/screen_manager/translations/en.json b/screen_manager/translations/en.json index 6f2d25c..2669da3 100644 --- a/screen_manager/translations/en.json +++ b/screen_manager/translations/en.json @@ -143,7 +143,6 @@ "tile": { "tap_to_open": "Tap to open", "page": "Page {n}", - "unavailable": "Unavailable", "refused": "Refused", "command_sent": "Command sent...", "confirmed": "Confirmed by Home Assistant" @@ -248,11 +247,6 @@ "tap_to_view": "Tap to view", "no_image_yet": "No image yet" }, - "button": { - "activate": "Activate", - "run": "Run", - "press": "Press" - }, "calibration": { "title": "Follow the calibration wizard", "touch_check": "GT911 touch check", @@ -266,6 +260,7 @@ "ha": { "on": "On", "off": "Off", + "unavailable": "Unavailable", "climate": { "off": "Off", "heat": "Heat", @@ -385,6 +380,11 @@ "vibration_off": "Clear", "window_on": "Open", "window_off": "Closed" + }, + "button": { + "activate": "Activate", + "run": "Run", + "press": "Press" } } }, diff --git a/screen_manager/translations/es.json b/screen_manager/translations/es.json index 5991851..963120b 100644 --- a/screen_manager/translations/es.json +++ b/screen_manager/translations/es.json @@ -200,6 +200,12 @@ "vibration_off": "No detectado", "window_on": "Abierto", "window_off": "Cerrado" + }, + "unavailable": "No disponible", + "button": { + "activate": "Activar", + "run": "Ejecutar", + "press": "Pulsa" } } }, diff --git a/screen_manager/translations/fr.json b/screen_manager/translations/fr.json index c20a1db..a0458cc 100644 --- a/screen_manager/translations/fr.json +++ b/screen_manager/translations/fr.json @@ -200,6 +200,12 @@ "vibration_off": "Non détectée", "window_on": "Ouvert", "window_off": "Fermé" + }, + "unavailable": "Indisponible", + "button": { + "activate": "Activer", + "run": "Exécuter", + "press": "Appuyer" } } }, diff --git a/screen_manager/translations/it.json b/screen_manager/translations/it.json index e43bcd6..374249c 100644 --- a/screen_manager/translations/it.json +++ b/screen_manager/translations/it.json @@ -200,6 +200,12 @@ "vibration_off": "Assente", "window_on": "Aperto", "window_off": "Chiuso" + }, + "unavailable": "Non disponibile", + "button": { + "activate": "Attiva", + "run": "Esegui", + "press": "Premi" } } }, diff --git a/screen_manager/translations/nl.json b/screen_manager/translations/nl.json index dfa3dad..db84d42 100644 --- a/screen_manager/translations/nl.json +++ b/screen_manager/translations/nl.json @@ -143,7 +143,6 @@ "tile": { "tap_to_open": "Tik om te openen", "page": "Pagina {n}", - "unavailable": "Niet beschikbaar", "refused": "Geweigerd", "command_sent": "Opdracht verstuurd...", "confirmed": "Bevestigd door Home Assistant" @@ -248,11 +247,6 @@ "tap_to_view": "Tik om te bekijken", "no_image_yet": "Nog geen beeld" }, - "button": { - "activate": "Activeren", - "run": "Uitvoeren", - "press": "Indrukken" - }, "calibration": { "title": "Volg de kalibratie", "touch_check": "GT911-aanraaktest", @@ -385,6 +379,12 @@ "vibration_off": "Niet gedetecteerd", "window_on": "Open", "window_off": "Dicht" + }, + "unavailable": "Niet beschikbaar", + "button": { + "activate": "Inschakelen", + "run": "Uitvoeren", + "press": "Klik" } } }, diff --git a/screen_manager/translations/pl.json b/screen_manager/translations/pl.json index 2421d28..138dade 100644 --- a/screen_manager/translations/pl.json +++ b/screen_manager/translations/pl.json @@ -200,6 +200,12 @@ "vibration_off": "brak", "window_on": "otwarto", "window_off": "zamknięto" + }, + "unavailable": "niedostępny", + "button": { + "activate": "Aktywuj", + "run": "Uruchom", + "press": "Naciśnij" } } }, diff --git a/screen_manager/translations/pt.json b/screen_manager/translations/pt.json index aa227fa..7c081c4 100644 --- a/screen_manager/translations/pt.json +++ b/screen_manager/translations/pt.json @@ -200,6 +200,12 @@ "vibration_off": "Limpo", "window_on": "Aberto", "window_off": "Fechada" + }, + "unavailable": "Indisponível", + "button": { + "activate": "Ativar", + "run": "Executar", + "press": "Pressione" } } }, diff --git a/tests/screen_text_en.h b/tests/screen_text_en.h index bdcfd2f..51c8f6e 100644 --- a/tests/screen_text_en.h +++ b/tests/screen_text_en.h @@ -3,7 +3,7 @@ #pragma once #include "components/smart_display/screen_text.h" -static_assert(screen_text::KEYS_HASH == 0x8971C0EBu && screen_text::KEY_COUNT == 305, +static_assert(screen_text::KEYS_HASH == 0xC6B1A3EDu && screen_text::KEY_COUNT == 305, "screen_text_keys.h does not match screen_manager/translations/en.json: run tools/i18n.py header"); const char *const screen_text::TABLE[] = { ".", @@ -118,7 +118,6 @@ const char *const screen_text::TABLE[] = { "{h} h {m}", "Tap to open", "Page {n}", - "Unavailable", "Refused", "Command sent...", "Confirmed by Home Assistant", @@ -196,9 +195,6 @@ const char *const screen_text::TABLE[] = { "Recording", "Tap to view", "No image yet", - "Activate", - "Run", - "Press", "Follow the calibration wizard", "GT911 touch check", "Hold each tap for a moment", @@ -209,6 +205,7 @@ const char *const screen_text::TABLE[] = { "Save failed; try again", "On", "Off", + "Unavailable", "Off", "Heat", "Cool", @@ -310,7 +307,10 @@ const char *const screen_text::TABLE[] = { "Detected", "Clear", "Open", - "Closed" + "Closed", + "Activate", + "Run", + "Press" }; const char *const screen_text::LANGUAGE = "en"; int screen_text::plural_index(int n) { return n == 1 ? 0 : 1; } diff --git a/tools/i18n.py b/tools/i18n.py index f1ed2f0..2f6e054 100644 --- a/tools/i18n.py +++ b/tools/i18n.py @@ -200,6 +200,28 @@ def ha_sources(): return sources +# The words Home Assistant's frontend itself shows (its own translation files, not the backend's). +FRONTEND_SOURCES = {'unavailable': 'state.default.unavailable', 'button.activate': 'ui.card.scene.activate', + 'button.run': 'ui.card.script.run', 'button.press': 'ui.card.button.press'} + + +def frontend_words(url, codes): + """{code: {key: word}} from the translation files Home Assistant's frontend loads (their names carry a hash that the + frontend's app script lists).""" + import urllib.request + page = urllib.request.urlopen(url + '/', timeout=20).read().decode('utf-8', 'replace') + found = {} + for script in dict.fromkeys(re.findall(r'(/frontend_latest/app\.[\w.]+\.js)', page)): + text = urllib.request.urlopen(url + script, timeout=30).read().decode('utf-8', 'replace') + for code in codes: + match = re.search(r'"' + re.escape(code) + r'":\{"nativeName":"[^"]*","hash":"([0-9a-f]+)"', text) + if match and code not in found: + data = json.loads(urllib.request.urlopen(f'{url}/static/translations/{code}-{match.group(1)}.json', timeout=30).read()) + flat = dict(generator().flatten(data)) + found[code] = {key: flat[source] for key, source in FRONTEND_SOURCES.items() if isinstance(flat.get(source), str)} + return found + + def ha_words(write): """screen.ha of every language from a Home Assistant (HA_URL and HA_TOKEN, or .esphome/ha_url and ha_token): its own words for states, English included, so a screen says what Home Assistant says (Max, app 0.2.90).""" @@ -236,6 +258,7 @@ def ha_words(write): langs = languages() found = asyncio.run(fetch(list(langs))) + frontend = frontend_words(url, list(langs)) status = 0 for code, data in langs.items(): words = found.get(code) or {} @@ -245,6 +268,7 @@ def ha_words(write): own[key] = words[source] else: print(f'{code}: Home Assistant has no {source}') + own.update(frontend.get(code, {})) reference = dict(generator().flatten(data.get('screen', {}).get('ha', {}))) if code == 'en': for key, word in own.items():