Fix Matter translation key not set for primary entities (#161708)

This commit is contained in:
TheJulianJES
2026-02-16 11:38:59 +01:00
committed by GitHub
parent e48bd88581
commit 63f4653a3b
11 changed files with 243 additions and 58 deletions

View File

@@ -124,8 +124,13 @@ class MatterEntity(Entity):
and ep.has_attribute(None, entity_info.primary_attribute)
):
self._name_postfix = str(self._endpoint.endpoint_id)
if self._platform_translation_key and not self.translation_key:
self._attr_translation_key = self._platform_translation_key
# Always set translation_key for state_attributes translations.
# For primary entities (no postfix), suppress the translated name,
# so only the device name is used.
if self._platform_translation_key and not self.translation_key:
self._attr_translation_key = self._platform_translation_key
if not self._name_postfix:
self._attr_name = None
# Matter labels can be used to modify the entity name
# by appending the text.

View File

@@ -37,7 +37,7 @@
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': <ClimateEntityFeature: 385>,
'translation_key': None,
'translation_key': 'thermostat',
'unique_id': '00000000000004D2-0000000000000064-MatterNodeDevice-1-MatterThermostat-513-0',
'unit_of_measurement': None,
})
@@ -102,7 +102,7 @@
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': <ClimateEntityFeature: 385>,
'translation_key': None,
'translation_key': 'thermostat',
'unique_id': '00000000000004D2-0000000000000014-MatterNodeDevice-1-MatterThermostat-513-0',
'unit_of_measurement': None,
})
@@ -167,7 +167,7 @@
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': <ClimateEntityFeature: 385>,
'translation_key': None,
'translation_key': 'thermostat',
'unique_id': '00000000000004D2-0000000000000021-MatterNodeDevice-1-MatterThermostat-513-0',
'unit_of_measurement': None,
})
@@ -232,7 +232,7 @@
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': <ClimateEntityFeature: 385>,
'translation_key': None,
'translation_key': 'thermostat',
'unique_id': '00000000000004D2-000000000000000C-MatterNodeDevice-1-MatterThermostat-513-0',
'unit_of_measurement': None,
})
@@ -299,7 +299,7 @@
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': <ClimateEntityFeature: 387>,
'translation_key': None,
'translation_key': 'thermostat',
'unique_id': '00000000000004D2-0000000000000004-MatterNodeDevice-1-MatterThermostat-513-0',
'unit_of_measurement': None,
})
@@ -368,7 +368,7 @@
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': <ClimateEntityFeature: 385>,
'translation_key': None,
'translation_key': 'thermostat',
'unique_id': '00000000000004D2-000000000000008F-MatterNodeDevice-5-MatterThermostat-513-0',
'unit_of_measurement': None,
})
@@ -437,7 +437,7 @@
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': <ClimateEntityFeature: 385>,
'translation_key': None,
'translation_key': 'thermostat',
'unique_id': '00000000000004D2-0000000000000024-MatterNodeDevice-1-MatterThermostat-513-0',
'unit_of_measurement': None,
})
@@ -508,7 +508,7 @@
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': <ClimateEntityFeature: 387>,
'translation_key': None,
'translation_key': 'thermostat',
'unique_id': '00000000000004D2-0000000000000096-MatterNodeDevice-1-MatterThermostat-513-0',
'unit_of_measurement': None,
})
@@ -580,7 +580,7 @@
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': <ClimateEntityFeature: 385>,
'translation_key': None,
'translation_key': 'thermostat',
'unique_id': '00000000000004D2-0000000000000004-MatterNodeDevice-1-MatterThermostat-513-0',
'unit_of_measurement': None,
})
@@ -647,7 +647,7 @@
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': <ClimateEntityFeature: 385>,
'translation_key': None,
'translation_key': 'thermostat',
'unique_id': '00000000000004D2-000000000000000C-MatterNodeDevice-1-MatterThermostat-513-0',
'unit_of_measurement': None,
})

View File

