18
Alexa in Arabic
Let us make a quote generator skill
The main points that will be tackled in this article:
- Implementing the Arabic language in Alexa
- Localization
- DynamoDB communication
To keep our skill simple, will create a random quote generator, when you ask Alexa for a quote it will say a random one from our list of quotes, later to make things a bit more interesting will add functionality that you can ask a quote for different modes, like for example "I want a motivation quote" or "give me a business quote" which will read the data from DynamoDB
English Interaction Model (en-US.json)
{
"interactionModel": {
"languageModel": {
"invocationName": "random quote",
"intents": [
{
"name": "AMAZON.CancelIntent",
"samples": []
},
{
"name": "AMAZON.HelpIntent",
"samples": []
},
{
"name": "AMAZON.StopIntent",
"samples": []
},
{
"name": "AMAZON.NavigateHomeIntent",
"samples": []
},
{
"name": "RandomQuoteIntent",
"slots": [],
"samples": [
"give me quote",
"I want a quote"
]
}
],
"types": []
}
}
}
Arabic Interaction Model (ar-SA.json)
{
"interactionModel": {
"languageModel": {
"invocationName": "قول عشوائي",
"intents": [
{
"name": "AMAZON.CancelIntent",
"samples": []
},
{
"name": "AMAZON.HelpIntent",
"samples": []
},
{
"name": "AMAZON.StopIntent",
"samples": []
},
{
"name": "AMAZON.NavigateHomeIntent",
"samples": []
},
{
"name": "AMAZON.FallbackIntent",
"samples": []
},
{
"name": "RandomQuoteIntent",
"slots": [],
"samples": [
"من فضلك أعطني قولاً",
"أريد قولًا"
]
}
],
"types": []
}
}
}
- The function is executed on the request interceptor: Request interceptors are invoked immediately before the execution of the selected handler for an incoming request. You can use them to add any logic that needs to be performed for each request, irrespective of the type of request.
Let us add i18Next package which will handle our internationalization logic
"dependencies": {
"ask-sdk-core": "^2.6.0",
"ask-sdk-model": "^1.18.0",
"aws-sdk": "^2.326.0",
"i18next": "^20.3.2"
}
Add inside the exports.handler
.addRequestInterceptors(
LocalisationRequestInterceptor
)
LocalisationRequestInterceptor function will check what language the user is using and it will return a list of locales for that specific language
const LocalisationRequestInterceptor = {
process(handlerInput) {
i18n.init({
lng: Alexa.getLocale(handlerInput.requestEnvelope),
resources: languageStrings
}).then((t) => {
handlerInput.t = (...args) => t(localizationClient(...args));
});
}
};
Our localizationClient function will check the local type if its object returns its value, else if its array, it will return a random value from it, how cool is that right? 😉 now all we have to do is to use the function and add some locales to our code
const localizationClient = function () {
const args = arguments;
const value = i18n.t(args[0], {
returnObjects: true
});
if (Array.isArray(value)) {
return value[Math.floor(Math.random() * value.length)];
} else {
return value;
}
}
Finally this way we can use our helper function "t" 😀
const speakOutput = handlerInput.t('WELCOME_MSG');
Now our locales.js file which holds all our speeches for different languages
module.exports = {
en: {
translation: {
WELCOME_MSG: `Welcome to random quote, say I want a quote`,
}
},
ar: {
translation: {
WELCOME_MSG: `مرحبًا بك في قول عشوائي ، قل أريد قولً`,
}
}
}
What's DynamoDB? Amazon DynamoDB is a fully managed proprietary NoSQL database service that supports key–value and document
data structures
First, add the right permissions so that our lambda function can access DynamoDB, below image shows the policy that can be attached to the role
Great now let's create our table, with the data in it. Will name the table randomQuote, and let's give a partition-key "languageId" which will hold our language type. This way it will become simple to make queries to it, and for the modes let's have two "motivation" & "business" types, below images show both the English and Arabic languages that are created.
Let's check our updated interaction models, for the customQuote slot we will use AMAZON.SearchQuery to keep things simple, but you can use custom slot types too where you need to define a list of synonyms.
{
"name": "CustomQuoteIntent",
"slots": [
{
"name": "customQuote",
"type": "AMAZON.SearchQuery"
}
],
"samples": [
"give me a {customQuote} quote",
"I want a {customQuote} quote"
]
}
{
"name": "CustomQuoteIntent",
"slots": [
{
"name": "customQuote",
"type": "AMAZON.SearchQuery"
}
],
"samples":
"أعطني مقولة {customQuote}",
"أريد مقولة {customQuote}"
]
}
In order to make our queries will have two helper functions, one that creates the connection with the database, and the other one that does the query
- dbHelper.js
const AWS = require("aws-sdk");
const CONFIG = require("../config/aws");
module.exports.dynamoDBHelper = async function dynamoDBHelper() {
AWS.config.update({region: CONFIG.REGION});
const dynamoDB = new AWS.DynamoDB.DocumentClient();
return dynamoDB;
}
- queryHelper.js
const CONFIG = require("../config/aws");
const tableName = CONFIG.TABLE_NAME;
const dbHelper = require("./dbHelper");
var queries = function () {};
queries.prototype.getQuotes = async (languageID) => {
const params = {
TableName: tableName,
KeyConditionExpression: "#languageID = :language_id",
ExpressionAttributeNames: {
"#languageID": "languageId"
},
ExpressionAttributeValues: {
":language_id": languageID
}
}
const dynamoDB = await dbHelper.dynamoDBHelper();
const response = await dynamoDB.query(params).promise();
return response;
}
module.exports = new queries();
let's have a quick look to our query response through Amazon CloudWatch.
Amazon CloudWatch is a monitoring and management service that provides data and actionable insights for AWS, hybrid, and on-premises applications and infrastructure resources. With CloudWatch, you can collect and access all your performance and operational data in form of logs and metrics from a single platform.
Nice, now let us check the Intent Handler function in index.js
const CustomQuoteIntentHandler = {
canHandle(handlerInput) {
return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
&& Alexa.getIntentName(handlerInput.requestEnvelope) === 'CustomQuoteIntent';
},
async handle(handlerInput) {
const slotValue = handlerInput.requestEnvelope.request.intent.slots.customQuote.value;
const languageID = Alexa.getLocale(handlerInput.requestEnvelope);
let speakOutput;
try {
let response = await queries.getQuotes(languageID);
let quoteArray = response.Items[0][slotValue];
speakOutput = quoteArray[Math.floor(Math.random() * quoteArray.length)];
} catch (error) {
console.log('-- ERROR --', error);
speakOutput = handlerInput.t('ERROR');
}
return handlerInput.responseBuilder
.speak(speakOutput)
.getResponse();
}
};
The function will make a query using our queryHelper functions, in these two lines we take the list of quotes (by its mode) and then a random quote from it
let quoteArray = response.Items[0][slotValue];
speakOutput = quoteArray[Math.floor(Math.random() * quoteArray.length)];
That's it 😊 This was a bit long article, hope it was really helpful and clear, of course, some stuff can be improved and enhanced but for the sake of keeping stuff simple, I didn't want to go too deep.
Moreover implementing the Arabic language in Alexa will open doors for many skills that can be done and published in new regions with Arabic language demand, again hope it was straightforward and easy to grab with me. I wish you all a fun and engaging skill development journey.
The source code
https://github.com/awedis/random-quote
18