"""Translation string lookup helpers.""" import logging from os import path from homeassistant import config_entries from homeassistant.loader import get_component, bind_hass from homeassistant.util.json import load_json _LOGGER = logging.getLogger(__name__) TRANSLATION_STRING_CACHE = 'translation_string_cache' def recursive_flatten(prefix, data): """Return a flattened representation of dict data.""" output = {} for key, value in data.items(): if isinstance(value, dict): output.update( recursive_flatten('{}{}.'.format(prefix, key), value)) else: output['{}{}'.format(prefix, key)] = value return output def flatten(data): """Return a flattened representation of dict data.""" return recursive_flatten('', data) def component_translation_file(hass, component, language): """Return the translation json file location for a component.""" if '.' in component: name = component.split('.', 1)[1] else: name = component module = get_component(hass, component) component_path = path.dirname(module.__file__) # If loading translations for the package root, (__init__.py), the # prefix should be skipped. if module.__name__ == module.__package__: filename = '{}.json'.format(language) else: filename = '{}.{}.json'.format(name, language) return path.join(component_path, '.translations', filename) def load_translations_files(translation_files): """Load and parse translation.json files.""" loaded = {} for component, translation_file in translation_files.items(): loaded[component] = load_json(translation_file) return loaded def build_resources(translation_cache, components): """Build the resources response for the given components.""" # Build response resources = {} for component in components: if '.' not in component: domain = component else: domain = component.split('.', 1)[0] if domain not in resources: resources[domain] = {} # Add the translations for this component to the domain resources. # Since clients cannot determine which platform an entity belongs to, # all translations for a domain will be returned together. resources[domain].update(translation_cache[component]) return resources @bind_hass async def async_get_component_resources(hass, language): """Return translation resources for all components.""" if TRANSLATION_STRING_CACHE not in hass.data: hass.data[TRANSLATION_STRING_CACHE] = {} if language not in hass.data[TRANSLATION_STRING_CACHE]: hass.data[TRANSLATION_STRING_CACHE][language] = {} translation_cache = hass.data[TRANSLATION_STRING_CACHE][language] # Get the set of components components = hass.config.components | set(config_entries.FLOWS) # Calculate the missing components missing_components = components - set(translation_cache) missing_files = {} for component in missing_components: missing_files[component] = component_translation_file( hass, component, language) # Load missing files if missing_files: loaded_translations = await hass.async_add_job( load_translations_files, missing_files) # Update cache for component, translation_data in loaded_translations.items(): translation_cache[component] = translation_data resources = build_resources(translation_cache, components) # Return the component translations resources under the 'component' # translation namespace return flatten({'component': resources}) @bind_hass async def async_get_translations(hass, language): """Return all backend translations.""" resources = await async_get_component_resources(hass, language) if language != 'en': # Fetch the English resources, as a fallback for missing keys base_resources = await async_get_component_resources(hass, 'en') resources = {**base_resources, **resources} return resources