Skip to content

Commit 3e36d76

Browse files
authored
Add missing translation strings for all translatable entities (#377)
* Add missing translation strings for all translatable entities Add translation entries for sensor.state, binary_sensor.fault, and switch.rain_delay. Also add translation_key to rain delay switch description and fix _attr_name init order to prevent platform_data resolution error. * Add dynamic translation strings for zone and program entities Use translation_placeholders for entities with dynamic names: - sensor.zone_history: "{zone_name} zone history" - switch.program: "{program_name} program" - valve.zone: "{zone_name} zone" Also add has_entity_name to BHyveZoneValve and BHyveZoneHistorySensor, and avoid accessing self.name in base __init__ to prevent platform_data resolution errors during entity construction. * Remove unused args from create_program_switch_description test helper * Set _attr_name as fallback for dynamic translation entities HA falls back to device_class name (e.g. "Timestamp") when translation placeholders can't be resolved at runtime. Set _attr_name explicitly so the entity always has a readable name, while keeping translation_key and translation_placeholders for proper i18n support. * Fix duplicated device name in zone history and valve entities Remove has_entity_name from BHyveZoneHistorySensor and BHyveZoneValve. These entities include the zone name in their entity name, which gets duplicated with the device name when has_entity_name is True. Since translation_key requires has_entity_name, also remove the unused translation_key and strings.json entries for these entities. * Fix duplicated device name in zone entity names When zone_name matches the device name (single-zone devices), use a short entity name ("Zone", "Zone history") since has_entity_name already prepends the device name. Multi-zone devices keep the zone name prefix to distinguish between zones. * Remove redundant program_name arg from BHyveProgramSwitch Extract program name directly from the program dict in __init__ instead of requiring it as a separate argument.
1 parent 589394e commit 3e36d76

8 files changed

Lines changed: 76 additions & 47 deletions

File tree

custom_components/bhyve/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ def __init__(
197197
"Creating %s: %s - %s",
198198
self.__class__.__name__,
199199
self._device_name,
200-
self.name,
200+
getattr(self, "_attr_name", None) or self._device_name,
201201
)
202202

203203
@property

custom_components/bhyve/sensor.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,9 +211,10 @@ async def async_setup_entry(
211211
coordinator,
212212
device,
213213
zone,
214+
zone_name,
214215
SensorEntityDescription(
215216
key="zone_history",
216-
name=f"{zone_name} zone history",
217+
translation_key="zone_history",
217218
icon="mdi:history",
218219
device_class=SensorDeviceClass.TIMESTAMP,
219220
entity_category=EntityCategory.DIAGNOSTIC,
@@ -338,16 +339,23 @@ class BHyveZoneHistorySensor(BHyveCoordinatorEntity, SensorEntity):
338339
"""Define a BHyve zone history sensor."""
339340

340341
entity_description: SensorEntityDescription
342+
_attr_has_entity_name = True
341343

342344
def __init__(
343345
self,
344346
coordinator: BHyveDataUpdateCoordinator,
345347
device: BHyveDevice,
346348
zone: dict,
349+
zone_name: str,
347350
description: SensorEntityDescription,
348351
) -> None:
349352
"""Initialize the sensor."""
350353
self.entity_description = description
354+
if zone_name == device.get("name"):
355+
self._attr_name = "Zone history"
356+
else:
357+
self._attr_name = f"{zone_name} zone history"
358+
self._attr_translation_placeholders = {"zone_name": zone_name}
351359
super().__init__(coordinator, device)
352360

353361
self._zone = zone

custom_components/bhyve/strings.json

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,12 +147,25 @@
147147
"temperature_alert": {
148148
"name": "Temperature alert",
149149
"state": {
150-
"on": "Problem detected",
150+
"on": "Problem",
151+
"off": "OK"
152+
}
153+
},
154+
"fault": {
155+
"name": "Fault",
156+
"state": {
157+
"on": "Fault",
151158
"off": "OK"
152159
}
153160
}
154161
},
155162
"sensor": {
163+
"state": {
164+
"name": "State"
165+
},
166+
"zone_history": {
167+
"name": "{zone_name} zone history"
168+
},
156169
"temperature": {
157170
"name": "Temperature"
158171
},
@@ -162,6 +175,19 @@
162175
"signal_strength": {
163176
"name": "Signal strength"
164177
}
178+
},
179+
"switch": {
180+
"rain_delay": {
181+
"name": "Rain delay"
182+
},
183+
"program": {
184+
"name": "{program_name} program"
185+
}
186+
},
187+
"valve": {
188+
"zone": {
189+
"name": "{zone_name} zone"
190+
}
165191
}
166192
}
167193
}

