Skip to content

Developing Translations

All first party simple integrations will follow the rough directory structure to properly support translations. A bit of work may be needed to convert a custom component, before being accepted into the base Home Intent.

Directory structure

    - timer
        ... other languages

The component will still be loaded from, however, to properly support translations, there is a import_module method in the home_intent object that gets passed to the setup method that will load the component file associated with the user's language.

from home_intent import HomeIntent, Intents

def setup(home_intent: HomeIntent):
    timer = home_intent.import_module(__name__) # will load the {lang}.py
    home_intent.register(timer.Timer(home_intent), timer.intents)

The file includes all the english (en) language specific sentences/code. Notably all things that can be shared across all languages should be placed in the file and imported in. This is where the _set_timer method is defined.

from .base_timer import intents, BaseTimer

class Timer(BaseTimer):
    def partial_time(self):
        return {
            "and [a] half": "half",
            "and [a] quarter": "quarter",
            "and [a] third": "third",

            "time = 0..128",
            "set timer [(<time>){hours} hours] [(<time>){minutes} minutes] [(<time>){seconds} seconds]",
            "set timer (<time>){hours} [($partial_time)] hours",
            "set timer (<time>){minutes} [($partial_time)] minutes",
            "set timer (<time>){seconds} [($partial_time)] seconds",
            "set a [(<time>){hours} hour] [(<time>){minutes} minute] [(<time>){seconds} second] timer",
            "set a (<time>){hours} [($partial_time)] hour timer",
            "set a (<time>){minutes} [($partial_time)] minute timer",
            "set a (<time>){seconds} [($partial_time)] second timer",
    def set_timer(
        self, hours: int = None, minutes: int = None, seconds: int = None, partial_time=None
        human_timer_duration = self._set_timer(
            "Your timer {0} has ended", hours, minutes, seconds, partial_time
        return f"Setting timer {human_timer_duration}"

There is a bit of nuance here. Depending on the language the partial_time might not make sense, in which case it can just be omitted from the translation and not passed to _set_timer. On the other hand, a translation might exist for "and a half", but it should still map to the english "half". In the dictionary, the half acts more like an enum than a string. You can see that in the get_partial_time_duration in the code below. But in general, the value side of a dictionary slot does not need to be translated.

The file includes the main timer functionality and contains code that all the languages can use. In the timer constructor below, we are activating the humanize module to load the specific language settings. In this case, the humanize module takes care of some of the translation heavy lifting.

from datetime import timedelta
from threading import Timer as ThreadingTimer

import humanize

from home_intent import HomeIntent, Intents

intents = Intents(__name__)

class TimerException(Exception):

class BaseTimer:
    def __init__(self, home_intent: HomeIntent):
        # TODO: keep track of timers and add ability to remove timers
        # self.timers = []
        self.home_intent = home_intent

        # for some reason the activate fails for "en", I think because it's not a "translation"
        if self.home_intent.language != "en":

    def _set_timer(
        timer_done_message: str,
        hours: int = None,
        minutes: int = None,
        seconds: int = None,
        timer_duration = timedelta(
            hours=int(hours or 0), minutes=int(minutes or 0), seconds=int(seconds or 0),
        if timer_duration == timedelta(0):
            raise TimerException("Timer has to be set for more than 0 seconds")
        if partial_time:
            timer_duration = timer_duration + get_partial_time_duration(
                partial_time, hours, minutes, seconds
        human_timer_duration = text_conversion_function(timer_duration)
        timer = ThreadingTimer(
            (human_timer_duration, timer_done_message),
        return human_timer_duration

    def complete_timer(self, human_timer_duration: str, timer_done_message: str):

def get_partial_time_duration(partial_time, hours=None, minutes=None, seconds=None):
    partial_of = None
    if hours:
        partial_of = "hours"
    elif minutes:
        partial_of = "minutes"
    elif seconds:
        partial_of = "seconds"

    if partial_time == "half":
        return timedelta(**{partial_of: 0.5})
    elif partial_time == "quarter":
        return timedelta(**{partial_of: 0.25})
    elif partial_time == "third":
        return timedelta(**{partial_of: 1 / 3})

Language Helpers

There are a couple of things that can help with language specifics including:


This keeps track of the two letter ISO639-1 language code and can be used as needed. In the example above, it is used in the humanize activation step in the BaseTimer constructor.


This is further explained in the Home Intent object reference, but to quickly summarize, the get_file method will load a file first looking in the Home Intent default_configs directory in the appropriate language code folder. Some external files are language dependent and can get loaded from there as needed.


The play_audio_file method has also been updated to support the language_dependent flag to load language specific sounds. However, it defaults to False, so it will not look for audio files in the language code folder in default_configs by default.