Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 31 additions & 43 deletions homeassistant/helpers/translation.py
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -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
"""
is_platform = '.' in component

if not is_platform:
module = get_component(hass, component)
assert module is not None
If component is just a single file, will return None.
"""
parts = component.split('.')
domain = parts[-1]
is_platform = len(parts) == 2

module_path = pathlib.Path(module.__file__)
integration = await async_get_integration(hass, domain)
assert integration is not None, domain

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]) \
Expand Down Expand Up @@ -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:
Expand Down
15 changes: 8 additions & 7 deletions tests/helpers/test_translation.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
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
Expand Down Expand Up @@ -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'))

Expand Down Expand Up @@ -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')
Expand Down