custom_components/bhyve/switch.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,14 +78,13 @@ def _create_program_switch(
7878
program: BHyveTimerProgram,
7979
) -> BHyveProgramSwitch:
8080
"""Create a program switch entity."""
81-
program_name = program.get("name", "unknown")
8281
return BHyveProgramSwitch(
8382
coordinator,
8483
device,
8584
program,
8685
BHyveSwitchEntityDescription(
8786
key="program",
88-
name=f"{program_name} program",
87+
translation_key="program",
8988
icon="mdi:bulletin-board",
9089
entity_category=EntityCategory.CONFIG,
9190
),
@@ -123,6 +122,7 @@ async def async_setup_entry(
123122
device,
124123
BHyveSwitchEntityDescription(
125124
key="rain_delay",
125+
translation_key="rain_delay",
126126
name="Rain delay",
127127
icon="mdi:weather-pouring",
128128
entity_category=EntityCategory.CONFIG,
@@ -182,7 +182,9 @@ def __init__(
182182
) -> None:
183183
"""Initialize the switch."""
184184
self.entity_description = description
185-
self._attr_name = description.name
185+
program_name = program.get("name", "unknown")
186+
self._attr_name = f"{program_name} program"
187+
self._attr_translation_placeholders = {"program_name": program_name}
186188

187189
super().__init__(coordinator, device)
188190

@@ -267,10 +269,10 @@ def __init__(
267269
) -> None:
268270
"""Initialize the switch."""
269271
self.entity_description = description
272+
self._attr_name = description.name
270273

271274
super().__init__(coordinator, device)
272275

273-
self._attr_name = description.name
274276
self._attr_unique_id = f"{self._mac_address}:{self._device_id}:rain_delay"
275277

276278
@property

custom_components/bhyve/valve.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ class BHyveValveEntityDescription(ValveEntityDescription):
142142

143143
VALVE_DESCRIPTION = BHyveValveEntityDescription(
144144
key="zone",
145+
translation_key="zone",
145146
device_class=ValveDeviceClass.WATER,
146147
reports_position=False,
147148
)
@@ -233,6 +234,7 @@ class BHyveZoneValve(BHyveCoordinatorEntity, ValveEntity):
233234
"""Define a BHyve zone valve."""
234235

235236
entity_description: BHyveValveEntityDescription
237+
_attr_has_entity_name = True
236238
_attr_supported_features = ValveEntityFeature.OPEN | ValveEntityFeature.CLOSE
237239

238240
def __init__(
@@ -245,7 +247,11 @@ def __init__(
245247
) -> None:
246248
"""Initialize the valve."""
247249
self.entity_description = VALVE_DESCRIPTION
248-
self._attr_name = f"{zone_name} zone"
250+
if zone_name == device.get("name"):
251+
self._attr_name = "Zone"
252+
else:
253+
self._attr_name = f"{zone_name} zone"
254+
self._attr_translation_placeholders = {"zone_name": zone_name}
249255

250256
super().__init__(coordinator, device)
251257

tests/test_sensor.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -394,7 +394,7 @@ async def test_zone_history_sensor_initialization(
394394
# Create description for the zone
395395
description = SensorEntityDescription(
396396
key="zone_history",
397-
name="Front Lawn zone history",
397+
translation_key="zone_history",
398398
icon="mdi:history",
399399
device_class=SensorDeviceClass.TIMESTAMP,
400400
entity_category=EntityCategory.DIAGNOSTIC,
@@ -404,11 +404,13 @@ async def test_zone_history_sensor_initialization(
404404
coordinator=coordinator,
405405
device=mock_sprinkler_device_with_battery,
406406
zone=zone,
407+
zone_name="Front Lawn",
407408
description=description,
408409
)
409410

410411
# Test basic properties
411-
assert sensor.name == "Front Lawn zone history"
412+
assert sensor._attr_name == "Front Lawn zone history"
413+
assert sensor._attr_translation_placeholders == {"zone_name": "Front Lawn"}
412414
assert sensor.device_class == SensorDeviceClass.TIMESTAMP
413415
assert sensor.entity_description.entity_category == EntityCategory.DIAGNOSTIC
414416

@@ -433,7 +435,7 @@ async def test_zone_history_sensor_attributes(
433435
# Create description for the zone
434436
description = SensorEntityDescription(
435437
key="zone_history",
436-
name="Front Lawn zone history",
438+
translation_key="zone_history",
437439
icon="mdi:history",
438440
device_class=SensorDeviceClass.TIMESTAMP,
439441
entity_category=EntityCategory.DIAGNOSTIC,
@@ -443,6 +445,7 @@ async def test_zone_history_sensor_attributes(
443445
coordinator=coordinator,
444446
device=mock_sprinkler_device_with_battery,
445447
zone=zone,
448+
zone_name="Front Lawn",
446449
description=description,
447450
)
448451

tests/test_switch.py

Lines changed: 18 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -40,14 +40,11 @@ def create_mock_coordinator(devices: dict, programs: dict) -> MagicMock:
4040
return coordinator
4141

4242

43-
def create_program_switch_description(
44-
_device: BHyveDevice, program: BHyveTimerProgram
45-
) -> BHyveSwitchEntityDescription:
43+
def create_program_switch_description() -> BHyveSwitchEntityDescription:
4644
"""Create a program switch description for testing."""
47-
program_name = program.get("name", "unknown")
4845
return BHyveSwitchEntityDescription(
4946
key="program",
50-
name=f"{program_name} program",
47+
translation_key="program",
5148
icon="mdi:bulletin-board",
5249
device_class=SwitchDeviceClass.SWITCH,
5350
entity_category=EntityCategory.CONFIG,
@@ -318,9 +315,7 @@ async def test_program_switch_initialization(
318315
mock_coordinator: MagicMock,
319316
) -> None:
320317
"""Test program switch entity initialization."""
321-
description = create_program_switch_description(
322-
mock_sprinkler_device, mock_timer_program
323-
)
318+
description = create_program_switch_description()
324319
switch = BHyveProgramSwitch(
325320
coordinator=mock_coordinator,
326321
device=mock_sprinkler_device,
@@ -329,7 +324,9 @@ async def test_program_switch_initialization(
329324
)
330325

331326
# Test basic properties
332-
assert switch.name == "Morning Schedule program"
327+
assert switch._attr_translation_placeholders == {
328+
"program_name": "Morning Schedule"
329+
}
333330
assert switch.device_class == SwitchDeviceClass.SWITCH
334331
assert switch.entity_category == EntityCategory.CONFIG
335332
assert switch.unique_id == f"bhyve:program:{TEST_PROGRAM_ID}"
@@ -341,9 +338,7 @@ async def test_program_switch_enabled_state(
341338
mock_coordinator: MagicMock,
342339
) -> None:
343340
"""Test program switch in enabled state."""
344-
description = create_program_switch_description(
345-
mock_sprinkler_device, mock_timer_program
346-
)
341+
description = create_program_switch_description()
347342
switch = BHyveProgramSwitch(
348343
coordinator=mock_coordinator,
349344
device=mock_sprinkler_device,
@@ -380,9 +375,7 @@ async def test_program_switch_disabled_state(
380375
{TEST_PROGRAM_ID: mock_timer_program_disabled},
381376
)
382377

383-
description = create_program_switch_description(
384-
mock_sprinkler_device, mock_timer_program_disabled
385-
)
378+
description = create_program_switch_description()
386379
switch = BHyveProgramSwitch(
387380
coordinator=coordinator,
388381
device=mock_sprinkler_device,
@@ -411,9 +404,7 @@ async def test_program_switch_turn_on(
411404
{TEST_PROGRAM_ID: mock_timer_program_disabled},
412405
)
413406

414-
description = create_program_switch_description(
415-
mock_sprinkler_device, mock_timer_program_disabled
416-
)
407+
description = create_program_switch_description()
417408
switch = BHyveProgramSwitch(
418409
coordinator=coordinator,
419410
device=mock_sprinkler_device,
@@ -437,9 +428,7 @@ async def test_program_switch_turn_off(
437428
mock_coordinator: MagicMock,
438429
) -> None:
439430
"""Test turning off a program switch."""
440-
description = create_program_switch_description(
441-
mock_sprinkler_device, mock_timer_program
442-
)
431+
description = create_program_switch_description()
443432
switch = BHyveProgramSwitch(
444433
coordinator=mock_coordinator,
445434
device=mock_sprinkler_device,
@@ -463,9 +452,7 @@ async def test_program_switch_start_program(
463452
mock_coordinator: MagicMock,
464453
) -> None:
465454
"""Test starting a program manually."""
466-
description = create_program_switch_description(
467-
mock_sprinkler_device, mock_timer_program
468-
)
455+
description = create_program_switch_description()
469456
switch = BHyveProgramSwitch(
470457
coordinator=mock_coordinator,
471458
device=mock_sprinkler_device,
@@ -491,9 +478,7 @@ async def test_program_switch_websocket_event(
491478
mock_coordinator: MagicMock,
492479
) -> None:
493480
"""Test program switch response to websocket events."""
494-
description = create_program_switch_description(
495-
mock_sprinkler_device, mock_timer_program
496-
)
481+
description = create_program_switch_description()
497482
switch = BHyveProgramSwitch(
498483
coordinator=mock_coordinator,
499484
device=mock_sprinkler_device,
@@ -517,9 +502,7 @@ async def test_program_switch_event_filtering(
517502
mock_coordinator: MagicMock,
518503
) -> None:
519504
"""Test program switch handles all events via coordinator."""
520-
description = create_program_switch_description(
521-
mock_sprinkler_device, mock_timer_program
522-
)
505+
description = create_program_switch_description()
523506
switch = BHyveProgramSwitch(
524507
coordinator=mock_coordinator,
525508
device=mock_sprinkler_device,
@@ -846,7 +829,9 @@ def track_add_entities(entities: list) -> None:
846829
assert len(all_added_entities[1]) == 1
847830
new_entity = all_added_entities[1][0]
848831
assert isinstance(new_entity, BHyveProgramSwitch)
849-
assert new_entity.name == "New Evening Schedule program"
832+
assert new_entity._attr_translation_placeholders == {
833+
"program_name": "New Evening Schedule"
834+
}
850835

851836
async def test_program_created_event_unknown_device(
852837
self,
@@ -945,7 +930,7 @@ async def test_switches_handle_missing_data_gracefully(
945930
description=description,
946931
)
947932

948-
description = create_program_switch_description(minimal_device, minimal_program)
933+
description = create_program_switch_description()
949934
program_switch = BHyveProgramSwitch(
950935
coordinator=coordinator,
951936
device=minimal_device,
@@ -966,9 +951,7 @@ async def test_switches_handle_websocket_events_without_data(
966951
mock_coordinator: MagicMock,
967952
) -> None:
968953
"""Test switches handle websocket events with missing fields."""
969-
description = create_program_switch_description(
970-
mock_sprinkler_device, mock_timer_program
971-
)
954+
description = create_program_switch_description()
972955
program_switch = BHyveProgramSwitch(
973956
coordinator=mock_coordinator,
974957
device=mock_sprinkler_device,

tests/test_valve.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,8 @@ async def test_zone_valve_initialization(
7070
)
7171

7272
# Test basic properties
73-
assert valve.name == f"{zone_name} zone"
73+
assert valve._attr_name == f"{zone_name} zone"
74+
assert valve._attr_translation_placeholders == {"zone_name": zone_name}
7475
expected_unique_id = (
7576
f"{mock_sprinkler_device['mac_address']}:"
7677
f"{mock_sprinkler_device['id']}:"

0 commit comments

Comments
 (0)