Internationalize and Localize Custom Component Responses

If you have custom components that return content in conversations, you'll also want to ensure that they return that content in the user's target language.

There are several ways to accomplish this:
  • Create resource bundle entries in the skill and reference them directly from the custom component. This approach enables you to handle translations of custom component messages in the same place as you do for other messages from the skill.
  • Use a system component and resource bundle entries for assembling the translatable strings that incorporate the data output of the custom component. This approach enables you to handle translations of custom component messages in the same place as you do for other messages from the skill while fostering a looser coupling between the custom component and that particular skill.
  • If you want to use the skill's translation service to translate the component's responses, set the custom component's translate property to true.
  • If your component retrieves and returns backend data that needs to be incorporated into a message and you want to use the skill's translation service to translate the component's responses, store that returned data in a variable in the dialog flow. You can then reference this variable in a system component.

Reference Resource Bundles from the Custom Component

Just as you can use resource bundles for messages in built-in components, answer intents, etc., you can use resource bundles for your custom components as well. To do so, you:

  1. Define resource bundle entries in the skill for your message. See Create Resource Bundle Keys.
  2. Using the Bots Node SDK's context.translate() method, reference the resource bundle keys from the custom component code.

    The context.translate() takes a specified resource bundle key name (and any parameters specified in the resource bundle entry) and generates the appropriate FreeMarker template required to load the named resource bundle language string when the conversation is sent back to the user via the context.reply() method.

  3. Use the context.reply helper method to print the translated response. For example:
    context.reply(translate('date.message', dateToday, dayOfWeek ));
  4. Document all of the resource bundle keys that the custom component references as well as the expected default strings. (Since the custom component directly references the resource bundle key within the skill, there needs to be a high degree of coordination between the developer of the custom component and those building out the skill to ensure that the referenced keys are valid within the skill).

In this example, date.message is a resource bundle key, dateToday and dayOfWeek are variables, and a FreeMarker expression like the following is returned:

${rb('date.message', 'Monday', 'July 12, 2021')}

Note

The context.translate() method only supports resource bundle values that have no parameters or that use positional (numbered) parameters. For example, in the case of the example date.message key, it's value might be something like “Today is {0}, {1}”. Named parameters and complex message formats are not supported.

Use a System Component to Reference a Resource Bundle

You can use a system component to assemble messages using resource bundle entries and data that has been returned from a custom component. You define the base message strings in resource bundle entries. The bundle entries might include parameters for data (such as numbers and dates) that are output from the custom component. Since the base message strings are defined in the dialog flow, this approach ensures that custom components are not dependent on specific implementation code and remain reusable.

Here are the general steps:

  1. For the custom component, include a required input parameter for the name of the context variable to store the returned data in.
  2. Since the custom component developer and dialog flow developer may not be the same person or even on the same team, carefully document what data the custom component returns in that variable and make the information available to any custom component consumers so that they understand how to present the returned data to the user in a message.
  3. In the dialog flow, create a variable to store the custom component's returned data and pass its name in the required input parameter.
  4. Define resource bundle entries in the skill for your message. See Create Resource Bundle Keys.
  5. In the dialog flow, reference the resource bundle entry and fill in any required parameters.

The following sample from a skill developed in YAML dialog mode references a custom component in the initializeReceipt state and passes the name of the variable (receipt) that holds the component response and purchaseId as input parameters. The printProduct state then incorporates the receipt value as a parameter in a reference to the resource bundle entry named receiptMessage.

  initializeReceipt:
    component: "sample.receipt.dataresponse"
    properties:
      dataVariable: "receipt"
      purchaseId: "${purchaseId.value}"
 ...
  printProduct:
    component: "System.CommonResponse"
    properties:
      keepTurn: true
      metadata:
        responseItems:        
        - type: "text" 
          text: "${rb('receiptMessage','${receipt.value}')}"

The custom code for accessing these input parameters might look something like the following code:

