Skip to content

Commit

Permalink
Frontend translation & translation_key from caller
Browse files Browse the repository at this point in the history
  • Loading branch information
jbouwh committed Oct 27, 2023
1 parent 173183b commit 20fa883
Show file tree
Hide file tree
Showing 7 changed files with 171 additions and 233 deletions.
87 changes: 37 additions & 50 deletions homeassistant/components/websocket_api/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@
json_dumps,
)
from homeassistant.helpers.service import async_get_all_descriptions
from homeassistant.helpers.translation import async_get_translations
from homeassistant.helpers.typing import EventType
from homeassistant.loader import (
Integration,
Expand Down Expand Up @@ -235,68 +234,56 @@ async def handle_call_service(
connection.send_result(msg["id"], {"context": context})
except ServiceNotFound as err:
if err.domain == msg["domain"] and err.service == msg["service"]:
err.translation_key = "service_not_found_general"
err.domain = const.DOMAIN
code = const.ERR_NOT_FOUND
connection.send_error(
msg["id"],
const.ERR_NOT_FOUND,
"Service not found.",
translation_domain=err.translation_domain,
translation_key=err.translation_key,
translation_placeholders=err.translation_placeholders,
)
else:
err.translation_key = "service_not_found"
err.domain = const.DOMAIN
code = const.ERR_HOME_ASSISTANT_ERROR
message = await async_build_error_message(hass, err)
connection.send_error(msg["id"], code, message)
# The called service called another service which does not exist
connection.send_error(
msg["id"],
const.ERR_HOME_ASSISTANT_ERROR,
str(err),
translation_domain=const.DOMAIN,
translation_key="service_not_found",
translation_placeholders={
"domain": err.domain,
"service": err.service,
},
)
except vol.Invalid as err:
connection.send_error(msg["id"], const.ERR_INVALID_FORMAT, str(err))
except ServiceValidationError as err:
connection.logger.error(err)
connection.logger.debug("", exc_info=err)
message = await async_build_error_message(hass, err)
connection.send_error(msg["id"], const.ERR_HOME_ASSISTANT_ERROR, message)
placeholders = err.translation_placeholders or {}
connection.send_error(
msg["id"],
const.ERR_HOME_ASSISTANT_ERROR,
"Validation error.",
translation_domain=err.translation_domain,
translation_key=err.translation_key,
translation_placeholders={**placeholders, **{"message": str(err)}},
)
except HomeAssistantError as err:
connection.logger.exception(err)
message = await async_build_error_message(hass, err)
connection.send_error(msg["id"], const.ERR_HOME_ASSISTANT_ERROR, message)
connection.send_error(
msg["id"],
const.ERR_HOME_ASSISTANT_ERROR,
str(err),
translation_domain=err.translation_domain,
translation_key=err.translation_key,
translation_placeholders=err.translation_placeholders,
)
except Exception as err: # pylint: disable=broad-except
connection.logger.exception(err)
connection.send_error(msg["id"], const.ERR_UNKNOWN_ERROR, str(err))


async def async_get_exception_translations(
hass: HomeAssistant, domain: str
) -> dict[str, Any]:
"""Get translations for exceptions for a domain."""
return await async_get_translations(
hass, hass.config.language, "exceptions", {domain}
)


async def async_build_error_message(
hass: HomeAssistant, err: HomeAssistantError
) -> str:
"""Build translated error message from exception."""
if (translation_key := err.translation_key) is None or (
domain := err.domain
) is None:
return str(err)

exception_translations: dict[str, dict[str, Any]] = hass.data.setdefault(
DATA_EXCEPTION_TRANSLATIONS, {}
)
if (domain_exception_translations := exception_translations.get(domain)) is None:
domain_exception_translations = await async_get_exception_translations(
hass, domain
)
exception_translations[domain] = domain_exception_translations
exception_message: str | None
if (
exception_message := domain_exception_translations.get(
f"component.{domain}.exceptions.{translation_key}.message",
)
) is None:
return str(err)
place_holders = err.translation_placeholders or {}
return exception_message.format(**place_holders, message=str(err)) # type: ignore[no-any-return]


@callback
def _async_get_allowed_states(
hass: HomeAssistant, connection: ActiveConnection
Expand Down
21 changes: 19 additions & 2 deletions homeassistant/components/websocket_api/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,9 +134,26 @@ def send_event(self, msg_id: int, event: Any | None = None) -> None:
self.send_message(messages.event_message(msg_id, event))

@callback
def send_error(self, msg_id: int, code: str, message: str) -> None:
def send_error(
self,
msg_id: int,
code: str,
message: str,
translation_key: str | None = None,
translation_domain: str | None = None,
translation_placeholders: dict[str, Any] | None = None,
) -> None:
"""Send a error message."""
self.send_message(messages.error_message(msg_id, code, message))
self.send_message(
messages.error_message(
msg_id,
code,
message,
translation_key=translation_key,
translation_domain=translation_domain,
translation_placeholders=translation_placeholders,
)
)

@callback
def async_handle_binary(self, handler_id: int, payload: bytes) -> None:
Expand Down
20 changes: 18 additions & 2 deletions homeassistant/components/websocket_api/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,12 +65,28 @@ def construct_result_message(iden: int, payload: str) -> str:
return f'{{"id":{iden},"type":"result","success":true,"result":{payload}}}'


def error_message(iden: int | None, code: str, message: str) -> dict[str, Any]:
def error_message(
iden: int | None,
code: str,
message: str,
translation_key: str | None = None,
translation_domain: str | None = None,
translation_placeholders: dict[str, Any] | None = None,
) -> dict[str, Any]:
"""Return an error result message."""
error_payload: dict[str, Any] = {
"code": code,
"message": message,
}
if translation_key is not None:
error_payload["translation_key"] = translation_key
error_payload["translation_placeholders"] = translation_placeholders or {}
if translation_domain is not None:
error_payload["translation_domain"] = translation_domain
return {
"id": iden,
**BASE_ERROR_MESSAGE,
"error": {"code": code, "message": message},
"error": error_payload,
}


Expand Down
5 changes: 1 addition & 4 deletions homeassistant/components/websocket_api/strings.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
{
"exceptions": {
"service_not_found_general": {
"message": "Service not found"
},
"service_not_found": {
"message": "Service {domain}.{service} not found"
"message": "Service {domain}.{service} not found."
}
}
}
13 changes: 9 additions & 4 deletions homeassistant/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ class HomeAssistantError(Exception):
def __init__(
self,
*args: object,
domain: str | None = None,
translation_domain: str | None = None,
translation_key: str | None = None,
translation_placeholders: dict[str, str] | None = None,
) -> None:
"""Initialize exception."""
super().__init__(*args)
self.domain = domain
self.translation_domain = translation_domain
self.translation_key = translation_key
self.translation_placeholders = translation_placeholders

Expand Down Expand Up @@ -182,8 +182,13 @@ class ServiceNotFound(HomeAssistantError):

def __init__(self, domain: str, service: str) -> None:
"""Initialize error."""
super().__init__(self, f"Service {domain}.{service} not found")
self.translation_placeholders = {"domain": domain, "service": service}
super().__init__(
self,
f"Service {domain}.{service} not found",
translation_domain=None,
translation_key="service_not_found",
translation_placeholders={"domain": domain, "service": service},
)
self.domain = domain
self.service = service

Expand Down
5 changes: 5 additions & 0 deletions homeassistant/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -131,5 +131,10 @@
"cloud_not_connected": "Not connected to Home Assistant Cloud."
}
}
},
"exceptions": {
"service_not_found": {
"message": "Service not found."
}
}
}
Loading

0 comments on commit 20fa883

Please sign in to comment.