Support multiple languages with Google Translate

With Business Messages's locales and Google Translate's intelligent, automated translation, you can expand your agent's reach by communicating with users in their preferred language. This tutorial walks you through a Proof of Concept integration of Google Translate with a Business Messages webhook.

What you'll need

To get started, you'll need the following few things prepared:

Get the code

This tutorial walks you through sample webhook code that integrates with Google Translate. To get the code, clone the repository from Github:

git clone https://github.com/google-business-communications/bm-nodejs-translation-tutorial

Navigate to the cloned directory and place your service account key into the resources directory:

cp credentials.json bm-nodejs-translation-sample/resources/bm-agent-service-account-credentials.json

TIP: If you need help setting up or downloading your service key, see the Google Cloud guide for managing service accounts.

Once this is done, you can deploy the code:

gcloud app deploy

Using your mobile device, send some messages to the agent. Try sending your messages in different languages and see what happens.

Set up the Translate API

The sample code comes with the Node package for the Translate API already installed. If you're interested in how to install the Node package, or how to install the Translate API in a different programming language, see the Cloud Translate API docs.

To use the Translate API, you need to import the library and create a Translate API client. Open the routes/index.js file. The relevant lines are:

// Import the Translate API library.
const { Translate } = require("@google-cloud/translate").v2;
// Create a new Translate API client.
const translate = new Translate();

From now on, you can access Translate API methods on the translate object.

Take a look at the variables created near the top of the file:

const SERVER_LANGUAGE = "en";
let currentLanguage = SERVER_LANGUAGE;

The sample code stores the server language as a constant because it's relatively fixed. However, the conversation's current language can change, so it's tracked in the currentLanguage variable.

Detecting the incoming language

The sample code detects whether the incoming language has changed, and if so, prompts the user to choose the language they'd like to use in the conversation. Try this feature out in your mobile device by typing a message to the agent in a language besides English. If you don't know any other languages, try typing "Hola" (that's Spanish for "Hello"!).

The agent responds with a prompt asking if the user would like to switch languages. The prompt includes suggested replies that the user can click to switch to that language.

Let's first take a look at the language detection feature.

/**
 * Detects input text language.
 *
 * @param {string} text The text received from the consumer.
 * @param {Context} context The user message request context.
 * @return A Promise with the detected language code.
 */
async function detectLanguage(text, context) {
  return new Promise(function (resolve, reject) {
    translate
      .detect(text)
      .then((result) => {
        if (result && result.length > 0) {
          if (result[0].confidence > CONFIDENCE_THRESHOLD) {
            resolve(result[0].language);
          }
          resolve(bcp47.parse(context.resolvedLocale).language);
        } else {
          reject("No language detected");
        }
      })
      .catch((err) => {
        console.error("ERROR:", err);
        reject(err);
      });
  });
}

This method uses the detect method on the translate client. Because the Translate API may detect multiple languages with different levels of confidence (and also because it supports multiple inputs), this method returns an array of results. The sample takes the first result, which is the result with the highest confidence.

Using resolved locale

Sometimes the Translate API can't determine the message language with high confidence. For example, if your cat runs across your keyboard and inputs a nonsense string, the Translate API still attempts to detect the language, but the detected language is probably incorrect. (After all, Google Translate doesn't support feline languages–yet!) The Translate API indicates this by setting a low confidence value in translate.detect's result.

In this scenario, the sample code falls back to the language in the Business Messages resolved locale, which is the Business Messages API's best guess at the language based on the message's context. Because the resolved locale is in BCP-47 format, you can use the corresponding Node.js package to parse the language code from the locale.

You can test this behavior by typing a long string of gibberish to the agent. In most cases, you shouldn't see a prompt to change the language (unless the resolved locale is different from the current language). The agent simply says it doesn't understand your request.

Prompting to change the language

After detecting that the language has changed, the agent sends back a prompt to change the language.

if (detectedLanguage != currentLanguage) {
        translateText(
          "Which language would you like to use?",
          SERVER_LANGUAGE,
          currentLanguage
        ).then((normalizedTranslationNotice) => {
          sendResponse(
            normalizedTranslationNotice,
            conversationId,
            [
              ...new Set([detectedLanguage, currentLanguage, SERVER_LANGUAGE]),
            ].map((x) => createSuggestedReply(x))
          );
        });
      }

The code creates a prompt, translates it to the current language (more on that in the Outbound message translation section), then sends a response with suggested replies. The user might want to talk in any of the following languages:

  • The detected incoming language.
  • The current conversation language.
  • The server's built-in language.

Since there may be overlap in these three languages (for example, if the current language is already the server language), the server uses a set object to remove duplicates. Then it creates a suggested reply for each language:

/**
 * Create a suggested reply for a language code.
 * @param {string} languageCode A ISO 6391 language code.
 * @return {Suggestion} The suggestion object for switching to the language.
 */
function createSuggestedReply(languageCode) {
  return {
    reply: {
      text: ISO6391.getNativeName(languageCode),
      postbackData: SWITCH_LANGUAGE_POSTBACK + languageCode,
    },
  };
}

The suggested reply shows the language's name in its own language. For example, Spanish appears as "Español." To get information about a language from its two-digit language code, you can use the ISO-639-1 library for Node.js.

Notice the postback data, which gets sent to the server when the user clicks on this suggestion. The postback data tells the server how to respond and provides context about the suggestion.

The sendResponse method attaches these suggestion objects to the reply:

let messageObject = {
    …
    suggestions: suggestedReplies,
  };

Changing the conversation's language