module.exports = {
  metadata: () => ({
    name: 'myComponent',   
    properties: {
       dataVariable: { required: true, type: 'string' },    
       purchaseId: { required: true, type: 'string' },
    },
...
    // Retrieve the value of the 'dataVariable' component property.
    const { dataVariable } = context.properties();
    if (!dataVariable) {
      context.transition();
      done(new Error('The state is missing the dataVariable property.'));
    }
...
    // Retrieve the value of the 'purchaseId' component property.
    const { purchaseId } = context.properties();
    if (!purchaseId) {
      context.transition();
      done(new Error('The state is missing the purchaseId property.'));
    }
...
    context.setVariable(dataVariable, data);      
    context.transition();
    done();
  }
}

Send Responses Directly to the Translation Service

If you don't have a way of knowing what the component's response text will be (e.g. if it is queried from a remote backend), you can use the skill's translation service to translate the responses. To do so:

  1. Make sure the component is set up to have its output sent to the translation service by defining the translate property on the component and setting it to true.
  2. In the custom component, use the context.reply helper method to return the response.

This approach only works with skills that are set up in the Translation Service language mode.

Use a System Component to Pass the Message to the Translation Service

Custom components that query backend services might return data in a complex format like an object or an array of objects. If you are using a translation service, these data objects can't be sent to the translation service as is. Instead, you need to form a message that references any necessary attributes of the data object individually.

  1. For the custom component, include a required input parameter for the name of the dialog flow variable to store the returned data in.
  2. Since the custom component developer and dialog flow developer may not be the same person or even on the same team, carefully document what data the custom component returns in that variable and make the information available to any custom component consumers so that they understand how to present the returned data to the user in a message.
  3. In the dialog flow, create a variable to store the custom component's returned data and pass its name in the required input parameter.
  4. Using the information in the variable, assemble the response in a system component, like Common Response.
  5. Make sure that the skill is configured for auto-translation.
    • For skills developed in Visual dialog mode, set the Translate Bot Response Message property on the skill's Settings page to true.
    • For skills developed in YAML dialog mode, you can handle this globally in the skill by setting the autoTranslate context variable. For example:
        setAutoTranslate:
          component: "System.SetVariable"   
          properties:
            variable: "autoTranslate"     
            value:
             input: true
             output: true
In the following example from a Common Response component's Metadata property, the variable is dialogVar. The data object that’s passed from the custom component to this variable is {product: "an apple", type: "fruit", origin: "Spain" }.

responseItems:        
- type: "text" 
  text: "The product in your cart is a ${dialogVar.value.type}. It is
   ${dialogVar.value.product} from ${dialogVar.value.origin}"

The custom code for accessing this input parameter might look something like the following code:

module.exports = {
  metadata: () => ({
    name: 'myComponent',   
    properties: {
       dialogVar: { required: true, type: 'string' },    
    },
...
    // Retrieve the value of the 'dialogVar' component property.
    const { dialogVar } = context.properties();
    if (!dialogVar) {
      context.transition();
      done(new Error('The state is missing the dialogVar property.'));
    }
...
    context.setVariable(dialogVar, data);    
    context.transition();
    done();
  }
}

Detect the User Language in a Custom Component

If the custom component needs the user's language to do things like provide correct date formats, you can provide it to the component in one of these ways:

  • Access the profile.locale and profile.languageTag variables from the custom component code as shown in the following example:
    //detect user locale. If not set, define a default
    const locale = context.getVariable('profile.locale') ?
                   context.getVariable('profile.locale') : 'en-AU';
    //Make sure locale is returned with hyphen, not underscore. JavaScript requires a hyphen.
    const jsLocale = locale.replace('_','-'); 
    //when profile languageTag is set, use it. If not, use profile.locale
    const languageTag = context.getVariable('profile.languageTag')?
                        context.getVariable('profile.languageTag') : jslocale;
  • Pass the values of profile.locale and/or profile.languageTag as input parameters to the component.
Note

If both variables are set, profile.languageTag takes precedence in the skill.