mirror of
https://github.com/home-assistant/core.git
synced 2025-04-19 14:57:52 +00:00
Remove unused legacy state translations (#112023)
* Remove unused state translations 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 * nothing does merging anymore * useless dispatch * remove * remove platform code from hassfest * preen * Update homeassistant/helpers/translation.py * ruff * fix merge * check is impossible now since we already know if translations exist or not * keep the function for now * remove unreachable code since we filter out `.` before now * reduce * reduce * fix merge conflict (again)
This commit is contained in:
parent
0200d1aa66
commit
33412dd9f6
@ -6,6 +6,7 @@ import asyncio
|
||||
from collections.abc import Iterable, Mapping
|
||||
from contextlib import suppress
|
||||
import logging
|
||||
import pathlib
|
||||
import string
|
||||
from typing import Any
|
||||
|
||||
@ -41,40 +42,18 @@ def recursive_flatten(prefix: Any, data: dict[str, Any]) -> dict[str, Any]:
|
||||
|
||||
|
||||
@callback
|
||||
def component_translation_path(
|
||||
component: str, language: str, integration: Integration
|
||||
) -> str | None:
|
||||
def component_translation_path(language: str, integration: Integration) -> pathlib.Path:
|
||||
"""Return the translation json file location for a component.
|
||||
|
||||
For component:
|
||||
- components/hue/translations/nl.json
|
||||
|
||||
For platform:
|
||||
- components/hue/translations/light.nl.json
|
||||
|
||||
If component is just a single file, will return None.
|
||||
"""
|
||||
parts = component.split(".")
|
||||
domain = parts[0]
|
||||
is_platform = len(parts) == 2
|
||||
|
||||
# If it's a component that is just one file, we don't support translations
|
||||
# Example custom_components/my_component.py
|
||||
if integration.file_path.name != domain:
|
||||
return None
|
||||
|
||||
if is_platform:
|
||||
filename = f"{parts[1]}.{language}.json"
|
||||
else:
|
||||
filename = f"{language}.json"
|
||||
|
||||
translation_path = integration.file_path / "translations"
|
||||
|
||||
return str(translation_path / filename)
|
||||
return integration.file_path / "translations" / f"{language}.json"
|
||||
|
||||
|
||||
def _load_translations_files_by_language(
|
||||
translation_files: dict[str, dict[str, str]],
|
||||
translation_files: dict[str, dict[str, pathlib.Path]],
|
||||
) -> dict[str, dict[str, Any]]:
|
||||
"""Load and parse translation.json files."""
|
||||
loaded: dict[str, dict[str, Any]] = {}
|
||||
@ -98,47 +77,6 @@ def _load_translations_files_by_language(
|
||||
return loaded
|
||||
|
||||
|
||||
def _merge_resources(
|
||||
translation_strings: dict[str, dict[str, Any]],
|
||||
components: set[str],
|
||||
category: str,
|
||||
) -> dict[str, dict[str, Any]]:
|
||||
"""Build and merge the resources response for the given components and platforms."""
|
||||
# Build response
|
||||
resources: dict[str, dict[str, Any]] = {}
|
||||
for component in components:
|
||||
domain = component.rpartition(".")[-1]
|
||||
|
||||
domain_resources = resources.setdefault(domain, {})
|
||||
|
||||
# Integrations are able to provide translations for their entities under other
|
||||
# integrations if they don't have an existing device class. This is done by
|
||||
# using a custom device class prefixed with their domain and two underscores.
|
||||
# These files are in platform specific files in the integration folder with
|
||||
# names like `strings.sensor.json`.
|
||||
# We are going to merge the translations for the custom device classes into
|
||||
# the translations of sensor.
|
||||
|
||||
new_value = translation_strings.get(component, {}).get(category)
|
||||
|
||||
if new_value is None:
|
||||
continue
|
||||
|
||||
if isinstance(new_value, dict):
|
||||
domain_resources.update(new_value)
|
||||
else:
|
||||
_LOGGER.error(
|
||||
(
|
||||
"An integration providing translations for %s provided invalid"
|
||||
" data: %s"
|
||||
),
|
||||
domain,
|
||||
new_value,
|
||||
)
|
||||
|
||||
return resources
|
||||
|
||||
|
||||
def build_resources(
|
||||
translation_strings: dict[str, dict[str, dict[str, Any] | str]],
|
||||
components: set[str],
|
||||
@ -163,32 +101,20 @@ async def _async_get_component_strings(
|
||||
"""Load translations."""
|
||||
translations_by_language: dict[str, dict[str, Any]] = {}
|
||||
# Determine paths of missing components/platforms
|
||||
files_to_load_by_language: dict[str, dict[str, str]] = {}
|
||||
files_to_load_by_language: dict[str, dict[str, pathlib.Path]] = {}
|
||||
loaded_translations_by_language: dict[str, dict[str, Any]] = {}
|
||||
has_files_to_load = False
|
||||
for language in languages:
|
||||
files_to_load: dict[str, str] = {}
|
||||
files_to_load_by_language[language] = files_to_load
|
||||
translations_by_language[language] = {}
|
||||
|
||||
for comp in components:
|
||||
domain, _, platform = comp.partition(".")
|
||||
files_to_load: dict[str, pathlib.Path] = {
|
||||
domain: component_translation_path(language, integration)
|
||||
for domain in components
|
||||
if (
|
||||
not (integration := integrations.get(domain))
|
||||
or not integration.has_translations
|
||||
):
|
||||
continue
|
||||
|
||||
if platform and integration.is_built_in:
|
||||
# Legacy state translations are no longer used for built-in integrations
|
||||
# and we avoid trying to load them. This is a temporary measure to allow
|
||||
# them to keep working for custom integrations until we can fully remove
|
||||
# them.
|
||||
continue
|
||||
|
||||
if path := component_translation_path(comp, language, integration):
|
||||
files_to_load[comp] = path
|
||||
has_files_to_load = True
|
||||
(integration := integrations.get(domain))
|
||||
and integration.has_translations
|
||||
)
|
||||
}
|
||||
files_to_load_by_language[language] = files_to_load
|
||||
has_files_to_load |= bool(files_to_load)
|
||||
|
||||
if has_files_to_load:
|
||||
loaded_translations_by_language = await hass.async_add_executor_job(
|
||||
@ -197,18 +123,15 @@ async def _async_get_component_strings(
|
||||
|
||||
for language in languages:
|
||||
loaded_translations = loaded_translations_by_language.setdefault(language, {})
|
||||
for comp in components:
|
||||
if "." in comp:
|
||||
continue
|
||||
|
||||
for domain in components:
|
||||
# Translations that miss "title" will get integration put in.
|
||||
component_translations = loaded_translations.setdefault(comp, {})
|
||||
component_translations = loaded_translations.setdefault(domain, {})
|
||||
if "title" not in component_translations and (
|
||||
integration := integrations.get(comp)
|
||||
integration := integrations.get(domain)
|
||||
):
|
||||
component_translations["title"] = integration.name
|
||||
|
||||
translations_by_language[language].update(loaded_translations)
|
||||
translations_by_language.setdefault(language, {}).update(loaded_translations)
|
||||
|
||||
return translations_by_language
|
||||
|
||||
@ -355,10 +278,12 @@ class _TranslationCache:
|
||||
_LOGGER.error(
|
||||
(
|
||||
"Validation of translation placeholders for localized (%s) string "
|
||||
"%s failed"
|
||||
"%s failed: (%s != %s)"
|
||||
),
|
||||
language,
|
||||
key,
|
||||
updated_placeholders,
|
||||
cached_placeholders,
|
||||
)
|
||||
mismatches.add(key)
|
||||
|
||||
@ -382,17 +307,7 @@ class _TranslationCache:
|
||||
categories.update(resource)
|
||||
|
||||
for category in categories:
|
||||
new_resources: Mapping[str, dict[str, Any] | str]
|
||||
|
||||
if category in ("state", "entity_component"):
|
||||
new_resources = _merge_resources(
|
||||
translation_strings, components, category
|
||||
)
|
||||
else:
|
||||
new_resources = build_resources(
|
||||
translation_strings, components, category
|
||||
)
|
||||
|
||||
new_resources = build_resources(translation_strings, components, category)
|
||||
category_cache = cached.setdefault(category, {})
|
||||
|
||||
for component, resource in new_resources.items():
|
||||
@ -430,7 +345,7 @@ async def async_get_translations(
|
||||
elif integrations is not None:
|
||||
components = set(integrations)
|
||||
else:
|
||||
components = _async_get_components(hass, category)
|
||||
components = {comp for comp in hass.config.components if "." not in comp}
|
||||
|
||||
return await _async_get_translations_cache(hass).async_fetch(
|
||||
language, category, components
|
||||
@ -452,7 +367,7 @@ def async_get_cached_translations(
|
||||
if integration is not None:
|
||||
components = {integration}
|
||||
else:
|
||||
components = _async_get_components(hass, category)
|
||||
components = {comp for comp in hass.config.components if "." not in comp}
|
||||
|
||||
return _async_get_translations_cache(hass).get_cached(
|
||||
language, category, components
|
||||
@ -466,21 +381,6 @@ def _async_get_translations_cache(hass: HomeAssistant) -> _TranslationCache:
|
||||
return cache
|
||||
|
||||
|
||||
_DIRECT_MAPPED_CATEGORIES = {"state", "entity_component", "services"}
|
||||
|
||||
|
||||
@callback
|
||||
def _async_get_components(
|
||||
hass: HomeAssistant,
|
||||
category: str,
|
||||
) -> set[str]:
|
||||
"""Return a set of components for which translations should be loaded."""
|
||||
if category in _DIRECT_MAPPED_CATEGORIES:
|
||||
return hass.config.components
|
||||
# Only 'state' supports merging, so remove platforms from selection
|
||||
return {component for component in hass.config.components if "." not in component}
|
||||
|
||||
|
||||
@callback
|
||||
def async_setup(hass: HomeAssistant) -> None:
|
||||
"""Create translation cache and register listeners for translation loaders.
|
||||
@ -590,13 +490,4 @@ def async_translate_state(
|
||||
if localize_key in translations:
|
||||
return translations[localize_key]
|
||||
|
||||
translations = async_get_cached_translations(hass, language, "state", domain)
|
||||
if device_class is not None:
|
||||
localize_key = f"component.{domain}.state.{device_class}.{state}"
|
||||
if localize_key in translations:
|
||||
return translations[localize_key]
|
||||
localize_key = f"component.{domain}.state._.{state}"
|
||||
if localize_key in translations:
|
||||
return translations[localize_key]
|
||||
|
||||
return state
|
||||
|
@ -3,7 +3,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from functools import partial
|
||||
from itertools import chain
|
||||
import json
|
||||
import re
|
||||
from typing import Any
|
||||
@ -12,7 +11,6 @@ import voluptuous as vol
|
||||
from voluptuous.humanize import humanize_error
|
||||
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.util import slugify
|
||||
from script.translations import upload
|
||||
|
||||
from .model import Config, Integration
|
||||
@ -414,49 +412,6 @@ def gen_ha_hardware_schema(config: Config, integration: Integration):
|
||||
)
|
||||
|
||||
|
||||
def gen_platform_strings_schema(config: Config, integration: Integration) -> vol.Schema:
|
||||
"""Generate platform strings schema like strings.sensor.json.
|
||||
|
||||
Example of valid data:
|
||||
{
|
||||
"state": {
|
||||
"moon__phase": {
|
||||
"full": "Full"
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
def device_class_validator(value: str) -> str:
|
||||
"""Key validator for platform states.
|
||||
|
||||
Platform states are only allowed to provide states for device classes they prefix.
|
||||
"""
|
||||
if not value.startswith(f"{integration.domain}__"):
|
||||
raise vol.Invalid(
|
||||
f"Device class need to start with '{integration.domain}__'. Key {value} is invalid. See https://developers.home-assistant.io/docs/internationalization/core#stringssensorjson"
|
||||
)
|
||||
|
||||
slug_friendly = value.replace("__", "_", 1)
|
||||
slugged = slugify(slug_friendly)
|
||||
|
||||
if slug_friendly != slugged:
|
||||
raise vol.Invalid(
|
||||
f"invalid device class {value}. After domain__, needs to be all lowercase, no spaces."
|
||||
)
|
||||
|
||||
return value
|
||||
|
||||
return vol.Schema(
|
||||
{
|
||||
vol.Optional("state"): cv.schema_with_slug_keys(
|
||||
cv.schema_with_slug_keys(str, slug_validator=translation_key_validator),
|
||||
slug_validator=device_class_validator,
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
ONBOARDING_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required("area"): {str: translation_value_validator},
|
||||
@ -525,32 +480,6 @@ def validate_translation_file( # noqa: C901
|
||||
"name or add exception to ALLOW_NAME_TRANSLATION",
|
||||
)
|
||||
|
||||
platform_string_schema = gen_platform_strings_schema(config, integration)
|
||||
platform_strings = [integration.path.glob("strings.*.json")]
|
||||
|
||||
if config.specific_integrations:
|
||||
platform_strings.append(integration.path.glob("translations/*.en.json"))
|
||||
|
||||
for path in chain(*platform_strings):
|
||||
name = str(path.relative_to(integration.path))
|
||||
|
||||
try:
|
||||
strings = json.loads(path.read_text())
|
||||
except ValueError as err:
|
||||
integration.add_error("translations", f"Invalid JSON in {name}: {err}")
|
||||
continue
|
||||
|
||||
try:
|
||||
platform_string_schema(strings)
|
||||
except vol.Invalid as err:
|
||||
msg = f"Invalid {path.name}: {humanize_error(strings, err)}"
|
||||
if config.specific_integrations:
|
||||
integration.add_warning("translations", msg)
|
||||
else:
|
||||
integration.add_error("translations", msg)
|
||||
else:
|
||||
find_references(strings, path.name, references)
|
||||
|
||||
if config.specific_integrations:
|
||||
return
|
||||
|
||||
|
@ -2050,12 +2050,7 @@ async def test_state_translated(
|
||||
):
|
||||
if category == "entity":
|
||||
return {
|
||||
"component.hue.entity.light.translation_key.state.on": "state_is_on"
|
||||
}
|
||||
if category == "state":
|
||||
return {
|
||||
"component.some_domain.state.some_device_class.off": "state_is_off",
|
||||
"component.some_domain.state._.foo": "state_is_foo",
|
||||
"component.hue.entity.light.translation_key.state.on": "state_is_on",
|
||||
}
|
||||
return {}
|
||||
|
||||
@ -2066,16 +2061,6 @@ async def test_state_translated(
|
||||
tpl8 = template.Template('{{ state_translated("light.hue_5678") }}', hass)
|
||||
assert tpl8.async_render() == "state_is_on"
|
||||
|
||||
tpl9 = template.Template(
|
||||
'{{ state_translated("some_domain.with_device_class_1") }}', hass
|
||||
)
|
||||
assert tpl9.async_render() == "state_is_off"
|
||||
|
||||
tpl10 = template.Template(
|
||||
'{{ state_translated("some_domain.with_device_class_2") }}', hass
|
||||
)
|
||||
assert tpl10.async_render() == "state_is_foo"
|
||||
|
||||
tpl11 = template.Template('{{ state_translated("domain.is_unavailable") }}', hass)
|
||||
assert tpl11.async_render() == "unavailable"
|
||||
|
||||
|
@ -47,35 +47,10 @@ async def test_component_translation_path(
|
||||
{"switch": [{"platform": "test"}, {"platform": "test_embedded"}]},
|
||||
)
|
||||
assert await async_setup_component(hass, "test_package", {"test_package": None})
|
||||
|
||||
(
|
||||
int_test,
|
||||
int_test_embedded,
|
||||
int_test_package,
|
||||
) = await asyncio.gather(
|
||||
async_get_integration(hass, "test"),
|
||||
async_get_integration(hass, "test_embedded"),
|
||||
async_get_integration(hass, "test_package"),
|
||||
)
|
||||
int_test_package = await async_get_integration(hass, "test_package")
|
||||
|
||||
assert path.normpath(
|
||||
translation.component_translation_path("test.switch", "en", int_test)
|
||||
) == path.normpath(
|
||||
hass.config.path("custom_components", "test", "translations", "switch.en.json")
|
||||
)
|
||||
|
||||
assert path.normpath(
|
||||
translation.component_translation_path(
|
||||
"test_embedded.switch", "en", int_test_embedded
|
||||
)
|
||||
) == path.normpath(
|
||||
hass.config.path(
|
||||
"custom_components", "test_embedded", "translations", "switch.en.json"
|
||||
)
|
||||
)
|
||||
|
||||
assert path.normpath(
|
||||
translation.component_translation_path("test_package", "en", int_test_package)
|
||||
translation.component_translation_path("en", int_test_package)
|
||||
) == path.normpath(
|
||||
hass.config.path("custom_components", "test_package", "translations", "en.json")
|
||||
)
|
||||
@ -86,28 +61,39 @@ def test__load_translations_files_by_language(
|
||||
) -> None:
|
||||
"""Test the load translation files function."""
|
||||
# Test one valid and one invalid file
|
||||
file1 = hass.config.path(
|
||||
"custom_components", "test", "translations", "switch.en.json"
|
||||
)
|
||||
file2 = hass.config.path(
|
||||
en_file = hass.config.path("custom_components", "test", "translations", "en.json")
|
||||
invalid_file = hass.config.path(
|
||||
"custom_components", "test", "translations", "invalid.json"
|
||||
)
|
||||
file3 = hass.config.path(
|
||||
"custom_components", "test", "translations", "_broken.en.json"
|
||||
broken_file = hass.config.path(
|
||||
"custom_components", "test", "translations", "_broken.json"
|
||||
)
|
||||
assert translation._load_translations_files_by_language(
|
||||
{"en": {"switch.test": file1, "invalid": file2, "broken": file3}}
|
||||
) == {
|
||||
"en": {
|
||||
"switch.test": {
|
||||
"state": {"string1": "Value 1", "string2": "Value 2"},
|
||||
"something": "else",
|
||||
},
|
||||
"invalid": {},
|
||||
{
|
||||
"en": {"test": en_file},
|
||||
"invalid": {"test": invalid_file},
|
||||
"broken": {"test": broken_file},
|
||||
}
|
||||
) == {
|
||||
"broken": {},
|
||||
"en": {
|
||||
"test": {
|
||||
"entity": {
|
||||
"switch": {
|
||||
"other1": {"name": "Other 1"},
|
||||
"other2": {"name": "Other 2"},
|
||||
"other3": {"name": "Other 3"},
|
||||
"other4": {"name": "Other 4"},
|
||||
"outlet": {"name": "Outlet " "{placeholder}"},
|
||||
}
|
||||
},
|
||||
"something": "else",
|
||||
}
|
||||
},
|
||||
"invalid": {"test": {}},
|
||||
}
|
||||
assert "Translation file is unexpected type" in caplog.text
|
||||
assert "_broken.en.json" in caplog.text
|
||||
assert "_broken.json" in caplog.text
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@ -185,33 +171,61 @@ async def test_get_translations(
|
||||
hass: HomeAssistant, mock_config_flows, enable_custom_integrations: None
|
||||
) -> None:
|
||||
"""Test the get translations helper."""
|
||||
translations = await translation.async_get_translations(hass, "en", "state")
|
||||
translations = await translation.async_get_translations(hass, "en", "entity")
|
||||
assert translations == {}
|
||||
|
||||
assert await async_setup_component(hass, "switch", {"switch": {"platform": "test"}})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
translations = await translation.async_get_translations(hass, "en", "state")
|
||||
translations = await translation.async_get_translations(
|
||||
hass, "en", "entity", {"test"}
|
||||
)
|
||||
|
||||
assert translations["component.switch.state.string1"] == "Value 1"
|
||||
assert translations["component.switch.state.string2"] == "Value 2"
|
||||
assert translations == {
|
||||
"component.test.entity.switch.other1.name": "Other 1",
|
||||
"component.test.entity.switch.other2.name": "Other 2",
|
||||
"component.test.entity.switch.other3.name": "Other 3",
|
||||
"component.test.entity.switch.other4.name": "Other 4",
|
||||
"component.test.entity.switch.outlet.name": "Outlet {placeholder}",
|
||||
}
|
||||
|
||||
translations = await translation.async_get_translations(hass, "de", "state")
|
||||
assert "component.switch.something" not in translations
|
||||
assert translations["component.switch.state.string1"] == "German Value 1"
|
||||
assert translations["component.switch.state.string2"] == "German Value 2"
|
||||
translations = await translation.async_get_translations(
|
||||
hass, "de", "entity", {"test"}
|
||||
)
|
||||
|
||||
assert translations == {
|
||||
"component.test.entity.switch.other1.name": "Anderes 1",
|
||||
"component.test.entity.switch.other2.name": "Other 2",
|
||||
"component.test.entity.switch.other3.name": "",
|
||||
"component.test.entity.switch.other4.name": "Other 4",
|
||||
"component.test.entity.switch.outlet.name": "Outlet {placeholder}",
|
||||
}
|
||||
|
||||
# Test a partial translation
|
||||
translations = await translation.async_get_translations(hass, "es", "state")
|
||||
assert translations["component.switch.state.string1"] == "Spanish Value 1"
|
||||
assert translations["component.switch.state.string2"] == "Value 2"
|
||||
translations = await translation.async_get_translations(
|
||||
hass, "es", "entity", {"test"}
|
||||
)
|
||||
|
||||
assert translations == {
|
||||
"component.test.entity.switch.other1.name": "Otra 1",
|
||||
"component.test.entity.switch.other2.name": "Otra 2",
|
||||
"component.test.entity.switch.other3.name": "Otra 3",
|
||||
"component.test.entity.switch.other4.name": "Otra 4",
|
||||
"component.test.entity.switch.outlet.name": "Enchufe {placeholder}",
|
||||
}
|
||||
|
||||
# Test that an untranslated language falls back to English.
|
||||
translations = await translation.async_get_translations(
|
||||
hass, "invalid-language", "state"
|
||||
hass, "invalid-language", "entity", {"test"}
|
||||
)
|
||||
assert translations["component.switch.state.string1"] == "Value 1"
|
||||
assert translations["component.switch.state.string2"] == "Value 2"
|
||||
|
||||
assert translations == {
|
||||
"component.test.entity.switch.other1.name": "Other 1",
|
||||
"component.test.entity.switch.other2.name": "Other 2",
|
||||
"component.test.entity.switch.other3.name": "Other 3",
|
||||
"component.test.entity.switch.other4.name": "Other 4",
|
||||
"component.test.entity.switch.outlet.name": "Outlet {placeholder}",
|
||||
}
|
||||
|
||||
|
||||
async def test_get_translations_loads_config_flows(
|
||||
@ -348,162 +362,6 @@ async def test_get_translation_categories(hass: HomeAssistant) -> None:
|
||||
assert "component.light.device_automation.action_type.turn_on" in translations
|
||||
|
||||
|
||||
async def test_legacy_platform_translations_not_used_built_in_integrations(
|
||||
hass: HomeAssistant, caplog: pytest.LogCaptureFixture
|
||||
) -> None:
|
||||
"""Test legacy platform translations are not used for built-in integrations."""
|
||||
hass.config.components.add("moon.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
|
||||
|
||||
def mock_load_translations_files(files):
|
||||
"""Mock loading."""
|
||||
result = orig_load_translations(files)
|
||||
result["en"]["test_legacy_state_translations.sensor"] = {
|
||||
"state": {
|
||||
"test_legacy_state_translations__phase": {
|
||||
"first_quarter": "First Quarter"
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
|
||||
with patch(
|
||||
"homeassistant.helpers.translation._load_translations_files_by_language",
|
||||
side_effect=mock_load_translations_files,
|
||||
):
|
||||
translations = await translation.async_get_translations(hass, "en", "state")
|
||||
|
||||
assert (
|
||||
"component.sensor.state.test_legacy_state_translations__phase.first_quarter"
|
||||
in translations
|
||||
)
|
||||
|
||||
hass.config.components.add("test_legacy_state_translations_bad_data.sensor")
|
||||
|
||||
# Patch in some bad translation data
|
||||
def mock_load_bad_translations_files(files):
|
||||
"""Mock loading."""
|
||||
result = orig_load_translations(files)
|
||||
result["en"]["test_legacy_state_translations_bad_data.sensor"] = {
|
||||
"state": "bad data"
|
||||
}
|
||||
return result
|
||||
|
||||
with patch(
|
||||
"homeassistant.helpers.translation._load_translations_files_by_language",
|
||||
side_effect=mock_load_bad_translations_files,
|
||||
):
|
||||
translations = await translation.async_get_translations(hass, "en", "state")
|
||||
|
||||
assert (
|
||||
"component.sensor.state.test_legacy_state_translations__phase.first_quarter"
|
||||
in translations
|
||||
)
|
||||
|
||||
assert (
|
||||
"An integration providing translations for sensor provided invalid data:"
|
||||
" bad data"
|
||||
) in caplog.text
|
||||
|
||||
|
||||
async def test_translation_merging_loaded_apart_custom_integrations(
|
||||
hass: HomeAssistant,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
enable_custom_integrations: None,
|
||||
) -> None:
|
||||
"""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
|
||||
|
||||
def mock_load_translations_files(files):
|
||||
"""Mock loading."""
|
||||
result = orig_load_translations(files)
|
||||
result["en"]["test_legacy_state_translations.sensor"] = {
|
||||
"state": {
|
||||
"test_legacy_state_translations__phase": {
|
||||
"first_quarter": "First Quarter"
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
|
||||
hass.config.components.add("sensor")
|
||||
|
||||
with patch(
|
||||
"homeassistant.helpers.translation._load_translations_files_by_language",
|
||||
side_effect=mock_load_translations_files,
|
||||
):
|
||||
translations = await translation.async_get_translations(hass, "en", "state")
|
||||
|
||||
assert (
|
||||
"component.sensor.state.test_legacy_state_translations__phase.first_quarter"
|
||||
not in translations
|
||||
)
|
||||
|
||||
hass.config.components.add("test_legacy_state_translations.sensor")
|
||||
|
||||
with patch(
|
||||
"homeassistant.helpers.translation._load_translations_files_by_language",
|
||||
side_effect=mock_load_translations_files,
|
||||
):
|
||||
translations = await translation.async_get_translations(hass, "en", "state")
|
||||
|
||||
assert (
|
||||
"component.sensor.state.test_legacy_state_translations__phase.first_quarter"
|
||||
in translations
|
||||
)
|
||||
|
||||
with patch(
|
||||
"homeassistant.helpers.translation._load_translations_files_by_language",
|
||||
side_effect=mock_load_translations_files,
|
||||
):
|
||||
translations = await translation.async_get_translations(
|
||||
hass, "en", "state", integrations={"sensor"}
|
||||
)
|
||||
|
||||
assert (
|
||||
"component.sensor.state.test_legacy_state_translations__phase.first_quarter"
|
||||
in translations
|
||||
)
|
||||
|
||||
|
||||
async def test_translation_merging_loaded_together(
|
||||
hass: HomeAssistant, caplog: pytest.LogCaptureFixture
|
||||
) -> None:
|
||||
@ -592,14 +450,14 @@ async def test_caching(hass: HomeAssistant) -> None:
|
||||
|
||||
# Patch with same method so we can count invocations
|
||||
with patch(
|
||||
"homeassistant.helpers.translation._merge_resources",
|
||||
side_effect=translation._merge_resources,
|
||||
) as mock_merge:
|
||||
"homeassistant.helpers.translation.build_resources",
|
||||
side_effect=translation.build_resources,
|
||||
) as mock_build_resources:
|
||||
load1 = await translation.async_get_translations(hass, "en", "entity_component")
|
||||
assert len(mock_merge.mock_calls) == 1
|
||||
assert len(mock_build_resources.mock_calls) == 5
|
||||
|
||||
load2 = await translation.async_get_translations(hass, "en", "entity_component")
|
||||
assert len(mock_merge.mock_calls) == 1
|
||||
assert len(mock_build_resources.mock_calls) == 5
|
||||
|
||||
assert load1 == load2
|
||||
|
||||
@ -665,47 +523,58 @@ async def test_custom_component_translations(
|
||||
|
||||
async def test_get_cached_translations(
|
||||
hass: HomeAssistant, mock_config_flows, enable_custom_integrations: None
|
||||
):
|
||||
) -> None:
|
||||
"""Test the get cached translations helper."""
|
||||
translations = translation.async_get_cached_translations(hass, "en", "state")
|
||||
translations = await translation.async_get_translations(hass, "en", "entity")
|
||||
assert translations == {}
|
||||
|
||||
assert await async_setup_component(hass, "switch", {"switch": {"platform": "test"}})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
await translation._async_get_translations_cache(hass).async_load(
|
||||
"en", hass.config.components
|
||||
)
|
||||
translations = translation.async_get_cached_translations(hass, "en", "state")
|
||||
await translation._async_get_translations_cache(hass).async_load("en", {"test"})
|
||||
|
||||
assert translations["component.switch.state.string1"] == "Value 1"
|
||||
assert translations["component.switch.state.string2"] == "Value 2"
|
||||
|
||||
await translation._async_get_translations_cache(hass).async_load(
|
||||
"de", hass.config.components
|
||||
translations = translation.async_get_cached_translations(
|
||||
hass, "en", "entity", "test"
|
||||
)
|
||||
translations = translation.async_get_cached_translations(hass, "de", "state")
|
||||
assert "component.switch.something" not in translations
|
||||
assert translations["component.switch.state.string1"] == "German Value 1"
|
||||
assert translations["component.switch.state.string2"] == "German Value 2"
|
||||
assert translations == {
|
||||
"component.test.entity.switch.other1.name": "Other 1",
|
||||
"component.test.entity.switch.other2.name": "Other 2",
|
||||
"component.test.entity.switch.other3.name": "Other 3",
|
||||
"component.test.entity.switch.other4.name": "Other 4",
|
||||
"component.test.entity.switch.outlet.name": "Outlet {placeholder}",
|
||||
}
|
||||
|
||||
await translation._async_get_translations_cache(hass).async_load("es", {"test"})
|
||||
|
||||
# Test a partial translation
|
||||
await translation._async_get_translations_cache(hass).async_load(
|
||||
"es", hass.config.components
|
||||
translations = translation.async_get_cached_translations(
|
||||
hass, "es", "entity", "test"
|
||||
)
|
||||
|
||||
assert translations == {
|
||||
"component.test.entity.switch.other1.name": "Otra 1",
|
||||
"component.test.entity.switch.other2.name": "Otra 2",
|
||||
"component.test.entity.switch.other3.name": "Otra 3",
|
||||
"component.test.entity.switch.other4.name": "Otra 4",
|
||||
"component.test.entity.switch.outlet.name": "Enchufe {placeholder}",
|
||||
}
|
||||
|
||||
await translation._async_get_translations_cache(hass).async_load(
|
||||
"invalid-language", {"test"}
|
||||
)
|
||||
translations = translation.async_get_cached_translations(hass, "es", "state")
|
||||
assert translations["component.switch.state.string1"] == "Spanish Value 1"
|
||||
assert translations["component.switch.state.string2"] == "Value 2"
|
||||
|
||||
# Test that an untranslated language falls back to English.
|
||||
await translation._async_get_translations_cache(hass).async_load(
|
||||
"invalid-language", hass.config.components
|
||||
)
|
||||
translations = translation.async_get_cached_translations(
|
||||
hass, "invalid-language", "state"
|
||||
hass, "invalid-language", "entity", "test"
|
||||
)
|
||||
assert translations["component.switch.state.string1"] == "Value 1"
|
||||
assert translations["component.switch.state.string2"] == "Value 2"
|
||||
|
||||
assert translations == {
|
||||
"component.test.entity.switch.other1.name": "Other 1",
|
||||
"component.test.entity.switch.other2.name": "Other 2",
|
||||
"component.test.entity.switch.other3.name": "Other 3",
|
||||
"component.test.entity.switch.other4.name": "Other 4",
|
||||
"component.test.entity.switch.outlet.name": "Outlet {placeholder}",
|
||||
}
|
||||
|
||||
|
||||
async def test_setup(hass: HomeAssistant):
|
||||
@ -784,36 +653,6 @@ async def test_translate_state(hass: HomeAssistant):
|
||||
mock.assert_called_once_with(hass, hass.config.language, "entity_component")
|
||||
assert result == "TRANSLATED"
|
||||
|
||||
with patch(
|
||||
"homeassistant.helpers.translation.async_get_cached_translations",
|
||||
return_value={"component.binary_sensor.state.device_class.on": "TRANSLATED"},
|
||||
) as mock:
|
||||
result = translation.async_translate_state(
|
||||
hass, "on", "binary_sensor", "platform", None, "device_class"
|
||||
)
|
||||
mock.assert_has_calls(
|
||||
[
|
||||
call(hass, hass.config.language, "entity_component"),
|
||||
call(hass, hass.config.language, "state", "binary_sensor"),
|
||||
]
|
||||
)
|
||||
assert result == "TRANSLATED"
|
||||
|
||||
with patch(
|
||||
"homeassistant.helpers.translation.async_get_cached_translations",
|
||||
return_value={"component.binary_sensor.state._.on": "TRANSLATED"},
|
||||
) as mock:
|
||||
result = translation.async_translate_state(
|
||||
hass, "on", "binary_sensor", "platform", None, None
|
||||
)
|
||||
mock.assert_has_calls(
|
||||
[
|
||||
call(hass, hass.config.language, "entity_component"),
|
||||
call(hass, hass.config.language, "state", "binary_sensor"),
|
||||
]
|
||||
)
|
||||
assert result == "TRANSLATED"
|
||||
|
||||
with patch(
|
||||
"homeassistant.helpers.translation.async_get_cached_translations",
|
||||
return_value={},
|
||||
@ -824,7 +663,6 @@ async def test_translate_state(hass: HomeAssistant):
|
||||
mock.assert_has_calls(
|
||||
[
|
||||
call(hass, hass.config.language, "entity_component"),
|
||||
call(hass, hass.config.language, "state", "binary_sensor"),
|
||||
]
|
||||
)
|
||||
assert result == "on"
|
||||
@ -840,7 +678,6 @@ async def test_translate_state(hass: HomeAssistant):
|
||||
[
|
||||
call(hass, hass.config.language, "entity"),
|
||||
call(hass, hass.config.language, "entity_component"),
|
||||
call(hass, hass.config.language, "state", "binary_sensor"),
|
||||
]
|
||||
)
|
||||
assert result == "on"
|
||||
|
@ -7,5 +7,6 @@
|
||||
"other4": { "name": "Other 4" },
|
||||
"outlet": { "name": "Outlet {placeholder}" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"something": "else"
|
||||
}
|
||||
|
@ -1,6 +0,0 @@
|
||||
{
|
||||
"state": {
|
||||
"string1": "German Value 1",
|
||||
"string2": "German Value 2"
|
||||
}
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
{
|
||||
"state": {
|
||||
"string1": "Value 1",
|
||||
"string2": "Value 2"
|
||||
},
|
||||
"something": "else"
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
{
|
||||
"state": {
|
||||
"string1": "Spanish Value 1"
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user