├── .github ├── FUNDING.yml └── workflows │ ├── automerge.yaml │ ├── hacs.yaml │ ├── hassfest.yaml │ └── release.yaml ├── LICENSE ├── README.md ├── custom_components └── custom_templates │ ├── __init__.py │ ├── const.py │ ├── manifest.json │ └── templates │ ├── __init__.py │ ├── all_translations.py │ ├── dict_merge.py │ ├── eval_template.py │ ├── is_available.py │ ├── state_attr_translated.py │ ├── state_translated.py │ ├── translatable_template.py │ ├── translated.py │ └── utils.py └── hacs.json /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | ko_fi: piotrmachowski 2 | custom: ["buycoffee.to/piotrmachowski", "paypal.me/PiMachowski", "revolut.me/314ma"] 3 | -------------------------------------------------------------------------------- /.github/workflows/automerge.yaml: -------------------------------------------------------------------------------- 1 | name: 'Automatically merge master -> dev' 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | build: 10 | name: Automatically merge master to dev 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v3 14 | name: Git checkout 15 | with: 16 | fetch-depth: 0 17 | - name: Merge master -> dev 18 | run: | 19 | git config user.name "GitHub Actions" 20 | git config user.email "PiotrMachowski@users.noreply.github.com" 21 | if (git checkout dev) 22 | then 23 | git merge --ff-only master || git merge --no-commit master 24 | git commit -m "Automatically merge master -> dev" || echo "No commit needed" 25 | git push origin dev 26 | else 27 | echo "No dev branch" 28 | fi -------------------------------------------------------------------------------- /.github/workflows/hacs.yaml: -------------------------------------------------------------------------------- 1 | name: Validate HACS 2 | on: 3 | push: 4 | pull_request: 5 | jobs: 6 | ci: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v2 10 | name: Download repo 11 | with: 12 | fetch-depth: 0 13 | 14 | - uses: actions/setup-python@v2 15 | name: Setup Python 16 | with: 17 | python-version: '3.10.x' 18 | 19 | - uses: actions/cache@v2 20 | name: Cache 21 | with: 22 | path: | 23 | ~/.cache/pip 24 | key: custom-component-ci 25 | 26 | - name: HACS Action 27 | uses: hacs/action@main 28 | with: 29 | category: integration 30 | -------------------------------------------------------------------------------- /.github/workflows/hassfest.yaml: -------------------------------------------------------------------------------- 1 | name: Validate with hassfest 2 | 3 | on: 4 | push: 5 | pull_request: 6 | 7 | jobs: 8 | validate: 9 | runs-on: "ubuntu-latest" 10 | steps: 11 | - uses: "actions/checkout@v2" 12 | - uses: home-assistant/actions/hassfest@master 13 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | jobs: 8 | release: 9 | name: Prepare release 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Download repo 13 | uses: actions/checkout@v1 14 | 15 | - name: Zip custom_templates dir 16 | run: | 17 | cd /home/runner/work/Home-Assistant-custom-components-Custom-Templates/Home-Assistant-custom-components-Custom-Templates/custom_components/custom_templates 18 | zip custom_templates.zip -r ./ 19 | 20 | - name: Upload zip to release 21 | uses: svenstaro/upload-release-action@v1-release 22 | with: 23 | repo_token: ${{ secrets.GITHUB_TOKEN }} 24 | file: /home/runner/work/Home-Assistant-custom-components-Custom-Templates/Home-Assistant-custom-components-Custom-Templates/custom_components/custom_templates/custom_templates.zip 25 | asset_name: custom_templates.zip 26 | tag: ${{ github.ref }} 27 | overwrite: true -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Piotr Machowski 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![HACS Default][hacs_shield]][hacs] 2 | [![GitHub Latest Release][releases_shield]][latest_release] 3 | [![GitHub All Releases][downloads_total_shield]][releases] 4 | [![Installations][installations_shield]][releases] 5 | [![Community Forum][community_forum_shield]][community_forum] 6 | [![Ko-Fi][ko_fi_shield]][ko_fi] 7 | [![buycoffee.to][buycoffee_to_shield]][buycoffee_to] 8 | [![PayPal.Me][paypal_me_shield]][paypal_me] 9 | [![Revolut.Me][revolut_me_shield]][revolut_me] 10 | 11 | 12 | 13 | [hacs_shield]: https://img.shields.io/static/v1.svg?label=HACS&message=Default&style=popout&color=green&labelColor=41bdf5&logo=HomeAssistantCommunityStore&logoColor=white 14 | [hacs]: https://hacs.xyz/docs/default_repositories 15 | 16 | [latest_release]: https://github.com/PiotrMachowski/Home-Assistant-custom-components-Custom-Templates/releases/latest 17 | [releases_shield]: https://img.shields.io/github/release/PiotrMachowski/Home-Assistant-custom-components-Custom-Templates.svg?style=popout 18 | 19 | [releases]: https://github.com/PiotrMachowski/Home-Assistant-custom-components-Custom-Templates/releases 20 | [downloads_total_shield]: https://img.shields.io/github/downloads/PiotrMachowski/Home-Assistant-custom-components-Custom-Templates/total 21 | 22 | [installations_shield]: https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fanalytics.home-assistant.io%2Fcustom_integrations.json&query=%24.custom_templates.total&style=popout&color=41bdf5&label=analytics 23 | 24 | [community_forum_shield]: https://img.shields.io/static/v1.svg?label=%20&message=Forum&style=popout&color=41bdf5&logo=HomeAssistant&logoColor=white 25 | [community_forum]: https://community.home-assistant.io/t/custom-templates/528378 26 | 27 | 28 | # Custom Templates 29 | 30 | > [!CAUTION] 31 | > This custom integration tampers with internal code of Home Assistant which _might_ cause some unforeseen issues (especially after HA updates). 32 | > 33 | > If you encounter any problems related to templating engine or translations try uninstalling this integration before raising an issue in Home Assistant repository. 34 | 35 | 36 | This integration adds possibility to use new functions in Home Assistant Jinja2 templating engine: 37 | - `ct_state_translated` - returns translated state of an entity 38 | - `ct_state_attr_translated` - returns translated value of an attribute of an entity 39 | - `ct_translated` - returns translation for a given key 40 | - `ct_all_translations` - returns all available translations (that can be used with `ct_translated`) 41 | - `ct_eval` - evaluates text as a template 42 | - `ct_is_available` - checks if given entity is available 43 | - `ct_dict_merge` - Merges two or more dictionaries together. 44 | 45 | ## Usage 46 | 47 | ### `ct_state_translated` 48 | 49 | This function returns translated state of an entity. 50 | Second parameter (language) is optional - it defaults to the language configured in Home Assistant. 51 | 52 | 53 | 54 | 57 | 60 | 61 | 62 | 73 | 84 | 85 |
55 | Input 56 | 58 | Output 59 |
63 | 64 | ``` 65 | State: {{ states("sun.sun") }} 66 | Translated en: {{ ct_state_translated("sun.sun", "en") }} 67 | Translated en: {{ "sun.sun" | ct_state_translated("en") }} 68 | Translated nl: {{ ct_state_translated("sun.sun", "nl") }} 69 | Translated nl: {{ "sun.sun" | ct_state_translated("nl") }} 70 | ``` 71 | 72 | 74 | 75 | ``` 76 | State: below_horizon 77 | Translated en: Below horizon 78 | Translated en: Below horizon 79 | Translated nl: Onder de horizon 80 | Translated nl: Onder de horizon 81 | ``` 82 | 83 |
86 | 87 | ### `ct_state_attr_translated` 88 | 89 | This function returns translated value of an attribute of an entity. 90 | Third parameter (language) is optional - it defaults to the language configured in Home Assistant. 91 | 92 | 93 | 94 | 97 | 100 | 101 | 102 | 113 | 124 | 125 |
95 | Input 96 | 98 | Output 99 |
103 | 104 | ``` 105 | Attribute: {{ state_attr("automation.example", "mode") }} 106 | Translated en: {{ ct_state_attr_translated("automation.example", "mode", "en") }} 107 | Translated en: {{ "automation.example" | ct_state_attr_translated("mode", "en") }} 108 | Translated nl: {{ ct_state_attr_translated("automation.example", "mode", "nl") }} 109 | Translated nl: {{ "automation.example" | ct_state_attr_translated("mode", "nl") }} 110 | ``` 111 | 112 | 114 | 115 | ``` 116 | Attribute: single 117 | Translated en: Single 118 | Translated en: Single 119 | Translated nl: Enkelvoudig 120 | Translated nl: Enkelvoudig 121 | ``` 122 | 123 |
126 | 127 | ### `ct_translated` 128 | 129 | This function returns translation for a given key. You can use `ct_all_translations` to check available keys. 130 | Second parameter (language) is optional - it defaults to the language configured in Home Assistant. 131 | 132 | 133 | 134 | 137 | 140 | 141 | 142 | 152 | 162 | 163 |
135 | Input 136 | 138 | Output 139 |
143 | 144 | ```yaml 145 | Translated en: {{ ct_translated("component.sun.entity_component._.state.below_horizon", "en") }} 146 | Translated en: {{ "component.sun.entity_component._.state.below_horizon" | ct_translated("en") }} 147 | Translated nl: {{ ct_translated("component.sun.entity_component._.state.below_horizon", "nl") }} 148 | Translated nl: {{ "component.sun.entity_component._.state.below_horizon" | ct_translated("nl") }} 149 | ``` 150 | 151 | 153 | 154 | ``` 155 | Translated en: Below horizon 156 | Translated en: Below horizon 157 | Translated nl: Onder de horizon 158 | Translated nl: Onder de horizon 159 | ``` 160 | 161 |
164 | 165 | ### `ct_all_translations` 166 | 167 | This function returns all available translations. 168 | Parameter (language) is optional - it defaults to the language configured in Home Assistant. 169 | 170 | 171 | 172 | 175 | 178 | 179 | 180 | 187 | 197 | 198 |
173 | Input 174 | 176 | Output 177 |
181 | 182 | ``` 183 | {{ ct_all_translations("en") }} 184 | ``` 185 | 186 | 188 | 189 | ```json 190 | { 191 | "component.sun.entity_component._.state.above_horizon": "Above horizon", 192 | "component.sun.entity_component._.state.below_horizon": "Below horizon" 193 | } 194 | ``` 195 | 196 |
199 | 200 | ### `ct_eval` 201 | 202 | This function evaluates text as a template. 203 | 204 | 205 | 206 | 209 | 212 | 213 | 214 | 223 | 231 | 232 |
207 | Input 208 | 210 | Output 211 |
215 | 216 | ```yaml 217 | {% set template_text = "{{ states('sun.sun') }}" %} 218 | {{ ct_eval(template_text) }} 219 | {{ template_text | ct_eval }} 220 | ``` 221 | 222 | 224 | 225 | ``` 226 | below_horizon 227 | below_horizon 228 | ``` 229 | 230 |
233 | 234 | Optional parameters: 235 | * `variables` (`dict[string, Any]`) - allows adding additional variables to evaluation context 236 | * `parse_result` (`bool`, default: `True`) - allows to disable result parsing for internal template evaluation 237 | * `pass_context` (`bool`, default: `True`) - allows to disable passing external context to evaluation of internal template 238 | 239 | ### `ct_is_available` 240 | 241 | This function checks if given entity has an available state. 242 | By default, the following states are treated as not available: `unknown`, `unavailable`, ``, `None`. 243 | It is possible to override this list by providing a second argument. 244 | 245 | 246 | 247 | 250 | 253 | 254 | 255 | 264 | 273 | 274 |
248 | Input 249 | 251 | Output 252 |
256 | 257 | ```yaml 258 | {{ states('sensor.invalid') }} 259 | {{ ct_is_available('sensor.invalid') }} 260 | {{ ct_is_available('sensor.invalid', ['', 'unknown']) }} 261 | ``` 262 | 263 | 265 | 266 | ``` 267 | unavailable 268 | true 269 | false 270 | ``` 271 | 272 |
275 | 276 | ### `ct_dict_merge` 277 | 278 | This function will merge one or more dictionaries (mappings) together into a single dictionary. 279 | If any key is shared between two or more dictionaries, the value of the key will be the last value passed. 280 | 281 | 282 | 283 | 286 | 289 | 290 | 291 | 304 | 317 | 318 |
284 | Input 285 | 287 | Output 288 |
292 | 293 | ```yaml 294 | {% set dict_1 = {'a':1,'b':2,'c':3} %} 295 | {% set dict_2 = {'d':4,'e':5,'f':6} %} 296 | {% set dict_3 = {'b':7,'d':8,'g':9} %} 297 | {{ ct_dict_merge(dict_1, dict_1) }} 298 | {{ ct_dict_merge(dict_1, dict_2) }} 299 | {{ ct_dict_merge(dict_2, dict_3) }} 300 | {{ ct_dict_merge(dict_1, dict_2, dict_3) }} 301 | ``` 302 | 303 | 305 | 306 | ```django 307 | 308 | 309 | 310 | {'a': 1, 'b': 2, 'c': 3} 311 | {'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5, 'f': 6} 312 | {'d': 8, 'e': 5, 'f': 6, 'b': 7, 'g': 9} 313 | {'a': 1, 'b': 7, 'c': 3, 'd': 8, 'e': 5, 'f': 6, 'g': 9} 314 | ``` 315 | 316 |
319 | 320 | ## Configuration 321 | 322 | To use this integration you have to add following config in `configuration.yaml`: 323 | 324 | * Without additional languages: 325 | ```yaml 326 | custom_templates: 327 | ``` 328 | 329 | * With additional languages: 330 | ```yaml 331 | custom_templates: 332 | preload_translations: 333 | - en 334 | - nl 335 | ``` 336 | 337 | A list of available language tags is available [here](https://github.com/home-assistant/core/blob/master/homeassistant/generated/languages.py), a list of descriptions of language tags is available [here](https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry). 338 | 339 | Section `preload_translations` should contain a list of languages you want to use with translations-related functions. 340 | If it is not provided only a language provided in HA config will be loaded. 341 | 342 | ## Installation 343 | 344 | Since version v1.4.0 the minimal supported version of Home Assistant is 2024.5.0. 345 | 346 | ### Using [HACS](https://hacs.xyz/) (recommended) 347 | 348 | This integration can be installed using HACS. 349 | To do it search for `Custom Templates` in *Integrations* section. 350 | 351 | ### Manual 352 | 353 | Download [*custom_templates.zip*](https://github.com/PiotrMachowski/Home-Assistant-custom-components-Custom-Templates/releases/latest/download/custom_templates.zip) and extract its contents to `config/custom_components/custom_templates` directory: 354 | ```bash 355 | mkdir -p custom_components/custom_templates 356 | cd custom_components/custom_templates 357 | wget https://github.com/PiotrMachowski/Home-Assistant-custom-components-Custom-Templates/releases/latest/download/custom_templates.zip 358 | unzip custom_templates.zip 359 | rm custom_templates.zip 360 | ``` 361 | 362 | Finally, restart Home Assistant and configure the integration. 363 | 364 | 365 | 366 | 367 | 368 | ## Support 369 | 370 | If you want to support my work with a donation you can use one of the following platforms: 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 385 | 388 | 392 | 393 | 394 | 395 | 399 | 402 | 403 | 404 | 405 | 406 | 409 | 412 | 415 | 416 | 417 | 418 | 422 | 425 | 428 | 429 |
PlatformPayment methodsLinkComment
Ko-fi 382 |
  • PayPal
  • 383 |
  • Credit card
  • 384 |
    386 | Buy Me a Coffee at ko-fi.com 387 | 389 |
  • No fees
  • 390 |
  • Single or monthly payment
  • 391 |
    buycoffee.to 396 |
  • BLIK
  • 397 |
  • Bank transfer
  • 398 |
    400 | Postaw mi kawę na buycoffee.to 401 |
    PayPal 407 |
  • PayPal
  • 408 |
    410 | PayPal Logo 411 | 413 |
  • No fees
  • 414 |
    Revolut 419 |
  • Revolut
  • 420 |
  • Credit Card
  • 421 |
    423 | Revolut 424 | 426 |
  • No fees
  • 427 |
    430 | 431 | ### Powered by 432 | [![PyCharm logo.](https://resources.jetbrains.com/storage/products/company/brand/logos/jetbrains.svg)](https://jb.gg/OpenSourceSupport) 433 | 434 | 435 | [ko_fi_shield]: https://img.shields.io/static/v1.svg?label=%20&message=Ko-Fi&color=F16061&logo=ko-fi&logoColor=white 436 | 437 | [ko_fi]: https://ko-fi.com/piotrmachowski 438 | 439 | [buycoffee_to_shield]: https://shields.io/badge/buycoffee.to-white?style=flat&labelColor=white&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABhmlDQ1BJQ0MgcHJvZmlsZQAAKJF9kT1Iw1AUhU9TpaIVh1YQcchQnayIijhKFYtgobQVWnUweemP0KQhSXFxFFwLDv4sVh1cnHV1cBUEwR8QVxcnRRcp8b6k0CLGC4/3cd49h/fuA4R6malmxzigapaRisfEbG5FDLzChxB6MIZ+iZl6Ir2QgWd93VM31V2UZ3n3/Vm9St5kgE8knmW6YRGvE09vWjrnfeIwK0kK8TnxqEEXJH7kuuzyG+eiwwLPDBuZ1BxxmFgstrHcxqxkqMRTxBFF1ShfyLqscN7irJarrHlP/sJgXltOc53WEOJYRAJJiJBRxQbKsBClXSPFRIrOYx7+QcefJJdMrg0wcsyjAhWS4wf/g9+zNQuTE25SMAZ0vtj2xzAQ2AUaNdv+PrbtxgngfwautJa/UgdmPkmvtbTIEdC3DVxctzR5D7jcAQaedMmQHMlPSygUgPcz+qYcELoFulfduTXPcfoAZGhWSzfAwSEwUqTsNY93d7XP7d+e5vx+AIahcq//o+yoAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH5wETCy4vFNqLzwAAAVpJREFUOMvd0rFLVXEYxvHPOedKJnKJhrDLuUFREULE7YDCMYj+AydpsCWiaKu29hZxiP4Al4aWwC1EdFI4Q3hqEmkIBI8ZChWXKNLLvS0/Qcza84V3enm/7/s878t/HxGkeTaIGziP+EB918nawu7Dq1d0e1+2J2bepnk2jFEUVVF+qKV51o9neBCaugfge70keoxxUbSWjrQ+4SUyzKZ5NlnDZdzGG7w4DIh+dtZEFntDA98l8S0MYwctNGrYz9WqKJePFLq80g5Sr+EHlnATp+NA+4qLaZ7FfzMrzbMBjGEdq8GrJMZnvAvFC/8wfAwjWMQ8XmMzaW9sdevNRgd3MFhvNpbaG1u/Dk2/hOc4gadVUa7Um425qii/7Z+xH9O4jwW8Cqv24Tru4hyeVEU588cfBMgpPMI9nMFe0BkFzVOYrYqycyQgQJLwTC2cDZCPeF8V5Y7jGb8BUpRicy7OU5MAAAAASUVORK5CYII= 440 | 441 | [buycoffee_to]: https://buycoffee.to/piotrmachowski 442 | 443 | [buy_me_a_coffee_shield]: https://img.shields.io/static/v1.svg?label=%20&message=Buy%20me%20a%20coffee&color=6f4e37&logo=buy%20me%20a%20coffee&logoColor=white 444 | 445 | [buy_me_a_coffee]: https://www.buymeacoffee.com/PiotrMachowski 446 | 447 | [paypal_me_shield]: https://img.shields.io/static/v1.svg?label=%20&message=PayPal.Me&logo=paypal 448 | 449 | [paypal_me]: https://paypal.me/PiMachowski 450 | 451 | [revolut_me_shield]: https://img.shields.io/static/v1.svg?label=%20&message=Revolut&logo=revolut 452 | 453 | [revolut_me]: https://revolut.me/314ma 454 | 455 | -------------------------------------------------------------------------------- /custom_components/custom_templates/__init__.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from typing import Any, Callable 3 | from jinja2 import pass_context 4 | 5 | from homeassistant.const import EVENT_COMPONENT_LOADED 6 | from homeassistant.core import Event, HomeAssistant 7 | from homeassistant.helpers.template import Template, TemplateEnvironment 8 | from homeassistant.helpers.translation import _TranslationCache, TRANSLATION_FLATTEN_CACHE 9 | 10 | from .const import (DOMAIN, CUSTOM_TEMPLATES_SCHEMA, CONF_PRELOAD_TRANSLATIONS, CONST_EVAL_FUNCTION_NAME, 11 | CONST_STATE_TRANSLATED_FUNCTION_NAME, CONST_STATE_ATTR_TRANSLATED_FUNCTION_NAME, 12 | CONST_TRANSLATED_FUNCTION_NAME, CONST_ALL_TRANSLATIONS_FUNCTION_NAME, 13 | CONST_IS_AVAILABLE_FUNCTION_NAME, 14 | CONST_DICT_MERGE_FUNCTION_NAME) 15 | from .templates.all_translations import AllTranslations 16 | from .templates.dict_merge import DictMerge 17 | from .templates.eval_template import EvalTemplate 18 | from .templates.is_available import IsAvailable 19 | from .templates.state_attr_translated import StateAttrTranslated 20 | from .templates.state_translated import StateTranslated 21 | from .templates.translated import Translated 22 | 23 | _LOGGER = logging.getLogger(__name__) 24 | 25 | CONFIG_SCHEMA = CUSTOM_TEMPLATES_SCHEMA 26 | ConfigType = dict[str, Any] 27 | 28 | 29 | # noinspection PyProtectedMember 30 | def setup(hass: HomeAssistant, config: ConfigType) -> bool: 31 | if DOMAIN not in config: 32 | return True 33 | languages = [] 34 | if CONF_PRELOAD_TRANSLATIONS in config[DOMAIN] and len(config[DOMAIN][CONF_PRELOAD_TRANSLATIONS]) > 0: 35 | languages = config[DOMAIN][CONF_PRELOAD_TRANSLATIONS] 36 | else: 37 | languages = [hass.config.language] 38 | cache: _TranslationCache = hass.data[TRANSLATION_FLATTEN_CACHE] 39 | 40 | async def _async_load_translations(_: Event) -> None: 41 | for language in languages: 42 | _LOGGER.debug("Loading translations for language: %s", language) 43 | components = set(filter(lambda c: "." not in c, hass.config.components)) 44 | await cache.async_load(language, components) 45 | 46 | hass.bus.async_listen( 47 | EVENT_COMPONENT_LOADED, 48 | _async_load_translations 49 | ) 50 | 51 | state_translated_template = StateTranslated(hass, languages) 52 | state_attr_translated_template = StateAttrTranslated(hass, languages) 53 | translated_template = Translated(hass, languages) 54 | all_translations_template = AllTranslations(hass, languages) 55 | eval_template = pass_context(EvalTemplate(hass)) 56 | is_available_template = IsAvailable(hass) 57 | dict_merge_template = DictMerge(hass) 58 | 59 | def is_safe_callable(self: TemplateEnvironment, obj) -> bool: 60 | # noinspection PyUnresolvedReferences 61 | return (isinstance(obj, ( 62 | StateTranslated, StateAttrTranslated, EvalTemplate, Translated, AllTranslations, IsAvailable, DictMerge)) 63 | or self.ct_original_is_safe_callable(obj)) 64 | 65 | def patch_environment(env: TemplateEnvironment) -> None: 66 | env.globals[CONST_STATE_TRANSLATED_FUNCTION_NAME] = state_translated_template 67 | env.globals[CONST_STATE_ATTR_TRANSLATED_FUNCTION_NAME] = state_attr_translated_template 68 | env.globals[CONST_TRANSLATED_FUNCTION_NAME] = translated_template 69 | env.globals[CONST_ALL_TRANSLATIONS_FUNCTION_NAME] = all_translations_template 70 | env.globals[CONST_EVAL_FUNCTION_NAME] = eval_template 71 | env.globals[CONST_IS_AVAILABLE_FUNCTION_NAME] = is_available_template 72 | env.globals[CONST_DICT_MERGE_FUNCTION_NAME] = dict_merge_template 73 | env.filters[CONST_STATE_TRANSLATED_FUNCTION_NAME] = state_translated_template 74 | env.filters[CONST_STATE_ATTR_TRANSLATED_FUNCTION_NAME] = state_attr_translated_template 75 | env.filters[CONST_TRANSLATED_FUNCTION_NAME] = translated_template 76 | env.filters[CONST_EVAL_FUNCTION_NAME] = eval_template 77 | env.filters[CONST_IS_AVAILABLE_FUNCTION_NAME] = is_available_template 78 | env.filters[CONST_DICT_MERGE_FUNCTION_NAME] = dict_merge_template 79 | 80 | def patched_init( 81 | self: TemplateEnvironment, 82 | hass_param: HomeAssistant | None, 83 | limited: bool | None = False, 84 | strict: bool | None = False, 85 | log_fn: Callable[[int, str], None] | None = None, 86 | ) -> None: 87 | # noinspection PyUnresolvedReferences 88 | self.ct_original__init__(hass_param, limited, strict, log_fn) 89 | patch_environment(self) 90 | 91 | if not hasattr(TemplateEnvironment, 'ct_original__init__'): 92 | TemplateEnvironment.ct_original__init__ = TemplateEnvironment.__init__ 93 | TemplateEnvironment.__init__ = patched_init 94 | 95 | if not hasattr(TemplateEnvironment, 'ct_original_is_safe_callable'): 96 | TemplateEnvironment.ct_original_is_safe_callable = TemplateEnvironment.is_safe_callable 97 | TemplateEnvironment.is_safe_callable = is_safe_callable 98 | 99 | tpl = Template("", hass) 100 | tpl._strict = False 101 | tpl._limited = False 102 | patch_environment(tpl._env) 103 | tpl._strict = True 104 | tpl._limited = False 105 | patch_environment(tpl._env) 106 | 107 | return True 108 | -------------------------------------------------------------------------------- /custom_components/custom_templates/const.py: -------------------------------------------------------------------------------- 1 | import homeassistant.helpers.config_validation as cv 2 | import voluptuous as vol 3 | 4 | DOMAIN = "custom_templates" 5 | CONF_PRELOAD_TRANSLATIONS = "preload_translations" 6 | 7 | CUSTOM_TEMPLATES_SCHEMA = vol.Schema( 8 | { 9 | DOMAIN: vol.Schema({ 10 | vol.Optional(CONF_PRELOAD_TRANSLATIONS): cv.ensure_list(cv.string) 11 | }) 12 | }, 13 | extra=vol.ALLOW_EXTRA 14 | ) 15 | 16 | DEFAULT_UNAVAILABLE_STATES = [ 17 | 'unknown', 18 | 'unavailable', 19 | '', 20 | None, 21 | ] 22 | 23 | CONST_EVAL_FUNCTION_NAME = "ct_eval" 24 | CONST_STATE_TRANSLATED_FUNCTION_NAME = "ct_state_translated" 25 | CONST_STATE_ATTR_TRANSLATED_FUNCTION_NAME = "ct_state_attr_translated" 26 | CONST_TRANSLATED_FUNCTION_NAME = "ct_translated" 27 | CONST_ALL_TRANSLATIONS_FUNCTION_NAME = "ct_all_translations" 28 | CONST_IS_AVAILABLE_FUNCTION_NAME = "ct_is_available" 29 | CONST_DICT_MERGE_FUNCTION_NAME = 'ct_dict_merge' 30 | -------------------------------------------------------------------------------- /custom_components/custom_templates/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "domain": "custom_templates", 3 | "name": "Custom Templates", 4 | "codeowners": ["@PiotrMachowski"], 5 | "documentation": "https://github.com/PiotrMachowski/Home-Assistant-custom-components-Custom-Templates", 6 | "iot_class": "calculated", 7 | "issue_tracker": "https://github.com/PiotrMachowski/Home-Assistant-custom-components-Custom-Templates/issues", 8 | "requirements": [], 9 | "version": "v1.4.1" 10 | } -------------------------------------------------------------------------------- /custom_components/custom_templates/templates/__init__.py: -------------------------------------------------------------------------------- 1 | # Templates available in custom_templates -------------------------------------------------------------------------------- /custom_components/custom_templates/templates/all_translations.py: -------------------------------------------------------------------------------- 1 | from homeassistant.core import HomeAssistant 2 | from homeassistant.helpers.translation import async_get_cached_translations 3 | 4 | from .translatable_template import TranslatableTemplate 5 | 6 | 7 | 8 | class AllTranslations(TranslatableTemplate): 9 | 10 | def __init__(self, hass: HomeAssistant, available_languages: list[str]) -> None: 11 | super().__init__(hass, available_languages) 12 | 13 | def __call__(self, language: str | None = None) -> dict[str, str]: 14 | language = self._validate_language(language) 15 | translations = {} 16 | translations.update(async_get_cached_translations(self._hass, language, "state")) 17 | translations.update(async_get_cached_translations(self._hass, language, "entity")) 18 | translations.update(async_get_cached_translations(self._hass, language, "entity_component")) 19 | return translations 20 | 21 | def __repr__(self) -> str: 22 | return "