diff --git a/homeassistant/helpers/translation.py b/homeassistant/helpers/translation.py index 63a6421d5f66..24e6f4f390d9 100644 --- a/homeassistant/helpers/translation.py +++ b/homeassistant/helpers/translation.py @@ -1,10 +1,9 @@ """Translation string lookup helpers.""" import logging -import pathlib -from typing import Any, Dict, Iterable +from typing import Any, Dict, Iterable, Optional from homeassistant import config_entries -from homeassistant.loader import get_component, get_platform, bind_hass +from homeassistant.loader import async_get_integration, bind_hass from homeassistant.util.json import load_json from .typing import HomeAssistantType @@ -30,53 +29,38 @@ def flatten(data: Dict) -> Dict[str, Any]: return recursive_flatten('', data) -def component_translation_file(hass: HomeAssistantType, component: str, - language: str) -> str: +async def component_translation_file(hass: HomeAssistantType, component: str, + language: str) -> Optional[str]: """Return the translation json file location for a component. - For component one of: - - components/light/.translations/nl.json - - components/.translations/group.nl.json + For component: + - components/hue/.translations/nl.json - For platform one of: - - components/light/.translations/hue.nl.json + For platform: - components/hue/.translations/light.nl.json + + If component is just a single file, will return None. """ - is_platform = '.' in component + parts = component.split('.') + domain = parts[-1] + is_platform = len(parts) == 2 - if not is_platform: - module = get_component(hass, component) - assert module is not None + integration = await async_get_integration(hass, domain) + assert integration is not None, domain - module_path = pathlib.Path(module.__file__) - - if module.__name__ == module.__package__: - # light/__init__.py - filename = '{}.json'.format(language) - else: - # group.py - filename = '{}.{}.json'.format(component, language) - - return str(module_path.parent / '.translations' / filename) - - # It's a platform - parts = component.split('.', 1) - module = get_platform(hass, *parts) - assert module is not None, component - - # Either within HA or custom_components - # Either light/hue.py or hue/light.py - module_path = pathlib.Path(module.__file__) - - # Compare to parent so we don't have to strip off `.py` - if module_path.parent.name == parts[0]: - # this is light/hue.py - filename = "{}.{}.json".format(parts[1], language) - else: - # this is hue/light.py + if is_platform: filename = "{}.{}.json".format(parts[0], language) + return str(integration.file_path / '.translations' / filename) - return str(module_path.parent / '.translations' / filename) + module = integration.get_component() + + # If it's a component that is just one file, we don't support translations + # Example custom_components/my_component.py + if module.__name__ != module.__package__: + return None + + filename = '{}.json'.format(language) + return str(integration.file_path / '.translations' / filename) def load_translations_files(translation_files: Dict[str, str]) \ @@ -130,8 +114,12 @@ async def async_get_component_resources(hass: HomeAssistantType, missing_components = components - set(translation_cache) missing_files = {} for component in missing_components: - missing_files[component] = component_translation_file( - hass, component, language) + path = await component_translation_file(hass, component, language) + # No translation available + if path is None: + translation_cache[component] = {} + else: + missing_files[component] = path # Load missing files if missing_files: diff --git a/tests/helpers/test_translation.py b/tests/helpers/test_translation.py index 34d929b285af..ebf883bfe128 100644 --- a/tests/helpers/test_translation.py +++ b/tests/helpers/test_translation.py @@ -8,6 +8,7 @@ import pytest from homeassistant import config_entries import homeassistant.helpers.translation as translation from homeassistant.setup import async_setup_component +from tests.common import mock_coro @pytest.fixture @@ -52,20 +53,20 @@ async def test_component_translation_file(hass): 'test_package' }) - assert path.normpath(translation.component_translation_file( + assert path.normpath(await translation.component_translation_file( hass, 'switch.test', 'en')) == path.normpath(hass.config.path( 'custom_components', 'test', '.translations', 'switch.en.json')) - assert path.normpath(translation.component_translation_file( + assert path.normpath(await translation.component_translation_file( hass, 'switch.test_embedded', 'en')) == path.normpath(hass.config.path( 'custom_components', 'test_embedded', '.translations', 'switch.en.json')) - assert path.normpath(translation.component_translation_file( - hass, 'test_standalone', 'en')) == path.normpath(hass.config.path( - 'custom_components', '.translations', 'test_standalone.en.json')) + assert await translation.component_translation_file( + hass, 'test_standalone', 'en' + ) is None - assert path.normpath(translation.component_translation_file( + assert path.normpath(await translation.component_translation_file( hass, 'test_package', 'en')) == path.normpath(hass.config.path( 'custom_components', 'test_package', '.translations', 'en.json')) @@ -133,7 +134,7 @@ async def test_get_translations_loads_config_flows(hass, mock_config_flows): mock_config_flows.append('component1') with patch.object(translation, 'component_translation_file', - return_value='bla.json'), \ + return_value=mock_coro('bla.json')), \ patch.object(translation, 'load_translations_files', return_value={ 'component1': {'hello': 'world'}}): translations = await translation.async_get_translations(hass, 'en')