diff --git a/components/smart_display/history_view.h b/components/smart_display/history_view.h index 605125a..176e714 100644 --- a/components/smart_display/history_view.h +++ b/components/smart_display/history_view.h @@ -76,10 +76,7 @@ inline std::string number(float value, int decimals, const std::string &unit) { auto dot = text.find('.'); std::string whole = text.substr(0, dot), rest = dot == std::string::npos ? "" : text.substr(dot); bool zero = whole.find_first_not_of('0') == std::string::npos && rest.find_first_not_of(".0") == std::string::npos; - const std::string group = screen_text::group_mark(); - for (int i = static_cast(whole.size()) - 3; i > 0; i -= 3) whole.insert(static_cast(i), group); - if (!rest.empty()) rest[0] = screen_text::decimal_mark(); - text = (zero ? "" : sign) + whole + rest; + text = (zero ? "" : sign) + screen_text::write_number(whole, rest.empty() ? std::string() : rest.substr(1)); if (unit.empty()) return text; if (unit == "%" || unit == "°") return text + unit; return text + " " + unit; diff --git a/components/smart_display/runtime_tiles.h b/components/smart_display/runtime_tiles.h index 8fef675..0b454c6 100644 --- a/components/smart_display/runtime_tiles.h +++ b/components/smart_display/runtime_tiles.h @@ -923,7 +923,7 @@ inline std::string detail_state(const Tile &t){ if(!t.available())return tr(txt::tile_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.state; + return screen_text::localize(t.state); } inline void hide_detail(){if(detail_root)lv_obj_add_flag(detail_root,LV_OBJ_FLAG_HIDDEN);} inline int slider_value(const Tile &t){ @@ -2545,9 +2545,10 @@ inline void render_clock(Widgets &w,const Tile &t,bool large,int width,int heigh } int x=dial+(large?10:6),room=std::max(1,width-x); if(!large){ - // Compact cards: "13 sep" in the large-value font beside the dial. + // Compact cards: "13 sep" in the large-value font beside the dial, in the language's order (screen.date.day_month). const lv_font_t *font=watch_value_font?watch_value_font:w.value_font; - part_label(w,16,font,x,std::max(0,int(height-lv_font_get_line_height(font))/2),room,LV_TEXT_ALIGN_CENTER,day+" "+month_short(now)); + part_label(w,16,font,x,std::max(0,int(height-lv_font_get_line_height(font))/2),room,LV_TEXT_ALIGN_CENTER, + fill(fill(txt::date_day_month,"day",day),"month",month_short(now))); return; } // Calendar block: weekday over a big day number with the short month beside it. @@ -3139,7 +3140,8 @@ inline void render_slot(size_t slot) { else if (!t.extra().state_word.empty()) value = t.extra().state_word; // A player's state in the screen's own words where Home Assistant sent none (firmware 0.2.64+). else if (d == "media_player") value = tile_controls::media_state_text(t.state); - else if (!t.unit.empty() && !watch) value += " " + t.unit; + else if (!t.unit.empty() && !watch) value = screen_text::localize(value) + " " + t.unit; + else value = screen_text::localize(value); bool pending=t.loading(esphome::millis()); if(d=="weather" && std::isfinite(t.current)) {value=screen_text::decimal(t.current,1);if(!watch)value+=" "+t.unit;} if(d=="vacuum" && std::isfinite(t.battery))value += " / "+std::to_string((int)t.battery)+"%"; diff --git a/components/smart_display/screen_text.h b/components/smart_display/screen_text.h index 9401ea1..7f9bf54 100644 --- a/components/smart_display/screen_text.h +++ b/components/smart_display/screen_text.h @@ -5,6 +5,7 @@ // flash: no text is copied, searched for or kept in RAM, and a screen carries no language but its own. #include #include +#include #include #include #include "screen_text_keys.h" @@ -62,6 +63,36 @@ inline const char *group_mark() { if (number_style == 3) return " "; return tr(txt::number_group); } +// How many digits a whole number needs before it gets separators: 4 ("1,234") in most languages, 5 in Italian, Spanish +// and Polish, which write 1234 but 12.345 (CLDR's minimum grouping digits, as Home Assistant's own numbers do). +inline size_t group_from() { + if (number_style) return 4; + int minimum = atoi(tr(txt::number_group_min)); + return minimum >= 2 ? 5 : 4; +} +// A number the way this screen writes numbers: `whole` digits (no sign) and the digits after the point. +inline std::string write_number(const std::string &whole, const std::string &fraction) { + std::string out = whole; + if (out.size() >= group_from()) { + const std::string group = group_mark(); + for (int i = static_cast(out.size()) - 3; i > 0; i -= 3) out.insert(static_cast(i), group); + } + if (!fraction.empty()) out += std::string(1, decimal_mark()) + fraction; + return out; +} +// A state as Home Assistant sends a number ("1234.5", "-3") written the way this screen writes numbers ("1.234,5" in +// Dutch); any other text as it is. +inline std::string localize(const std::string &state) { + size_t start = !state.empty() && state[0] == '-' ? 1 : 0, dot = std::string::npos; + if (start == state.size()) return state; + for (size_t i = start; i < state.size(); ++i) { + if (state[i] == '.' && dot == std::string::npos && i > start && i + 1 < state.size()) dot = i; + else if (state[i] < '0' || state[i] > '9') return state; + } + std::string whole = state.substr(start, dot == std::string::npos ? std::string::npos : dot - start); + std::string fraction = dot == std::string::npos ? std::string() : state.substr(dot + 1); + return state.substr(0, start) + write_number(whole, fraction); +} // "point", "comma", "space" or "auto" as its number_style; -1 for anything else. inline int number_style_of(const std::string &name) { if (name == "auto") return 0; diff --git a/components/smart_display/screen_text_keys.h b/components/smart_display/screen_text_keys.h index 407a61e..77f5c35 100644 --- a/components/smart_display/screen_text_keys.h +++ b/components/smart_display/screen_text_keys.h @@ -8,272 +8,275 @@ namespace screen_text { namespace txt { constexpr uint16_t number_decimal = 0; constexpr uint16_t number_group = 1; -constexpr uint16_t time_just_now = 2; -constexpr uint16_t time_minutes_ago = 3; -constexpr uint16_t time_hours_ago = 4; -constexpr uint16_t time_yesterday = 5; -constexpr uint16_t time_days_ago = 6; -constexpr uint16_t time_weeks_ago = 7; -constexpr uint16_t time_months_ago = 8; -constexpr uint16_t time_years_ago = 9; -constexpr uint16_t time_in_minutes = 10; -constexpr uint16_t time_in_hours = 11; -constexpr uint16_t time_tomorrow = 12; -constexpr uint16_t time_in_days = 13; -constexpr uint16_t time_am = 14; -constexpr uint16_t time_pm = 15; -constexpr uint16_t date_top_bar = 61; -constexpr uint16_t date_full = 62; -constexpr uint16_t date_day_month = 63; -constexpr uint16_t date_weekday_time = 64; -constexpr uint16_t duration_seconds = 65; -constexpr uint16_t duration_minutes = 66; -constexpr uint16_t duration_hours = 67; -constexpr uint16_t duration_hours_minutes = 68; -constexpr uint16_t duration_days = 69; -constexpr uint16_t duration_days_hours = 70; -constexpr uint16_t status_connecting = 71; -constexpr uint16_t status_waiting = 72; -constexpr uint16_t status_loading_tiles = 73; -constexpr uint16_t status_ha_not_connected = 74; -constexpr uint16_t status_manager_not_active = 75; -constexpr uint16_t status_choose_tiles = 76; -constexpr uint16_t status_home = 77; -constexpr uint16_t status_no_network = 78; -constexpr uint16_t settings_title = 79; -constexpr uint16_t settings_brightness = 80; -constexpr uint16_t settings_night = 81; -constexpr uint16_t settings_screen = 82; -constexpr uint16_t settings_this_screen = 83; -constexpr uint16_t settings_dark_mode = 84; -constexpr uint16_t settings_auto_standby = 85; -constexpr uint16_t settings_standby_after = 86; -constexpr uint16_t settings_standby_brightness = 87; -constexpr uint16_t settings_night_mode = 88; -constexpr uint16_t settings_starts = 89; -constexpr uint16_t settings_ends = 90; -constexpr uint16_t settings_night_brightness = 91; -constexpr uint16_t settings_back_to_page_1 = 92; -constexpr uint16_t settings_after = 93; -constexpr uint16_t settings_also_on_standby = 94; -constexpr uint16_t settings_swipe_between_pages = 95; -constexpr uint16_t settings_page_buttons = 96; -constexpr uint16_t settings_rotation = 97; -constexpr uint16_t settings_address = 98; -constexpr uint16_t settings_firmware = 99; -constexpr uint16_t settings_home_assistant = 100; -constexpr uint16_t settings_restart = 101; -constexpr uint16_t settings_tap_again_to_restart = 102; -constexpr uint16_t settings_seconds = 103; -constexpr uint16_t settings_minutes = 104; -constexpr uint16_t settings_hours = 105; -constexpr uint16_t settings_hours_minutes = 106; -constexpr uint16_t tile_tap_to_open = 107; -constexpr uint16_t tile_page = 108; -constexpr uint16_t tile_unavailable = 109; -constexpr uint16_t tile_refused = 110; -constexpr uint16_t tile_command_sent = 111; -constexpr uint16_t tile_confirmed = 112; -constexpr uint16_t alert_notification = 113; -constexpr uint16_t alert_ok = 114; -constexpr uint16_t light_color = 115; -constexpr uint16_t light_color_temperature = 116; -constexpr uint16_t light_brightness = 117; -constexpr uint16_t light_waiting = 118; -constexpr uint16_t effects_effect = 119; -constexpr uint16_t effects_nothing = 120; -constexpr uint16_t effects_loading = 121; -constexpr uint16_t effects_not_connected = 122; -constexpr uint16_t climate_target = 123; -constexpr uint16_t climate_mode = 124; -constexpr uint16_t climate_fan = 125; -constexpr uint16_t climate_swing = 126; -constexpr uint16_t climate_now = 127; -constexpr uint16_t vacuum_check_robot = 128; -constexpr uint16_t vacuum_auto_suction = 129; -constexpr uint16_t vacuum_robot_chooses = 130; -constexpr uint16_t vacuum_per_room = 131; -constexpr uint16_t vacuum_speed_quiet = 132; -constexpr uint16_t vacuum_speed_balanced = 133; -constexpr uint16_t vacuum_speed_turbo = 134; -constexpr uint16_t vacuum_speed_max = 135; -constexpr uint16_t vacuum_pause_cleaning = 136; -constexpr uint16_t vacuum_pause = 137; -constexpr uint16_t vacuum_resume = 138; -constexpr uint16_t vacuum_start_cleaning = 139; -constexpr uint16_t vacuum_clean = 140; -constexpr uint16_t vacuum_dock = 141; -constexpr uint16_t cover_position = 142; -constexpr uint16_t cover_tilt = 143; -constexpr uint16_t cover_tilt_value = 144; -constexpr uint16_t weather_feels_like = 145; -constexpr uint16_t weather_no_forecast = 146; -constexpr uint16_t weather_coming_days = 147; -constexpr uint16_t history_no_data = 148; -constexpr uint16_t history_average = 149; -constexpr uint16_t history_average_of = 150; -constexpr uint16_t history_now = 151; -constexpr uint16_t history_range_hour = 152; -constexpr uint16_t history_range_day = 153; -constexpr uint16_t history_range_week = 154; -constexpr uint16_t history_high = 155; -constexpr uint16_t history_low = 156; -constexpr uint16_t history_times = 157; -constexpr uint16_t history_loading = 158; -constexpr uint16_t history_empty = 159; -constexpr uint16_t history_unavailable = 160; -constexpr uint16_t media_muted = 161; -constexpr uint16_t media_not_playing = 162; -constexpr uint16_t media_loading = 163; -constexpr uint16_t timer_running = 164; -constexpr uint16_t timer_paused = 165; -constexpr uint16_t timer_stopped = 166; -constexpr uint16_t timer_pause = 167; -constexpr uint16_t timer_start = 168; -constexpr uint16_t timer_cancel = 169; -constexpr uint16_t timer_paused_left = 170; -constexpr uint16_t sun_name = 171; -constexpr uint16_t sun_sunrise = 172; -constexpr uint16_t sun_sunset = 173; -constexpr uint16_t sun_rise = 174; -constexpr uint16_t sun_set = 175; -constexpr uint16_t script_running = 176; -constexpr uint16_t script_never_run = 177; -constexpr uint16_t script_last_time = 178; -constexpr uint16_t script_yesterday_time = 179; -constexpr uint16_t script_last_date = 180; -constexpr uint16_t camera_no_image = 181; -constexpr uint16_t camera_loading = 182; -constexpr uint16_t camera_live = 183; -constexpr uint16_t camera_recording = 184; -constexpr uint16_t camera_tap_to_view = 185; -constexpr uint16_t camera_no_image_yet = 186; -constexpr uint16_t button_activate = 187; -constexpr uint16_t button_run = 188; -constexpr uint16_t button_press = 189; -constexpr uint16_t calibration_title = 190; -constexpr uint16_t calibration_touch_check = 191; -constexpr uint16_t calibration_hold = 192; -constexpr uint16_t calibration_crosshair = 193; -constexpr uint16_t calibration_gently = 194; -constexpr uint16_t calibration_same = 195; -constexpr uint16_t calibration_rejected = 196; -constexpr uint16_t calibration_save_failed = 197; -constexpr uint16_t ha_on = 198; -constexpr uint16_t ha_off = 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 date_weekdays = 16; +constexpr uint16_t number_group_min = 2; +constexpr uint16_t time_just_now = 3; +constexpr uint16_t time_minutes_ago = 4; +constexpr uint16_t time_hours_ago = 5; +constexpr uint16_t time_yesterday = 6; +constexpr uint16_t time_days_ago = 7; +constexpr uint16_t time_weeks_ago = 8; +constexpr uint16_t time_months_ago = 9; +constexpr uint16_t time_years_ago = 10; +constexpr uint16_t time_in_minutes = 11; +constexpr uint16_t time_in_hours = 12; +constexpr uint16_t time_tomorrow = 13; +constexpr uint16_t time_in_days = 14; +constexpr uint16_t time_am = 15; +constexpr uint16_t time_pm = 16; +constexpr uint16_t date_top_bar = 62; +constexpr uint16_t date_full = 63; +constexpr uint16_t date_day_month = 64; +constexpr uint16_t date_weekday_time = 65; +constexpr uint16_t duration_seconds = 66; +constexpr uint16_t duration_minutes = 67; +constexpr uint16_t duration_hours = 68; +constexpr uint16_t duration_hours_minutes = 69; +constexpr uint16_t duration_days = 70; +constexpr uint16_t duration_days_hours = 71; +constexpr uint16_t status_connecting = 72; +constexpr uint16_t status_waiting = 73; +constexpr uint16_t status_loading_tiles = 74; +constexpr uint16_t status_ha_not_connected = 75; +constexpr uint16_t status_manager_not_active = 76; +constexpr uint16_t status_choose_tiles = 77; +constexpr uint16_t status_home = 78; +constexpr uint16_t status_no_network = 79; +constexpr uint16_t settings_title = 80; +constexpr uint16_t settings_brightness = 81; +constexpr uint16_t settings_night = 82; +constexpr uint16_t settings_screen = 83; +constexpr uint16_t settings_this_screen = 84; +constexpr uint16_t settings_dark_mode = 85; +constexpr uint16_t settings_auto_standby = 86; +constexpr uint16_t settings_standby_after = 87; +constexpr uint16_t settings_standby_brightness = 88; +constexpr uint16_t settings_night_mode = 89; +constexpr uint16_t settings_starts = 90; +constexpr uint16_t settings_ends = 91; +constexpr uint16_t settings_night_brightness = 92; +constexpr uint16_t settings_back_to_page_1 = 93; +constexpr uint16_t settings_after = 94; +constexpr uint16_t settings_also_on_standby = 95; +constexpr uint16_t settings_swipe_between_pages = 96; +constexpr uint16_t settings_page_buttons = 97; +constexpr uint16_t settings_rotation = 98; +constexpr uint16_t settings_address = 99; +constexpr uint16_t settings_firmware = 100; +constexpr uint16_t settings_home_assistant = 101; +constexpr uint16_t settings_restart = 102; +constexpr uint16_t settings_connected = 103; +constexpr uint16_t settings_not_connected = 104; +constexpr uint16_t settings_tap_again_to_restart = 105; +constexpr uint16_t settings_seconds = 106; +constexpr uint16_t settings_minutes = 107; +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 date_weekdays = 17; constexpr uint16_t date_weekdays_count = 7; -constexpr uint16_t date_weekdays_short = 23; +constexpr uint16_t date_weekdays_short = 24; constexpr uint16_t date_weekdays_short_count = 7; -constexpr uint16_t date_weekdays_min = 30; +constexpr uint16_t date_weekdays_min = 31; constexpr uint16_t date_weekdays_min_count = 7; -constexpr uint16_t date_months = 37; +constexpr uint16_t date_months = 38; constexpr uint16_t date_months_count = 12; -constexpr uint16_t date_months_short = 49; +constexpr uint16_t date_months_short = 50; constexpr uint16_t date_months_short_count = 12; } // namespace txt -constexpr uint16_t KEY_COUNT = 302; -constexpr uint32_t KEYS_HASH = 0x725A92EEu; +constexpr uint16_t KEY_COUNT = 305; +constexpr uint32_t KEYS_HASH = 0x8971C0EBu; } // namespace screen_text diff --git a/packages/core.yaml b/packages/core.yaml index 71438ca..e41fb41 100644 --- a/packages/core.yaml +++ b/packages/core.yaml @@ -156,7 +156,7 @@ esphome: id(active_climate_action) = t.extra().hvac_action; id(open_value_overlay).execute(t.name, t.entity, t.domain(), "auto", t.active(), 0, t.brightness, t.percentage, t.position, runtime_tiles::icon_for(t), - "Off", "On", t.modes, + screen_text::tr(screen_text::txt::ha_off), screen_text::tr(screen_text::txt::ha_on), t.modes, t.current, t.target, t.humidity, t.state, t.extra().hvac_modes, std::to_string(t.minimum), std::to_string(t.maximum), std::to_string(t.step)); }; @@ -218,7 +218,9 @@ esphome: settings_screen::name_text = []() { return std::string("${DEVICE_FRIENDLY_NAME}"); }; settings_screen::address_text = []() { return id(wifi_ip).state.empty() ? std::string(screen_text::tr(screen_text::txt::status_no_network)) : id(wifi_ip).state; }; settings_screen::firmware_text = []() { return std::string("${SCREEN_FIRMWARE_VERSION}"); }; - settings_screen::link_text = []() { return std::string(esphome::api_is_connected() ? "Connected" : "Not connected"); }; + settings_screen::link_text = []() { + return std::string(screen_text::tr(esphome::api_is_connected() ? screen_text::txt::settings_connected : screen_text::txt::settings_not_connected)); + }; settings_screen::restart_device = []() { ESP_LOGI("settings", "restart from the screen"); App.safe_reboot(); }; // A dimmed screen, a calibration or an alert owns the glass; the page waits. settings_screen::may_open = []() { @@ -669,7 +671,7 @@ font: glyphs: ['<','>','—','&','@','!',',','.','?','"','%','(',')','+','-','_',':','°','0','1','2','3','4','5', '6','7','8','9','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T', 'U','V','W','X','Y','Z',' ','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q', - 'r','s','t','u','v','w','x','y','z','/','·', + 'r','s','t','u','v','w','x','y','z','/','·',"'",';','…', 'À','Á','Â','Ã','Ä','Å','Æ','Ç','È','É','Ê','Ë','Ì','Í','Î','Ï','Ð','Ñ','Ò','Ó','Ô','Õ','Ö','Ø', 'Ù','Ú','Û','Ü','Ý','Þ','ß','à','á','â','ã','ä','å','æ','ç','è','é','ê','ë','ì','í','î','ï','ð', 'ñ','ò','ó','ô','õ','ö','ø','ù','ú','û','ü','ý','þ','ÿ','Ą','ą','Ć','ć','Ę','ę','Ł','ł','Ń','ń', @@ -686,7 +688,7 @@ font: glyphs: ['<','>','—','&','@','!',',','.','?','"','%','(',')','+','-','_',':','°','0','1','2','3','4','5', '6','7','8','9','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T', 'U','V','W','X','Y','Z',' ','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q', - 'r','s','t','u','v','w','x','y','z','/','·', + 'r','s','t','u','v','w','x','y','z','/','·',"'",';','…', 'À','Á','Â','Ã','Ä','Å','Æ','Ç','È','É','Ê','Ë','Ì','Í','Î','Ï','Ð','Ñ','Ò','Ó','Ô','Õ','Ö','Ø', 'Ù','Ú','Û','Ü','Ý','Þ','ß','à','á','â','ã','ä','å','æ','ç','è','é','ê','ë','ì','í','î','ï','ð', 'ñ','ò','ó','ô','õ','ö','ø','ù','ú','û','ü','ý','þ','ÿ','Ą','ą','Ć','ć','Ę','ę','Ł','ł','Ń','ń', @@ -717,7 +719,7 @@ font: glyphs: ['<','>','—','&','@','!',',','.','?','"','%','(',')','+','-','_',':','°','0','1','2','3','4','5', '6','7','8','9','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T', 'U','V','W','X','Y','Z',' ','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q', - 'r','s','t','u','v','w','x','y','z','/','·', + 'r','s','t','u','v','w','x','y','z','/','·',"'",';','…', 'À','Á','Â','Ã','Ä','Å','Æ','Ç','È','É','Ê','Ë','Ì','Í','Î','Ï','Ð','Ñ','Ò','Ó','Ô','Õ','Ö','Ø', 'Ù','Ú','Û','Ü','Ý','Þ','ß','à','á','â','ã','ä','å','æ','ç','è','é','ê','ë','ì','í','î','ï','ð', 'ñ','ò','ó','ô','õ','ö','ø','ù','ú','û','ü','ý','þ','ÿ','Ą','ą','Ć','ć','Ę','ę','Ł','ł','Ń','ń', @@ -734,7 +736,7 @@ font: glyphs: ['<','>','—','&','@','!',',','.','?','"','%','(',')','+','-','_',':','°','0','1','2','3','4','5', '6','7','8','9','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T', 'U','V','W','X','Y','Z',' ','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q', - 'r','s','t','u','v','w','x','y','z','/','…','·', + 'r','s','t','u','v','w','x','y','z','/','…','·',"'",';', 'À','Á','Â','Ã','Ä','Å','Æ','Ç','È','É','Ê','Ë','Ì','Í','Î','Ï','Ð','Ñ','Ò','Ó','Ô','Õ','Ö','Ø', 'Ù','Ú','Û','Ü','Ý','Þ','ß','à','á','â','ã','ä','å','æ','ç','è','é','ê','ë','ì','í','î','ï','ð', 'ñ','ò','ó','ô','õ','ö','ø','ù','ú','û','ü','ý','þ','ÿ','Ą','ą','Ć','ć','Ę','ę','Ł','ł','Ń','ń', diff --git a/screen_manager/translations/de.json b/screen_manager/translations/de.json new file mode 100644 index 0000000..ceeef42 --- /dev/null +++ b/screen_manager/translations/de.json @@ -0,0 +1,208 @@ +{ + "_meta": { + "name": "Deutsch", + "english": "German", + "script": "latin", + "plural": "one_other", + "clock": "24", + "checked": false + }, + "screen": { + "number": { + "decimal": ",", + "group": ".", + "group_min": "1" + }, + "time": { + "am": "AM", + "pm": "PM" + }, + "date": { + "weekdays": [ + "Sonntag", + "Montag", + "Dienstag", + "Mittwoch", + "Donnerstag", + "Freitag", + "Samstag" + ], + "weekdays_short": [ + "So", + "Mo", + "Di", + "Mi", + "Do", + "Fr", + "Sa" + ], + "weekdays_min": [ + "So", + "Mo", + "Di", + "Mi", + "Do", + "Fr", + "Sa" + ], + "months": [ + "Januar", + "Februar", + "März", + "April", + "Mai", + "Juni", + "Juli", + "August", + "September", + "Oktober", + "November", + "Dezember" + ], + "months_short": [ + "Jan.", + "Feb.", + "März", + "Apr.", + "Mai", + "Juni", + "Juli", + "Aug.", + "Sept.", + "Okt.", + "Nov.", + "Dez." + ], + "top_bar": "{weekday}, {day}. {month}", + "full": "{weekday}, {day}. {month}", + "day_month": "{day}. {month}", + "weekday_time": "{weekday}, {time}" + }, + "ha": { + "on": "Ein", + "off": "Aus", + "climate": { + "off": "Aus", + "heat": "Heizbetrieb", + "cool": "Kühlbetrieb", + "heat_cool": "Heizen/Kühlen", + "auto": "Auto", + "dry": "Entfeuchtung", + "fan_only": "Nur Lüftung" + }, + "hvac_action": { + "heating": "Heizbetrieb", + "cooling": "Kühlbetrieb", + "idle": "Leerlauf", + "off": "Aus", + "drying": "Entfeuchtung", + "fan": "Lüftung", + "preheating": "Vorheizen", + "defrosting": "Enteisung" + }, + "cover": { + "open": "Geöffnet", + "closed": "Geschlossen", + "opening": "Wird geöffnet", + "closing": "Wird geschlossen" + }, + "media": { + "playing": "Wiedergabe", + "paused": "Pausiert", + "idle": "Leerlauf", + "standby": "Standby" + }, + "person": { + "home": "Zuhause", + "not_home": "Abwesend" + }, + "sun": { + "above_horizon": "Über dem Horizont", + "below_horizon": "Unter dem Horizont" + }, + "vacuum": { + "docked": "Angedockt", + "cleaning": "Reinigt", + "paused": "Pausiert", + "returning": "Rückkehr zur Ladestation", + "idle": "Leerlauf" + }, + "weather": { + "sunny": "Sonnig", + "clear_night": "Klare Nacht", + "cloudy": "Bewölkt", + "partlycloudy": "Teilweise bewölkt", + "rainy": "Regnerisch", + "pouring": "Strömender Regen", + "snowy": "Schneefall", + "snowy_rainy": "Schneeregen", + "fog": "Nebel", + "hail": "Hagel", + "lightning": "Gewitter", + "lightning_rainy": "Gewitter, regnerisch", + "windy": "Windig", + "exceptional": "Außergewöhnlich" + }, + "binary": { + "battery_on": "Niedrig", + "battery_off": "Normal", + "battery_charging_on": "Wird geladen", + "battery_charging_off": "Wird nicht geladen", + "carbon_monoxide_on": "Erkannt", + "carbon_monoxide_off": "Frei", + "cold_on": "Kalt", + "cold_off": "Normal", + "connectivity_on": "Verbunden", + "connectivity_off": "Getrennt", + "door_on": "Geöffnet", + "door_off": "Geschlossen", + "garage_door_on": "Geöffnet", + "garage_door_off": "Geschlossen", + "gas_on": "Erkannt", + "gas_off": "Frei", + "heat_on": "Heiß", + "heat_off": "Normal", + "light_on": "Licht erkannt", + "light_off": "Kein Licht", + "lock_on": "Aufgeschlossen", + "lock_off": "Abgeschlossen", + "moisture_on": "Nass", + "moisture_off": "Trocken", + "motion_on": "Erkannt", + "motion_off": "Frei", + "moving_on": "Bewegt sich", + "moving_off": "Bewegt sich nicht", + "occupancy_on": "Erkannt", + "occupancy_off": "Frei", + "opening_on": "Geöffnet", + "opening_off": "Geschlossen", + "plug_on": "Eingesteckt", + "plug_off": "Ausgesteckt", + "power_on": "Ein", + "power_off": "Aus", + "presence_on": "Zuhause", + "presence_off": "Abwesend", + "problem_on": "Problem", + "problem_off": "OK", + "running_on": "In Betrieb", + "running_off": "Außer Betrieb", + "safety_on": "Unsicher", + "safety_off": "Sicher", + "smoke_on": "Erkannt", + "smoke_off": "Frei", + "sound_on": "Erkannt", + "sound_off": "Frei", + "tamper_on": "Manipulation erkannt", + "tamper_off": "Frei", + "update_on": "Update verfügbar", + "update_off": "Aktuell", + "vibration_on": "Erkannt", + "vibration_off": "Frei", + "window_on": "Geöffnet", + "window_off": "Geschlossen" + } + } + }, + "addon": {}, + "editor": {} +} diff --git a/screen_manager/translations/en-GB.json b/screen_manager/translations/en-GB.json index d9c984f..cff2711 100644 --- a/screen_manager/translations/en-GB.json +++ b/screen_manager/translations/en-GB.json @@ -11,6 +11,130 @@ "light": { "color": "Colour", "color_temperature": "Colour temperature" + }, + "ha": { + "on": "On", + "off": "Off", + "climate": { + "off": "Off", + "heat": "Heat", + "cool": "Cool", + "heat_cool": "Heat/Cool", + "auto": "Auto", + "dry": "Dry", + "fan_only": "Fan only" + }, + "hvac_action": { + "heating": "Heating", + "cooling": "Cooling", + "idle": "Idle", + "off": "Off", + "drying": "Drying", + "fan": "Fan", + "preheating": "Preheating", + "defrosting": "Defrosting" + }, + "cover": { + "open": "Open", + "closed": "Closed", + "opening": "Opening", + "closing": "Closing" + }, + "media": { + "playing": "Playing", + "paused": "Paused", + "idle": "Idle", + "standby": "Standby" + }, + "person": { + "home": "Home", + "not_home": "Away" + }, + "sun": { + "above_horizon": "Above horizon", + "below_horizon": "Below horizon" + }, + "vacuum": { + "docked": "Docked", + "cleaning": "Cleaning", + "paused": "Paused", + "returning": "Returning to dock", + "idle": "Idle" + }, + "weather": { + "sunny": "Sunny", + "clear_night": "Clear, night", + "cloudy": "Cloudy", + "partlycloudy": "Partly cloudy", + "rainy": "Rainy", + "pouring": "Pouring", + "snowy": "Snowy", + "snowy_rainy": "Snowy, rainy", + "fog": "Fog", + "hail": "Hail", + "lightning": "Lightning", + "lightning_rainy": "Lightning, rainy", + "windy": "Windy", + "exceptional": "Exceptional" + }, + "binary": { + "battery_on": "Low", + "battery_off": "Normal", + "battery_charging_on": "Charging", + "battery_charging_off": "Not charging", + "carbon_monoxide_on": "Detected", + "carbon_monoxide_off": "Clear", + "cold_on": "Cold", + "cold_off": "Normal", + "connectivity_on": "Connected", + "connectivity_off": "Disconnected", + "door_on": "Open", + "door_off": "Closed", + "garage_door_on": "Open", + "garage_door_off": "Closed", + "gas_on": "Detected", + "gas_off": "Clear", + "heat_on": "Hot", + "heat_off": "Normal", + "light_on": "Light detected", + "light_off": "No light", + "lock_on": "Unlocked", + "lock_off": "Locked", + "moisture_on": "Wet", + "moisture_off": "Dry", + "motion_on": "Detected", + "motion_off": "Clear", + "moving_on": "Moving", + "moving_off": "Not moving", + "occupancy_on": "Detected", + "occupancy_off": "Clear", + "opening_on": "Open", + "opening_off": "Closed", + "plug_on": "Plugged in", + "plug_off": "Unplugged", + "power_on": "On", + "power_off": "Off", + "presence_on": "Home", + "presence_off": "Away", + "problem_on": "Problem", + "problem_off": "OK", + "running_on": "Running", + "running_off": "Not running", + "safety_on": "Unsafe", + "safety_off": "Safe", + "smoke_on": "Detected", + "smoke_off": "Clear", + "sound_on": "Detected", + "sound_off": "Clear", + "tamper_on": "Tampering detected", + "tamper_off": "Clear", + "update_on": "Update available", + "update_off": "Up-to-date", + "vibration_on": "Detected", + "vibration_off": "Clear", + "window_on": "Open", + "window_off": "Closed" + } } } } diff --git a/screen_manager/translations/en.json b/screen_manager/translations/en.json index bf6143d..6f2d25c 100644 --- a/screen_manager/translations/en.json +++ b/screen_manager/translations/en.json @@ -10,7 +10,8 @@ "screen": { "number": { "decimal": ".", - "group": "," + "group": ",", + "group_min": "1" }, "time": { "just_now": "Just now", @@ -131,6 +132,8 @@ "firmware": "Firmware", "home_assistant": "Home Assistant", "restart": "Restart", + "connected": "Connected", + "not_connected": "Not connected", "tap_again_to_restart": "Tap again to restart", "seconds": "{n} sec", "minutes": "{n} min", @@ -299,8 +302,8 @@ "not_home": "Away" }, "sun": { - "above_horizon": "Above the horizon", - "below_horizon": "Below the horizon" + "above_horizon": "Above horizon", + "below_horizon": "Below horizon" }, "vacuum": { "docked": "Docked", @@ -330,8 +333,8 @@ "battery_off": "Normal", "battery_charging_on": "Charging", "battery_charging_off": "Not charging", - "carbon_monoxide_on": "Danger", - "carbon_monoxide_off": "Safe", + "carbon_monoxide_on": "Detected", + "carbon_monoxide_off": "Clear", "cold_on": "Cold", "cold_off": "Normal", "connectivity_on": "Connected", @@ -340,21 +343,21 @@ "door_off": "Closed", "garage_door_on": "Open", "garage_door_off": "Closed", - "gas_on": "Danger", - "gas_off": "Safe", + "gas_on": "Detected", + "gas_off": "Clear", "heat_on": "Hot", "heat_off": "Normal", - "light_on": "Light", - "light_off": "Dark", - "lock_on": "Open", + "light_on": "Light detected", + "light_off": "No light", + "lock_on": "Unlocked", "lock_off": "Locked", "moisture_on": "Wet", "moisture_off": "Dry", - "motion_on": "Motion", - "motion_off": "No motion", + "motion_on": "Detected", + "motion_off": "Clear", "moving_on": "Moving", - "moving_off": "Still", - "occupancy_on": "Occupied", + "moving_off": "Not moving", + "occupancy_on": "Detected", "occupancy_off": "Clear", "opening_on": "Open", "opening_off": "Closed", @@ -366,20 +369,20 @@ "presence_off": "Away", "problem_on": "Problem", "problem_off": "OK", - "running_on": "Active", - "running_off": "Inactive", + "running_on": "Running", + "running_off": "Not running", "safety_on": "Unsafe", "safety_off": "Safe", - "smoke_on": "Smoke", - "smoke_off": "No smoke", - "sound_on": "Sound", - "sound_off": "Silent", - "tamper_on": "Tampering", - "tamper_off": "OK", - "update_on": "Update", - "update_off": "Up to date", - "vibration_on": "Vibration", - "vibration_off": "Still", + "smoke_on": "Detected", + "smoke_off": "Clear", + "sound_on": "Detected", + "sound_off": "Clear", + "tamper_on": "Tampering detected", + "tamper_off": "Clear", + "update_on": "Update available", + "update_off": "Up-to-date", + "vibration_on": "Detected", + "vibration_off": "Clear", "window_on": "Open", "window_off": "Closed" } diff --git a/screen_manager/translations/es.json b/screen_manager/translations/es.json new file mode 100644 index 0000000..5991851 --- /dev/null +++ b/screen_manager/translations/es.json @@ -0,0 +1,208 @@ +{ + "_meta": { + "name": "Español", + "english": "Spanish", + "script": "latin", + "plural": "one_other", + "clock": "24", + "checked": false + }, + "screen": { + "number": { + "decimal": ",", + "group": ".", + "group_min": "2" + }, + "time": { + "am": "a. m.", + "pm": "p. m." + }, + "date": { + "weekdays": [ + "domingo", + "lunes", + "martes", + "miércoles", + "jueves", + "viernes", + "sábado" + ], + "weekdays_short": [ + "dom", + "lun", + "mar", + "mié", + "jue", + "vie", + "sáb" + ], + "weekdays_min": [ + "dom", + "lun", + "mar", + "mié", + "jue", + "vie", + "sáb" + ], + "months": [ + "enero", + "febrero", + "marzo", + "abril", + "mayo", + "junio", + "julio", + "agosto", + "septiembre", + "octubre", + "noviembre", + "diciembre" + ], + "months_short": [ + "ene", + "feb", + "mar", + "abr", + "may", + "jun", + "jul", + "ago", + "sept", + "oct", + "nov", + "dic" + ], + "top_bar": "{weekday}, {day} {month}", + "full": "{weekday}, {day} de {month}", + "day_month": "{day} {month}", + "weekday_time": "{weekday}, {time}" + }, + "ha": { + "on": "Encendido", + "off": "Apagado", + "climate": { + "off": "Apagado", + "heat": "Calor", + "cool": "Frío", + "heat_cool": "Calor/Frío", + "auto": "Auto", + "dry": "Seco", + "fan_only": "Solo ventilador" + }, + "hvac_action": { + "heating": "Calefacción", + "cooling": "Refrigeración", + "idle": "Inactivo", + "off": "Apagado", + "drying": "Secando", + "fan": "Ventilador", + "preheating": "Precalentamiento", + "defrosting": "Antihielo" + }, + "cover": { + "open": "Abierto", + "closed": "Cerrado", + "opening": "Abriendo", + "closing": "Cerrando" + }, + "media": { + "playing": "Reproduciendo", + "paused": "En pausa", + "idle": "Inactivo", + "standby": "En espera" + }, + "person": { + "home": "En casa", + "not_home": "Fuera" + }, + "sun": { + "above_horizon": "Sobre el horizonte", + "below_horizon": "Bajo el horizonte" + }, + "vacuum": { + "docked": "En la base", + "cleaning": "Limpiando", + "paused": "En pausa", + "returning": "Volviendo a la base", + "idle": "Inactivo" + }, + "weather": { + "sunny": "Soleado", + "clear_night": "Despejado, de noche", + "cloudy": "Nublado", + "partlycloudy": "Parcialmente nublado", + "rainy": "Lluvioso", + "pouring": "Lluvia torrencial", + "snowy": "Nevado", + "snowy_rainy": "Nevado, lluvioso", + "fog": "Niebla", + "hail": "Granizo", + "lightning": "Relámpagos", + "lightning_rainy": "Relámpagos, lluvioso", + "windy": "Ventoso", + "exceptional": "Excepcional" + }, + "binary": { + "battery_on": "Bajo", + "battery_off": "Normal", + "battery_charging_on": "Cargando", + "battery_charging_off": "No está cargando", + "carbon_monoxide_on": "Detectado", + "carbon_monoxide_off": "No detectado", + "cold_on": "Frío", + "cold_off": "Normal", + "connectivity_on": "Conectado", + "connectivity_off": "Desconectado", + "door_on": "Abierto", + "door_off": "Cerrado", + "garage_door_on": "Abierto", + "garage_door_off": "Cerrado", + "gas_on": "Detectado", + "gas_off": "No detectado", + "heat_on": "Caliente", + "heat_off": "Normal", + "light_on": "Luz detectada", + "light_off": "Sin luz", + "lock_on": "Desbloqueado", + "lock_off": "Bloqueado", + "moisture_on": "Húmedo", + "moisture_off": "Seco", + "motion_on": "Detectado", + "motion_off": "No detectado", + "moving_on": "En movimiento", + "moving_off": "No se mueve", + "occupancy_on": "Detectado", + "occupancy_off": "No detectado", + "opening_on": "Abierto", + "opening_off": "Cerrado", + "plug_on": "Enchufado", + "plug_off": "Desenchufado", + "power_on": "Encendido", + "power_off": "Apagado", + "presence_on": "En casa", + "presence_off": "Fuera", + "problem_on": "Problema", + "problem_off": "OK", + "running_on": "Ejecutando", + "running_off": "No se está ejecutando", + "safety_on": "Inseguro", + "safety_off": "Seguro", + "smoke_on": "Detectado", + "smoke_off": "No detectado", + "sound_on": "Detectado", + "sound_off": "No detectado", + "tamper_on": "Manipulación detectada", + "tamper_off": "No detectado", + "update_on": "Actualización disponible", + "update_off": "Actualizado", + "vibration_on": "Detectado", + "vibration_off": "No detectado", + "window_on": "Abierto", + "window_off": "Cerrado" + } + } + }, + "addon": {}, + "editor": {} +} diff --git a/screen_manager/translations/fr.json b/screen_manager/translations/fr.json new file mode 100644 index 0000000..c20a1db --- /dev/null +++ b/screen_manager/translations/fr.json @@ -0,0 +1,208 @@ +{ + "_meta": { + "name": "Français", + "english": "French", + "script": "latin", + "plural": "one_upto_1", + "clock": "24", + "checked": false + }, + "screen": { + "number": { + "decimal": ",", + "group": " ", + "group_min": "1" + }, + "time": { + "am": "AM", + "pm": "PM" + }, + "date": { + "weekdays": [ + "dimanche", + "lundi", + "mardi", + "mercredi", + "jeudi", + "vendredi", + "samedi" + ], + "weekdays_short": [ + "dim.", + "lun.", + "mar.", + "mer.", + "jeu.", + "ven.", + "sam." + ], + "weekdays_min": [ + "dim.", + "lun.", + "mar.", + "mer.", + "jeu.", + "ven.", + "sam." + ], + "months": [ + "janvier", + "février", + "mars", + "avril", + "mai", + "juin", + "juillet", + "août", + "septembre", + "octobre", + "novembre", + "décembre" + ], + "months_short": [ + "janv.", + "févr.", + "mars", + "avr.", + "mai", + "juin", + "juil.", + "août", + "sept.", + "oct.", + "nov.", + "déc." + ], + "top_bar": "{weekday} {day} {month}", + "full": "{weekday} {day} {month}", + "day_month": "{day} {month}", + "weekday_time": "{weekday} {time}" + }, + "ha": { + "on": "Activé", + "off": "Désactivé", + "climate": { + "off": "Éteint", + "heat": "Chauffage", + "cool": "Climatisation", + "heat_cool": "Chauffage/Climatisation", + "auto": "Auto", + "dry": "Déshumidification", + "fan_only": "Ventilation uniquement" + }, + "hvac_action": { + "heating": "Chauffe", + "cooling": "Rafraîchit", + "idle": "Inactif", + "off": "Éteint", + "drying": "Déshumidifie", + "fan": "Ventile", + "preheating": "Préchauffage", + "defrosting": "Dégivrage" + }, + "cover": { + "open": "Ouvert", + "closed": "Fermé", + "opening": "Ouverture", + "closing": "Fermeture" + }, + "media": { + "playing": "Lecture en cours", + "paused": "En pause", + "idle": "Inactif", + "standby": "En veille" + }, + "person": { + "home": "Maison", + "not_home": "Absent" + }, + "sun": { + "above_horizon": "Au-dessus de l'horizon", + "below_horizon": "Sous l'horizon" + }, + "vacuum": { + "docked": "À la station d'accueil", + "cleaning": "Nettoyage", + "paused": "En pause", + "returning": "Retour à la station d'accueil", + "idle": "Inactif" + }, + "weather": { + "sunny": "Ensoleillé", + "clear_night": "Nuit dégagée", + "cloudy": "Nuageux", + "partlycloudy": "Éclaircies", + "rainy": "Pluvieux", + "pouring": "Averses", + "snowy": "Neigeux", + "snowy_rainy": "Neigeux, pluvieux", + "fog": "Brouillard", + "hail": "Grêle", + "lightning": "Orage", + "lightning_rainy": "Orage / Pluie", + "windy": "Venteux", + "exceptional": "Exceptionnel" + }, + "binary": { + "battery_on": "Faible", + "battery_off": "Normal", + "battery_charging_on": "En charge", + "battery_charging_off": "Pas en charge", + "carbon_monoxide_on": "Détecté", + "carbon_monoxide_off": "Non détecté", + "cold_on": "Froid", + "cold_off": "Normal", + "connectivity_on": "Connecté", + "connectivity_off": "Déconnecté", + "door_on": "Ouvert", + "door_off": "Fermé", + "garage_door_on": "Ouvert", + "garage_door_off": "Fermé", + "gas_on": "Détecté", + "gas_off": "Non détecté", + "heat_on": "Chaud", + "heat_off": "Normal", + "light_on": "Lumière détectée", + "light_off": "Pas de lumière", + "lock_on": "Déverrouillé", + "lock_off": "Verrouillé", + "moisture_on": "Humide", + "moisture_off": "Sec", + "motion_on": "Détecté", + "motion_off": "Non détecté", + "moving_on": "En mouvement", + "moving_off": "Immobile", + "occupancy_on": "Détecté", + "occupancy_off": "Non détecté", + "opening_on": "Ouvert", + "opening_off": "Fermé", + "plug_on": "Branché", + "plug_off": "Débranché", + "power_on": "Activé", + "power_off": "Désactivé", + "presence_on": "Maison", + "presence_off": "Absent", + "problem_on": "Problème", + "problem_off": "OK", + "running_on": "En cours", + "running_off": "Arrêté", + "safety_on": "Dangereux", + "safety_off": "Sûr", + "smoke_on": "Détectée", + "smoke_off": "Non détectée", + "sound_on": "Détecté", + "sound_off": "Non détecté", + "tamper_on": "Manipulation détectée", + "tamper_off": "Non détectée", + "update_on": "Mise à jour disponible", + "update_off": "À jour", + "vibration_on": "Détectée", + "vibration_off": "Non détectée", + "window_on": "Ouvert", + "window_off": "Fermé" + } + } + }, + "addon": {}, + "editor": {} +} diff --git a/screen_manager/translations/it.json b/screen_manager/translations/it.json new file mode 100644 index 0000000..e43bcd6 --- /dev/null +++ b/screen_manager/translations/it.json @@ -0,0 +1,208 @@ +{ + "_meta": { + "name": "Italiano", + "english": "Italian", + "script": "latin", + "plural": "one_other", + "clock": "24", + "checked": false + }, + "screen": { + "number": { + "decimal": ",", + "group": ".", + "group_min": "2" + }, + "time": { + "am": "AM", + "pm": "PM" + }, + "date": { + "weekdays": [ + "domenica", + "lunedì", + "martedì", + "mercoledì", + "giovedì", + "venerdì", + "sabato" + ], + "weekdays_short": [ + "dom", + "lun", + "mar", + "mer", + "gio", + "ven", + "sab" + ], + "weekdays_min": [ + "dom", + "lun", + "mar", + "mer", + "gio", + "ven", + "sab" + ], + "months": [ + "gennaio", + "febbraio", + "marzo", + "aprile", + "maggio", + "giugno", + "luglio", + "agosto", + "settembre", + "ottobre", + "novembre", + "dicembre" + ], + "months_short": [ + "gen", + "feb", + "mar", + "apr", + "mag", + "giu", + "lug", + "ago", + "set", + "ott", + "nov", + "dic" + ], + "top_bar": "{weekday} {day} {month}", + "full": "{weekday} {day} {month}", + "day_month": "{day} {month}", + "weekday_time": "{weekday} {time}" + }, + "ha": { + "on": "Acceso", + "off": "Spento", + "climate": { + "off": "Spento", + "heat": "Riscaldamento", + "cool": "Freddo", + "heat_cool": "Caldo/Freddo", + "auto": "Automatico", + "dry": "Deumidificazione", + "fan_only": "Ventilazione" + }, + "hvac_action": { + "heating": "Riscaldamento", + "cooling": "Raffreddando", + "idle": "Inattivo", + "off": "Spento", + "drying": "In deumidificazione", + "fan": "Ventilatore", + "preheating": "Preriscaldamento", + "defrosting": "Defrosting" + }, + "cover": { + "open": "Aperto", + "closed": "Chiuso", + "opening": "In apertura", + "closing": "In chiusura" + }, + "media": { + "playing": "In esecuzione", + "paused": "In pausa", + "idle": "Inattivo", + "standby": "In attesa" + }, + "person": { + "home": "In casa", + "not_home": "Fuori casa" + }, + "sun": { + "above_horizon": "Sopra l'orizzonte", + "below_horizon": "Sotto l'orizzonte" + }, + "vacuum": { + "docked": "Alla base", + "cleaning": "In pulizia", + "paused": "In pausa", + "returning": "Ritornando alla base", + "idle": "Inattivo" + }, + "weather": { + "sunny": "Soleggiato", + "clear_night": "Sereno, notte", + "cloudy": "Nuvoloso", + "partlycloudy": "Parzialmente nuvoloso", + "rainy": "Piovoso", + "pouring": "Rovescio", + "snowy": "Nevoso", + "snowy_rainy": "Nevoso, piovoso", + "fog": "Nebbia", + "hail": "Grandine", + "lightning": "Temporale", + "lightning_rainy": "Temporale, piovoso", + "windy": "Ventoso", + "exceptional": "Eccezionale" + }, + "binary": { + "battery_on": "Basso/a", + "battery_off": "Normale", + "battery_charging_on": "In carica", + "battery_charging_off": "Non in carica", + "carbon_monoxide_on": "Rilevato", + "carbon_monoxide_off": "Assente", + "cold_on": "Freddo", + "cold_off": "Normale", + "connectivity_on": "Connesso", + "connectivity_off": "Disconnesso", + "door_on": "Aperto", + "door_off": "Chiuso", + "garage_door_on": "Aperto", + "garage_door_off": "Chiuso", + "gas_on": "Rilevato", + "gas_off": "Assente", + "heat_on": "Caldo", + "heat_off": "Normale", + "light_on": "Luce rilevata", + "light_off": "Nessuna luce", + "lock_on": "Sbloccato/a", + "lock_off": "Bloccato/a", + "moisture_on": "Bagnato", + "moisture_off": "Asciutto", + "motion_on": "Rilevato", + "motion_off": "Assente", + "moving_on": "In movimento", + "moving_off": "Non si muove", + "occupancy_on": "Rilevato", + "occupancy_off": "Assente", + "opening_on": "Aperto", + "opening_off": "Chiuso", + "plug_on": "Collegato", + "plug_off": "Scollegato", + "power_on": "Acceso", + "power_off": "Spento", + "presence_on": "In casa", + "presence_off": "Fuori casa", + "problem_on": "Problema", + "problem_off": "OK", + "running_on": "In esecuzione", + "running_off": "Non in esecuzione", + "safety_on": "Non Sicuro", + "safety_off": "Sicuro", + "smoke_on": "Rilevato", + "smoke_off": "Assente", + "sound_on": "Rilevato", + "sound_off": "Assente", + "tamper_on": "Rilevata manomissione", + "tamper_off": "Assente", + "update_on": "Aggiornamento disponibile", + "update_off": "Aggiornato", + "vibration_on": "Rilevato", + "vibration_off": "Assente", + "window_on": "Aperto", + "window_off": "Chiuso" + } + } + }, + "addon": {}, + "editor": {} +} diff --git a/screen_manager/translations/nl.json b/screen_manager/translations/nl.json new file mode 100644 index 0000000..27f5c14 --- /dev/null +++ b/screen_manager/translations/nl.json @@ -0,0 +1,208 @@ +{ + "_meta": { + "name": "Nederlands", + "english": "Dutch", + "script": "latin", + "plural": "one_other", + "clock": "24", + "checked": false + }, + "screen": { + "number": { + "decimal": ",", + "group": ".", + "group_min": "1" + }, + "time": { + "am": "a.m.", + "pm": "p.m." + }, + "date": { + "weekdays": [ + "zondag", + "maandag", + "dinsdag", + "woensdag", + "donderdag", + "vrijdag", + "zaterdag" + ], + "weekdays_short": [ + "zo", + "ma", + "di", + "wo", + "do", + "vr", + "za" + ], + "weekdays_min": [ + "zo", + "ma", + "di", + "wo", + "do", + "vr", + "za" + ], + "months": [ + "januari", + "februari", + "maart", + "april", + "mei", + "juni", + "juli", + "augustus", + "september", + "oktober", + "november", + "december" + ], + "months_short": [ + "jan", + "feb", + "mrt", + "apr", + "mei", + "jun", + "jul", + "aug", + "sep", + "okt", + "nov", + "dec" + ], + "top_bar": "{weekday} {day} {month}", + "full": "{weekday} {day} {month}", + "day_month": "{day} {month}", + "weekday_time": "{weekday} {time}" + }, + "ha": { + "on": "Aan", + "off": "Uit", + "climate": { + "off": "Uit", + "heat": "Verwarmen", + "cool": "Koelen", + "heat_cool": "Verwarmen/koelen", + "auto": "Automatisch", + "dry": "Droog", + "fan_only": "Alleen ventilator" + }, + "hvac_action": { + "heating": "Verwarmen", + "cooling": "Koelen", + "idle": "Inactief", + "off": "Uit", + "drying": "Drogen", + "fan": "Ventilator", + "preheating": "Voorverwarmen", + "defrosting": "Ontdooien" + }, + "cover": { + "open": "Open", + "closed": "Dicht", + "opening": "Openen", + "closing": "Sluiten" + }, + "media": { + "playing": "Speelt", + "paused": "Gepauzeerd", + "idle": "Inactief", + "standby": "Stand-by" + }, + "person": { + "home": "Thuis", + "not_home": "Afwezig" + }, + "sun": { + "above_horizon": "Boven de horizon", + "below_horizon": "Onder de horizon" + }, + "vacuum": { + "docked": "Gedockt", + "cleaning": "Schoonmaken", + "paused": "Gepauzeerd", + "returning": "Terugkeren naar dock", + "idle": "Inactief" + }, + "weather": { + "sunny": "Zonnig", + "clear_night": "Helder, nacht", + "cloudy": "Bewolkt", + "partlycloudy": "Half bewolkt", + "rainy": "Regenachtig", + "pouring": "Regen", + "snowy": "Sneeuwachtig", + "snowy_rainy": "Sneeuw-, regenachtig", + "fog": "Mist", + "hail": "Hagel", + "lightning": "Bliksem", + "lightning_rainy": "Bliksem, regenachtig", + "windy": "Winderig", + "exceptional": "Uitzonderlijk" + }, + "binary": { + "battery_on": "Laag", + "battery_off": "Normaal", + "battery_charging_on": "Opladen", + "battery_charging_off": "Niet aan het opladen", + "carbon_monoxide_on": "Gedetecteerd", + "carbon_monoxide_off": "Niet gedetecteerd", + "cold_on": "Koud", + "cold_off": "Normal", + "connectivity_on": "Verbonden", + "connectivity_off": "Verbroken", + "door_on": "Open", + "door_off": "Dicht", + "garage_door_on": "Open", + "garage_door_off": "Dicht", + "gas_on": "Gedetecteerd", + "gas_off": "Niet gedetecteerd", + "heat_on": "Heet", + "heat_off": "Normal", + "light_on": "Licht gedetecteerd", + "light_off": "Geen licht", + "lock_on": "Ontgrendeld", + "lock_off": "Vergrendeld", + "moisture_on": "Nat", + "moisture_off": "Droog", + "motion_on": "Gedetecteerd", + "motion_off": "Niet gedetecteerd", + "moving_on": "Beweging", + "moving_off": "Geen beweging", + "occupancy_on": "Gedetecteerd", + "occupancy_off": "Niet gedetecteerd", + "opening_on": "Open", + "opening_off": "Dicht", + "plug_on": "Ingeplugd", + "plug_off": "Losgekoppeld", + "power_on": "Aan", + "power_off": "Uit", + "presence_on": "Thuis", + "presence_off": "Afwezig", + "problem_on": "Probleem", + "problem_off": "OK", + "running_on": "Actief", + "running_off": "Niet actief", + "safety_on": "Onveilig", + "safety_off": "Veilig", + "smoke_on": "Gedetecteerd", + "smoke_off": "Niet gedetecteerd", + "sound_on": "Gedetecteerd", + "sound_off": "Niet gedetecteerd", + "tamper_on": "Sabotage ontdekt", + "tamper_off": "Niet gedetecteerd", + "update_on": "Update beschikbaar", + "update_off": "Up-to-date", + "vibration_on": "Gedetecteerd", + "vibration_off": "Niet gedetecteerd", + "window_on": "Open", + "window_off": "Dicht" + } + } + }, + "addon": {}, + "editor": {} +} diff --git a/screen_manager/translations/pl.json b/screen_manager/translations/pl.json new file mode 100644 index 0000000..2421d28 --- /dev/null +++ b/screen_manager/translations/pl.json @@ -0,0 +1,208 @@ +{ + "_meta": { + "name": "Polski", + "english": "Polish", + "script": "latin", + "plural": "slavic_pl", + "clock": "24", + "checked": false + }, + "screen": { + "number": { + "decimal": ",", + "group": " ", + "group_min": "2" + }, + "time": { + "am": "AM", + "pm": "PM" + }, + "date": { + "weekdays": [ + "niedziela", + "poniedziałek", + "wtorek", + "środa", + "czwartek", + "piątek", + "sobota" + ], + "weekdays_short": [ + "niedz.", + "pon.", + "wt.", + "śr.", + "czw.", + "pt.", + "sob." + ], + "weekdays_min": [ + "niedz.", + "pon.", + "wt.", + "śr.", + "czw.", + "pt.", + "sob." + ], + "months": [ + "stycznia", + "lutego", + "marca", + "kwietnia", + "maja", + "czerwca", + "lipca", + "sierpnia", + "września", + "października", + "listopada", + "grudnia" + ], + "months_short": [ + "sty", + "lut", + "mar", + "kwi", + "maj", + "cze", + "lip", + "sie", + "wrz", + "paź", + "lis", + "gru" + ], + "top_bar": "{weekday}, {day} {month}", + "full": "{weekday}, {day} {month}", + "day_month": "{day} {month}", + "weekday_time": "{weekday}, {time}" + }, + "ha": { + "on": "włączono", + "off": "wyłączono", + "climate": { + "off": "wyłączono", + "heat": "grzanie", + "cool": "chłodzenie", + "heat_cool": "grzanie/chłodzenie", + "auto": "Auto", + "dry": "osuszanie", + "fan_only": "tylko wentylator" + }, + "hvac_action": { + "heating": "grzanie", + "cooling": "chłodzenie", + "idle": "brak aktywności", + "off": "wyłączono", + "drying": "osuszanie", + "fan": "wentylator", + "preheating": "podgrzewanie wstępne", + "defrosting": "rozmrażanie" + }, + "cover": { + "open": "otwarto", + "closed": "zamknięto", + "opening": "otwieranie", + "closing": "zamykanie" + }, + "media": { + "playing": "odtwarzanie", + "paused": "wstrzymano", + "idle": "brak aktywności", + "standby": "tryb czuwania" + }, + "person": { + "home": "w domu", + "not_home": "poza domem" + }, + "sun": { + "above_horizon": "powyżej horyzontu", + "below_horizon": "poniżej horyzontu" + }, + "vacuum": { + "docked": "zadokowany", + "cleaning": "sprzątanie", + "paused": "wstrzymano", + "returning": "wraca do stacji dokującej", + "idle": "brak aktywności" + }, + "weather": { + "sunny": "słonecznie", + "clear_night": "pogodna noc", + "cloudy": "pochmurno", + "partlycloudy": "częściowe zachmurzenie", + "rainy": "deszczowo", + "pouring": "ulewa", + "snowy": "opady śniegu", + "snowy_rainy": "deszcz ze śniegiem", + "fog": "mgła", + "hail": "grad", + "lightning": "wyładowania atmosferyczne", + "lightning_rainy": "burza", + "windy": "wietrznie", + "exceptional": "warunki nadzwyczajne" + }, + "binary": { + "battery_on": "Low", + "battery_off": "Normal", + "battery_charging_on": "Charging", + "battery_charging_off": "brak ładowania", + "carbon_monoxide_on": "wykryto", + "carbon_monoxide_off": "brak", + "cold_on": "zimno", + "cold_off": "Normal", + "connectivity_on": "połączono", + "connectivity_off": "rozłączono", + "door_on": "otwarto", + "door_off": "zamknięto", + "garage_door_on": "otwarto", + "garage_door_off": "zamknięto", + "gas_on": "wykryto", + "gas_off": "brak", + "heat_on": "gorąco", + "heat_off": "Normal", + "light_on": "wykryto", + "light_off": "brak", + "lock_on": "odblokowano", + "lock_off": "zablokowano", + "moisture_on": "wykryto", + "moisture_off": "brak", + "motion_on": "wykryto", + "motion_off": "brak", + "moving_on": "w ruchu", + "moving_off": "brak ruchu", + "occupancy_on": "wykryto", + "occupancy_off": "brak", + "opening_on": "otwarto", + "opening_off": "zamknięto", + "plug_on": "podłączono", + "plug_off": "odłączono", + "power_on": "włączono", + "power_off": "wyłączono", + "presence_on": "Home", + "presence_off": "Away", + "problem_on": "problem", + "problem_off": "ok", + "running_on": "działa", + "running_off": "nie działa", + "safety_on": "zagrożenie", + "safety_off": "brak zagrożenia", + "smoke_on": "wykryto", + "smoke_off": "brak", + "sound_on": "wykryto", + "sound_off": "brak", + "tamper_on": "Tampering detected", + "tamper_off": "brak", + "update_on": "dostępna aktualizacja", + "update_off": "brak aktualizacji", + "vibration_on": "wykryto", + "vibration_off": "brak", + "window_on": "otwarto", + "window_off": "zamknięto" + } + } + }, + "addon": {}, + "editor": {} +} diff --git a/screen_manager/translations/pt.json b/screen_manager/translations/pt.json new file mode 100644 index 0000000..aa227fa --- /dev/null +++ b/screen_manager/translations/pt.json @@ -0,0 +1,208 @@ +{ + "_meta": { + "name": "Português", + "english": "Portuguese", + "script": "latin", + "plural": "one_upto_1", + "clock": "24", + "checked": false + }, + "screen": { + "number": { + "decimal": ",", + "group": " ", + "group_min": "2" + }, + "time": { + "am": "da manhã", + "pm": "da tarde" + }, + "date": { + "weekdays": [ + "domingo", + "segunda-feira", + "terça-feira", + "quarta-feira", + "quinta-feira", + "sexta-feira", + "sábado" + ], + "weekdays_short": [ + "domingo", + "segunda", + "terça", + "quarta", + "quinta", + "sexta", + "sábado" + ], + "weekdays_min": [ + "domingo", + "segunda", + "terça", + "quarta", + "quinta", + "sexta", + "sábado" + ], + "months": [ + "janeiro", + "fevereiro", + "março", + "abril", + "maio", + "junho", + "julho", + "agosto", + "setembro", + "outubro", + "novembro", + "dezembro" + ], + "months_short": [ + "jan.", + "fev.", + "mar.", + "abr.", + "mai.", + "jun.", + "jul.", + "ago.", + "set.", + "out.", + "nov.", + "dez." + ], + "top_bar": "{weekday}, {day} {month}", + "full": "{weekday}, {day} de {month}", + "day_month": "{day} {month}", + "weekday_time": "{weekday}, {time}" + }, + "ha": { + "on": "Ligado", + "off": "Desligado", + "climate": { + "off": "Desligado", + "heat": "Aquecer", + "cool": "Arrefecer", + "heat_cool": "Aquecer/Arrefecer", + "auto": "Auto", + "dry": "Secar", + "fan_only": "Ventilação" + }, + "hvac_action": { + "heating": "A aquecer", + "cooling": "A arrefecer", + "idle": "Inativo", + "off": "Desligado", + "drying": "Desumidificação", + "fan": "Ventilação", + "preheating": "Pré-Aquecimento", + "defrosting": "Descongelação" + }, + "cover": { + "open": "Aberta", + "closed": "Fechado", + "opening": "A abrir", + "closing": "A fechar" + }, + "media": { + "playing": "A reproduzir", + "paused": "Pausado", + "idle": "Inativo", + "standby": "Standby" + }, + "person": { + "home": "Casa", + "not_home": "Ausente" + }, + "sun": { + "above_horizon": "Acima do horizonte", + "below_horizon": "Abaixo do horizonte" + }, + "vacuum": { + "docked": "Na Base", + "cleaning": "A Limpar", + "paused": "Em pausa", + "returning": "A regressar à base", + "idle": "Inativo" + }, + "weather": { + "sunny": "Ensolarado", + "clear_night": "Limpo, noite", + "cloudy": "Nublado", + "partlycloudy": "Parcialmente nublado", + "rainy": "Chuvoso", + "pouring": "Aguaceiros", + "snowy": "Nevoso", + "snowy_rainy": "Nevoso, chuvoso", + "fog": "Nevoeiro", + "hail": "Granizo", + "lightning": "Trovoada", + "lightning_rainy": "Trovoada, chuva", + "windy": "Ventoso", + "exceptional": "Excecional" + }, + "binary": { + "battery_on": "Baixo", + "battery_off": "Normal", + "battery_charging_on": "A carregar", + "battery_charging_off": "Não está a carregar", + "carbon_monoxide_on": "Detetado", + "carbon_monoxide_off": "Limpo", + "cold_on": "Frio", + "cold_off": "Normal", + "connectivity_on": "Ligado", + "connectivity_off": "Desligado", + "door_on": "Aberto", + "door_off": "Fechada", + "garage_door_on": "Aberta", + "garage_door_off": "Fechado", + "gas_on": "Detetado", + "gas_off": "Limpo", + "heat_on": "Quente", + "heat_off": "Normal", + "light_on": "Luz detetada", + "light_off": "Sem luz", + "lock_on": "Destrancada", + "lock_off": "Trancado", + "moisture_on": "Húmido", + "moisture_off": "Seco", + "motion_on": "Detetado", + "motion_off": "Limpo", + "moving_on": "Em movimento", + "moving_off": "Parado", + "occupancy_on": "Detetado", + "occupancy_off": "Limpo", + "opening_on": "Aberta", + "opening_off": "Fechada", + "plug_on": "Ligado", + "plug_off": "Desligado", + "power_on": "Detetado", + "power_off": "Desligado", + "presence_on": "Em casa", + "presence_off": "Ausente", + "problem_on": "Problema", + "problem_off": "OK", + "running_on": "Em execução", + "running_off": "Parado", + "safety_on": "Inseguro", + "safety_off": "Seguro", + "smoke_on": "Detetado", + "smoke_off": "Limpo", + "sound_on": "Detetado", + "sound_off": "Limpo", + "tamper_on": "Adulteração detetada", + "tamper_off": "Limpo", + "update_on": "Atualização disponível", + "update_off": "Atualizado", + "vibration_on": "Detetado", + "vibration_off": "Limpo", + "window_on": "Aberto", + "window_off": "Fechada" + } + } + }, + "addon": {}, + "editor": {} +} diff --git a/tests/screen_text_en.h b/tests/screen_text_en.h index 3748a18..bdcfd2f 100644 --- a/tests/screen_text_en.h +++ b/tests/screen_text_en.h @@ -3,11 +3,12 @@ #pragma once #include "components/smart_display/screen_text.h" -static_assert(screen_text::KEYS_HASH == 0x725A92EEu && screen_text::KEY_COUNT == 302, +static_assert(screen_text::KEYS_HASH == 0x8971C0EBu && 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[] = { ".", ",", + "1", "Just now", "{n} min ago", "1 hour ago | {n} hours ago", @@ -108,6 +109,8 @@ const char *const screen_text::TABLE[] = { "Firmware", "Home Assistant", "Restart", + "Connected", + "Not connected", "Tap again to restart", "{n} sec", "{n} min", @@ -231,8 +234,8 @@ const char *const screen_text::TABLE[] = { "Standby", "Home", "Away", - "Above the horizon", - "Below the horizon", + "Above horizon", + "Below horizon", "Docked", "Cleaning", "Paused", @@ -256,8 +259,8 @@ const char *const screen_text::TABLE[] = { "Normal", "Charging", "Not charging", - "Danger", - "Safe", + "Detected", + "Clear", "Cold", "Normal", "Connected", @@ -266,21 +269,21 @@ const char *const screen_text::TABLE[] = { "Closed", "Open", "Closed", - "Danger", - "Safe", + "Detected", + "Clear", "Hot", "Normal", - "Light", - "Dark", - "Open", + "Light detected", + "No light", + "Unlocked", "Locked", "Wet", "Dry", - "Motion", - "No motion", + "Detected", + "Clear", "Moving", - "Still", - "Occupied", + "Not moving", + "Detected", "Clear", "Open", "Closed", @@ -292,20 +295,20 @@ const char *const screen_text::TABLE[] = { "Away", "Problem", "OK", - "Active", - "Inactive", + "Running", + "Not running", "Unsafe", "Safe", - "Smoke", - "No smoke", - "Sound", - "Silent", - "Tampering", - "OK", - "Update", - "Up to date", - "Vibration", - "Still", + "Detected", + "Clear", + "Detected", + "Clear", + "Tampering detected", + "Clear", + "Update available", + "Up-to-date", + "Detected", + "Clear", "Open", "Closed" }; diff --git a/tests/test_screen_text.cpp b/tests/test_screen_text.cpp new file mode 100644 index 0000000..46c74a9 --- /dev/null +++ b/tests/test_screen_text.cpp @@ -0,0 +1,46 @@ +#include "screen_text_en.h" +// The screen's texts (app 0.2.90): lookups, placeholders, plural forms and numbers, with the English table the tests +// build with (tests/screen_text_en.h). A screen's own build writes its language's table instead. +#include +#include +#include +using namespace screen_text; + +int main() { + assert(std::string(LANGUAGE) == "en"); + assert(std::string(tr(txt::settings_title)) == "Settings"); + assert(std::string(tr(KEY_COUNT)) == "" && std::string(tr(0xFFFF)) == ""); + // A list is its first key plus a count: the month names in order. + assert(txt::date_months_short_count == 12 && std::string(tr(txt::date_months_short + 8)) == "Sep"); + assert(txt::date_weekdays_count == 7 && std::string(tr(txt::date_weekdays)) == "Sunday"); + + // Named placeholders, every occurrence; a translation is never a printf format. + assert(fill(std::string("{n} of {n}"), "n", "3") == "3 of 3"); + assert(fill(txt::tile_page, "n", 2) == "Page 2"); + assert(fill(std::string("100%"), "n", "1") == "100%"); + + // Plural forms by the language's rule (English: one for 1, other for the rest); the one form may name the number. + assert(plural(txt::time_hours_ago, 1) == "1 hour ago" && plural(txt::time_hours_ago, 2) == "2 hours ago"); + assert(plural(txt::time_hours_ago, 0) == "0 hours ago" && plural(txt::history_times, 1) == "once"); + assert(plural(txt::history_times, 21) == "21 times"); + // A text without forms is the same for every n. + assert(plural(txt::time_minutes_ago, 5) == "5 min ago"); + + // Numbers: the language's marks unless Settings -> Language & region chose a format for every screen. + number_style = 0; + assert(localize("1234.5") == "1,234.5" && localize("-3") == "-3" && localize("123") == "123"); + assert(localize("1234567") == "1,234,567" && localize("0.25") == "0.25"); + for (const char *text : {"on", "1.2.3", "-", ".5", "5.", "12a", "", "1e5"}) assert(localize(text) == text); + assert(decimal(21.54f, 1) == "21.5"); + number_style = 2; // 1.234,5 + assert(localize("1234.5") == "1.234,5" && localize("-1234") == "-1.234" && decimal(21.54f, 1) == "21,5"); + number_style = 3; // 1 234,5 + assert(localize("12345.75") == "12 345,75"); + number_style = 1; // 1,234.5 + assert(localize("1234.5") == "1,234.5"); + assert(number_style_of("auto") == 0 && number_style_of("point") == 1 && number_style_of("comma") == 2 && + number_style_of("space") == 3 && number_style_of("dot") == -1); + number_style = 0; + std::puts("test_screen_text: PASS"); + return 0; +} diff --git a/tests/test_tile_controls.cpp b/tests/test_tile_controls.cpp index dd265d9..c5ca074 100644 --- a/tests/test_tile_controls.cpp +++ b/tests/test_tile_controls.cpp @@ -184,9 +184,10 @@ int main() { rows = vacuum_rows(old_robot2, 0); assert(rows.suction && !rows.water && !choice_action(old_robot2, 'm', "mop").valid()); - // Binary sensors (firmware 0.2.53+) in the words of their device class; no class or an unknown one says On and Off. + // Binary sensors (firmware 0.2.53+) in the words of their device class, Home Assistant's own since app 0.2.90 (the + // English table of the tests); no class or an unknown one says On and Off. assert(!strcmp(binary_state_text("door", true), "Open") && !strcmp(binary_state_text("door", false), "Closed")); - assert(!strcmp(binary_state_text("motion", true), "Motion") && !strcmp(binary_state_text("motion", false), "No motion")); + assert(!strcmp(binary_state_text("motion", true), "Detected") && !strcmp(binary_state_text("motion", false), "Clear")); assert(!strcmp(binary_state_text("moisture", true), "Wet") && !strcmp(binary_state_text("moisture", false), "Dry")); assert(!strcmp(binary_state_text("battery_charging", false), "Not charging") && !strcmp(binary_state_text("battery", true), "Low")); assert(!strcmp(binary_state_text("", true), "On") && !strcmp(binary_state_text("", false), "Off")); diff --git a/tools/i18n.py b/tools/i18n.py index 24d5518..f1ed2f0 100644 --- a/tools/i18n.py +++ b/tools/i18n.py @@ -3,10 +3,15 @@ python3 tools/i18n.py check every language against English: keys, placeholders, plural forms, letters the screens can't draw, screen texts much longer than the English + python3 tools/i18n.py lint English words left in the firmware's code instead of the translations python3 tools/i18n.py header [--check] write (or compare) the key header and the tests' English table that the firmware's `screen` texts need, after changing the keys of en.json python3 tools/i18n.py cldr [--write] day and month names, date orders and the decimal mark from the Unicode CLDR (Node's Intl), into every language's screen.date and screen.number + python3 tools/i18n.py ha-words [--write] Home Assistant's own words for states (screen.ha) in every language, from a + Home Assistant (HA_URL and HA_TOKEN); needs aiohttp + python3 tools/i18n.py new CODE NAME ENGLISH PLURAL + a new language file, such as `new sv Svenska Swedish one_other` """ import argparse import importlib.util @@ -105,10 +110,13 @@ def check(): if plural and forms and len(text.split('|')) not in (1, forms): problems.append(f'{code}: {key} needs {forms} forms separated by " | "') if key.startswith('screen.') and drawable: - unknown = sorted(set(text) - drawable - {'\n'}) + shown = PLACEHOLDER.sub('', text).replace('|', '') + unknown = sorted(set(shown) - drawable - {'\n'}) if unknown: problems.append(f'{code}: {key} uses letters the screens can\'t draw: {"".join(unknown)}') - if code != 'en' and len(text) > max(8, len(source) * 1.6) and not plural: + # Home Assistant's words and the calendar's are what they are; our own texts should stay about as short. + ours = not key.startswith(('screen.ha.', 'screen.date.', 'screen.number.')) + if ours and code != 'en' and len(text) > max(8, len(source) * 1.6) and not plural: notes.append(f'{code}: {key} is much longer than the English ({len(text)} vs {len(source)})') for line in notes: print('note:', line) @@ -118,26 +126,227 @@ def check(): return 1 if problems else 0 +# English that stays in the firmware on purpose, with why. Everything else a screen shows comes from the translations, +# and `lint` fails on a new English text in the code, so it never slips back in. +LINT_KEEP = { + # Statuses the app reads and acts on (screen_manager/app/server.py RESEND_STATES, the "Error" prefix): protocol. + 'Synced', 'Loading tiles', 'Layout received', 'Resend needed', 'Ready for tile configuration', + 'Use the Easy Setup profile', 'Swipe test started', 'no answer', 'Error: message too large', + 'Error: invalid message', 'Error: protocol version', 'Error: screen settings', 'Error: outdated tile', + 'Error: no memory for ', 'Error: incomplete message', 'Error: invalid encoding', + # Only a log line or the rate limiter's reason shows these. + 'history range', 'card button ', 'media key ', 'let go', 'too short (', 'already handled in this contact', + 'same button within the debounce window', 'no runtime tiles', 'setting off', 'screen dimmed', 'card open', + 'detail card open', 'camera open', 'USB calibration ready; no tile actions', 'GT911 touch test ready; no tile actions', + 'UI_TEST START: page/overlay render stress, no HA actions', 'Color', 'Color temperature', 'Brightness', + # Home Assistant's own values and units the code compares with, not words it shows. + 'None', 'Auto', 'Wh', + # Placeholders of the YAML tree that the runtime tiles replace before a screen shows them, and profile defaults. + 'Lamp', 'Plug', 'Evening', 'All off', 'AC', 'Vacuum', 'Tile 7', 'Tile 8', 'Tile 9', 'Tile 10', 'Light', 'Light Color', + 'Climate', 'Example lamp', 'My CYD', 'My Guition', +} +LINT_FILES = ('components/smart_display/*.h', 'packages/core.yaml', 'packages/boards/*.yaml') + + +def lint(): + """English words in the firmware's code that should be a key of the translations' `screen` section.""" + literal = re.compile(r'"((?:[^"\\]|\\.)*)"') + found = [] + for pattern in LINT_FILES: + for path in sorted(ROOT.glob(pattern)): + for number, line in enumerate(path.read_text(encoding='utf-8').splitlines(), 1): + stripped = line.strip() + # Comments, logs, and Home Assistant's entity names (renaming one gives it a new entity id). + if stripped.startswith(('//', '#', '*')) or 'ESP_LOG' in line or 'static_assert' in line or re.match(r'\s*name: "', line): + continue + code = line.split('//')[0] + if path.suffix == '.yaml': + code = re.split(r'\s#', code)[0] + for match in literal.finditer(code): + text = match.group(1) + if text in LINT_KEEP or re.search(r'[();]|::|->|\$\{', text): + continue + if re.match(r'^[A-Z][a-z]', text) or re.search(r'[A-Za-z]{2,} [a-z]{2,}', text): + found.append(f'{path.relative_to(ROOT)}:{number}: "{text}"') + for line in found: + print('English in the firmware (a key of screen_manager/translations/en.json, or tools/i18n.py LINT_KEEP):', line) + return 1 if found else 0 + + +# Where screen.ha's words come from: Home Assistant's own translations (frontend/get_translations, `entity_component`), +# so a screen says what Home Assistant says in the same language. +def ha_sources(): + sources = {'on': 'component.switch.entity_component._.state.on', 'off': 'component.switch.entity_component._.state.off'} + for mode in ('off', 'heat', 'cool', 'heat_cool', 'auto', 'dry', 'fan_only'): + sources[f'climate.{mode}'] = f'component.climate.entity_component._.state.{mode}' + for action in ('heating', 'cooling', 'idle', 'off', 'drying', 'fan', 'preheating', 'defrosting'): + sources[f'hvac_action.{action}'] = f'component.climate.entity_component._.state_attributes.hvac_action.state.{action}' + for state in ('open', 'closed', 'opening', 'closing'): + sources[f'cover.{state}'] = f'component.cover.entity_component._.state.{state}' + for state in ('playing', 'paused', 'idle', 'standby'): + sources[f'media.{state}'] = f'component.media_player.entity_component._.state.{state}' + for state in ('home', 'not_home'): + sources[f'person.{state}'] = f'component.person.entity_component._.state.{state}' + for state in ('above_horizon', 'below_horizon'): + sources[f'sun.{state}'] = f'component.sun.entity_component._.state.{state}' + for state in ('docked', 'cleaning', 'paused', 'returning', 'idle'): + sources[f'vacuum.{state}'] = f'component.vacuum.entity_component._.state.{state}' + english = json.loads((FOLDER / 'en.json').read_text(encoding='utf-8'))['screen']['ha'] + for key in english['weather']: + sources[f'weather.{key}'] = f'component.weather.entity_component._.state.{key.replace("_", "-") if key != "partlycloudy" else key}' + for key in english['binary']: + device_class, state = key.rsplit('_', 1) + sources[f'binary.{key}'] = f'component.binary_sensor.entity_component.{device_class}.state.{state}' + return sources + + +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).""" + import asyncio + import os + try: + import aiohttp + except ImportError: + raise SystemExit('ha-words needs aiohttp (.venv-portal/bin/python has it)') + def setting(name, file): + value = os.environ.get(name) + for folder in (ROOT, ROOT.parent / 'esphome-cyd-display'): + if not value and (folder / '.esphome' / file).exists(): + value = (folder / '.esphome' / file).read_text().strip() + if not value: + raise SystemExit(f'Set {name} or .esphome/{file}') + return value + url, token = setting('HA_URL', 'ha_url').rstrip('/'), setting('HA_TOKEN', 'ha_token') + sources = ha_sources() + + async def fetch(codes): + found = {} + async with aiohttp.ClientSession() as session: + async with session.ws_connect(url.replace('http', 'ws', 1) + '/api/websocket') as ws: + await ws.receive_json() + await ws.send_json({'type': 'auth', 'access_token': token}) + if (await ws.receive_json()).get('type') != 'auth_ok': + raise SystemExit('Home Assistant refused the token') + for number, code in enumerate(codes, 1): + await ws.send_json({'id': number, 'type': 'frontend/get_translations', 'language': code, 'category': 'entity_component'}) + answer = await ws.receive_json() + found[code] = (answer.get('result') or {}).get('resources') or {} + return found + + langs = languages() + found = asyncio.run(fetch(list(langs))) + status = 0 + for code, data in langs.items(): + words = found.get(code) or {} + own = {} + for key, source in sources.items(): + if isinstance(words.get(source), str) and words[source]: + own[key] = words[source] + else: + print(f'{code}: Home Assistant has no {source}') + reference = dict(generator().flatten(data.get('screen', {}).get('ha', {}))) + if code == 'en': + for key, word in own.items(): + if reference.get(key) != word: + print(f'en: screen.ha.{key} "{reference.get(key)}" becomes Home Assistant\'s "{word}"') + tree = {} + for key, word in own.items(): + node = tree + parts = key.split('.') + for part in parts[:-1]: + node = node.setdefault(part, {}) + node[parts[-1]] = word + before = json.dumps(data, ensure_ascii=False) + merge(data.setdefault('screen', {}).setdefault('ha', {}), tree) + if json.dumps(data, ensure_ascii=False) != before: + print(f'{code}: {"wrote" if write else "would change"} {len(own)} words from Home Assistant') + if write: + write_language(code, data) + return status + + def main(): parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) sub = parser.add_subparsers(dest='command', required=True) sub.add_parser('check') + sub.add_parser('lint') head = sub.add_parser('header') head.add_argument('--check', action='store_true') cldr = sub.add_parser('cldr') cldr.add_argument('--write', action='store_true') + words = sub.add_parser('ha-words') + words.add_argument('--write', action='store_true') + fresh = sub.add_parser('new') + fresh.add_argument('code') + fresh.add_argument('name') + fresh.add_argument('english') + fresh.add_argument('plural') args = parser.parse_args() + if args.command == 'new': + return new_language(args.code, args.name, args.english, args.plural) if args.command == 'check': return check() + if args.command == 'lint': + return lint() if args.command == 'header': return header(args.check) if args.command == 'cldr': return cldr_dates(args.write) + if args.command == 'ha-words': + return ha_words(args.write) return 2 -def cldr_dates(write): - raise SystemExit('not yet') +# The CLDR locale of a file where it differs: Portuguese is Home Assistant's pt, the one of Portugal. +CLDR_LOCALE = {'pt': 'pt-PT'} + + +def write_language(code, data): + (FOLDER / f'{code}.json').write_text(json.dumps(data, ensure_ascii=False, indent=2) + '\n', encoding='utf-8') + + +def merge(target, source): + """`source` into `target` in place, keeping the order of what `target` already has.""" + for key, value in source.items(): + if isinstance(value, dict) and isinstance(target.get(key), dict): + merge(target[key], value) + else: + target[key] = value + + +def cldr_dates(write, codes=None): + """Day and month names, date orders, AM/PM, the number marks and the clock of every language but English (whose + texts are the source) from Node's Intl, into screen.date, screen.time, screen.number and _meta.clock.""" + codes = codes or [code for code in languages() if not code.startswith('en')] + if not codes: + return 0 + locales = [CLDR_LOCALE.get(code, code) for code in codes] + found = json.loads(subprocess.run(['node', str(ROOT / 'tools' / 'i18n_cldr.mjs'), *locales], check=True, + capture_output=True, text=True).stdout) + for code, locale in zip(codes, locales): + path = FOLDER / f'{code}.json' + data = json.loads(path.read_text(encoding='utf-8')) + cldr = found[locale] + before = json.dumps(data, ensure_ascii=False) + data.setdefault('_meta', {})['clock'] = cldr['clock'] + merge(data.setdefault('screen', {}), {'number': cldr['number'], 'time': cldr['time'], 'date': cldr['date']}) + if json.dumps(data, ensure_ascii=False) != before: + print(f'{code}: {"wrote" if write else "would change"} the CLDR texts') + if write: + write_language(code, data) + return 0 + + +def new_language(code, name, english, plural): + """A new language file: its _meta and the CLDR texts, every other text still English until someone translates it.""" + if not re.fullmatch(r'[a-z]{2,3}(-[A-Za-z0-9]{2,8})?', code) or (FOLDER / f'{code}.json').exists(): + raise SystemExit(f'{code}: not a new language code') + if plural not in FORMS: + raise SystemExit(f'plural must be one of {", ".join(FORMS)}') + write_language(code, {'_meta': {'name': name, 'english': english, 'script': 'latin', 'plural': plural, 'clock': '24', + 'checked': False}, 'screen': {}, 'addon': {}, 'editor': {}}) + return cldr_dates(True, [code]) if __name__ == '__main__': diff --git a/tools/i18n_cldr.mjs b/tools/i18n_cldr.mjs new file mode 100644 index 0000000..e1e3334 --- /dev/null +++ b/tools/i18n_cldr.mjs @@ -0,0 +1,52 @@ +// Day and month names, date orders, the decimal mark and the clock of languages, from the Unicode CLDR that Node's Intl +// carries (app 0.2.90); tools/i18n.py cldr writes them into the translations. Prints JSON: {code: {...}}. +const codes = process.argv.slice(2); +const space = (text) => text.replace(/[   ]/g, ' '); +// 2026-03-01 is a Sunday: day i of that week is Sunday + i, as the screens count (ESPHome's day_of_week 1 is Sunday). +const day = (i) => new Date(Date.UTC(2026, 2, 1 + i, 12)); +const month = (m) => new Date(Date.UTC(2026, m, 14, 12)); +const pattern = (code, options, names) => { + const parts = new Intl.DateTimeFormat(code, { ...options, timeZone: 'UTC' }).formatToParts(new Date(Date.UTC(2026, 8, 14, 14, 5))); + let out = ''; + for (const part of parts) { + if (names[part.type]) out += `{${names[part.type]}}`; + else if (part.type === 'literal') out += space(part.value); + } + return out.replace(/\{hour\}:\{minute\}/, '{time}').replace(/\{hour\}\.\{minute\}/, '{time}'); +}; +const result = {}; +for (const code of codes) { + const short = new Intl.DateTimeFormat(code, { weekday: 'short', timeZone: 'UTC' }); + const long = new Intl.DateTimeFormat(code, { weekday: 'long', timeZone: 'UTC' }); + const monthLong = (m) => new Intl.DateTimeFormat(code, { day: 'numeric', month: 'long', timeZone: 'UTC' }).formatToParts(month(m)).find((p) => p.type === 'month').value; + const monthShort = (m) => new Intl.DateTimeFormat(code, { day: 'numeric', month: 'short', timeZone: 'UTC' }).formatToParts(month(m)).find((p) => p.type === 'month').value; + const numbers = new Intl.NumberFormat(code); + const parts = numbers.formatToParts(12345.5); + const decimal = parts.find((p) => p.type === 'decimal').value; + const group = space(parts.find((p) => p.type === 'group').value); + const period = (hour) => (new Intl.DateTimeFormat(code, { hour: 'numeric', hour12: true, timeZone: 'UTC' }) + .formatToParts(new Date(Date.UTC(2026, 0, 1, hour))).find((p) => p.type === 'dayPeriod') || { value: hour < 12 ? 'AM' : 'PM' }).value; + const cycle = new Intl.DateTimeFormat(code, { hour: 'numeric', timeZone: 'UTC' }).resolvedOptions().hourCycle; + // A locale that writes a short date with the month as a number (Portugal: "14/09") gets the standalone short names + // and "day month" instead: the screens' calendar shows a month's name beside the big day. + const numericMonth = /^\d+$/.test(monthShort(8)); + const standalone = (m) => space(new Intl.DateTimeFormat(code, { month: 'short', timeZone: 'UTC' }).format(month(m))); + const withNames = (text) => (numericMonth ? text.replace(/\{day\}\W*\{month\}/, '{day} {month}') : text); + result[code] = { + clock: cycle === 'h12' || cycle === 'h11' ? '12' : '24', + number: { decimal, group, group_min: numbers.format(1234).includes(parts.find((p) => p.type === 'group').value) ? '1' : '2' }, + time: { am: space(period(9)), pm: space(period(21)) }, + date: { + weekdays: [0, 1, 2, 3, 4, 5, 6].map((i) => long.format(day(i))), + weekdays_short: [0, 1, 2, 3, 4, 5, 6].map((i) => short.format(day(i))), + weekdays_min: [0, 1, 2, 3, 4, 5, 6].map((i) => short.format(day(i))), + months: [...Array(12).keys()].map(monthLong), + months_short: [...Array(12).keys()].map(numericMonth ? standalone : monthShort), + top_bar: withNames(pattern(code, { weekday: 'short', day: 'numeric', month: 'short' }, { weekday: 'weekday', day: 'day', month: 'month' })), + full: pattern(code, { weekday: 'long', day: 'numeric', month: 'long' }, { weekday: 'weekday', day: 'day', month: 'month' }), + day_month: withNames(pattern(code, { day: 'numeric', month: 'short' }, { day: 'day', month: 'month' })), + weekday_time: pattern(code, { weekday: 'short', hour: '2-digit', minute: '2-digit', hourCycle: 'h23' }, { weekday: 'weekday', hour: 'hour', minute: 'minute' }), + }, + }; +} +console.log(JSON.stringify(result, null, 1));