Skip to content

Add 8 new languages, full i18n, new sensors and fixes#3

Open
ADNPolymerase wants to merge 21 commits into
SmartServicePL:mainfrom
ADNPolymerase:i18n
Open

Add 8 new languages, full i18n, new sensors and fixes#3
ADNPolymerase wants to merge 21 commits into
SmartServicePL:mainfrom
ADNPolymerase:i18n

Conversation

@ADNPolymerase

@ADNPolymerase ADNPolymerase commented Jun 27, 2026

Copy link
Copy Markdown

Summary

Adds 8 new languages (fr, de, nl, es, it, sv, no, da) and routes the whole integration through Home Assistant's native i18n. Several strings were hard-coded Polish in the Python code (entity names, sensor states, schedule, calendar, zone options) and bypassed the translation files; those are fixed too. Also adds a few sensors, fixes the daily progress calculation, and improves entity naming for readability.

No dependency changes: still pyworxcloud==6.3.6.

Translations

  • New translations/fr.json, de.json, nl.json, es.json, it.json, sv.json, no.json, da.json — identical key structure to en/pl (222 keys each, validated), natural (non-literal) wording.
  • Language coverage matches the community landroid_cloud integration (de/fr/nl/es/it/sv/no/da, plus more could follow: cs/hu/ro/et/ru).
  • Added the missing device_tracker.rtk_position name to every language.

Entity names that ignored translations

Three entities used a hard-coded Polish _attr_name (which overrides translation_key):

Entity Was Now
Calendar "Harmonogram koszenia" translation_key = "schedule"
RTK map camera "Mapa RTK" translation_key = "rtk_map_camera"
RTK position tracker "Pozycja RTK" translation_key = "rtk_position"

Sensor states

status, error, mowing_readiness, cloud_connection and maintenance_status are now SensorDeviceClass.ENUM sensors emitting canonical keys, with localized labels under each sensor's state in every language. The original Polish wording is preserved in pl.json.

Free-text localized by UI language

