Skip to content

Commit

Permalink
Merge pull request #3 from PiotrMachowski/dev
Browse files Browse the repository at this point in the history
v1.2.0
  • Loading branch information
PiotrMachowski authored May 3, 2023
2 parents 93217b6 + dfe0b2c commit 41dc045
Show file tree
Hide file tree
Showing 6 changed files with 140 additions and 43 deletions.
2 changes: 1 addition & 1 deletion .github/FUNDING.yml
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
ko_fi: piotrmachowski
custom: ["buycoffee.to/piotrmachowski", "paypal.me/PiMachowski"]
custom: ["buycoffee.to/piotrmachowski", "paypal.me/PiMachowski", "revolut.me/314ma"]
1 change: 0 additions & 1 deletion .github/workflows/hacs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,3 @@ jobs:
uses: hacs/action@main
with:
category: integration
ignore: brands
45 changes: 45 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
[![Ko-Fi][ko_fi_shield]][ko_fi]
[![buycoffee.to][buycoffee_to_shield]][buycoffee_to]
[![PayPal.Me][paypal_me_shield]][paypal_me]
[![Revolut.Me][revolut_me_shield]][revolut_me]


[hacs_shield]: https://img.shields.io/static/v1.svg?label=HACS&message=Custom&style=popout&color=orange&labelColor=41bdf5&logo=HomeAssistantCommunityStore&logoColor=white
Expand All @@ -31,11 +32,15 @@
[paypal_me_shield]: https://img.shields.io/static/v1.svg?label=%20&message=PayPal.Me&logo=paypal
[paypal_me]: https://paypal.me/PiMachowski

[revolut_me_shield]: https://img.shields.io/static/v1.svg?label=%20&message=Revolut&logo=revolut
[revolut_me]: https://revolut.me/314ma


# Custom Templates

This integration adds possibility to use new functions in Home Assistant Jinja2 templating engine:
- `ct_state_translated` - returns translated state of an entity
- `ct_state_attr_translated` - returns translated value of an attribute of an entity
- `ct_translated` - returns translation for a given key
- `ct_all_translations` - returns all available translations (that can be used with `ct_translated`)
- `ct_eval` - evaluates text as a template
Expand Down Expand Up @@ -81,6 +86,45 @@ Translated nl: Onder de horizon
</tr>
</table>

### `ct_state_attr_translated`

This function returns translated value of an attribute of an entity.

<table>
<tr>
<th>
Input
</th>
<th>
Output
</th>
</tr>
<tr>
<td>

```
Attribute: {{ state_attr("automation.example", "mode") }}
Translated en: {{ ct_state_attr_translated("automation.example", "mode", "en") }}
Translated en: {{ "automation.example" | ct_state_attr_translated("mode", "en") }}
Translated nl: {{ ct_state_attr_translated("automation.example", "mode", "nl") }}
Translated nl: {{ "automation.example" | ct_state_attr_translated("mode", "nl") }}
```

</td>
<td>

```
Attribute: single
Translated en: Single
Translated en: Single
Translated nl: Enkelvoudig
Translated nl: Enkelvoudig
```

</td>
</tr>
</table>

### `ct_translated`

This function returns translation for a given key. You can use `ct_all_translations` to check available keys.
Expand Down Expand Up @@ -224,3 +268,4 @@ Finally, restart Home Assistant and configure the integration.
<a href='https://ko-fi.com/piotrmachowski' target='_blank'><img height='35px' src='https://az743702.vo.msecnd.net/cdn/kofi3.png?v=0' border='0' alt='Buy Me a Coffee at ko-fi.com' />
<a href="https://buycoffee.to/piotrmachowski" target="_blank"><img src="https://buycoffee.to/btn/buycoffeeto-btn-primary.svg" height="35px" alt="Postaw mi kawę na buycoffee.to"></a>
<a href="https://paypal.me/PiMachowski" target="_blank"><img src="https://www.paypalobjects.com/webstatic/mktg/logo/pp_cc_mark_37x23.jpg" border="0" alt="PayPal Logo" height="35px" style="height: auto !important;width: auto !important;"></a>
<a href="https://revolut.me/314ma" target="_blank"><img src="https://www.revolut.com/favicon/android-chrome-192x192.png" height="35px" alt="Revolut"></a>
126 changes: 90 additions & 36 deletions custom_components/custom_templates/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,32 @@
from homeassistant.loader import bind_hass

from .const import (DOMAIN, CUSTOM_TEMPLATES_SCHEMA, CONF_PRELOAD_TRANSLATIONS, CONST_EVAL_FUNCTION_NAME,
CONST_STATE_TRANSLATED_FUNCTION_NAME, CONST_TRANSLATED_FUNCTION_NAME,
CONST_ALL_TRANSLATIONS_FUNCTION_NAME)
CONST_STATE_TRANSLATED_FUNCTION_NAME, CONST_STATE_ATTR_TRANSLATED_FUNCTION_NAME,
CONST_TRANSLATED_FUNCTION_NAME, CONST_ALL_TRANSLATIONS_FUNCTION_NAME)