@@ -37,7 +37,7 @@
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': <FanEntityFeature: 56>,
'translation_key': None,
'translation_key': 'fan',
'unique_id': '00000000000004D2-0000000000000004-MatterNodeDevice-1-MatterFan-514-0',
'unit_of_measurement': None,
})
@@ -103,7 +103,7 @@
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': <FanEntityFeature: 63>,
'translation_key': None,
'translation_key': 'fan',
'unique_id': '00000000000004D2-000000000000008F-MatterNodeDevice-1-MatterFan-514-0',
'unit_of_measurement': None,
})
@@ -172,7 +172,7 @@
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': <FanEntityFeature: 56>,
'translation_key': None,
'translation_key': 'fan',
'unique_id': '00000000000004D2-0000000000000049-MatterNodeDevice-1-MatterFan-514-0',
'unit_of_measurement': None,
})
@@ -237,7 +237,7 @@
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': <FanEntityFeature: 57>,
'translation_key': None,
'translation_key': 'fan',
'unique_id': '00000000000004D2-000000000000001D-MatterNodeDevice-1-MatterFan-514-0',
'unit_of_measurement': None,
})
@@ -306,7 +306,7 @@
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': <FanEntityFeature: 57>,
'translation_key': None,
'translation_key': 'fan',
'unique_id': '00000000000004D2-0000000000000024-MatterNodeDevice-1-MatterFan-514-0',
'unit_of_measurement': None,
})

View File

@@ -36,7 +36,7 @@
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': <LightEntityFeature: 32>,
'translation_key': None,
'translation_key': 'light',
'unique_id': '00000000000004D2-0000000000000001-MatterNodeDevice-1-MatterLight-6-0',
'unit_of_measurement': None,
})
@@ -115,7 +115,7 @@
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': <LightEntityFeature: 32>,
'translation_key': None,
'translation_key': 'light',
'unique_id': '00000000000004D2-0000000000000001-MatterNodeDevice-1-MatterLight-6-0',
'unit_of_measurement': None,
})
@@ -393,7 +393,7 @@
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': <LightEntityFeature: 32>,
'translation_key': None,
'translation_key': 'light',
'unique_id': '00000000000004D2-0000000000000001-MatterNodeDevice-1-MatterLight-6-0',
'unit_of_measurement': None,
})
@@ -452,7 +452,7 @@
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': <LightEntityFeature: 32>,
'translation_key': None,
'translation_key': 'light',
'unique_id': '00000000000004D2-0000000000000024-MatterNodeDevice-1-MatterLight-6-0',
'unit_of_measurement': None,
})
@@ -511,7 +511,7 @@
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': <LightEntityFeature: 32>,
'translation_key': None,
'translation_key': 'light',
'unique_id': '00000000000004D2-000000000000000E-MatterNodeDevice-1-MatterLight-6-0',
'unit_of_measurement': None,
})
@@ -568,7 +568,7 @@
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': None,
'translation_key': 'light',
'unique_id': '00000000000004D2-0000000000000001-MatterNodeDevice-1-MatterLight-6-0',
'unit_of_measurement': None,
})
@@ -630,7 +630,7 @@
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': <LightEntityFeature: 32>,
'translation_key': None,
'translation_key': 'light',
'unique_id': '00000000000004D2-0000000000000001-MatterNodeDevice-1-MatterLight-6-0',
'unit_of_measurement': None,
})
@@ -701,7 +701,7 @@
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': <LightEntityFeature: 32>,
'translation_key': None,
'translation_key': 'light',
'unique_id': '00000000000004D2-0000000000000001-MatterNodeDevice-1-MatterLight-6-0',
'unit_of_measurement': None,
})
@@ -768,7 +768,7 @@
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': None,
'translation_key': 'light',
'unique_id': '00000000000004D2-0000000000000008-MatterNodeDevice-1-MatterLight-6-0',
'unit_of_measurement': None,
})

View File