The schedule sensor summary, the calendar event title/description, and the one-time mowing zone select options are free-form (not declarable in translations/*.json), so they now follow hass.config.language, with English fallback for languages without a schedule mapping. Weekday names included.

New / fixed sensors

  • Next schedule timestamp sensor (next mowing start). Prefers pyworxcloud's schedules["next_schedule_start"] and falls back to deriving it from the weekly slots.
  • Total area mowed sensor (lifetime total reported by the mower).
  • Today mowed area is now a real daily value (delta from a local-midnight baseline, persisted across restarts) instead of the lifetime total.
  • Daily progress / Remaining to mow now use today's area, so they change during the day (previously divided the lifetime total by the lawn size and were stuck at 100% / 0%).
  • Renamed the zone select to a shorter "Mowing zone".

Primary mower entity has no name (readability + third-party card compatibility)

The lawn_mower entity is the device's primary entity, so it now has no name of its own (_attr_name = None, the standard HA convention) instead of translation_key = "mower". With has_entity_name = True its friendly_name becomes exactly the device name.

This is both a readability improvement (no more redundant "Vision Cloud Mower" / "Vision Cloud Tondeuse") and a compatibility fix: cards such as landroid-card strip the device name from every other entity's label by matching the primary entity's friendly_name as the prefix (its getEntityName() helper). Previously that prefix included the extra "Mower"/"Tondeuse" word and never matched, so every label in info_card / statistics_card / battery_card kept showing the full "Vision Cloud " instead of just "". Removed the now-unused lawn_mower.mower.name translation key from every language.

Misc

  • Use non-deprecated device_tracker imports (TrackerEntity/SourceType from homeassistant.components.device_tracker).
  • README updated (new sensors, languages, entity naming, and a note that mowed-area figures are covered area and can exceed the lawn size).

⚠️ Behavior changes

  • Sensor state values change from localized text to canonical keys (e.g. koszeniemowing, displayed via translations). Automations/templates that compared the raw Polish text must compare the canonical key instead. This is the standard HA enum approach required for multi-language support.
  • The lawn_mower entity's displayed name changes from "<Device> Mower" to just "<Device>" (entity_id unchanged, nothing else affected).

Validation

  • All 10 languages have identical key structure (222 keys), valid JSON, no empty values.
  • python -m py_compile passes on all modified modules.
  • Verified live on a real Vision mower: localized names/states, French schedule days, correct Next schedule, Total/Today mowed area, and confirmed the landroid-card device-name stripping now works correctly.

- 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)
@ADNPolymerase ADNPolymerase changed the title Make entity names and sensor states translatable (i18n) Add French (fr) translation and make entity names/states translatable Jun 27, 2026
- 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
- New 'Next schedule' timestamp sensor exposing the next mowing start
  (drop-in replacement for the MTrab landroid_cloud entity)
- The mower's area_mowed is a lifetime total: expose it as a new
  'Total area mowed' sensor (state_class total_increasing)
- 'Area mowed today' is now a real daily value (delta from a midnight
  baseline, persisted across restarts via RestoreSensor)
- Fix 'Daily progress' / 'Remaining to mow' to use today's area instead
  of the lifetime total (previously always clamped to 100% / 0%)
- Add next_schedule + area_mowed_total names in en/pl/fr/de
- Weekday abbreviations (schedule sensor, calendar, attributes) now follow
  the HA language: en/de/fr/pl (e.g. German Mo/Di/Mi...), English fallback
- Localize the schedule sensor free-text fallbacks (+ edge / no active slots)
- Localize the hard-coded Polish calendar event title and description
  (Mowing / Day / Duration / Edge cutting / Source) per language
- Convert the schedule sensor to a class so it can read the UI language
Use schedules[next_schedule_start] from pyworxcloud (the authoritative,
upstream-maintained computation, 14-day lookahead) when present, and fall
back to deriving it from the weekly slots ourselves. No regression: the
fallback covers the case where the library value is missing or unparseable.
Import TrackerEntity and SourceType from homeassistant.components.device_tracker
instead of the deprecated .config_entry / .const aliases (removed in HA 2027.6).
- Mention next schedule sensor, today/total mowed area, FR/DE translations
- Add a Mowed area section clarifying that figures are covered area
  (overlapping passes), so they can exceed the lawn size
@ADNPolymerase ADNPolymerase changed the title Add French (fr) translation and make entity names/states translatable Add French + German, full i18n, new sensors and fixes Jun 30, 2026
Set _attr_name = None on the lawn_mower entity instead of translation_key
'mower'. It is the device's primary entity, so with has_entity_name=True its
friendly_name now becomes exactly the device name.

This also fixes third-party cards like landroid-card, which strip the device
name from other entities' friendly_name by matching the primary entity's
friendly_name as the device name prefix — previously that prefix included
' Mower'/' Tondeuse'/etc. and never matched, so every entity in info_card /
statistics_card / battery_card kept showing the full 'Vision Cloud <name>'.

Removed the now-unused lawn_mower.mower.name key from en/pl/fr/de.
Add an Entity naming section clarifying why the lawn_mower entity has
no name of its own: readability, and compatibility with cards like
landroid-card that strip the device name from other entities' labels.
Same structure and coverage as en/pl/fr/de (222 keys each, including
sensor states, calendar/schedule names, zone select). Natural wording,
matching the language coverage of the community landroid_cloud integration.
@ADNPolymerase ADNPolymerase changed the title Add French + German, full i18n, new sensors and fixes Add 8 new languages, full i18n, new sensors and fixes Jul 1, 2026
_device_map() returns the same DeviceHandler instances pyworxcloud already
holds, and _enrich_device() mutates them in place (product-item area_mowed,
firmware info, RTK map). With always_update=False the DataUpdateCoordinator
skips notifying listeners whenever the returned data compares equal to the
previous data — which is always true here, since the dict contains the same
object references before and after, regardless of what changed on them.

This silently prevented entities like area_mowed_total (and the daily
progress/remaining sensors derived from it) from ever refreshing outside of
MQTT push updates, which don't carry the REST-only product-item fields.
Confirmed live: pressing the refresh button did not update last_updated at
all before this fix, despite the coordinator's REST refresh path running.
_refresh_from_cloud_cache() previously swallowed all exceptions from
_enrich_device() via asyncio.gather(return_exceptions=True) with no
logging at all, making it impossible to tell whether the REST refresh
path (area_mowed, firmware, RTK map) was actually running, failing
silently, or genuinely returning stale data from Worx's own API.

Now logs a warning per device on failure, and a debug line with the
fetched area_mowed value on success, to diagnose why Total area mowed
appeared to only ever refresh at integration startup/reload.
area_mowed only refreshes when Worx's REST product-item endpoint reports
a new figure, which can lag for hours during active mowing (confirmed:
the endpoint call succeeds and Worx genuinely returns the same stale
value for hours). Add a new sensor that estimates today's coverage from
today's work time (live via MQTT statistics, delta from a local-midnight
baseline like the other daily sensors) multiplied by the mower's average
mowing efficiency (m2/h). This moves during the day even when Total/Today
area mowed are stuck waiting for Worx to recompute the real figure.

Also factor out _work_time_total_minutes() (statistics-preferring, REST
fallback) instead of duplicating that logic inline in mower_runtime_total.
_mowing_efficiency() deliberately keeps using the REST-only work_time to
stay paired with the REST-only area_mowed_total from the same snapshot.

Translation added to all 10 languages (224 keys each, validated).
Confirmed live: pressing the Refresh button asks the mower to publish a
fresh MQTT payload (including work-time statistics), which updates the
estimated area/progress sensors, but passive push updates during active
mowing do not reliably include those statistics fields. Manually
clicking Refresh every time is impractical, so the coordinator now asks
every known device to report in automatically every 5 minutes
(LIVE_REFRESH_INTERVAL), same effect as the button, with proper
subscribe/unsubscribe on setup/shutdown.

Also add 'Estimated daily progress': same today's-runtime x average-
efficiency estimate as Estimated area mowed today, expressed as a
percentage of the known lawn area, so progress is visible during the
day even when the REST-based daily progress sensor is stuck.

Translation added to all 10 languages (226 keys each, validated).
The Last update timestamp changes on every MQTT push (as often as every
~20 seconds), which made Home Assistant's logbook narrate a 'changed'
entry that often for essentially no user value. Replace the generic
STANDARD_SENSORS entry with a dedicated WorxLastUpdateSensor that only
accepts a new value once per LAST_UPDATE_REPORT_INTERVAL (24h) and holds
the previous one otherwise, restoring the accepted value/timestamp across
restarts via RestoreSensor. The logbook now only sees one real change per
day for this entity, while last_update_age (minutes since last update)
is unaffected and still reflects live data.
Confirmed live on a real Vision mower: Worx's work-time statistics
(device.statistics / product-item) can stay frozen for 20+ minutes of
continuous mowing despite the periodic forced refresh, so basing the
estimated area/progress sensors on that figure inherited the same
staleness problem they were meant to work around.

Replace _WorxDailyRuntimeBase with _WorxDailyMowingTimeBase, which
tracks wall-clock time spent in the mowing/starting status ourselves
(accumulated seconds + an in-progress streak start time, both persisted
via RestoreSensor), independent of whether Worx reports fresh work-time
data. Every coordinator update (MQTT push, ~every 20s) recomputes this,
so the estimate now genuinely moves during active mowing.

Move MOWING_STATUS_IDS/STARTING_STATUS_IDS to helpers.py as the single
source of truth, imported by both lawn_mower.py and sensor.py, so the
new accumulator always agrees with what the lawn_mower entity shows.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant