Support translating entity names (#88242)

This commit is contained in:
Erik Montnemery 2023-03-12 15:55:04 +01:00 committed by GitHub
parent 376a6eb82a
commit cf7e500a8e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 40 additions and 3 deletions

View File

@ -1003,6 +1003,10 @@ jobs:
run: | run: |
. venv/bin/activate . venv/bin/activate
pip install mysqlclient sqlalchemy_utils pip install mysqlclient sqlalchemy_utils
- name: Compile English translations
run: |
. venv/bin/activate
python3 -m script.translations develop --all
- name: Run pytest (partially) - name: Run pytest (partially)
timeout-minutes: 20 timeout-minutes: 20
shell: bash shell: bash
@ -1107,6 +1111,10 @@ jobs:
run: | run: |
. venv/bin/activate . venv/bin/activate
pip install psycopg2 sqlalchemy_utils pip install psycopg2 sqlalchemy_utils
- name: Compile English translations
run: |
. venv/bin/activate
python3 -m script.translations develop --all
- name: Run pytest (partially) - name: Run pytest (partially)
timeout-minutes: 20 timeout-minutes: 20
shell: bash shell: bash

View File

@ -126,7 +126,7 @@ async def async_setup_platform(
), ),
DemoSensor( DemoSensor(
unique_id="sensor_10", unique_id="sensor_10",
name="Thermostat mode", name=None,
state="eco", state="eco",
device_class=SensorDeviceClass.ENUM, device_class=SensorDeviceClass.ENUM,
state_class=None, state_class=None,
@ -156,7 +156,7 @@ class DemoSensor(SensorEntity):
def __init__( def __init__(
self, self,
unique_id: str, unique_id: str,
name: str, name: str | None,
state: StateType, state: StateType,
device_class: SensorDeviceClass, device_class: SensorDeviceClass,
state_class: SensorStateClass | None, state_class: SensorStateClass | None,
@ -167,7 +167,10 @@ class DemoSensor(SensorEntity):
) -> None: ) -> None:
"""Initialize the sensor.""" """Initialize the sensor."""
self._attr_device_class = device_class self._attr_device_class = device_class
if name is not None:
self._attr_name = name self._attr_name = name
else:
self._attr_has_entity_name = True
self._attr_native_unit_of_measurement = unit_of_measurement self._attr_native_unit_of_measurement = unit_of_measurement
self._attr_native_value = state self._attr_native_value = state
self._attr_state_class = state_class self._attr_state_class = state_class

View File

@ -98,6 +98,7 @@
}, },
"sensor": { "sensor": {
"thermostat_mode": { "thermostat_mode": {
"name": "Thermostat mode",
"state": { "state": {
"away": "Away", "away": "Away",
"comfort": "Comfort", "comfort": "Comfort",

View File

@ -319,6 +319,15 @@ class Entity(ABC):
"""Return the name of the entity.""" """Return the name of the entity."""
if hasattr(self, "_attr_name"): if hasattr(self, "_attr_name"):
return 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"): if hasattr(self, "entity_description"):
return self.entity_description.name return self.entity_description.name
return None return None

View File

@ -39,6 +39,7 @@ from . import (
device_registry as dev_reg, device_registry as dev_reg,
entity_registry as ent_reg, entity_registry as ent_reg,
service, service,
translation,
) )
from .device_registry import DeviceRegistry from .device_registry import DeviceRegistry
from .entity_registry import EntityRegistry, RegistryEntryDisabler, RegistryEntryHider from .entity_registry import EntityRegistry, RegistryEntryDisabler, RegistryEntryHider
@ -124,6 +125,7 @@ class EntityPlatform:
self.entity_namespace = entity_namespace self.entity_namespace = entity_namespace
self.config_entry: config_entries.ConfigEntry | None = None self.config_entry: config_entries.ConfigEntry | None = None
self.entities: dict[str, Entity] = {} self.entities: dict[str, Entity] = {}
self.entity_translations: dict[str, Any] = {}
self._tasks: list[asyncio.Task[None]] = [] self._tasks: list[asyncio.Task[None]] = []
# Stop tracking tasks after setup is completed # Stop tracking tasks after setup is completed
self._setup_complete = False self._setup_complete = False
@ -276,6 +278,15 @@ class EntityPlatform:
hass = self.hass hass = self.hass
full_name = f"{self.domain}.{self.platform_name}" 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) logger.info("Setting up %s", full_name)
warn_task = hass.loop.call_later( warn_task = hass.loop.call_later(
SLOW_SETUP_WARNING, SLOW_SETUP_WARNING,

View File

@ -286,6 +286,7 @@ def gen_strings_schema(config: Config, integration: Integration) -> vol.Schema:
vol.Optional("entity"): { vol.Optional("entity"): {
str: { str: {
str: { str: {
vol.Optional("name"): cv.string_with_no_html,
vol.Optional("state_attributes"): { vol.Optional("state_attributes"): {
str: { str: {
vol.Optional("name"): cv.string_with_no_html, vol.Optional("name"): cv.string_with_no_html,

View File

@ -214,6 +214,7 @@ async def test_setup_get_template_headers_params(hass: HomeAssistant) -> None:
}, },
) )
await async_setup_component(hass, "homeassistant", {}) 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["Accept"] == CONTENT_TYPE_JSON
assert respx.calls.last.request.headers["User-Agent"] == "Mozilla/5.0" assert respx.calls.last.request.headers["User-Agent"] == "Mozilla/5.0"

View File

@ -318,6 +318,7 @@ async def test_setup_get_templated_headers_params(hass: HomeAssistant) -> None:
}, },
) )
await async_setup_component(hass, "homeassistant", {}) 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["Accept"] == CONTENT_TYPE_JSON
assert respx.calls.last.request.headers["User-Agent"] == "Mozilla/5.0" assert respx.calls.last.request.headers["User-Agent"] == "Mozilla/5.0"

View File

@ -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) states: list[State] = await hass.async_add_executor_job(_fetch_states)
assert len(states) > 1 assert len(states) > 1
for state in states: for state in states:
if state.domain != DOMAIN:
continue
assert ATTR_OPTIONS not in state.attributes assert ATTR_OPTIONS not in state.attributes
assert ATTR_FRIENDLY_NAME in state.attributes assert ATTR_FRIENDLY_NAME in state.attributes