Now go back to your mobile device and try clicking a new language option in the prompt from earlier. For example, if you typed "Hola," try clicking "Español" in the suggested replies.

The agent responds in the new language. We will cover how to translate outgoing replies in a later step. For now, look at the code that receives and processes the suggested reply you clicked.

if (requestBody.suggestionResponse !== undefined) {
    let postbackData = requestBody.suggestionResponse.postbackData;
    if (postbackData.startsWith(SWITCH_LANGUAGE_POSTBACK)) {
      let languageCode = postbackData.substr(SWITCH_LANGUAGE_POSTBACK.length);
      currentLanguage = languageCode;
      translateText(
        "The language was set to " +
          ISO6391.getName(languageCode) +
          ". Please repeat your request.",
        SERVER_LANGUAGE,
        languageCode
      ).then((translationNotice) => {
        sendResponse(translationNotice, conversationId, []);
      });
    }
  }

If the request contains a reply to a suggestion, the server uses the postback data to determine what to do. In this simple case, the server only supports one type of postback data, the SWITCH_LANGUAGE_POSTBACK, which signifies that the conversation should change to the language in the immediately following language code. After parsing this language code, the server sends a message to notify the user that the language has changed.

Inbound message translation

With the language now changed, you can send a request to the agent in that language on your mobile device. Try sending the word for "help" in the new language. If you changed the language to Spanish, type "ayuda" and send the message.

The server understands your request for help and replies with the menu of options. Try any of these to see a hard-coded sample response.

The sample code uses the translateText method to translate both incoming and outgoing messages. Take a look at it now:

/**
 * Translates text to a given target language. No translation if source and
 * target language match.
 *
 * @param {string} text the text to translate
 * @param {string} sourceLanguage The language of the source text.
 * @param {string} targetLanguage The target language.
 * @return A Promise with the translated text.
 */
async function translateText(text, sourceLanguage, targetLanguage) {
  if (sourceLanguage === targetLanguage) {
    return new Promise(function (resolve, reject) {
      resolve(text);
    });
  }
  return new Promise(function (resolve, reject) {
    translate
      .translate(text, targetLanguage)
      .then((result) => {
        if (result && result.length > 0) {
          resolve(result[0]);
        } else {
          reject("Could not translate message");
        }
      })
      .catch((err) => {
        console.error("ERROR:", err);
        reject(err);
      });
  });
}

If the source language is the same as the target language, there is nothing to do. Otherwise, the server calls the translate method on the Translate API client. Like the detect method, the translate method can take multiple inputs. Since the server only provides one input, it takes the first result from the Translate API.

Look at the section of the callback method that responds to incoming messages in the current language:

translateText(incomingMessage, currentLanguage, SERVER_LANGUAGE).then(
          (normalizedMessage) => {
            let serverResponse = chooseResponseMessage(normalizedMessage);
            …
          }
        );

The server uses the output from translateText to choose a response message. The next section delves into the process of choosing the response message and translating it.

Outbound message translation

After the server translates your incoming message to its native English, it has to choose, translate, and send an appropriate response to the user's request. The sample code uses a very simple scheme that maps keywords to responses. Have a look at the chooseResponseMessage method.

/**
 * Select a topically appropriate response based on the message
 * content that the user sent to the agent.
 *
 * @param {string} incomingMessage The content of the message that the user typed in.
 * @param {string} conversationId The unique id for this user and agent.
 * @return {string} A response message.
 */
function chooseResponseMessage(incomingMessage) {
  let responseMapping = {
    balance: "Your current balance is $500.",
    deposit: "Please enter your deposit amount.",
    transfer:
      "Please enter the account number where you wish to transfer the funds.",
    withdraw: "Please enter the amount you wish to withdraw.",
    help: "Please choose what you'd like to do: balance, deposit, transfer, or withdraw.",
  };

  for (const [key, value] of Object.entries(responseMapping)) {
    if (incomingMessage.toLowerCase().includes(key)) {
      return value;
    }
  }

  return "I didn't understand your request. Please try again.";
}

This scheme only supports English on the server, meaning that the server must translate all inbound and outbound messages. A more sophisticated system might support multiple languages and respond natively to requests in other languages. For example, if your agent supports Spanish, it might already have a key for "ayuda" in the response map. More sophisticated systems might also rely on other methodologies for choosing an appropriate response, like ML or scoring algorithms. One way to create more intelligent responses with Business Messages is to integrate with Dialogflow.

Now look at the code that sends the chosen message back to the user.

let serverResponse = chooseResponseMessage(normalizedMessage);
            translateText(
              serverResponse,
              SERVER_LANGUAGE,
              currentLanguage
            ).then((normalizedResponse) => {
              sendResponse(normalizedResponse, conversationId, []);
            });

The sample code reuses the translateText method to translate the chosen response into the current conversation language. The sendResponse method then akes care of creating the new Message object and sending it to the user.

Summary

In this tutorial, you've learned how to create a simple integration with Cloud Translate API and make use of Business Messages locale features to reach more users. If you like, you can use the sample code in this tutorial as a starting point for your own integration, or you can try something new! Here are some ideas:

  • Send bilingual messages which contain both original message content and the automatically translated content.
  • Present the user with a full menu of supported languages when they begin a conversation.
  • Make use of advanced Translate API features like glossaries to reliably translate words specific to your business.

By integrating with Translate APIs, you can leverage high-quality machine translation to communicate with more users in their most comfortable language. Your agent can have more productive and efficient conversations, increasing customer satisfaction and task completion.