Advanced | Flow of the Week: Localize messages used in Flows using JSON object variables

In my posts to the Flow blog, I like showing simple techniques to help simplify flow logic definitions. I've used the technique I'll discuss here twice in the past week to simplify what would have been far more complex logic. With this pattern, the flows will be far easier to read, maintain, and extend in the future.

The example scenario is as follows: Let's say that you have a Flow that will send short messages to customers, but you want the subject, body, etc. to be localized in the customer's preferred language. One solution would be to have one Flow for each locale, and each would be customized to a language. The user (or logic) that triggers the process must figure out the right language for each customer, and then run the right Flow. That's a.) a nightmare to maintain (propagating changes to each copy, etc.) and b.) no fun, and not blog-worthy. Instead, I'll show you a technique that will let you maintain one simple Flow that easily scales to the addition of new localized messages, new languages, and provides a single place for the logic to be defined/updated in case of requirement changes.

To set the scene, I have set up a simple SharePoint list that will act as my data source — a list of customer contacts with email address and the preferred language:

SharePoint list with contact address and language

I'll use the "For a selected item" trigger to kick off this Flow for a specific list item at a time. After the trigger, I'll fetch the full list item with a Get item action to fetch the customer contact email address and language column value. 

One way of solving this would be to switch on the language, and then initialize a variable for each string that I want to use in the email. Then I could embed references to each variable as needed in the Send an email action. The downsides of this approach are:

  1. Adding a new language means extending the switch statement with a new case statement. More languages mean a more difficult flow to read and maintain (and not to mention scroll through). Headache.

2. Adding a new localized string means going to each and every case statement in the switch and updating them. More updates, more chances of typos/errors. Also a headache.

Instead, we can use an object variable to define our "lookup table" that will let us map all languages to a set of strings in a single action. Then we can use a Designer expression to read from the object to select a set of strings all at once. The JSON data I will use in the action looks like the following: 

{
    "en-US": {
        "greeting": "Hello",
        "message": "Thank you"
    },
    "fr-FR": {
        "greeting": "Bonjour",
        "message": "Merci"
    },
    "de-DE": {
        "greeting": "Hallo",
        "message": "Danke"
    }
}

Initializing object variable with JSON

After the table itself is initialized, I can initialize a new variable to store the set of strings to use. I'll use the SharePoint column value to choose the right set of strings to use in the email. I'll use the expression editer to enter my expression that will read from the body (output) of the Get item action, take the Language column value, and access the corresponding table property:

variables('localizedStringTable')?[body('Get_item')?['Language']]

Let's break this expression apart and build it back up to see what's going on:

  • variables('localizedStringTable') — this references to table object variable we just initialized
  • variables('localizedStringTable')?[ ... ] — this reads a property off of the variable object. The "?" signals that the property might not be found. We'll pass in the language value into the [ … ] to look up the property (another object), corresponding to the language.
  • body('Get_item)?['Language'] — this reads the Language property off of the output object of the Get item action.

When this expression is evaluated as part of the variable initialization, it will grab the sub-object off of the table variable and assign it. Now we have one object (with properties like greeting and message) with all the right strings in one place.

But what if the Language for the customer is not found in our table of languages and strings? Right now the flow will fail (or worst case, just send an empty email!). Can we somehow fallback to a default set of strings? Can we do it elegantly? 

First we'll add to the JSON definition to define a "default" set of strings (in this case we'll duplicate the English strings):

"default": {
    "greeting": "Hello",
    "message": "Thank you"
}

If the table doesn't contain the user's preferred language, we should read this default set of strings, ensuring that we always populate an email. We could first try to read the language from the table (or use a contains expression to check before reading), but it is simpler to use coalesce. The coalesce function takes a dynamic set of parameters, and returns the first (in the left-to-right sense), parameter that is non-null. We'll pass in the above variable reference expression as the first parameter — if it is found (non-null), coalesce will return it. If it is not found, coalesce will check the second parameter, which will be our "default" lookup:

coalesce(variables('localizedStringTable')?[body('Get_item')?['Language']], variables('localizedStringTable')['default'])

Initializing object variable with coalesce expression

Now that we have an object with all of our necessary strings selected, we can compose the email. In various locations within the Send an email card, we can embed expressions to reference the message and greeting properties:

  • variables('localizedStrings')?['message']
  • variables('localizedStrings')?['greeting']

Referencing properties of object variable in email subject and body