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

First let's see the steps need to be done to add the Arabic language

  • Route to language settings & add a new language from the available list, choose Arabic and save it.
    The images shown below are a quick flow of how can be done using the console.
    screen 1
    screen 2
    screen 3

  • We will have 2 interaction models one for the English language & another one for the Arabic.

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": []
        }
    }
}

Our localization function, where all the magic happens

  • 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: `مرحبًا بك في قول عشوائي ، قل أريد قولً`,
        }
    }
}

Outputs:
output-1-en
output-1-ar

Let's make our skill more interesting using DynamoDB 😎

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
iam
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.
dynamo-en
dynamo-ar
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.
cloudwatch
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)];

Our output
output-2-en
output-2-ar

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.

18