@@ -30,7 +30,7 @@
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': None,
'translation_key': 'lock',
'unique_id': '00000000000004D2-0000000000000014-MatterNodeDevice-1-MatterLock-257-0',
'unit_of_measurement': None,
})
@@ -81,7 +81,7 @@
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': None,
'translation_key': 'lock',
'unique_id': '00000000000004D2-0000000000000001-MatterNodeDevice-1-MatterLock-257-0',
'unit_of_measurement': None,
})
@@ -132,7 +132,7 @@
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': <LockEntityFeature: 1>,
'translation_key': None,
'translation_key': 'lock',
'unique_id': '00000000000004D2-0000000000000001-MatterNodeDevice-1-MatterLock-257-0',
'unit_of_measurement': None,
})
@@ -183,7 +183,7 @@
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': <LockEntityFeature: 1>,
'translation_key': None,
'translation_key': 'lock',
'unique_id': '00000000000004D2-0000000000000097-MatterNodeDevice-1-MatterLock-257-0',
'unit_of_measurement': None,
})
@@ -234,7 +234,7 @@
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': None,
'translation_key': 'lock',
'unique_id': '00000000000004D2-0000000000000002-MatterNodeDevice-1-MatterLock-257-0',
'unit_of_measurement': None,
})

View File

@@ -130,7 +130,7 @@
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': None,
'translation_key': 'switch',
'unique_id': '00000000000004D2-0000000000000053-MatterNodeDevice-1-MatterPlug-6-0',
'unit_of_measurement': None,
})
@@ -180,7 +180,7 @@
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': None,
'translation_key': 'switch',
'unique_id': '00000000000004D2-00000000000000B7-MatterNodeDevice-1-MatterPlug-6-0',
'unit_of_measurement': None,
})
@@ -328,7 +328,7 @@
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': None,
'translation_key': 'switch',
'unique_id': '00000000000004D2-0000000000000025-MatterNodeDevice-1-MatterSwitch-6-0',
'unit_of_measurement': None,
})
@@ -428,7 +428,7 @@
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': None,
'translation_key': 'switch',
'unique_id': '00000000000004D2-0000000000000004-MatterNodeDevice-1-MatterSwitch-6-0',
'unit_of_measurement': None,
})
@@ -578,7 +578,7 @@
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': None,
'translation_key': 'switch',
'unique_id': '00000000000004D2-0000000000000001-MatterNodeDevice-1-MatterSwitch-6-0',
'unit_of_measurement': None,
})
@@ -677,7 +677,7 @@
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': None,
'translation_key': 'switch',
'unique_id': '00000000000004D2-0000000000000001-MatterNodeDevice-1-MatterSwitch-6-0',
'unit_of_measurement': None,
})
@@ -875,7 +875,7 @@
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': None,
'translation_key': 'switch',
'unique_id': '00000000000004D2-0000000000000001-MatterNodeDevice-1-MatterPlug-6-0',
'unit_of_measurement': None,
})
@@ -1174,7 +1174,7 @@
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': None,
'translation_key': 'switch',
'unique_id': '00000000000004D2-0000000000000001-MatterNodeDevice-1-MatterSwitch-6-0',
'unit_of_measurement': None,
})
@@ -1323,7 +1323,7 @@
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': None,
'translation_key': 'switch',
'unique_id': '00000000000004D2-0000000000000004-MatterNodeDevice-1-MatterPlug-6-0',
'unit_of_measurement': None,
})

View File

@@ -30,7 +30,7 @@
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': <VacuumEntityFeature: 12828>,
'translation_key': None,
'translation_key': 'vacuum',
'unique_id': '00000000000004D2-000000000000002F-MatterNodeDevice-1-MatterVacuumCleaner-84-1',
'unit_of_measurement': None,
})
@@ -80,7 +80,7 @@
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': <VacuumEntityFeature: 12828>,
'translation_key': None,
'translation_key': 'vacuum',
'unique_id': '00000000000004D2-0000000000000028-MatterNodeDevice-1-MatterVacuumCleaner-84-1',
'unit_of_measurement': None,
})
@@ -130,7 +130,7 @@
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': <VacuumEntityFeature: 12316>,
'translation_key': None,
'translation_key': 'vacuum',
'unique_id': '00000000000004D2-0000000000000042-MatterNodeDevice-1-MatterVacuumCleaner-84-1',
'unit_of_measurement': None,
})
@@ -180,7 +180,7 @@
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': <VacuumEntityFeature: 12316>,
'translation_key': None,
'translation_key': 'vacuum',
'unique_id': '00000000000004D2-0000000000000061-MatterNodeDevice-1-MatterVacuumCleaner-84-1',
'unit_of_measurement': None,
})

