Limit legacy state translations to custom components (#112295)

* Limit legacy state translations to custom components

We were trying to load **thousands** of `*.light.json`, `*.switch.json` files at run time that did not exist.

There have been replaced with entity translations: https://github.com/home-assistant/developers.home-assistant/pull/1557 https://github.com/home-assistant/core/pull/82701

https://github.com/home-assistant/core/pull/112023 will completely remove them, but
for now we will only load them for custom components to reduce the number
of files having to be examined

* reduce

* reduce

* reduce

* reduce

* comment

* coverage

* try to remove empty dict in loaded_translations fallback when missing
This commit is contained in:
J. Nick Koston 2024-03-05 12:27:45 -10:00 committed by GitHub
parent d34e2c1f12
commit fbabbc8f92
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 106 additions and 26 deletions

View file

@ -165,20 +165,21 @@ async def _async_get_component_strings(
for language in languages: for language in languages:
files_to_load: dict[str, str] = {} files_to_load: dict[str, str] = {}
files_to_load_by_language[language] = files_to_load files_to_load_by_language[language] = files_to_load
translations_by_language[language] = {}
loaded_translations: dict[str, Any] = {}
translations_by_language[language] = loaded_translations
for loaded in components: for loaded in components:
domain = loaded.partition(".")[0] domain, _, platform = loaded.partition(".")
if not (integration := integrations.get(domain)): if not (integration := integrations.get(domain)):
continue continue
path = component_translation_path(loaded, language, integration) if platform and integration.is_built_in:
# No translation available # Legacy state translations are no longer used for built-in integrations
if path is None: # and we avoid trying to load them. This is a temporary measure to allow
loaded_translations[loaded] = {} # them to keep working for custom integrations until we can fully remove
else: # them.
continue
if path := component_translation_path(loaded, language, integration):
files_to_load[loaded] = path files_to_load[loaded] = path
if not files_to_load: if not files_to_load:

View file

@ -335,20 +335,57 @@ async def test_get_translation_categories(hass: HomeAssistant) -> None:
assert "component.light.device_automation.action_type.turn_on" in translations assert "component.light.device_automation.action_type.turn_on" in translations
async def test_translation_merging( async def test_legacy_platform_translations_not_used_built_in_integrations(
hass: HomeAssistant, caplog: pytest.LogCaptureFixture hass: HomeAssistant, caplog: pytest.LogCaptureFixture
) -> None: ) -> None:
"""Test we merge translations of two integrations.""" """Test legacy platform translations are not used for built-in integrations."""
hass.config.components.add("moon.sensor") hass.config.components.add("moon.sensor")
hass.config.components.add("sensor") hass.config.components.add("sensor")
load_requests = []
def mock_load_translations_files_by_language(files):
load_requests.append(files)
return {}
with patch(
"homeassistant.helpers.translation._load_translations_files_by_language",
mock_load_translations_files_by_language,
):
await translation.async_get_translations(hass, "en", "state")
assert len(load_requests) == 1
to_load = load_requests[0]
assert len(to_load) == 1
en_load = to_load["en"]
assert len(en_load) == 1
assert "sensor" in en_load
assert "moon.sensor" not in en_load
async def test_translation_merging_custom_components(
hass: HomeAssistant,
caplog: pytest.LogCaptureFixture,
enable_custom_integrations: None,
) -> None:
"""Test we merge translations of two integrations.
Legacy state translations only used for custom integrations.
"""
hass.config.components.add("test_legacy_state_translations.sensor")
hass.config.components.add("sensor")
orig_load_translations = translation._load_translations_files_by_language orig_load_translations = translation._load_translations_files_by_language
def mock_load_translations_files(files): def mock_load_translations_files(files):
"""Mock loading.""" """Mock loading."""
result = orig_load_translations(files) result = orig_load_translations(files)
result["en"]["moon.sensor"] = { result["en"]["test_legacy_state_translations.sensor"] = {
"state": {"moon__phase": {"first_quarter": "First Quarter"}} "state": {
"test_legacy_state_translations__phase": {
"first_quarter": "First Quarter"
}
}
} }
return result return result
@ -358,15 +395,20 @@ async def test_translation_merging(
): ):
translations = await translation.async_get_translations(hass, "en", "state") translations = await translation.async_get_translations(hass, "en", "state")
assert "component.sensor.state.moon__phase.first_quarter" in translations assert (
"component.sensor.state.test_legacy_state_translations__phase.first_quarter"
in translations
)
hass.config.components.add("season.sensor") hass.config.components.add("test_legacy_state_translations_bad_data.sensor")
# Patch in some bad translation data # Patch in some bad translation data
def mock_load_bad_translations_files(files): def mock_load_bad_translations_files(files):
"""Mock loading.""" """Mock loading."""
result = orig_load_translations(files) result = orig_load_translations(files)
result["en"]["season.sensor"] = {"state": "bad data"} result["en"]["test_legacy_state_translations_bad_data.sensor"] = {
"state": "bad data"
}
return result return result
with patch( with patch(
@ -375,7 +417,10 @@ async def test_translation_merging(
): ):
translations = await translation.async_get_translations(hass, "en", "state") translations = await translation.async_get_translations(hass, "en", "state")
assert "component.sensor.state.moon__phase.first_quarter" in translations assert (
"component.sensor.state.test_legacy_state_translations__phase.first_quarter"
in translations
)
assert ( assert (
"An integration providing translations for sensor provided invalid data:" "An integration providing translations for sensor provided invalid data:"
@ -383,17 +428,26 @@ async def test_translation_merging(
) in caplog.text ) in caplog.text
async def test_translation_merging_loaded_apart( async def test_translation_merging_loaded_apart_custom_integrations(
hass: HomeAssistant, caplog: pytest.LogCaptureFixture hass: HomeAssistant,
caplog: pytest.LogCaptureFixture,
enable_custom_integrations: None,
) -> None: ) -> None:
"""Test we merge translations of two integrations when they are not loaded at the same time.""" """Test we merge translations of two integrations when they are not loaded at the same time.
Legacy state translations only used for custom integrations.
"""
orig_load_translations = translation._load_translations_files_by_language orig_load_translations = translation._load_translations_files_by_language
def mock_load_translations_files(files): def mock_load_translations_files(files):
"""Mock loading.""" """Mock loading."""
result = orig_load_translations(files) result = orig_load_translations(files)
result["en"]["moon.sensor"] = { result["en"]["test_legacy_state_translations.sensor"] = {
"state": {"moon__phase": {"first_quarter": "First Quarter"}} "state": {
"test_legacy_state_translations__phase": {
"first_quarter": "First Quarter"
}
}
} }
return result return result
@ -405,9 +459,12 @@ async def test_translation_merging_loaded_apart(
): ):
translations = await translation.async_get_translations(hass, "en", "state") translations = await translation.async_get_translations(hass, "en", "state")
assert "component.sensor.state.moon__phase.first_quarter" not in translations assert (
"component.sensor.state.test_legacy_state_translations__phase.first_quarter"
not in translations
)
hass.config.components.add("moon.sensor") hass.config.components.add("test_legacy_state_translations.sensor")
with patch( with patch(
"homeassistant.helpers.translation._load_translations_files_by_language", "homeassistant.helpers.translation._load_translations_files_by_language",
@ -415,7 +472,10 @@ async def test_translation_merging_loaded_apart(
): ):
translations = await translation.async_get_translations(hass, "en", "state") translations = await translation.async_get_translations(hass, "en", "state")
assert "component.sensor.state.moon__phase.first_quarter" in translations assert (
"component.sensor.state.test_legacy_state_translations__phase.first_quarter"
in translations
)
with patch( with patch(
"homeassistant.helpers.translation._load_translations_files_by_language", "homeassistant.helpers.translation._load_translations_files_by_language",
@ -425,7 +485,10 @@ async def test_translation_merging_loaded_apart(
hass, "en", "state", integrations={"sensor"} hass, "en", "state", integrations={"sensor"}
) )
assert "component.sensor.state.moon__phase.first_quarter" in translations assert (
"component.sensor.state.test_legacy_state_translations__phase.first_quarter"
in translations
)
async def test_translation_merging_loaded_together( async def test_translation_merging_loaded_together(

View file

@ -0,0 +1 @@
"""Provide a mock package component."""

View file

@ -0,0 +1,7 @@
{
"domain": "test_legacy_state_translations",
"name": "Test package for legacy state translations",
"documentation": "http://test-package.io",
"config_flow": true,
"version": "1.2.3"
}

View file

@ -0,0 +1 @@
"""Provide a mock package component."""

View file

@ -0,0 +1,7 @@
{
"domain": "test_legacy_state_translations_bad_data",
"name": "Test package for legacy state translations",
"documentation": "http://test-package.io",
"config_flow": true,
"version": "1.2.3"
}