From cf7e500a8eee524ddafd553bfdb174a1c408a3a2 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Sun, 12 Mar 2023 15:55:04 +0100 Subject: [PATCH] Support translating entity names (#88242) --- .github/workflows/ci.yaml | 8 ++++++++ homeassistant/components/demo/sensor.py | 9 ++++++--- homeassistant/components/demo/strings.json | 1 + homeassistant/helpers/entity.py | 9 +++++++++ homeassistant/helpers/entity_platform.py | 11 +++++++++++ script/hassfest/translations.py | 1 + tests/components/rest/test_binary_sensor.py | 1 + tests/components/rest/test_sensor.py | 1 + tests/components/sensor/test_recorder.py | 2 ++ 9 files changed, 40 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index b65039c42bf..86972558882 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -1003,6 +1003,10 @@ jobs: run: | . venv/bin/activate pip install mysqlclient sqlalchemy_utils + - name: Compile English translations + run: | + . venv/bin/activate + python3 -m script.translations develop --all - name: Run pytest (partially) timeout-minutes: 20 shell: bash @@ -1107,6 +1111,10 @@ jobs: run: | . venv/bin/activate pip install psycopg2 sqlalchemy_utils + - name: Compile English translations + run: | + . venv/bin/activate + python3 -m script.translations develop --all - name: Run pytest (partially) timeout-minutes: 20 shell: bash diff --git a/homeassistant/components/demo/sensor.py b/homeassistant/components/demo/sensor.py index 67a7b346a3e..84758f0c294 100644 --- a/homeassistant/components/demo/sensor.py +++ b/homeassistant/components/demo/sensor.py @@ -126,7 +126,7 @@ async def async_setup_platform( ), DemoSensor( unique_id="sensor_10", - name="Thermostat mode", + name=None, state="eco", device_class=SensorDeviceClass.ENUM, state_class=None, @@ -156,7 +156,7 @@ class DemoSensor(SensorEntity): def __init__( self, unique_id: str, - name: str, + name: str | None, state: StateType, device_class: SensorDeviceClass, state_class: SensorStateClass | None, @@ -167,7 +167,10 @@ class DemoSensor(SensorEntity): ) -> None: """Initialize the sensor.""" self._attr_device_class = device_class - self._attr_name = name + if name is not None: + self._attr_name = name + else: + self._attr_has_entity_name = True self._attr_native_unit_of_measurement = unit_of_measurement self._attr_native_value = state self._attr_state_class = state_class diff --git a/homeassistant/components/demo/strings.json b/homeassistant/components/demo/strings.json index cdbe8dc1bd5..add04c236e7 100644 --- a/homeassistant/components/demo/strings.json +++ b/homeassistant/components/demo/strings.json @@ -98,6 +98,7 @@ }, "sensor": { "thermostat_mode": { + "name": "Thermostat mode", "state": { "away": "Away", "comfort": "Comfort", diff --git a/homeassistant/helpers/entity.py b/homeassistant/helpers/entity.py index 9c1bbe5b209..63b70aa13d9 100644 --- a/homeassistant/helpers/entity.py +++ b/homeassistant/helpers/entity.py @@ -319,6 +319,15 @@ class Entity(ABC): """Return the name of the entity.""" if hasattr(self, "_attr_name"): return self._attr_name + if self.translation_key is not None and self.has_entity_name: + assert self.platform + name_translation_key = ( + f"component.{self.platform.platform_name}.entity.{self.platform.domain}" + f".{self.translation_key}.name" + ) + if name_translation_key in self.platform.entity_translations: + name: str = self.platform.entity_translations[name_translation_key] + return name if hasattr(self, "entity_description"): return self.entity_description.name return None diff --git a/homeassistant/helpers/entity_platform.py b/homeassistant/helpers/entity_platform.py index e085f819e3c..6687af6a27d 100644 --- a/homeassistant/helpers/entity_platform.py +++ b/homeassistant/helpers/entity_platform.py @@ -39,6 +39,7 @@ from . import ( device_registry as dev_reg, entity_registry as ent_reg, service, + translation, ) from .device_registry import DeviceRegistry from .entity_registry import EntityRegistry, RegistryEntryDisabler, RegistryEntryHider @@ -124,6 +125,7 @@ class EntityPlatform: self.entity_namespace = entity_namespace self.config_entry: config_entries.ConfigEntry | None = None self.entities: dict[str, Entity] = {} + self.entity_translations: dict[str, Any] = {} self._tasks: list[asyncio.Task[None]] = [] # Stop tracking tasks after setup is completed self._setup_complete = False @@ -276,6 +278,15 @@ class EntityPlatform: hass = self.hass full_name = f"{self.domain}.{self.platform_name}" + try: + self.entity_translations = await translation.async_get_translations( + hass, hass.config.language, "entity", {self.platform_name} + ) + except Exception as err: # pylint: disable=broad-exception-caught + _LOGGER.debug( + "Could not load translations for %s", self.platform_name, exc_info=err + ) + logger.info("Setting up %s", full_name) warn_task = hass.loop.call_later( SLOW_SETUP_WARNING, diff --git a/script/hassfest/translations.py b/script/hassfest/translations.py index 5233911111e..92a1047c304 100644 --- a/script/hassfest/translations.py +++ b/script/hassfest/translations.py @@ -286,6 +286,7 @@ def gen_strings_schema(config: Config, integration: Integration) -> vol.Schema: vol.Optional("entity"): { str: { str: { + vol.Optional("name"): cv.string_with_no_html, vol.Optional("state_attributes"): { str: { vol.Optional("name"): cv.string_with_no_html, diff --git a/tests/components/rest/test_binary_sensor.py b/tests/components/rest/test_binary_sensor.py index 757c331529e..99d378983b9 100644 --- a/tests/components/rest/test_binary_sensor.py +++ b/tests/components/rest/test_binary_sensor.py @@ -214,6 +214,7 @@ async def test_setup_get_template_headers_params(hass: HomeAssistant) -> None: }, ) await async_setup_component(hass, "homeassistant", {}) + await hass.async_block_till_done() assert respx.calls.last.request.headers["Accept"] == CONTENT_TYPE_JSON assert respx.calls.last.request.headers["User-Agent"] == "Mozilla/5.0" diff --git a/tests/components/rest/test_sensor.py b/tests/components/rest/test_sensor.py index 5dcaed6985d..46a972628e5 100644 --- a/tests/components/rest/test_sensor.py +++ b/tests/components/rest/test_sensor.py @@ -318,6 +318,7 @@ async def test_setup_get_templated_headers_params(hass: HomeAssistant) -> None: }, ) await async_setup_component(hass, "homeassistant", {}) + await hass.async_block_till_done() assert respx.calls.last.request.headers["Accept"] == CONTENT_TYPE_JSON assert respx.calls.last.request.headers["User-Agent"] == "Mozilla/5.0" diff --git a/tests/components/sensor/test_recorder.py b/tests/components/sensor/test_recorder.py index add7bc5e016..55eda6c03b0 100644 --- a/tests/components/sensor/test_recorder.py +++ b/tests/components/sensor/test_recorder.py @@ -4748,5 +4748,7 @@ async def test_exclude_attributes(recorder_mock: Recorder, hass: HomeAssistant) states: list[State] = await hass.async_add_executor_job(_fetch_states) assert len(states) > 1 for state in states: + if state.domain != DOMAIN: + continue assert ATTR_OPTIONS not in state.attributes assert ATTR_FRIENDLY_NAME in state.attributes