_LOGGER = logging.getLogger(__name__)

CONFIG_SCHEMA = CUSTOM_TEMPLATES_SCHEMA


class StateTranslated:
class TranslatableTemplate:

def __init__(self, hass: HomeAssistant, available_languages):
self._hass = hass
self._available_languages = available_languages

def __call__(self, entity_id: str, language: str):
def validate_language(self, language):
if language not in self._available_languages:
return f"Language {language} is not loaded"
raise TemplateError(f"Language {language} is not loaded") # type: ignore[arg-type]


class StateTranslated(TranslatableTemplate):

def __init__(self, hass: HomeAssistant, available_languages):
super().__init__(hass, available_languages)

def __call__(self, entity_id: str, language: str):
self.validate_language(language)
state = None
if "." in entity_id:
state = _get_state_if_valid(self._hass, entity_id)
Expand All @@ -42,8 +51,10 @@ def __call__(self, entity_id: str, language: str):
if state is None:
return STATE_UNKNOWN
entry = async_get(self._hass).async_get(entity_id)
translations = []
key = ""
translations = get_cached_translations(self._hass, language, "entity_component")
key = f"component.{state.domain}.entity_component._.state.{state.state}"
if len(translations) > 0 and key in translations:
return str(translations[key])
if (entry is not None and
entry.unique_id is not None and
hasattr(entry, "translation_key") and
Expand All @@ -61,24 +72,67 @@ def __call__(self, entity_id: str, language: str):
translations = get_cached_translations(self._hass, language, "state", state.domain)
if len(translations) > 0 and key in translations:
return str(translations[key])
_LOGGER.warning(f"No translation found for entity: f{entity_id}")
_LOGGER.warning(f"No translation found for entity: {entity_id}")
return state.state

def __repr__(self):
return "<template StateTranslated>"


class Translated:
class StateAttrTranslated(TranslatableTemplate):

def __init__(self, hass: HomeAssistant, available_languages):
self._hass = hass
self._available_languages = available_languages
super().__init__(hass, available_languages)

def __call__(self, key: str, language: str):
if language not in self._available_languages:
return f"Language {language} is not loaded"
def __call__(self, entity_id: str, attribute: str, language: str):
self.validate_language(language)
state = None
if "." in entity_id:
state = _get_state_if_valid(self._hass, entity_id)

else:
if entity_id in _RESERVED_NAMES:
return None

if not valid_entity_id(f"{entity_id}.entity"):
raise TemplateError(f"Invalid domain name '{entity_id}'") # type: ignore[arg-type]

if state is None:
return STATE_UNKNOWN
attribute_value = None
if attribute in state.attributes:
attribute_value = state.attributes.get(attribute)
entry = async_get(self._hass).async_get(entity_id)
translations = get_cached_translations(self._hass, language, "entity_component")
key = f"component.{state.domain}.entity_component._.state_attributes.{attribute}.state.{attribute_value}"
if len(translations) > 0 and key in translations:
return str(translations[key])
if (entry is not None and
entry.unique_id is not None and
hasattr(entry, "translation_key") and
entry.translation_key is not None):
key = f"component.{entry.platform}.entity.{state.domain}.{entry.translation_key}.state_attributes.{attribute}.state.{attribute_value}"
translations = get_cached_translations(self._hass, language, "entity")
if len(translations) > 0 and key in translations:
return str(translations[key])
_LOGGER.warning(f"No translation found for entity: {entity_id} and attribute: {attribute}")
return attribute_value

def __repr__(self):
return "<template StateAttrTranslated>"


class Translated(TranslatableTemplate):

def __init__(self, hass: HomeAssistant, available_languages):
super().__init__(hass, available_languages)

def __call__(self, key: str, language: str):
self.validate_language(language)
translations = get_cached_translations(self._hass, language, "state")
if len(translations) > 0 and key in translations:
return str(translations[key])
translations = get_cached_translations(self._hass, language, "entity_component")
if len(translations) > 0 and key in translations:
return str(translations[key])
translations = get_cached_translations(self._hass, language, "entity")
Expand All @@ -91,18 +145,17 @@ def __repr__(self):
return "<template Translated>"


class AllTranslations:
class AllTranslations(TranslatableTemplate):

def __init__(self, hass: HomeAssistant, available_languages):
self._hass = hass
self._available_languages = available_languages
super().__init__(hass, available_languages)

def __call__(self, language: str):
if language not in self._available_languages:
return f"Language {language} is not loaded"
self.validate_language(language)
translations = {}
translations.update(get_cached_translations(self._hass, language, "state"))
translations.update(get_cached_translations(self._hass, language, "entity"))
translations.update(get_cached_translations(self._hass, language, "entity_component"))
return translations

def __repr__(self):
Expand Down Expand Up @@ -148,6 +201,7 @@ async def load_translations_to_cache(
cache = hass.data.setdefault(TRANSLATION_FLATTEN_CACHE, _TranslationCache(hass))
await cache.async_fetch(language, "entity", components_entities)
await cache.async_fetch(language, "states", components_state)
await cache.async_fetch(language, "entity_component", components_state)


@bind_hass
Expand All @@ -172,6 +226,7 @@ def get_cached_translations(
return dict(ChainMap(*cached))


# noinspection PyProtectedMember
def setup(hass, config):
if DOMAIN not in config:
return True
Expand All @@ -188,35 +243,34 @@ async def load_translations(_event: Event):
_TranslationCache.get_cached = get_cached

def is_safe_callable(self, obj):
return isinstance(obj,
(StateTranslated, EvalTemplate, Translated, AllTranslations)) or self.is_safe_callable_old(
obj)
return (isinstance(obj, (StateTranslated, StateAttrTranslated, EvalTemplate, Translated, AllTranslations))
or self.is_safe_callable_old(obj))

def patch_environment(env: TemplateEnvironment):
env.globals[CONST_STATE_TRANSLATED_FUNCTION_NAME] = state_translated_template
env.globals[CONST_STATE_ATTR_TRANSLATED_FUNCTION_NAME] = state_attr_translated_template
env.globals[CONST_TRANSLATED_FUNCTION_NAME] = translated_template
env.globals[CONST_ALL_TRANSLATIONS_FUNCTION_NAME] = all_translations_template
env.globals[CONST_EVAL_FUNCTION_NAME] = eval_template
env.filters[CONST_STATE_TRANSLATED_FUNCTION_NAME] = state_translated_template
env.filters[CONST_STATE_ATTR_TRANSLATED_FUNCTION_NAME] = state_attr_translated_template
env.filters[CONST_TRANSLATED_FUNCTION_NAME] = translated_template
env.filters[CONST_EVAL_FUNCTION_NAME] = eval_template

TemplateEnvironment.is_safe_callable_old = TemplateEnvironment.is_safe_callable
TemplateEnvironment.is_safe_callable = is_safe_callable

state_translated_template = StateTranslated(hass, languages)
state_attr_translated_template = StateAttrTranslated(hass, languages)
translated_template = Translated(hass, languages)
all_translations_template = AllTranslations(hass, languages)
eval_template = EvalTemplate(hass)
tpl = Template("", hass)
tpl._strict = False
tpl._limited = False
tpl._env.globals[CONST_STATE_TRANSLATED_FUNCTION_NAME] = state_translated_template
tpl._env.globals[CONST_TRANSLATED_FUNCTION_NAME] = translated_template
tpl._env.globals[CONST_ALL_TRANSLATIONS_FUNCTION_NAME] = all_translations_template
tpl._env.globals[CONST_EVAL_FUNCTION_NAME] = eval_template
tpl._env.filters[CONST_STATE_TRANSLATED_FUNCTION_NAME] = state_translated_template
tpl._env.filters[CONST_TRANSLATED_FUNCTION_NAME] = translated_template
tpl._env.filters[CONST_EVAL_FUNCTION_NAME] = eval_template
patch_environment(tpl._env)
tpl._strict = True
tpl._limited = False
tpl._env.globals[CONST_STATE_TRANSLATED_FUNCTION_NAME] = state_translated_template
tpl._env.globals[CONST_TRANSLATED_FUNCTION_NAME] = translated_template
tpl._env.globals[CONST_ALL_TRANSLATIONS_FUNCTION_NAME] = all_translations_template
tpl._env.globals[CONST_EVAL_FUNCTION_NAME] = eval_template
tpl._env.filters[CONST_STATE_TRANSLATED_FUNCTION_NAME] = state_translated_template
tpl._env.filters[CONST_TRANSLATED_FUNCTION_NAME] = translated_template
tpl._env.filters[CONST_EVAL_FUNCTION_NAME] = eval_template
patch_environment(tpl._env)

return True
1 change: 1 addition & 0 deletions custom_components/custom_templates/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,6 @@

CONST_EVAL_FUNCTION_NAME = "ct_eval"
CONST_STATE_TRANSLATED_FUNCTION_NAME = "ct_state_translated"
CONST_STATE_ATTR_TRANSLATED_FUNCTION_NAME = "ct_state_attr_translated"
CONST_TRANSLATED_FUNCTION_NAME = "ct_translated"
CONST_ALL_TRANSLATIONS_FUNCTION_NAME = "ct_all_translations"
8 changes: 3 additions & 5 deletions custom_components/custom_templates/manifest.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
{
"domain": "custom_templates",
"name": "Custom Templates",
"codeowners": ["@PiotrMachowski"],
"documentation": "https://github.com/PiotrMachowski/Home-Assistant-custom-components-Custom-Templates",
"iot_class": "calculated",
"issue_tracker": "https://github.com/PiotrMachowski/Home-Assistant-custom-components-Custom-Templates/issues",
"version": "1.1.0",
"requirements": [],
"codeowners": [
"@PiotrMachowski"
],
"iot_class": "calculated"
"version": "v1.2.0"
}

0 comments on commit 41dc045

Please sign in to comment.