Skip to content

Commit

Permalink
0.8.1 - Multi language ready + German language support (#56)
Browse files Browse the repository at this point in the history
* Create prompts.py

* Create language_strings.json

* Create locale_de_de.json

* MUI language support

Added code to support Multi language responses by Alexa.
- included prompts.py required for matching
- added AbstractRequestInterceptor -> LocalizationInterceptor
- replaced all (except debugging one) language strings in text with dynamic objects
  • Loading branch information
mk-maddin authored Aug 23, 2020
1 parent 9c6cd64 commit af9e6a5
Show file tree
Hide file tree
Showing 4 changed files with 216 additions and 18 deletions.
69 changes: 51 additions & 18 deletions lambda_function.py → lambda/lambda_function.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
## VERSION 0.7.2
## VERSION 0.8.1

# UPDATE THESE VARIABLES WITH YOUR CONFIG
HOME_ASSISTANT_URL = 'https://yourhainstall.com' # REPLACE WITH THE URL FOR YOUR HA FRONTEND
Expand All @@ -10,12 +10,14 @@
import urllib3
import json
import isodate
import prompts
from datetime import datetime

import ask_sdk_core.utils as ask_utils
from ask_sdk_core.skill_builder import SkillBuilder
from ask_sdk_core.dispatch_components import AbstractRequestHandler
from ask_sdk_core.dispatch_components import AbstractExceptionHandler
from ask_sdk_core.dispatch_components import AbstractRequestInterceptor
from ask_sdk_core.handler_input import HandlerInput
from ask_sdk_model.slu.entityresolution import StatusCode
from ask_sdk_model import Response
Expand Down Expand Up @@ -59,19 +61,19 @@ def _fetch_token(self):
return ask_utils.get_account_linking_access_token(self.handler_input)

def _check_response_errors(self, response):
data = self.handler_input.attributes_manager.request_attributes["_"]
if response.status == 401:
print("401 Error", response.data) ## Add proper logging
return "It looks like I am unauthorized to reach home assistant, \
please check your account linking or your long lived access \
token and try again."
logger.error("401 Error", response.data)
speak_output = "Error 401 " + data[prompts.ERROR_401]
return speak_output
elif response.status == 404:
print("404 Error", response.data) ## Add proper logging
return "It looks like I may not be able to find the input text entity. \
Please check that you've added it to home assistant and try again"
logger.error("404 Error", response.data)
speak_output = "Error 404 " + data[prompts.ERROR_404]
return speak_output
elif response.status >= 400:
print(f"{response.status} Error", response.data) ## Add proper logging
return "Could not communicate with home assistant. Please check the Amazon \
CloudWatch logs in the custom skill developer console."
logger.error("{response.status} Error", response.data)
speak_output = "Error {response.status} " + data[prompts.ERROR_400]
return speak_output

return None

Expand Down Expand Up @@ -141,8 +143,10 @@ def post_ha_event(self, response: str, response_type: str, **kwargs):
if error:
return error

data = self.handler_input.attributes_manager.request_attributes["_"]
speak_output = data[prompts.OKAY]
self._clear_state()
return "Okay"
return speak_output

def get_value_for_slot(self, slot_name):
""""Get value from slot, also know as the (why does amazon make you do this)"""
Expand Down Expand Up @@ -233,7 +237,8 @@ def handle(self, handler_input):
selection = home_assistant_object.get_value_for_slot("Selections")
if selection:
home_assistant_object.post_ha_event(selection, RESPONSE_SELECT)
speak_output = "You selected " + selection
data = handler_input.attributes_manager.request_attributes["_"]
speak_output = data[prompts.SELECTED].format(selection)
else:
raise

Expand Down Expand Up @@ -275,7 +280,8 @@ def handle(self, handler_input):
if not dates and not times:
raise

speak_output = "Sorry, I can not do specific dates right now, try a duration instead, like... in 5 hours"
data = handler_input.attributes_manager.request_attributes["_"]
speak_output = data[prompts.ERROR_SPECIFIC_DATE]

return (
handler_input.response_builder
Expand All @@ -293,7 +299,8 @@ def can_handle(self, handler_input):

def handle(self, handler_input):
print("CancelOrStopIntentHandler")
speak_output = "Goodbye!"
data = handler_input.attributes_manager.request_attributes["_"]
speak_output = data[prompts.STOP_MESSAGE]

return (
handler_input.response_builder
Expand Down Expand Up @@ -347,27 +354,49 @@ def handle(self, handler_input, exception):
logger.error(exception, exc_info=True)
home_assistant_object = HomeAssistant()

data = handler_input.attributes_manager.request_attributes["_"]
if hasattr(home_assistant_object, 'ha_state') and home_assistant_object.ha_state != None and 'text' in home_assistant_object.ha_state:
speak_output = data[prompts.ERROR_ACOUSTIC].format(home_assistant_object.ha_state['text'])
return (
handler_input.response_builder
.speak("Sorry I did not catch that... <break time='200ms'/> " + home_assistant_object.ha_state['text'])
.speak(speak_output)
.ask('')
.response
)
else:
speak_output = data[prompts.ERROR_CONFIG].format(home_assistant_object.ha_state['text'])
return (
handler_input.response_builder
.speak("Sorry, I am having trouble, please check your configuration, in the custom skill and try again.")
.speak(speak_output)
.response
)

class LocalizationInterceptor(AbstractRequestInterceptor):
"""Add function to request attributes, that can load locale specific data."""

def process(self, handler_input):
locale = handler_input.request_envelope.request.locale
logger.info("Locale is {}".format(locale[:2]))

# localized strings stored in language_strings.json
with open("language_strings.json") as language_prompts:
language_data = json.load(language_prompts)
# set default translation data to broader translation
data = language_data[locale[:2]]
# if a more specialized translation exists, then select it instead
# example: "fr-CA" will pick "fr" translations first, but if "fr-CA" translation exists,
# then pick that instead
if locale in language_data:
data.update(language_data[locale])
handler_input.attributes_manager.request_attributes["_"] = data

# The SkillBuilder object acts as the entry point for your skill, routing all request and response
# payloads to the handlers above. Make sure any new handlers or interceptors you've
# defined are included below. The order matters - they're processed top to bottom.

sb = SkillBuilder()

# register request / intent handlers
sb.add_request_handler(LaunchRequestHandler())
sb.add_request_handler(YesIntentHanlder())
sb.add_request_handler(NoIntentHanlder())
Expand All @@ -377,8 +406,12 @@ def handle(self, handler_input, exception):
sb.add_request_handler(DateTimeIntentHandler())
sb.add_request_handler(CancelOrStopIntentHandler())
sb.add_request_handler(SessionEndedRequestHandler())
sb.add_request_handler(IntentReflectorHandler())

sb.add_request_handler(IntentReflectorHandler())
# register exception handlers
sb.add_exception_handler(CatchAllExceptionHandler())

# register response interceptors
sb.add_global_request_interceptor(LocalizationInterceptor())

lambda_handler = sb.lambda_handler()
30 changes: 30 additions & 0 deletions lambda/language_strings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"en": {
"ERROR_401": "It looks like I am unauthorized to reach home assistant, please check your account linking or your long lived access token and try again",
"ERROR_404": "It looks like I may not be able to find the input text entity. Please check that you've added it to home assistant and try again",
"ERROR_400": "Could not communicate with home assistant. Please check the Amazon CloudWatch logs in the custom skill developer console.",
"ERROR_ACOUSTIC": "Sorry I did not catch that... <break time='200ms'/> {}",
"ERROR_CONFIG": "Sorry, I am having trouble, please check your configuration, in the custom skill and try again.",
"ERROR_SPECIFIC_DATE": "Sorry, I can not do specific dates right now, try a duration instead, like... in 5 hours",
"HELP_MESSAGE": "This skill should be only reactively while triggered via home assistant.",
"OKAY": "Okay",
"SKILL_NAME": "home assistant custom actions",
"STOP_MESSAGE": "Goodbye!",
"SELECTED" : "You selected {}",
"WELCOME_MESSAGE": "Welcome to {}. Input text is: {}"
},
"de": {
"ERROR_401": "Ich habe scheinbar keine Berechtigungen für die verbindung zu home assistant. Bitte prüfe deinen H A Benutzer oder Access Token.",
"ERROR_404": "Ich kann kein input text Objekt in home assistant finden. Bitte überprüfe ob du dieses bereits hinzugefügt hast.",
"ERROR_400": "Ich kann keine Verbindung zum home assistant vornehmen. Bitte prüfe die Amazon CloudWatch logdateien in der Alexa Entwickler Console.",
"ERROR_ACOUSTIC": "Das habe ich leider nicht verstanden... <break time='200ms'/> {}",
"ERROR_CONFIG": "Entschuldige, hier ist etwas schief gelaufen. Bitte prüfe deine Konfiugration im custom skill und versuche es erneut.",
"ERROR_SPECIFIC_DATE": "Ich kann leider noch kein spezifisches Datum verarbeiten. Bitte nutze stattedessen Zeiträume wie beispielsweise... in 5 Stunden.",
"HELP_MESSAGE": "Dieser Skill sollte nur reaktiv genutzt werden, wenn er via home assistant angestossen wird",
"OKAY": "Okay",
"SELECTED" : "Du hast {} gewählt",
"SKILL_NAME": "home assistant custom actions",
"STOP_MESSAGE": "Machs gut!",
"WELCOME_MESSAGE": "Willkommen bei {}. Der input text ist: {}"
}
}
13 changes: 13 additions & 0 deletions lambda/prompts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Alexa Prompts Language Constants
ERROR_401 = "ERROR_401"
ERROR_404 = "ERROR_404"
ERROR_400 = "ERROR_400"
ERROR_ACOUSTIC = "ERROR_ACOUSTIC"
ERROR_CONFIG = "ERROR_CONFIG"
ERROR_SPECIFIC_DATE = "ERROR_SPECIFIC_DATE"
HELP_MESSAGE = "HELP_MESSAGE"
OKAY = "OKAY"
SELECTED = "SELECTED"
SKILL_NAME = "SKILL_NAME"
STOP_MESSAGE = "STOP_MESSAGE"
WELCOME_MESSAGE = "WELCOME_MESSAGE"
122 changes: 122 additions & 0 deletions skill-manifests/locale_de_de.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
{
"interactionModel": {
"languageModel": {
"invocationName": "custom actions",
"intents": [
{
"name": "AMAZON.CancelIntent",
"samples": []
},
{
"name": "AMAZON.HelpIntent",
"samples": []
},
{
"name": "AMAZON.StopIntent",
"samples": []
},
{
"name": "AMAZON.NavigateHomeIntent",
"samples": []
},
{
"name": "AMAZON.YesIntent",
"samples": [
"yes"
]
},
{
"name": "AMAZON.NoIntent",
"samples": [
"no"
]
},
{
"name": "Select",
"slots": [
{
"name": "Selections",
"type": "Selections"
}
],
"samples": [
"{Selections}"
]
},
{
"name": "Number",
"slots": [
{
"name": "Numbers",
"type": "AMAZON.FOUR_DIGIT_NUMBER"
}
],
"samples": [
"{Numbers}"
]
},
{
"name": "Duration",
"slots": [
{
"name": "Durations",
"type": "AMAZON.DURATION"
}
],
"samples": [
"{Durations}"
]
},
{
"name": "Date",
"slots": [
{
"name": "Dates",
"type": "AMAZON.DATE"
},
{
"name": "Times",
"type": "AMAZON.TIME"
}
],
"samples": [
"{Dates} at {Times}",
"at {Times}",
"{Dates}"
]
}
],
"types": [
{
"name": "Selections",
"values": [
{
"name": {
"value": "Amazon Prime",
"synonyms": [
"Amazon",
"Amazon Video"
]
}
},
{
"name": {
"value": "Hulu"
}
},
{
"name": {
"value": "YouTube"
}
},
{
"name": {
"value": "Netflix"
}
}
]
}
]
}
}
}

0 comments on commit af9e6a5

Please sign in to comment.