diff --git a/homeassistant/components/frontend/__init__.py b/homeassistant/components/frontend/__init__.py index 0c540a477ef..91b9c9a126f 100644 --- a/homeassistant/components/frontend/__init__.py +++ b/homeassistant/components/frontend/__init__.py @@ -667,7 +667,7 @@ def websocket_get_themes( "type": "frontend/get_translations", vol.Required("language"): str, vol.Required("category"): str, - vol.Optional("integration"): str, + vol.Optional("integration"): vol.All(cv.ensure_list, [str]), vol.Optional("config_flow"): bool, } ) diff --git a/homeassistant/components/onboarding/views.py b/homeassistant/components/onboarding/views.py index b277bd97edf..07b3dfa9cc2 100644 --- a/homeassistant/components/onboarding/views.py +++ b/homeassistant/components/onboarding/views.py @@ -15,6 +15,7 @@ from homeassistant.components.http.data_validator import RequestDataValidator from homeassistant.components.http.view import HomeAssistantView from homeassistant.core import callback from homeassistant.helpers.system_info import async_get_system_info +from homeassistant.helpers.translation import async_get_translations from .const import ( DEFAULT_AREAS, @@ -147,8 +148,8 @@ class UserOnboardingView(_BaseOnboardingView): await person.async_create_person(hass, data["name"], user_id=user.id) # Create default areas using the users supplied language. - translations = await hass.helpers.translation.async_get_translations( - data["language"], "area", DOMAIN + translations = await async_get_translations( + hass, data["language"], "area", {DOMAIN} ) area_registry = await hass.helpers.area_registry.async_get_registry() diff --git a/homeassistant/helpers/translation.py b/homeassistant/helpers/translation.py index 976c66dda56..cda50de535b 100644 --- a/homeassistant/helpers/translation.py +++ b/homeassistant/helpers/translation.py @@ -3,7 +3,7 @@ from __future__ import annotations import asyncio from collections import ChainMap -from collections.abc import Mapping +from collections.abc import Iterable, Mapping import logging from typing import Any @@ -286,7 +286,7 @@ async def async_get_translations( hass: HomeAssistant, language: str, category: str, - integration: str | None = None, + integrations: Iterable[str] | None = None, config_flow: bool | None = None, ) -> dict[str, Any]: """Return all backend translations. @@ -297,8 +297,8 @@ async def async_get_translations( """ lock = hass.data.setdefault(TRANSLATION_LOAD_LOCK, asyncio.Lock()) - if integration is not None: - components = {integration} + if integrations is not None: + components = set(integrations) elif config_flow: components = (await async_get_config_flows(hass)) - hass.config.components elif category == "state": @@ -310,7 +310,10 @@ async def async_get_translations( } async with lock: - cache = hass.data.setdefault(TRANSLATION_FLATTEN_CACHE, _TranslationCache(hass)) + if TRANSLATION_FLATTEN_CACHE in hass.data: + cache = hass.data[TRANSLATION_FLATTEN_CACHE] + else: + cache = hass.data[TRANSLATION_FLATTEN_CACHE] = _TranslationCache(hass) cached = await cache.async_fetch(language, category, components) return dict(ChainMap(*cached)) diff --git a/tests/components/frontend/test_init.py b/tests/components/frontend/test_init.py index 8c8fd3bf671..84ca04df3ba 100644 --- a/tests/components/frontend/test_init.py +++ b/tests/components/frontend/test_init.py @@ -424,7 +424,7 @@ async def test_get_translations(hass, ws_client): """Test get_translations command.""" with patch( "homeassistant.components.frontend.async_get_translations", - side_effect=lambda hass, lang, category, integration, config_flow: { + side_effect=lambda hass, lang, category, integrations, config_flow: { "lang": lang }, ): @@ -444,6 +444,58 @@ async def test_get_translations(hass, ws_client): assert msg["result"] == {"resources": {"lang": "nl"}} +async def test_get_translations_for_integrations(hass, ws_client): + """Test get_translations for integrations command.""" + with patch( + "homeassistant.components.frontend.async_get_translations", + side_effect=lambda hass, lang, category, integration, config_flow: { + "lang": lang, + "integration": integration, + }, + ): + await ws_client.send_json( + { + "id": 5, + "type": "frontend/get_translations", + "integration": ["frontend", "http"], + "language": "nl", + "category": "lang", + } + ) + msg = await ws_client.receive_json() + + assert msg["id"] == 5 + assert msg["type"] == TYPE_RESULT + assert msg["success"] + assert set(msg["result"]["resources"]["integration"]) == {"frontend", "http"} + + +async def test_get_translations_for_single_integration(hass, ws_client): + """Test get_translations for integration command.""" + with patch( + "homeassistant.components.frontend.async_get_translations", + side_effect=lambda hass, lang, category, integrations, config_flow: { + "lang": lang, + "integration": integrations, + }, + ): + await ws_client.send_json( + { + "id": 5, + "type": "frontend/get_translations", + "integration": "http", + "language": "nl", + "category": "lang", + } + ) + msg = await ws_client.receive_json() + + assert msg["id"] == 5 + assert msg["type"] == TYPE_RESULT + assert msg["success"] + assert msg["result"] == {"resources": {"lang": "nl", "integration": ["http"]}} + + async def test_auth_load(hass): """Test auth component loaded by default.""" frontend = await async_get_integration(hass, "frontend") diff --git a/tests/helpers/test_translation.py b/tests/helpers/test_translation.py index 5520269ca9d..2e30a649a7b 100644 --- a/tests/helpers/test_translation.py +++ b/tests/helpers/test_translation.py @@ -290,12 +290,29 @@ async def test_translation_merging_loaded_apart(hass, caplog): assert "component.sensor.state.moon__phase.first_quarter" in translations translations = await translation.async_get_translations( - hass, "en", "state", integration="sensor" + hass, "en", "state", integrations={"sensor"} ) assert "component.sensor.state.moon__phase.first_quarter" in translations +async def test_translation_merging_loaded_together(hass, caplog): + """Test we merge translations of two integrations when they are loaded at the same time.""" + hass.config.components.add("hue") + hass.config.components.add("homekit") + hue_translations = await translation.async_get_translations( + hass, "en", "config", integrations={"hue"} + ) + homekit_translations = await translation.async_get_translations( + hass, "en", "config", integrations={"homekit"} + ) + + translations = await translation.async_get_translations( + hass, "en", "config", integrations={"hue", "homekit"} + ) + assert translations == hue_translations | homekit_translations + + async def test_caching(hass): """Test we cache data.""" hass.config.components.add("sensor") @@ -320,14 +337,14 @@ async def test_caching(hass): ) load_sensor_only = await translation.async_get_translations( - hass, "en", "state", integration="sensor" + hass, "en", "state", integrations={"sensor"} ) assert load_sensor_only for key in load_sensor_only: assert key.startswith("component.sensor.state.") load_light_only = await translation.async_get_translations( - hass, "en", "state", integration="light" + hass, "en", "state", integrations={"light"} ) assert load_light_only for key in load_light_only: @@ -341,7 +358,7 @@ async def test_caching(hass): side_effect=translation._build_resources, ) as mock_build: load_sensor_only = await translation.async_get_translations( - hass, "en", "title", integration="sensor" + hass, "en", "title", integrations={"sensor"} ) assert load_sensor_only for key in load_sensor_only: @@ -349,12 +366,12 @@ async def test_caching(hass): assert len(mock_build.mock_calls) == 0 assert await translation.async_get_translations( - hass, "en", "title", integration="sensor" + hass, "en", "title", integrations={"sensor"} ) assert len(mock_build.mock_calls) == 0 load_light_only = await translation.async_get_translations( - hass, "en", "title", integration="media_player" + hass, "en", "title", integrations={"media_player"} ) assert load_light_only for key in load_light_only: