Add translation support to Config Entry errors (#106305)

* Config Entry error translation

* split key and placeholders

* Fix config entries tests

* translation optional

* Mods
pull/114371/head
G Johansson 2024-03-28 10:52:21 +01:00 committed by GitHub
parent aa9d58df67
commit fc4d960d17
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 122 additions and 4 deletions

View File

@ -243,7 +243,14 @@ class OperationNotAllowed(ConfigError):
UpdateListenerType = Callable[[HomeAssistant, "ConfigEntry"], Coroutine[Any, Any, None]] UpdateListenerType = Callable[[HomeAssistant, "ConfigEntry"], Coroutine[Any, Any, None]]
FROZEN_CONFIG_ENTRY_ATTRS = {"entry_id", "domain", "state", "reason"} FROZEN_CONFIG_ENTRY_ATTRS = {
"entry_id",
"domain",
"state",
"reason",
"error_reason_translation_key",
"error_reason_translation_placeholders",
}
UPDATE_ENTRY_CONFIG_ENTRY_ATTRS = { UPDATE_ENTRY_CONFIG_ENTRY_ATTRS = {
"unique_id", "unique_id",
"title", "title",
@ -274,6 +281,8 @@ class ConfigEntry:
unique_id: str | None unique_id: str | None
state: ConfigEntryState state: ConfigEntryState
reason: str | None reason: str | None
error_reason_translation_key: str | None
error_reason_translation_placeholders: dict[str, Any] | None
pref_disable_new_entities: bool pref_disable_new_entities: bool
pref_disable_polling: bool pref_disable_polling: bool
version: int version: int
@ -369,6 +378,8 @@ class ConfigEntry:
# Reason why config entry is in a failed state # Reason why config entry is in a failed state
_setter(self, "reason", None) _setter(self, "reason", None)
_setter(self, "error_reason_translation_key", None)
_setter(self, "error_reason_translation_placeholders", None)
# Function to cancel a scheduled retry # Function to cancel a scheduled retry
self._async_cancel_retry_setup: Callable[[], Any] | None = None self._async_cancel_retry_setup: Callable[[], Any] | None = None
@ -472,6 +483,8 @@ class ConfigEntry:
"pref_disable_polling": self.pref_disable_polling, "pref_disable_polling": self.pref_disable_polling,
"disabled_by": self.disabled_by, "disabled_by": self.disabled_by,
"reason": self.reason, "reason": self.reason,
"error_reason_translation_key": self.error_reason_translation_key,
"error_reason_translation_placeholders": self.error_reason_translation_placeholders,
} }
return json_fragment(json_bytes(json_repr)) return json_fragment(json_bytes(json_repr))
@ -543,6 +556,8 @@ class ConfigEntry:
setup_phase = SetupPhases.CONFIG_ENTRY_PLATFORM_SETUP setup_phase = SetupPhases.CONFIG_ENTRY_PLATFORM_SETUP
error_reason = None error_reason = None
error_reason_translation_key = None
error_reason_translation_placeholders = None
try: try:
with async_start_setup( with async_start_setup(
@ -557,6 +572,8 @@ class ConfigEntry:
result = False result = False
except ConfigEntryError as exc: except ConfigEntryError as exc:
error_reason = str(exc) or "Unknown fatal config entry error" error_reason = str(exc) or "Unknown fatal config entry error"
error_reason_translation_key = exc.translation_key
error_reason_translation_placeholders = exc.translation_placeholders
_LOGGER.exception( _LOGGER.exception(
"Error setting up entry %s for %s: %s", "Error setting up entry %s for %s: %s",
self.title, self.title,
@ -569,6 +586,8 @@ class ConfigEntry:
message = str(exc) message = str(exc)
auth_base_message = "could not authenticate" auth_base_message = "could not authenticate"
error_reason = message or auth_base_message error_reason = message or auth_base_message
error_reason_translation_key = exc.translation_key
error_reason_translation_placeholders = exc.translation_placeholders
auth_message = ( auth_message = (
f"{auth_base_message}: {message}" if message else auth_base_message f"{auth_base_message}: {message}" if message else auth_base_message
) )
@ -583,7 +602,15 @@ class ConfigEntry:
result = False result = False
except ConfigEntryNotReady as exc: except ConfigEntryNotReady as exc:
message = str(exc) message = str(exc)
self._async_set_state(hass, ConfigEntryState.SETUP_RETRY, message or None) error_reason_translation_key = exc.translation_key
error_reason_translation_placeholders = exc.translation_placeholders
self._async_set_state(
hass,
ConfigEntryState.SETUP_RETRY,
message or None,
error_reason_translation_key,
error_reason_translation_placeholders,
)
wait_time = 2 ** min(self._tries, 4) * 5 + ( wait_time = 2 ** min(self._tries, 4) * 5 + (
randint(RANDOM_MICROSECOND_MIN, RANDOM_MICROSECOND_MAX) / 1000000 randint(RANDOM_MICROSECOND_MIN, RANDOM_MICROSECOND_MAX) / 1000000
) )
@ -644,7 +671,13 @@ class ConfigEntry:
if result: if result:
self._async_set_state(hass, ConfigEntryState.LOADED, None) self._async_set_state(hass, ConfigEntryState.LOADED, None)
else: else:
self._async_set_state(hass, ConfigEntryState.SETUP_ERROR, error_reason) self._async_set_state(
hass,
ConfigEntryState.SETUP_ERROR,
error_reason,
error_reason_translation_key,
error_reason_translation_placeholders,
)
@callback @callback
def _async_setup_again(self, hass: HomeAssistant, *_: Any) -> None: def _async_setup_again(self, hass: HomeAssistant, *_: Any) -> None:
@ -771,7 +804,12 @@ class ConfigEntry:
@callback @callback
def _async_set_state( def _async_set_state(
self, hass: HomeAssistant, state: ConfigEntryState, reason: str | None self,
hass: HomeAssistant,
state: ConfigEntryState,
reason: str | None,
error_reason_translation_key: str | None = None,
error_reason_translation_placeholders: dict[str, str] | None = None,
) -> None: ) -> None:
"""Set the state of the config entry.""" """Set the state of the config entry."""
if state not in NO_RESET_TRIES_STATES: if state not in NO_RESET_TRIES_STATES:
@ -779,6 +817,12 @@ class ConfigEntry:
_setter = object.__setattr__ _setter = object.__setattr__
_setter(self, "state", state) _setter(self, "state", state)
_setter(self, "reason", reason) _setter(self, "reason", reason)
_setter(self, "error_reason_translation_key", error_reason_translation_key)
_setter(
self,
"error_reason_translation_placeholders",
error_reason_translation_placeholders,
)
self.clear_cache() self.clear_cache()
async_dispatcher_send( async_dispatcher_send(
hass, SIGNAL_CONFIG_ENTRY_CHANGED, ConfigEntryChange.UPDATED, self hass, SIGNAL_CONFIG_ENTRY_CHANGED, ConfigEntryChange.UPDATED, self

View File

@ -131,6 +131,8 @@ async def test_get_entries(hass: HomeAssistant, client, clear_handlers) -> None:
"pref_disable_polling": False, "pref_disable_polling": False,
"disabled_by": None, "disabled_by": None,
"reason": None, "reason": None,
"error_reason_translation_key": None,
"error_reason_translation_placeholders": None,
}, },
{ {
"domain": "comp2", "domain": "comp2",
@ -145,6 +147,8 @@ async def test_get_entries(hass: HomeAssistant, client, clear_handlers) -> None:
"pref_disable_polling": False, "pref_disable_polling": False,
"disabled_by": None, "disabled_by": None,
"reason": "Unsupported API", "reason": "Unsupported API",
"error_reason_translation_key": None,
"error_reason_translation_placeholders": None,
}, },
{ {
"domain": "comp3", "domain": "comp3",
@ -159,6 +163,8 @@ async def test_get_entries(hass: HomeAssistant, client, clear_handlers) -> None:
"pref_disable_polling": False, "pref_disable_polling": False,
"disabled_by": core_ce.ConfigEntryDisabler.USER, "disabled_by": core_ce.ConfigEntryDisabler.USER,
"reason": None, "reason": None,
"error_reason_translation_key": None,
"error_reason_translation_placeholders": None,
}, },
{ {
"domain": "comp4", "domain": "comp4",
@ -173,6 +179,8 @@ async def test_get_entries(hass: HomeAssistant, client, clear_handlers) -> None:
"pref_disable_polling": False, "pref_disable_polling": False,
"disabled_by": None, "disabled_by": None,
"reason": None, "reason": None,
"error_reason_translation_key": None,
"error_reason_translation_placeholders": None,
}, },
{ {
"domain": "comp5", "domain": "comp5",
@ -187,6 +195,8 @@ async def test_get_entries(hass: HomeAssistant, client, clear_handlers) -> None:
"pref_disable_polling": False, "pref_disable_polling": False,
"disabled_by": None, "disabled_by": None,
"reason": None, "reason": None,
"error_reason_translation_key": None,
"error_reason_translation_placeholders": None,
}, },
] ]
@ -536,6 +546,8 @@ async def test_create_account(
"pref_disable_polling": False, "pref_disable_polling": False,
"title": "Test Entry", "title": "Test Entry",
"reason": None, "reason": None,
"error_reason_translation_key": None,
"error_reason_translation_placeholders": None,
}, },
"description": None, "description": None,
"description_placeholders": None, "description_placeholders": None,
@ -615,6 +627,8 @@ async def test_two_step_flow(
"pref_disable_polling": False, "pref_disable_polling": False,
"title": "user-title", "title": "user-title",
"reason": None, "reason": None,
"error_reason_translation_key": None,
"error_reason_translation_placeholders": None,
}, },
"description": None, "description": None,
"description_placeholders": None, "description_placeholders": None,
@ -1058,6 +1072,8 @@ async def test_get_single(
"pref_disable_new_entities": False, "pref_disable_new_entities": False,
"pref_disable_polling": False, "pref_disable_polling": False,
"reason": None, "reason": None,
"error_reason_translation_key": None,
"error_reason_translation_placeholders": None,
"source": "user", "source": "user",
"state": "loaded", "state": "loaded",
"supports_reconfigure": False, "supports_reconfigure": False,
@ -1393,6 +1409,8 @@ async def test_get_matching_entries_ws(
"pref_disable_new_entities": False, "pref_disable_new_entities": False,
"pref_disable_polling": False, "pref_disable_polling": False,
"reason": None, "reason": None,
"error_reason_translation_key": None,
"error_reason_translation_placeholders": None,
"source": "bla", "source": "bla",
"state": "not_loaded", "state": "not_loaded",
"supports_reconfigure": False, "supports_reconfigure": False,
@ -1408,6 +1426,8 @@ async def test_get_matching_entries_ws(
"pref_disable_new_entities": False, "pref_disable_new_entities": False,
"pref_disable_polling": False, "pref_disable_polling": False,
"reason": "Unsupported API", "reason": "Unsupported API",
"error_reason_translation_key": None,
"error_reason_translation_placeholders": None,
"source": "bla2", "source": "bla2",
"state": "setup_error", "state": "setup_error",
"supports_reconfigure": False, "supports_reconfigure": False,
@ -1423,6 +1443,8 @@ async def test_get_matching_entries_ws(
"pref_disable_new_entities": False, "pref_disable_new_entities": False,
"pref_disable_polling": False, "pref_disable_polling": False,
"reason": None, "reason": None,
"error_reason_translation_key": None,
"error_reason_translation_placeholders": None,
"source": "bla3", "source": "bla3",
"state": "not_loaded", "state": "not_loaded",
"supports_reconfigure": False, "supports_reconfigure": False,
@ -1438,6 +1460,8 @@ async def test_get_matching_entries_ws(
"pref_disable_new_entities": False, "pref_disable_new_entities": False,
"pref_disable_polling": False, "pref_disable_polling": False,
"reason": None, "reason": None,
"error_reason_translation_key": None,
"error_reason_translation_placeholders": None,
"source": "bla4", "source": "bla4",
"state": "not_loaded", "state": "not_loaded",
"supports_reconfigure": False, "supports_reconfigure": False,
@ -1453,6 +1477,8 @@ async def test_get_matching_entries_ws(
"pref_disable_new_entities": False, "pref_disable_new_entities": False,
"pref_disable_polling": False, "pref_disable_polling": False,
"reason": None, "reason": None,
"error_reason_translation_key": None,
"error_reason_translation_placeholders": None,
"source": "bla5", "source": "bla5",
"state": "not_loaded", "state": "not_loaded",
"supports_reconfigure": False, "supports_reconfigure": False,
@ -1479,6 +1505,8 @@ async def test_get_matching_entries_ws(
"pref_disable_new_entities": False, "pref_disable_new_entities": False,
"pref_disable_polling": False, "pref_disable_polling": False,
"reason": None, "reason": None,
"error_reason_translation_key": None,
"error_reason_translation_placeholders": None,
"source": "bla", "source": "bla",
"state": "not_loaded", "state": "not_loaded",
"supports_reconfigure": False, "supports_reconfigure": False,
@ -1504,6 +1532,8 @@ async def test_get_matching_entries_ws(
"pref_disable_new_entities": False, "pref_disable_new_entities": False,
"pref_disable_polling": False, "pref_disable_polling": False,
"reason": None, "reason": None,
"error_reason_translation_key": None,
"error_reason_translation_placeholders": None,
"source": "bla4", "source": "bla4",
"state": "not_loaded", "state": "not_loaded",
"supports_reconfigure": False, "supports_reconfigure": False,
@ -1519,6 +1549,8 @@ async def test_get_matching_entries_ws(
"pref_disable_new_entities": False, "pref_disable_new_entities": False,
"pref_disable_polling": False, "pref_disable_polling": False,
"reason": None, "reason": None,
"error_reason_translation_key": None,
"error_reason_translation_placeholders": None,
"source": "bla5", "source": "bla5",
"state": "not_loaded", "state": "not_loaded",
"supports_reconfigure": False, "supports_reconfigure": False,
@ -1544,6 +1576,8 @@ async def test_get_matching_entries_ws(
"pref_disable_new_entities": False, "pref_disable_new_entities": False,
"pref_disable_polling": False, "pref_disable_polling": False,
"reason": None, "reason": None,
"error_reason_translation_key": None,
"error_reason_translation_placeholders": None,
"source": "bla", "source": "bla",
"state": "not_loaded", "state": "not_loaded",
"supports_reconfigure": False, "supports_reconfigure": False,
@ -1559,6 +1593,8 @@ async def test_get_matching_entries_ws(
"pref_disable_new_entities": False, "pref_disable_new_entities": False,
"pref_disable_polling": False, "pref_disable_polling": False,
"reason": None, "reason": None,
"error_reason_translation_key": None,
"error_reason_translation_placeholders": None,
"source": "bla3", "source": "bla3",
"state": "not_loaded", "state": "not_loaded",
"supports_reconfigure": False, "supports_reconfigure": False,
@ -1590,6 +1626,8 @@ async def test_get_matching_entries_ws(
"pref_disable_new_entities": False, "pref_disable_new_entities": False,
"pref_disable_polling": False, "pref_disable_polling": False,
"reason": None, "reason": None,
"error_reason_translation_key": None,
"error_reason_translation_placeholders": None,
"source": "bla", "source": "bla",
"state": "not_loaded", "state": "not_loaded",
"supports_reconfigure": False, "supports_reconfigure": False,
@ -1605,6 +1643,8 @@ async def test_get_matching_entries_ws(
"pref_disable_new_entities": False, "pref_disable_new_entities": False,
"pref_disable_polling": False, "pref_disable_polling": False,
"reason": "Unsupported API", "reason": "Unsupported API",
"error_reason_translation_key": None,
"error_reason_translation_placeholders": None,
"source": "bla2", "source": "bla2",
"state": "setup_error", "state": "setup_error",
"supports_reconfigure": False, "supports_reconfigure": False,
@ -1620,6 +1660,8 @@ async def test_get_matching_entries_ws(
"pref_disable_new_entities": False, "pref_disable_new_entities": False,
"pref_disable_polling": False, "pref_disable_polling": False,
"reason": None, "reason": None,
"error_reason_translation_key": None,
"error_reason_translation_placeholders": None,
"source": "bla3", "source": "bla3",
"state": "not_loaded", "state": "not_loaded",
"supports_reconfigure": False, "supports_reconfigure": False,
@ -1635,6 +1677,8 @@ async def test_get_matching_entries_ws(
"pref_disable_new_entities": False, "pref_disable_new_entities": False,
"pref_disable_polling": False, "pref_disable_polling": False,
"reason": None, "reason": None,
"error_reason_translation_key": None,
"error_reason_translation_placeholders": None,
"source": "bla4", "source": "bla4",
"state": "not_loaded", "state": "not_loaded",
"supports_reconfigure": False, "supports_reconfigure": False,
@ -1650,6 +1694,8 @@ async def test_get_matching_entries_ws(
"pref_disable_new_entities": False, "pref_disable_new_entities": False,
"pref_disable_polling": False, "pref_disable_polling": False,
"reason": None, "reason": None,
"error_reason_translation_key": None,
"error_reason_translation_placeholders": None,
"source": "bla5", "source": "bla5",
"state": "not_loaded", "state": "not_loaded",
"supports_reconfigure": False, "supports_reconfigure": False,
@ -1749,6 +1795,8 @@ async def test_subscribe_entries_ws(
"pref_disable_new_entities": False, "pref_disable_new_entities": False,
"pref_disable_polling": False, "pref_disable_polling": False,
"reason": None, "reason": None,
"error_reason_translation_key": None,
"error_reason_translation_placeholders": None,
"source": "bla", "source": "bla",
"state": "not_loaded", "state": "not_loaded",
"supports_reconfigure": False, "supports_reconfigure": False,
@ -1767,6 +1815,8 @@ async def test_subscribe_entries_ws(
"pref_disable_new_entities": False, "pref_disable_new_entities": False,
"pref_disable_polling": False, "pref_disable_polling": False,
"reason": "Unsupported API", "reason": "Unsupported API",
"error_reason_translation_key": None,
"error_reason_translation_placeholders": None,
"source": "bla2", "source": "bla2",
"state": "setup_error", "state": "setup_error",
"supports_reconfigure": False, "supports_reconfigure": False,
@ -1785,6 +1835,8 @@ async def test_subscribe_entries_ws(
"pref_disable_new_entities": False, "pref_disable_new_entities": False,
"pref_disable_polling": False, "pref_disable_polling": False,
"reason": None, "reason": None,
"error_reason_translation_key": None,
"error_reason_translation_placeholders": None,
"source": "bla3", "source": "bla3",
"state": "not_loaded", "state": "not_loaded",
"supports_reconfigure": False, "supports_reconfigure": False,
@ -1807,6 +1859,8 @@ async def test_subscribe_entries_ws(
"pref_disable_new_entities": False, "pref_disable_new_entities": False,
"pref_disable_polling": False, "pref_disable_polling": False,
"reason": None, "reason": None,
"error_reason_translation_key": None,
"error_reason_translation_placeholders": None,
"source": "bla", "source": "bla",
"state": "not_loaded", "state": "not_loaded",
"supports_reconfigure": False, "supports_reconfigure": False,
@ -1830,6 +1884,8 @@ async def test_subscribe_entries_ws(
"pref_disable_new_entities": False, "pref_disable_new_entities": False,
"pref_disable_polling": False, "pref_disable_polling": False,
"reason": None, "reason": None,
"error_reason_translation_key": None,
"error_reason_translation_placeholders": None,
"source": "bla", "source": "bla",
"state": "not_loaded", "state": "not_loaded",
"supports_reconfigure": False, "supports_reconfigure": False,
@ -1853,6 +1909,8 @@ async def test_subscribe_entries_ws(
"pref_disable_new_entities": False, "pref_disable_new_entities": False,
"pref_disable_polling": False, "pref_disable_polling": False,
"reason": None, "reason": None,
"error_reason_translation_key": None,
"error_reason_translation_placeholders": None,
"source": "bla", "source": "bla",
"state": "not_loaded", "state": "not_loaded",
"supports_reconfigure": False, "supports_reconfigure": False,
@ -1935,6 +1993,8 @@ async def test_subscribe_entries_ws_filtered(
"pref_disable_new_entities": False, "pref_disable_new_entities": False,
"pref_disable_polling": False, "pref_disable_polling": False,
"reason": None, "reason": None,
"error_reason_translation_key": None,
"error_reason_translation_placeholders": None,
"source": "bla", "source": "bla",
"state": "not_loaded", "state": "not_loaded",
"supports_reconfigure": False, "supports_reconfigure": False,
@ -1953,6 +2013,8 @@ async def test_subscribe_entries_ws_filtered(
"pref_disable_new_entities": False, "pref_disable_new_entities": False,
"pref_disable_polling": False, "pref_disable_polling": False,
"reason": None, "reason": None,
"error_reason_translation_key": None,
"error_reason_translation_placeholders": None,
"source": "bla3", "source": "bla3",
"state": "not_loaded", "state": "not_loaded",
"supports_reconfigure": False, "supports_reconfigure": False,
@ -1977,6 +2039,8 @@ async def test_subscribe_entries_ws_filtered(
"pref_disable_new_entities": False, "pref_disable_new_entities": False,
"pref_disable_polling": False, "pref_disable_polling": False,
"reason": None, "reason": None,
"error_reason_translation_key": None,
"error_reason_translation_placeholders": None,
"source": "bla", "source": "bla",
"state": "not_loaded", "state": "not_loaded",
"supports_reconfigure": False, "supports_reconfigure": False,
@ -1999,6 +2063,8 @@ async def test_subscribe_entries_ws_filtered(
"pref_disable_new_entities": False, "pref_disable_new_entities": False,
"pref_disable_polling": False, "pref_disable_polling": False,
"reason": None, "reason": None,
"error_reason_translation_key": None,
"error_reason_translation_placeholders": None,
"source": "bla3", "source": "bla3",
"state": "not_loaded", "state": "not_loaded",
"supports_reconfigure": False, "supports_reconfigure": False,
@ -2023,6 +2089,8 @@ async def test_subscribe_entries_ws_filtered(
"pref_disable_new_entities": False, "pref_disable_new_entities": False,
"pref_disable_polling": False, "pref_disable_polling": False,
"reason": None, "reason": None,
"error_reason_translation_key": None,
"error_reason_translation_placeholders": None,
"source": "bla", "source": "bla",
"state": "not_loaded", "state": "not_loaded",
"supports_reconfigure": False, "supports_reconfigure": False,
@ -2046,6 +2114,8 @@ async def test_subscribe_entries_ws_filtered(
"pref_disable_new_entities": False, "pref_disable_new_entities": False,
"pref_disable_polling": False, "pref_disable_polling": False,
"reason": None, "reason": None,
"error_reason_translation_key": None,
"error_reason_translation_placeholders": None,
"source": "bla", "source": "bla",
"state": "not_loaded", "state": "not_loaded",
"supports_reconfigure": False, "supports_reconfigure": False,
@ -2225,6 +2295,8 @@ async def test_supports_reconfigure(
"pref_disable_polling": False, "pref_disable_polling": False,
"title": "Test Entry", "title": "Test Entry",
"reason": None, "reason": None,
"error_reason_translation_key": None,
"error_reason_translation_placeholders": None,
}, },
"description": None, "description": None,
"description_placeholders": None, "description_placeholders": None,

View File

@ -821,6 +821,8 @@ async def test_as_dict(snapshot: SnapshotAssertion) -> None:
"_setup_lock", "_setup_lock",
"update_listeners", "update_listeners",
"reason", "reason",
"error_reason_translation_key",
"error_reason_translation_placeholders",
"_async_cancel_retry_setup", "_async_cancel_retry_setup",
"_on_unload", "_on_unload",
"reload_lock", "reload_lock",