View File

@@ -30,7 +30,7 @@
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': <ValveEntityFeature: 3>,
'translation_key': None,
'translation_key': 'valve',
'unique_id': '00000000000004D2-000000000000004B-MatterNodeDevice-1-MatterValve-129-4',
'unit_of_measurement': None,
})

View File

@@ -38,7 +38,7 @@
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': <WaterHeaterEntityFeature: 11>,
'translation_key': None,
'translation_key': 'water_heater',
'unique_id': '00000000000004D2-0000000000000019-MatterNodeDevice-2-MatterWaterHeater-513-18',
'unit_of_measurement': None,
})

View File

@@ -155,18 +155,6 @@ async def test_device_registry_single_node_composed_device(
assert len(device_registry.devices) == 1
@pytest.mark.usefixtures("matter_node")
@pytest.mark.parametrize("node_fixture", ["inovelli_vtm31"])
async def test_multi_endpoint_name(hass: HomeAssistant) -> None:
"""Test that the entity name gets postfixed if the device has multiple primary endpoints."""
entity_state = hass.states.get("light.inovelli_light_1")
assert entity_state
assert entity_state.name == "Inovelli Light (1)"
entity_state = hass.states.get("light.inovelli_light_6")
assert entity_state
assert entity_state.name == "Inovelli Light (6)"
async def test_get_clean_name() -> None:
"""Test get_clean_name helper.

View File

@@ -0,0 +1,192 @@
"""Test Matter entity behavior."""
from __future__ import annotations
import pytest
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
@pytest.mark.usefixtures("matter_node")
@pytest.mark.parametrize(
("node_fixture", "entity_id", "expected_translation_key", "expected_name"),
[
("mock_onoff_light", "light.mock_onoff_light", "light", "Mock OnOff Light"),
("mock_door_lock", "lock.mock_door_lock", "lock", "Mock Door Lock"),
("mock_thermostat", "climate.mock_thermostat", "thermostat", "Mock Thermostat"),
("mock_valve", "valve.mock_valve", "valve", "Mock Valve"),
("mock_fan", "fan.mocked_fan_switch", "fan", "Mocked Fan Switch"),
("eve_energy_plug", "switch.eve_energy_plug", "switch", "Eve Energy Plug"),
("mock_vacuum_cleaner", "vacuum.mock_vacuum", "vacuum", "Mock Vacuum"),
(
"silabs_water_heater",
"water_heater.water_heater",
"water_heater",
"Water Heater",
),
],
)
async def test_single_endpoint_platform_translation_key(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
entity_id: str,
expected_translation_key: str,
expected_name: str,
) -> None:
"""Test single-endpoint entities on platforms with _platform_translation_key.
The translation key must always be present for state_attributes translations
and icon translations. When there is no endpoint postfix, the entity name
should be suppressed (None) so only the device name is displayed.
"""
entry = entity_registry.async_get(entity_id)
assert entry is not None
assert entry.translation_key == expected_translation_key
# No original_name means the entity name is suppressed,
# so only the device name is shown
assert entry.original_name is None
state = hass.states.get(entity_id)
assert state is not None
# The friendly name should be just the device name (no entity name appended)
assert state.name == expected_name
@pytest.mark.usefixtures("matter_node")
@pytest.mark.parametrize("node_fixture", ["inovelli_vtm31"])
async def test_multi_endpoint_entity_translation_key(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
) -> None:
"""Test that multi-endpoint entities have a translation key and a name postfix.
When a device has the same primary attribute on multiple endpoints,
the entity name gets postfixed with the endpoint ID. The translation key
must still always be set for translations.
"""
# Endpoint 1
entry_1 = entity_registry.async_get("light.inovelli_light_1")
assert entry_1 is not None
assert entry_1.translation_key == "light"
assert entry_1.original_name == "Light (1)"
state_1 = hass.states.get("light.inovelli_light_1")
assert state_1 is not None
assert state_1.name == "Inovelli Light (1)"
# Endpoint 6
entry_6 = entity_registry.async_get("light.inovelli_light_6")
assert entry_6 is not None
assert entry_6.translation_key == "light"
assert entry_6.original_name == "Light (6)"
state_6 = hass.states.get("light.inovelli_light_6")
assert state_6 is not None
assert state_6.name == "Inovelli Light (6)"
@pytest.mark.usefixtures("matter_node")
@pytest.mark.parametrize("node_fixture", ["eve_energy_20ecn4101"])
async def test_label_modified_entity_translation_key(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
) -> None:
"""Test that label-modified entities have a translation key and a label postfix.
When a device uses Matter labels to differentiate endpoints,
the entity name gets the label as a postfix. The translation key
must still always be set for translations.
"""
# Top outlet
entry_top = entity_registry.async_get("switch.eve_energy_20ecn4101_switch_top")
assert entry_top is not None
assert entry_top.translation_key == "switch"
assert entry_top.original_name == "Switch (top)"
state_top = hass.states.get("switch.eve_energy_20ecn4101_switch_top")
assert state_top is not None
assert state_top.name == "Eve Energy 20ECN4101 Switch (top)"
# Bottom outlet
entry_bottom = entity_registry.async_get(
"switch.eve_energy_20ecn4101_switch_bottom"
)
assert entry_bottom is not None
assert entry_bottom.translation_key == "switch"
assert entry_bottom.original_name == "Switch (bottom)"
state_bottom = hass.states.get("switch.eve_energy_20ecn4101_switch_bottom")
assert state_bottom is not None
assert state_bottom.name == "Eve Energy 20ECN4101 Switch (bottom)"
@pytest.mark.usefixtures("matter_node")
@pytest.mark.parametrize("node_fixture", ["eve_thermo_v4"])
async def test_description_translation_key_not_overridden(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
) -> None:
"""Test that a description-level translation key is not overridden.
When an entity description already sets translation_key (e.g. "child_lock"),
the _platform_translation_key logic should not override it. The entity keeps
its description-level translation key and name.
"""
entry = entity_registry.async_get("switch.eve_thermo_20ebp1701_child_lock")
assert entry is not None
# The description-level translation key should be preserved, not overridden
# by _platform_translation_key ("switch")
assert entry.translation_key == "child_lock"
assert entry.original_name == "Child lock"
state = hass.states.get("switch.eve_thermo_20ebp1701_child_lock")
assert state is not None
assert state.name == "Eve Thermo 20EBP1701 Child lock"
@pytest.mark.usefixtures("matter_node")
@pytest.mark.parametrize("node_fixture", ["air_quality_sensor"])
async def test_entity_name_from_description_translation_key(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
) -> None:
"""Test entity name derived from an explicit description translation key.
Sensor entities do not set _platform_translation_key on the platform class.
When the entity description sets translation_key explicitly, the entity name
is derived from that translation key.
"""
entry = entity_registry.async_get(
"sensor.lightfi_aq1_air_quality_sensor_air_quality"
)
assert entry is not None
assert entry.translation_key == "air_quality"
assert entry.original_name == "Air quality"
state = hass.states.get("sensor.lightfi_aq1_air_quality_sensor_air_quality")
assert state is not None
assert state.name == "lightfi-aq1-air-quality-sensor Air quality"
@pytest.mark.usefixtures("matter_node")
@pytest.mark.parametrize("node_fixture", ["mock_temperature_sensor"])
async def test_entity_name_from_device_class(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
) -> None:
"""Test entity name derived from device class when no translation key is set.
Sensor entities do not set _platform_translation_key on the platform class.
When the entity description also has no translation_key, the entity name
is derived from the device class instead.
"""
entry = entity_registry.async_get("sensor.mock_temperature_sensor_temperature")
assert entry is not None
assert entry.translation_key is None
# Name is derived from the device class
assert entry.original_name == "Temperature"
state = hass.states.get("sensor.mock_temperature_sensor_temperature")
assert state is not None
assert state.name == "Mock Temperature Sensor Temperature"