From 83fb1b8bbc4cf341940606c2c91bea7428389824 Mon Sep 17 00:00:00 2001 From: ADNPolymerase <111017981+ADNPolymerase@users.noreply.github.com> Date: Sat, 27 Jun 2026 08:48:06 +0200 Subject: [PATCH 1/6] Add French (fr) translation --- .../worx_vision_cloud/translations/fr.json | 261 ++++++++++++++++++ 1 file changed, 261 insertions(+) create mode 100644 custom_components/worx_vision_cloud/translations/fr.json diff --git a/custom_components/worx_vision_cloud/translations/fr.json b/custom_components/worx_vision_cloud/translations/fr.json new file mode 100644 index 0000000..622d072 --- /dev/null +++ b/custom_components/worx_vision_cloud/translations/fr.json @@ -0,0 +1,261 @@ +{ + "title": "Worx Vision Cloud PLUS", + "config": { + "step": { + "user": { + "title": "Worx Vision Cloud PLUS", + "description": "Utilisez les mêmes identifiants que dans l'application Worx Landroid.", + "data": { + "email": "Adresse e-mail", + "password": "Mot de passe", + "cloud": "Cloud", + "verify_ssl": "Vérifier le certificat SSL", + "expose_raw_entities": "Exposer tous les champs bruts du payload en tant qu'entités" + } + } + }, + "error": { + "cannot_connect": "Impossible de se connecter au Cloud Worx.", + "invalid_auth": "Identifiant ou mot de passe incorrect.", + "rate_limited": "Limite de requêtes du Cloud Worx atteinte. Réessayez plus tard." + }, + "abort": { + "already_configured": "Ce compte est déjà configuré." + } + }, + "entity": { + "lawn_mower": { + "mower": { + "name": "Tondeuse" + } + }, + "sensor": { + "battery_percent": { + "name": "Batterie" + }, + "status": { + "name": "État" + }, + "error": { + "name": "Erreur" + }, + "rssi": { + "name": "RSSI" + }, + "zone_current": { + "name": "Zone actuelle" + }, + "schedule": { + "name": "Programme" + }, + "mowing_readiness": { + "name": "Aptitude à la tonte" + }, + "cloud_connection": { + "name": "Connexion au cloud" + }, + "api_capabilities": { + "name": "Capacités de l'API" + }, + "push_notifications": { + "name": "Notifications push" + }, + "daily_progress": { + "name": "Progression quotidienne" + }, + "remaining_progress": { + "name": "Reste à tondre" + }, + "area_mowed_today": { + "name": "Surface tondue aujourd'hui" + }, + "lawn_area": { + "name": "Surface de la pelouse" + }, + "mowing_efficiency": { + "name": "Efficacité de tonte" + }, + "rtk_map": { + "name": "Carte RTK" + }, + "rtk_trail_points": { + "name": "Points de trace RTK" + }, + "rtk_address": { + "name": "Adresse RTK" + }, + "rain_delay": { + "name": "Délai pluie" + }, + "rain_remaining": { + "name": "Délai pluie restant" + }, + "battery_voltage": { + "name": "Tension de la batterie" + }, + "battery_temperature": { + "name": "Température de la batterie" + }, + "battery_cycles_total": { + "name": "Cycles de batterie (total)" + }, + "battery_cycles_since_reset": { + "name": "Cycles de batterie depuis réinitialisation" + }, + "battery_cycles_reset_at": { + "name": "Dernière réinitialisation des cycles de batterie" + }, + "blade_runtime_total": { + "name": "Temps de fonctionnement des lames (total)" + }, + "blade_runtime_current": { + "name": "Temps de fonctionnement des lames (actuel)" + }, + "blade_runtime_reset_at": { + "name": "Dernière réinitialisation du temps des lames" + }, + "mower_runtime_total": { + "name": "Temps de fonctionnement total" + }, + "mower_home_time_total": { + "name": "Temps total en station" + }, + "mower_charging_time_total": { + "name": "Temps de charge total" + }, + "mower_error_time_total": { + "name": "Temps total en erreur" + }, + "maintenance_status": { + "name": "État de maintenance" + }, + "pitch": { + "name": "Tangage" + }, + "roll": { + "name": "Roulis" + }, + "yaw": { + "name": "Lacet" + }, + "last_update": { + "name": "Dernière mise à jour" + }, + "last_update_age": { + "name": "Ancienneté de la dernière mise à jour" + } + }, + "binary_sensor": { + "online": { + "name": "En ligne" + }, + "iot_registered": { + "name": "IoT enregistré" + }, + "mqtt_registered": { + "name": "MQTT enregistré" + }, + "locked": { + "name": "Verrouillé" + }, + "rain_triggered": { + "name": "Pluie détectée" + }, + "robot_lifted": { + "name": "Robot soulevé" + }, + "off_limits_enabled": { + "name": "Zones interdites activées" + }, + "acs_enabled": { + "name": "ACS activé" + }, + "party_mode_enabled": { + "name": "Mode festif activé" + }, + "pause_mode_enabled": { + "name": "Mode pause activé" + }, + "smart_edge_cut": { + "name": "Coupe de bordure intelligente" + }, + "save_hedgehogs": { + "name": "Mode hérissons" + } + }, + "switch": { + "firmware_auto_upgrade": { + "name": "Mise à jour automatique du firmware" + }, + "lock": { + "name": "Verrouillage" + }, + "native_schedule": { + "name": "Programme natif" + }, + "smart_edge_cut": { + "name": "Coupe de bordure intelligente" + }, + "save_hedgehogs": { + "name": "Mode hérissons" + }, + "one_time_mowing_edge_cut": { + "name": "Coupe de bordure (tonte unique)" + } + }, + "button": { + "refresh": { + "name": "Actualiser" + }, + "reset_blade_counter": { + "name": "Réinitialiser le temps des lames" + }, + "reset_battery_cycle_counter": { + "name": "Réinitialiser les cycles de batterie" + }, + "start_edge_cut": { + "name": "Démarrer la coupe de bordure" + }, + "start_one_time_mowing": { + "name": "Démarrer la tonte unique" + } + }, + "number": { + "rain_delay_minutes": { + "name": "Délai pluie" + }, + "time_extension": { + "name": "Prolongation de durée" + }, + "lawn_area": { + "name": "Surface de la pelouse" + }, + "lawn_perimeter": { + "name": "Périmètre de la pelouse" + }, + "one_time_mowing_runtime": { + "name": "Durée de la tonte unique" + } + }, + "select": { + "one_time_mowing_zones": { + "name": "Zones de la tonte unique" + } + }, + "update": { + "firmware": { + "name": "Firmware" + } + }, + "camera": { + "rtk_map_camera": { + "name": "Carte RTK" + } + }, + "calendar": { + "schedule": { + "name": "Programme de tonte" + } + } + } +} From a62e0f05837b2f7a86254b0ff49dfe2913dfc9c0 Mon Sep 17 00:00:00 2001 From: ADNPolymerase <111017981+ADNPolymerase@users.noreply.github.com> Date: Sat, 27 Jun 2026 09:05:19 +0200 Subject: [PATCH 2/6] =?UTF-8?q?Use=20'Sauvons=20les=20h=C3=A9rissons'=20fo?= =?UTF-8?q?r=20save=5Fhedgehogs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- custom_components/worx_vision_cloud/translations/fr.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/custom_components/worx_vision_cloud/translations/fr.json b/custom_components/worx_vision_cloud/translations/fr.json index 622d072..f255237 100644 --- a/custom_components/worx_vision_cloud/translations/fr.json +++ b/custom_components/worx_vision_cloud/translations/fr.json @@ -180,7 +180,7 @@ "name": "Coupe de bordure intelligente" }, "save_hedgehogs": { - "name": "Mode hérissons" + "name": "Sauvons les hérissons" } }, "switch": { @@ -197,7 +197,7 @@ "name": "Coupe de bordure intelligente" }, "save_hedgehogs": { - "name": "Mode hérissons" + "name": "Sauvons les hérissons" }, "one_time_mowing_edge_cut": { "name": "Coupe de bordure (tonte unique)" From 2c6226624c138048e4165f77919e626c9ab2e112 Mon Sep 17 00:00:00 2001 From: ADNPolymerase <111017981+ADNPolymerase@users.noreply.github.com> Date: Sat, 27 Jun 2026 18:06:24 +0200 Subject: [PATCH 3/6] Shorten switch label to 'Coupe de bordure' --- custom_components/worx_vision_cloud/translations/fr.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/worx_vision_cloud/translations/fr.json b/custom_components/worx_vision_cloud/translations/fr.json index f255237..81419b3 100644 --- a/custom_components/worx_vision_cloud/translations/fr.json +++ b/custom_components/worx_vision_cloud/translations/fr.json @@ -200,7 +200,7 @@ "name": "Sauvons les hérissons" }, "one_time_mowing_edge_cut": { - "name": "Coupe de bordure (tonte unique)" + "name": "Coupe de bordure" } }, "button": { From 637e03f82eec18886b16ab048cb4d654dacdd263 Mon Sep 17 00:00:00 2001 From: ADNPolymerase <111017981+ADNPolymerase@users.noreply.github.com> Date: Sat, 27 Jun 2026 18:15:46 +0200 Subject: [PATCH 4/6] Make entity names and sensor states translatable (en/pl/fr) - Replace hardcoded Polish _attr_name with translation_key on the calendar, RTK map camera and RTK position device_tracker entities - Add the missing device_tracker translation section (en/pl/fr) - Convert status, error, mowing_readiness, cloud_connection and maintenance_status to enum sensors that emit canonical state keys, with localized labels in translations/*.json (Polish wording preserved) - Replace remaining hardcoded Polish in the free-text schedule summary and day labels with neutral English (HA cannot translate free text) --- .../worx_vision_cloud/calendar.py | 2 +- custom_components/worx_vision_cloud/camera.py | 2 +- .../worx_vision_cloud/device_tracker.py | 2 +- .../worx_vision_cloud/helpers.py | 20 ++-- custom_components/worx_vision_cloud/sensor.py | 108 ++++++++++++------ .../worx_vision_cloud/translations/en.json | 67 ++++++++++- .../worx_vision_cloud/translations/fr.json | 67 ++++++++++- .../worx_vision_cloud/translations/pl.json | 67 ++++++++++- 8 files changed, 271 insertions(+), 64 deletions(-) diff --git a/custom_components/worx_vision_cloud/calendar.py b/custom_components/worx_vision_cloud/calendar.py index d7d3297..70fd988 100644 --- a/custom_components/worx_vision_cloud/calendar.py +++ b/custom_components/worx_vision_cloud/calendar.py @@ -39,7 +39,7 @@ class WorxVisionScheduleCalendar(WorxVisionEntity, CalendarEntity): """Read-only mowing schedule calendar.""" _attr_icon = "mdi:calendar-clock" - _attr_name = "Harmonogram koszenia" + _attr_translation_key = "schedule" def __init__(self, coordinator, entry, serial_number: str) -> None: """Initialize schedule calendar.""" diff --git a/custom_components/worx_vision_cloud/camera.py b/custom_components/worx_vision_cloud/camera.py index 77bf7f1..452422b 100644 --- a/custom_components/worx_vision_cloud/camera.py +++ b/custom_components/worx_vision_cloud/camera.py @@ -75,7 +75,7 @@ class WorxVisionMapCamera(WorxVisionEntity, Camera): """RTK map rendered from Worx map geometry.""" _attr_icon = "mdi:map" - _attr_name = "Mapa RTK" + _attr_translation_key = "rtk_map_camera" def __init__(self, coordinator, entry, serial_number: str) -> None: """Initialize RTK map camera.""" diff --git a/custom_components/worx_vision_cloud/device_tracker.py b/custom_components/worx_vision_cloud/device_tracker.py index f47a043..86486b3 100644 --- a/custom_components/worx_vision_cloud/device_tracker.py +++ b/custom_components/worx_vision_cloud/device_tracker.py @@ -33,7 +33,7 @@ class WorxVisionLocationTracker(WorxVisionEntity, TrackerEntity): """GPS/RTK location tracker for one mower.""" _attr_icon = "mdi:map-marker-radius-outline" - _attr_name = "Pozycja RTK" + _attr_translation_key = "rtk_position" _attr_source_type = SourceType.GPS def __init__(self, coordinator, entry, serial_number: str) -> None: diff --git a/custom_components/worx_vision_cloud/helpers.py b/custom_components/worx_vision_cloud/helpers.py index e3cf90d..cd9190d 100644 --- a/custom_components/worx_vision_cloud/helpers.py +++ b/custom_components/worx_vision_cloud/helpers.py @@ -49,13 +49,13 @@ } SCHEDULE_DAY_LABELS = { - "monday": "pon", - "tuesday": "wt", - "wednesday": "sr", - "thursday": "czw", - "friday": "pt", - "saturday": "sob", - "sunday": "niedz", + "monday": "Mon", + "tuesday": "Tue", + "wednesday": "Wed", + "thursday": "Thu", + "friday": "Fri", + "saturday": "Sat", + "sunday": "Sun", } SCHEDULE_DAY_INDEX = { @@ -393,7 +393,7 @@ def schedule_slot_summary(slot: Any) -> str: text = day or "slot" if get_dict_value(slot, "boundary"): - text = f"{text} + krawedz" + text = f"{text} + edge" return text @@ -401,12 +401,12 @@ def schedule_summary(device: Any) -> str | None: """Return a compact schedule summary for Home Assistant state.""" slots = schedule_slots(device) if not slots: - return "brak aktywnych slotow" + return "no active slots" summary = ", ".join(schedule_slot_summary(slot) for slot in slots) if len(summary) <= MAX_STRING_STATE_LENGTH: return summary - return f"{len(slots)} aktywnych slotow" + return f"{len(slots)} active slots" def schedule_attributes(device: Any) -> dict[str, Any]: diff --git a/custom_components/worx_vision_cloud/sensor.py b/custom_components/worx_vision_cloud/sensor.py index 312fff5..f4cf65c 100644 --- a/custom_components/worx_vision_cloud/sensor.py +++ b/custom_components/worx_vision_cloud/sensor.py @@ -58,47 +58,71 @@ class WorxSensorDescription(SensorEntityDescription): attrs_fn: Callable[[Any], dict[str, Any] | None] | None = None -STATUS_LABELS_PL = { - "home": "w bazie", - "leaving home": "wyjazd z bazy", - "going home": "powrót do bazy", - "mowing": "koszenie", - "cutting edge": "przycinanie krawędzi", - "edge cutting": "przycinanie krawędzi", - "border cut": "przycinanie krawędzi", - "charging": "ładowanie", - "paused": "pauza", - "pause": "pauza", - "idle": "bezczynna", - "manual stop": "zatrzymana ręcznie", - "rain delay": "opóźnienie po deszczu", - "rain_delay": "opóźnienie po deszczu", - "locked": "zablokowana", - "error": "błąd", - "no error": "brak błędu", +# Map the raw descriptions reported by Worx to canonical, language-neutral state +# keys. The human-readable labels live in translations/*.json so Home Assistant can +# localize them per user (en/pl/fr/...), instead of being hard-coded here. +STATUS_STATE_KEYS = { + "home": "home", + "leaving home": "leaving_home", + "going home": "going_home", + "mowing": "mowing", + "cutting edge": "edge_cutting", + "edge cutting": "edge_cutting", + "border cut": "edge_cutting", + "charging": "charging", + "paused": "paused", + "pause": "paused", + "idle": "idle", + "manual stop": "manual_stop", + "rain delay": "rain_delay", + "rain_delay": "rain_delay", + "locked": "locked", + "error": "error", + "no error": "no_error", "offline": "offline", } -READINESS_LABELS_PL = { - "ready": "gotowa", - "mowing": "koszenie", - "charging": "ładowanie", - "battery_low": "niski poziom baterii", - "rain_delay": "opóźnienie po deszczu", - "error": "błąd", - "locked": "zablokowana", - "offline": "offline", -} +# Canonical option lists exposed as enum sensor states. +STATUS_STATE_OPTIONS = [ + "home", + "leaving_home", + "going_home", + "mowing", + "edge_cutting", + "charging", + "paused", + "idle", + "manual_stop", + "rain_delay", + "locked", + "error", + "no_error", + "offline", +] + +READINESS_STATE_OPTIONS = [ + "ready", + "mowing", + "charging", + "battery_low", + "rain_delay", + "error", + "locked", + "offline", +] + +CLOUD_CONNECTION_OPTIONS = ["ok", "check", "offline"] + +MAINTENANCE_STATE_OPTIONS = ["ok", "blade_service_due", "battery_service_due"] RAIN_DELAY_ERROR_DESCRIPTIONS = {"rain delay", "rain_delay"} -def _label_pl(value: Any, labels: dict[str, str]) -> str | None: - """Return a Polish label for a known Worx state.""" +def _state_key(value: Any, mapping: dict[str, str]) -> str | None: + """Map a raw Worx description to a canonical, translatable state key.""" if value is None: return None - text = str(value) - return labels.get(text.strip().lower(), text) + return mapping.get(str(value).strip().lower()) def _battery(device, key, default=None): @@ -123,8 +147,8 @@ def _status(device, key, default=None): def _status_state(device) -> str | None: if _is_rain_delay(device): - return _label_pl("rain_delay", READINESS_LABELS_PL) - return _label_pl(_status(device, "description"), STATUS_LABELS_PL) + return "rain_delay" + return _state_key(_status(device, "description"), STATUS_STATE_KEYS) def _error(device, key, default=None): @@ -132,7 +156,9 @@ def _error(device, key, default=None): def _error_state(device) -> str | None: - return _label_pl(_error(device, "description"), STATUS_LABELS_PL) + # Unmapped/rare device error descriptions surface via the raw_description + # attribute; the enum state stays None to avoid noisy non-option warnings. + return _state_key(_error(device, "description"), STATUS_STATE_KEYS) def _is_rain_delay(device) -> bool: @@ -394,7 +420,7 @@ def _mowing_readiness_code(device) -> str | None: def _mowing_readiness_state(device) -> str | None: - return _label_pl(_mowing_readiness_code(device), READINESS_LABELS_PL) + return _mowing_readiness_code(device) def _mowing_readiness_attributes(device) -> dict[str, Any]: @@ -587,6 +613,8 @@ def _rtk_address_attributes( key="status", translation_key="status", icon="mdi:robot-mower", + device_class=SensorDeviceClass.ENUM, + options=STATUS_STATE_OPTIONS, value_fn=_status_state, attrs_fn=_status_attributes, ), @@ -595,6 +623,8 @@ def _rtk_address_attributes( translation_key="error", icon="mdi:alert-circle-outline", entity_category=EntityCategory.DIAGNOSTIC, + device_class=SensorDeviceClass.ENUM, + options=STATUS_STATE_OPTIONS, value_fn=_error_state, attrs_fn=lambda d: { "id": _error(d, "id"), @@ -633,6 +663,8 @@ def _rtk_address_attributes( key="mowing_readiness", translation_key="mowing_readiness", icon="mdi:clipboard-check-outline", + device_class=SensorDeviceClass.ENUM, + options=READINESS_STATE_OPTIONS, value_fn=_mowing_readiness_state, attrs_fn=_mowing_readiness_attributes, ), @@ -641,6 +673,8 @@ def _rtk_address_attributes( translation_key="cloud_connection", icon="mdi:cloud-check-outline", entity_category=EntityCategory.DIAGNOSTIC, + device_class=SensorDeviceClass.ENUM, + options=CLOUD_CONNECTION_OPTIONS, value_fn=_cloud_connection_state, attrs_fn=_cloud_connection_attributes, ), @@ -872,6 +906,8 @@ def _rtk_address_attributes( translation_key="maintenance_status", icon="mdi:wrench-clock", entity_category=EntityCategory.DIAGNOSTIC, + device_class=SensorDeviceClass.ENUM, + options=MAINTENANCE_STATE_OPTIONS, value_fn=_maintenance_state, attrs_fn=_maintenance_attributes, ), diff --git a/custom_components/worx_vision_cloud/translations/en.json b/custom_components/worx_vision_cloud/translations/en.json index d878c30..97aba7e 100644 --- a/custom_components/worx_vision_cloud/translations/en.json +++ b/custom_components/worx_vision_cloud/translations/en.json @@ -34,10 +34,42 @@ "name": "Battery" }, "status": { - "name": "Status" + "name": "Status", + "state": { + "home": "Docked", + "leaving_home": "Leaving base", + "going_home": "Returning to base", + "mowing": "Mowing", + "edge_cutting": "Edge cutting", + "charging": "Charging", + "paused": "Paused", + "idle": "Idle", + "manual_stop": "Manual stop", + "rain_delay": "Rain delay", + "locked": "Locked", + "error": "Error", + "no_error": "No error", + "offline": "Offline" + } }, "error": { - "name": "Error" + "name": "Error", + "state": { + "home": "Docked", + "leaving_home": "Leaving base", + "going_home": "Returning to base", + "mowing": "Mowing", + "edge_cutting": "Edge cutting", + "charging": "Charging", + "paused": "Paused", + "idle": "Idle", + "manual_stop": "Manual stop", + "rain_delay": "Rain delay", + "locked": "Locked", + "error": "Error", + "no_error": "No error", + "offline": "Offline" + } }, "rssi": { "name": "RSSI" @@ -49,10 +81,25 @@ "name": "Schedule" }, "mowing_readiness": { - "name": "Mowing readiness" + "name": "Mowing readiness", + "state": { + "ready": "Ready", + "mowing": "Mowing", + "charging": "Charging", + "battery_low": "Battery low", + "rain_delay": "Rain delay", + "error": "Error", + "locked": "Locked", + "offline": "Offline" + } }, "cloud_connection": { - "name": "Cloud connection" + "name": "Cloud connection", + "state": { + "ok": "Connected", + "check": "Checking", + "offline": "Offline" + } }, "api_capabilities": { "name": "API capabilities" @@ -127,7 +174,12 @@ "name": "Mower error time total" }, "maintenance_status": { - "name": "Maintenance status" + "name": "Maintenance status", + "state": { + "ok": "OK", + "blade_service_due": "Blade service due", + "battery_service_due": "Battery service due" + } }, "pitch": { "name": "Pitch" @@ -256,6 +308,11 @@ "schedule": { "name": "Mowing schedule" } + }, + "device_tracker": { + "rtk_position": { + "name": "RTK position" + } } } } diff --git a/custom_components/worx_vision_cloud/translations/fr.json b/custom_components/worx_vision_cloud/translations/fr.json index 81419b3..ff0851e 100644 --- a/custom_components/worx_vision_cloud/translations/fr.json +++ b/custom_components/worx_vision_cloud/translations/fr.json @@ -34,10 +34,42 @@ "name": "Batterie" }, "status": { - "name": "État" + "name": "État", + "state": { + "home": "En station", + "leaving_home": "Sortie de la base", + "going_home": "Retour à la base", + "mowing": "En train de tondre", + "edge_cutting": "Coupe de bordure", + "charging": "En charge", + "paused": "En pause", + "idle": "Inactive", + "manual_stop": "Arrêt manuel", + "rain_delay": "Délai pluie", + "locked": "Verrouillée", + "error": "Erreur", + "no_error": "Aucune erreur", + "offline": "Hors ligne" + } }, "error": { - "name": "Erreur" + "name": "Erreur", + "state": { + "home": "En station", + "leaving_home": "Sortie de la base", + "going_home": "Retour à la base", + "mowing": "En train de tondre", + "edge_cutting": "Coupe de bordure", + "charging": "En charge", + "paused": "En pause", + "idle": "Inactive", + "manual_stop": "Arrêt manuel", + "rain_delay": "Délai pluie", + "locked": "Verrouillée", + "error": "Erreur", + "no_error": "Aucune erreur", + "offline": "Hors ligne" + } }, "rssi": { "name": "RSSI" @@ -49,10 +81,25 @@ "name": "Programme" }, "mowing_readiness": { - "name": "Aptitude à la tonte" + "name": "Aptitude à la tonte", + "state": { + "ready": "Prête", + "mowing": "En train de tondre", + "charging": "En charge", + "battery_low": "Batterie faible", + "rain_delay": "Délai pluie", + "error": "Erreur", + "locked": "Verrouillée", + "offline": "Hors ligne" + } }, "cloud_connection": { - "name": "Connexion au cloud" + "name": "Connexion au cloud", + "state": { + "ok": "Connecté", + "check": "Vérification", + "offline": "Hors ligne" + } }, "api_capabilities": { "name": "Capacités de l'API" @@ -127,7 +174,12 @@ "name": "Temps total en erreur" }, "maintenance_status": { - "name": "État de maintenance" + "name": "État de maintenance", + "state": { + "ok": "OK", + "blade_service_due": "Entretien des lames requis", + "battery_service_due": "Entretien de la batterie requis" + } }, "pitch": { "name": "Tangage" @@ -256,6 +308,11 @@ "schedule": { "name": "Programme de tonte" } + }, + "device_tracker": { + "rtk_position": { + "name": "Position RTK" + } } } } diff --git a/custom_components/worx_vision_cloud/translations/pl.json b/custom_components/worx_vision_cloud/translations/pl.json index fdf657e..2ab88c0 100644 --- a/custom_components/worx_vision_cloud/translations/pl.json +++ b/custom_components/worx_vision_cloud/translations/pl.json @@ -34,10 +34,42 @@ "name": "Bateria" }, "status": { - "name": "Status" + "name": "Status", + "state": { + "home": "w bazie", + "leaving_home": "wyjazd z bazy", + "going_home": "powrót do bazy", + "mowing": "koszenie", + "edge_cutting": "przycinanie krawędzi", + "charging": "ładowanie", + "paused": "pauza", + "idle": "bezczynna", + "manual_stop": "zatrzymana ręcznie", + "rain_delay": "opóźnienie po deszczu", + "locked": "zablokowana", + "error": "błąd", + "no_error": "brak błędu", + "offline": "offline" + } }, "error": { - "name": "Błąd" + "name": "Błąd", + "state": { + "home": "w bazie", + "leaving_home": "wyjazd z bazy", + "going_home": "powrót do bazy", + "mowing": "koszenie", + "edge_cutting": "przycinanie krawędzi", + "charging": "ładowanie", + "paused": "pauza", + "idle": "bezczynna", + "manual_stop": "zatrzymana ręcznie", + "rain_delay": "opóźnienie po deszczu", + "locked": "zablokowana", + "error": "błąd", + "no_error": "brak błędu", + "offline": "offline" + } }, "rssi": { "name": "RSSI" @@ -49,10 +81,25 @@ "name": "Harmonogram" }, "mowing_readiness": { - "name": "Gotowość do koszenia" + "name": "Gotowość do koszenia", + "state": { + "ready": "gotowa", + "mowing": "koszenie", + "charging": "ładowanie", + "battery_low": "niski poziom baterii", + "rain_delay": "opóźnienie po deszczu", + "error": "błąd", + "locked": "zablokowana", + "offline": "offline" + } }, "cloud_connection": { - "name": "Połączenie z chmurą" + "name": "Połączenie z chmurą", + "state": { + "ok": "Połączono", + "check": "Sprawdzanie", + "offline": "Offline" + } }, "api_capabilities": { "name": "Możliwości API" @@ -127,7 +174,12 @@ "name": "Czas błędów razem" }, "maintenance_status": { - "name": "Status serwisowy" + "name": "Status serwisowy", + "state": { + "ok": "OK", + "blade_service_due": "Wymiana ostrzy wymagana", + "battery_service_due": "Serwis baterii wymagany" + } }, "pitch": { "name": "Nachylenie X" @@ -256,6 +308,11 @@ "schedule": { "name": "Harmonogram koszenia" } + }, + "device_tracker": { + "rtk_position": { + "name": "Pozycja RTK" + } } } } From 4695a8d9a5d0b02bd07a0825ddbe7b44cb21c346 Mon Sep 17 00:00:00 2001 From: ADNPolymerase <111017981+ADNPolymerase@users.noreply.github.com> Date: Sat, 27 Jun 2026 20:56:32 +0200 Subject: [PATCH 5/6] Add German (de) translation --- .../worx_vision_cloud/translations/de.json | 318 ++++++++++++++++++ 1 file changed, 318 insertions(+) create mode 100644 custom_components/worx_vision_cloud/translations/de.json diff --git a/custom_components/worx_vision_cloud/translations/de.json b/custom_components/worx_vision_cloud/translations/de.json new file mode 100644 index 0000000..5d089e9 --- /dev/null +++ b/custom_components/worx_vision_cloud/translations/de.json @@ -0,0 +1,318 @@ +{ + "title": "Worx Vision Cloud PLUS", + "config": { + "step": { + "user": { + "title": "Worx Vision Cloud PLUS", + "description": "Verwenden Sie dieselben Anmeldedaten wie in der Worx-Landroid-App.", + "data": { + "email": "E-Mail-Adresse", + "password": "Passwort", + "cloud": "Cloud", + "verify_ssl": "SSL-Zertifikat überprüfen", + "expose_raw_entities": "Alle Roh-Payload-Felder als Entitäten bereitstellen" + } + } + }, + "error": { + "cannot_connect": "Verbindung zur Worx Cloud fehlgeschlagen.", + "invalid_auth": "Ungültiger Benutzername oder ungültiges Passwort.", + "rate_limited": "Anfragelimit der Worx Cloud erreicht. Bitte später erneut versuchen." + }, + "abort": { + "already_configured": "Dieses Konto ist bereits konfiguriert." + } + }, + "entity": { + "lawn_mower": { + "mower": { + "name": "Mähroboter" + } + }, + "sensor": { + "battery_percent": { + "name": "Akku" + }, + "status": { + "name": "Status", + "state": { + "home": "In der Station", + "leaving_home": "Verlässt die Station", + "going_home": "Rückkehr zur Station", + "mowing": "Mäht", + "edge_cutting": "Kantenschnitt", + "charging": "Lädt", + "paused": "Pausiert", + "idle": "Inaktiv", + "manual_stop": "Manuell gestoppt", + "rain_delay": "Regenverzögerung", + "locked": "Gesperrt", + "error": "Fehler", + "no_error": "Kein Fehler", + "offline": "Offline" + } + }, + "error": { + "name": "Fehler", + "state": { + "home": "In der Station", + "leaving_home": "Verlässt die Station", + "going_home": "Rückkehr zur Station", + "mowing": "Mäht", + "edge_cutting": "Kantenschnitt", + "charging": "Lädt", + "paused": "Pausiert", + "idle": "Inaktiv", + "manual_stop": "Manuell gestoppt", + "rain_delay": "Regenverzögerung", + "locked": "Gesperrt", + "error": "Fehler", + "no_error": "Kein Fehler", + "offline": "Offline" + } + }, + "rssi": { + "name": "RSSI" + }, + "zone_current": { + "name": "Aktuelle Zone" + }, + "schedule": { + "name": "Zeitplan" + }, + "mowing_readiness": { + "name": "Mähbereitschaft", + "state": { + "ready": "Bereit", + "mowing": "Mäht", + "charging": "Lädt", + "battery_low": "Akku niedrig", + "rain_delay": "Regenverzögerung", + "error": "Fehler", + "locked": "Gesperrt", + "offline": "Offline" + } + }, + "cloud_connection": { + "name": "Cloud-Verbindung", + "state": { + "ok": "Verbunden", + "check": "Wird geprüft", + "offline": "Offline" + } + }, + "api_capabilities": { + "name": "API-Funktionen" + }, + "push_notifications": { + "name": "Push-Benachrichtigungen" + }, + "daily_progress": { + "name": "Tagesfortschritt" + }, + "remaining_progress": { + "name": "Verbleibend zu mähen" + }, + "area_mowed_today": { + "name": "Heute gemähte Fläche" + }, + "lawn_area": { + "name": "Rasenfläche" + }, + "mowing_efficiency": { + "name": "Mäheffizienz" + }, + "rtk_map": { + "name": "RTK-Karte" + }, + "rtk_trail_points": { + "name": "RTK-Spurpunkte" + }, + "rtk_address": { + "name": "RTK-Adresse" + }, + "rain_delay": { + "name": "Regenverzögerung" + }, + "rain_remaining": { + "name": "Verbleibende Regenverzögerung" + }, + "battery_voltage": { + "name": "Akkuspannung" + }, + "battery_temperature": { + "name": "Akkutemperatur" + }, + "battery_cycles_total": { + "name": "Akkuzyklen (gesamt)" + }, + "battery_cycles_since_reset": { + "name": "Akkuzyklen seit Zurücksetzen" + }, + "battery_cycles_reset_at": { + "name": "Letztes Zurücksetzen der Akkuzyklen" + }, + "blade_runtime_total": { + "name": "Messer-Laufzeit (gesamt)" + }, + "blade_runtime_current": { + "name": "Messer-Laufzeit (aktuell)" + }, + "blade_runtime_reset_at": { + "name": "Letztes Zurücksetzen der Messer-Laufzeit" + }, + "mower_runtime_total": { + "name": "Gesamtlaufzeit" + }, + "mower_home_time_total": { + "name": "Gesamtzeit in der Station" + }, + "mower_charging_time_total": { + "name": "Gesamtladezeit" + }, + "mower_error_time_total": { + "name": "Gesamtzeit im Fehlerzustand" + }, + "maintenance_status": { + "name": "Wartungsstatus", + "state": { + "ok": "OK", + "blade_service_due": "Messerwartung fällig", + "battery_service_due": "Akkuwartung fällig" + } + }, + "pitch": { + "name": "Nicken" + }, + "roll": { + "name": "Rollen" + }, + "yaw": { + "name": "Gieren" + }, + "last_update": { + "name": "Letzte Aktualisierung" + }, + "last_update_age": { + "name": "Alter der letzten Aktualisierung" + } + }, + "binary_sensor": { + "online": { + "name": "Online" + }, + "iot_registered": { + "name": "IoT registriert" + }, + "mqtt_registered": { + "name": "MQTT registriert" + }, + "locked": { + "name": "Gesperrt" + }, + "rain_triggered": { + "name": "Regen erkannt" + }, + "robot_lifted": { + "name": "Roboter angehoben" + }, + "off_limits_enabled": { + "name": "Sperrzonen aktiviert" + }, + "acs_enabled": { + "name": "ACS aktiviert" + }, + "party_mode_enabled": { + "name": "Partymodus aktiviert" + }, + "pause_mode_enabled": { + "name": "Pausenmodus aktiviert" + }, + "smart_edge_cut": { + "name": "Intelligenter Kantenschnitt" + }, + "save_hedgehogs": { + "name": "Igel retten" + } + }, + "switch": { + "firmware_auto_upgrade": { + "name": "Firmware automatisch aktualisieren" + }, + "lock": { + "name": "Sperren" + }, + "native_schedule": { + "name": "Nativer Zeitplan" + }, + "smart_edge_cut": { + "name": "Intelligenter Kantenschnitt" + }, + "save_hedgehogs": { + "name": "Igel retten" + }, + "one_time_mowing_edge_cut": { + "name": "Kantenschnitt" + } + }, + "button": { + "refresh": { + "name": "Aktualisieren" + }, + "reset_blade_counter": { + "name": "Messer-Laufzeit zurücksetzen" + }, + "reset_battery_cycle_counter": { + "name": "Akkuzyklen zurücksetzen" + }, + "start_edge_cut": { + "name": "Kantenschnitt starten" + }, + "start_one_time_mowing": { + "name": "Einmaliges Mähen starten" + } + }, + "number": { + "rain_delay_minutes": { + "name": "Regenverzögerung" + }, + "time_extension": { + "name": "Zeitverlängerung" + }, + "lawn_area": { + "name": "Rasenfläche" + }, + "lawn_perimeter": { + "name": "Rasenumfang" + }, + "one_time_mowing_runtime": { + "name": "Laufzeit für einmaliges Mähen" + } + }, + "select": { + "one_time_mowing_zones": { + "name": "Zonen für einmaliges Mähen" + } + }, + "update": { + "firmware": { + "name": "Firmware" + } + }, + "camera": { + "rtk_map_camera": { + "name": "RTK-Karte" + } + }, + "calendar": { + "schedule": { + "name": "Mähplan" + } + }, + "device_tracker": { + "rtk_position": { + "name": "RTK-Position" + } + } + } +} From fd5fe0c9836c55cf4a0e816d096fd01e6af1d698 Mon Sep 17 00:00:00 2001 From: ADNPolymerase <111017981+ADNPolymerase@users.noreply.github.com> Date: Sat, 27 Jun 2026 21:00:44 +0200 Subject: [PATCH 6/6] Localize one-time mowing zone select (name + dynamic options) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Rename the select to a shorter 'Mowing zone' / 'Zone de tonte' / 'Mähzone' - Localize the dynamically built zone option labels (all zones / Zone N / Zones N, M) from the HA UI language instead of hard-coded Polish; Polish wording preserved, unknown languages fall back to English --- custom_components/worx_vision_cloud/select.py | 68 +++++++++++++++---- .../worx_vision_cloud/translations/de.json | 2 +- .../worx_vision_cloud/translations/en.json | 2 +- .../worx_vision_cloud/translations/fr.json | 2 +- 4 files changed, 57 insertions(+), 17 deletions(-) diff --git a/custom_components/worx_vision_cloud/select.py b/custom_components/worx_vision_cloud/select.py index a88d202..213a1c5 100644 --- a/custom_components/worx_vision_cloud/select.py +++ b/custom_components/worx_vision_cloud/select.py @@ -14,9 +14,36 @@ from .entity import WorxVisionEntity from .helpers import get_dict_value, rtk_map_attributes -ALL_ZONES_OPTION = "Wszystkie strefy" +DEFAULT_LANGUAGE = "en" MAX_COMBINATION_ZONES = 5 +# The select options are built from dynamic RTK zone combinations, so they cannot be +# declared in translations/*.json. They are localized here from the HA UI language; +# unknown languages fall back to English. Polish wording is preserved. +ALL_ZONES_LABELS = { + "en": "All zones", + "fr": "Toutes les zones", + "de": "Alle Zonen", + "pl": "Wszystkie strefy", +} +ZONE_SINGULAR_LABELS = { + "en": "Zone", + "fr": "Zone", + "de": "Zone", + "pl": "Strefa", +} +ZONE_PLURAL_LABELS = { + "en": "Zones", + "fr": "Zones", + "de": "Zonen", + "pl": "Strefy", +} + + +def _all_zones_label(language: str) -> str: + """Return the localized 'all zones' option label.""" + return ALL_ZONES_LABELS.get(language, ALL_ZONES_LABELS[DEFAULT_LANGUAGE]) + async def async_setup_entry( hass: HomeAssistant, @@ -48,26 +75,28 @@ def _zone_ids(device: Any) -> list[int]: return sorted(zone_ids) -def _option_label(zone_ids: list[int]) -> str: +def _option_label(zone_ids: list[int], language: str = DEFAULT_LANGUAGE) -> str: """Return a user-facing label for one zone selection.""" if not zone_ids: - return ALL_ZONES_OPTION + return _all_zones_label(language) if len(zone_ids) == 1: - return f"Strefa {zone_ids[0]}" - return "Strefy " + ", ".join(str(zone_id) for zone_id in zone_ids) + singular = ZONE_SINGULAR_LABELS.get(language, ZONE_SINGULAR_LABELS[DEFAULT_LANGUAGE]) + return f"{singular} {zone_ids[0]}" + plural = ZONE_PLURAL_LABELS.get(language, ZONE_PLURAL_LABELS[DEFAULT_LANGUAGE]) + return plural + " " + ", ".join(str(zone_id) for zone_id in zone_ids) -def _option_map(zone_ids: list[int]) -> dict[str, list[int]]: +def _option_map(zone_ids: list[int], language: str = DEFAULT_LANGUAGE) -> dict[str, list[int]]: """Return select option label to zone ID list mapping.""" - result: dict[str, list[int]] = {ALL_ZONES_OPTION: []} + result: dict[str, list[int]] = {_all_zones_label(language): []} if len(zone_ids) <= MAX_COMBINATION_ZONES: for count in range(1, len(zone_ids) + 1): for combo in combinations(zone_ids, count): selected = list(combo) - result[_option_label(selected)] = selected + result[_option_label(selected, language)] = selected else: for zone_id in zone_ids: - result[_option_label([zone_id])] = [zone_id] + result[_option_label([zone_id], language)] = [zone_id] return result @@ -81,12 +110,20 @@ def __init__(self, coordinator, entry, serial_number: str) -> None: """Initialize one-time mowing zones select.""" super().__init__(coordinator, entry, serial_number, "one_time_mowing_zones") + @property + def _language(self) -> str: + """Return the active Home Assistant UI language.""" + hass = getattr(self, "hass", None) + config = getattr(hass, "config", None) + return getattr(config, "language", None) or DEFAULT_LANGUAGE + @property def options(self) -> list[str]: """Return available zone choices.""" - options = _option_map(_zone_ids(self.device)) + language = self._language + options = _option_map(_zone_ids(self.device), language) current_label = _option_label( - self.coordinator.one_time_mowing_zones(self._serial_number) + self.coordinator.one_time_mowing_zones(self._serial_number), language ) if current_label not in options: options[current_label] = self.coordinator.one_time_mowing_zones( @@ -97,7 +134,9 @@ def options(self) -> list[str]: @property def current_option(self) -> str | None: """Return selected zone choice.""" - return _option_label(self.coordinator.one_time_mowing_zones(self._serial_number)) + return _option_label( + self.coordinator.one_time_mowing_zones(self._serial_number), self._language + ) @property def extra_state_attributes(self) -> dict[str, Any]: @@ -111,9 +150,10 @@ def extra_state_attributes(self) -> dict[str, Any]: async def async_select_option(self, option: str) -> None: """Select one zone choice.""" - options = _option_map(_zone_ids(self.device)) + language = self._language + options = _option_map(_zone_ids(self.device), language) current_zones = self.coordinator.one_time_mowing_zones(self._serial_number) - current_label = _option_label(current_zones) + current_label = _option_label(current_zones, language) if current_label not in options: options[current_label] = current_zones if option not in options: diff --git a/custom_components/worx_vision_cloud/translations/de.json b/custom_components/worx_vision_cloud/translations/de.json index 5d089e9..c9fc01f 100644 --- a/custom_components/worx_vision_cloud/translations/de.json +++ b/custom_components/worx_vision_cloud/translations/de.json @@ -291,7 +291,7 @@ }, "select": { "one_time_mowing_zones": { - "name": "Zonen für einmaliges Mähen" + "name": "Mähzone" } }, "update": { diff --git a/custom_components/worx_vision_cloud/translations/en.json b/custom_components/worx_vision_cloud/translations/en.json index 97aba7e..56c46bf 100644 --- a/custom_components/worx_vision_cloud/translations/en.json +++ b/custom_components/worx_vision_cloud/translations/en.json @@ -291,7 +291,7 @@ }, "select": { "one_time_mowing_zones": { - "name": "One-time mowing zones" + "name": "Mowing zone" } }, "update": { diff --git a/custom_components/worx_vision_cloud/translations/fr.json b/custom_components/worx_vision_cloud/translations/fr.json index ff0851e..da65200 100644 --- a/custom_components/worx_vision_cloud/translations/fr.json +++ b/custom_components/worx_vision_cloud/translations/fr.json @@ -291,7 +291,7 @@ }, "select": { "one_time_mowing_zones": { - "name": "Zones de la tonte unique" + "name": "Zone de tonte" } }, "update": {