Search code examples
c#botframework

First prompt of proactive localised formflow in wrong language in bot framework


I've submitted this as a bug report but also filing it here in case I'm doing something wrong and this isn't really a bug.

Bot framework version

3.16.1.38846

Describe the issue

I'm trying to create a localised formflow that can be proactively triggered. I'm able to create the form and trigger it through an API call using the proactive dialog trigger. However, the first question is always in English, despite the locale not being English. Nonetheless, it expects an answer in the locale in play (Mandarin in this case, (zh-SG)).

If I were to not trigger it through my API, all my questions are localised based on whatever locale I send in through the bot framework emulator. I tested this by setting up a keyword check in the root dialog, and I'm able to get all my formflow questions asked in the language specified. I've attached screenshots of how this seems to play out too.

To Reproduce

Steps to reproduce the behavior:

  1. Create a simple form
  2. Localise the form using the guide in the documentation
  3. Call the form using the bot framework emulator using a simple keyword check in the root dialog. Use the default locale of en-US (Sample below)
  4. Call the form using the bot framework emulator using a simple keyword check in the root dialog. Use the other language's locale (in this case, zh-SG)
  5. Call the form using a proactive dialog trigger through a WebAPI. Method looks like this. Parameters such as the activity object have been previously seralised to a database. I've obscured certain parameters to protect some confidential information

Sample trigger

if (activity.Text.Equals("Trigger"))
{
    var form = new FormDialog<Form1>(new Form1(), Form1.BuildForm, FormOptions.PromptInStart, null);
    context.Call(form, formCompleteAsync);
}

WebAPI method

public IHttpActionResult Post([FromBody]Model Model)
{
  if (ModelState.IsValid)
  {
      try
      {
          StartProactiveDialogAsync(model.someId, model.anotherId)
          return Ok();
      }
      catch (Exception ex)
      { 
          return BadRequest(ex.Message);
      }
  }
  else
  {
      return BadRequest(ModelState);
  }
}

StartProactiveDialogAsync

public async Task StartProactiveDialogAsync(someId, anotherId )
{
    try
    {
        // Recreate the message from the conversation reference that was saved previously.
        Activity activity = JsonConvert.DeserializeObject<Activity>(BotUserData.ConversationReference);

        MicrosoftAppCredentials.TrustServiceUrl(activity.ServiceUrl);
        var client = new ConnectorClient(new Uri(activity.ServiceUrl));

        // Create a scope that can be used to work with state from bot framework.
        using (var scope = DialogModule.BeginLifetimeScope(Conversation.Container, activity))
        {
            var botData = scope.Resolve<IBotData>();
            await botData.LoadAsync(CancellationToken.None);

            // This is the dialog stack.
            var stack = scope.Resolve<IDialogTask>();

            // Create the new dialog and add it to the stack.
            var dialog = new CallDialog(parameter1, parameter2);
            stack.Call(dialog.Void<object, IMessageActivity>(), null);
            await stack.PollAsync(CancellationToken.None);

            // Flush the dialog stack back to its state store.
            await botData.FlushAsync(CancellationToken.None);
        }
    }
    catch (Exception e)
    {
        await ProprietaryDiagnosticsTool.SendDiagnostic(e);
    }
}

CallDialog

public class CallDialog : IDialog<object>
{
    Parameter1 param1;
    Parameter2 param2;
    public CallDialog(Parameter1 param1, Parameter2 param2)
    {
        this.param1 = param1;
        this.param2 = param2;
    }
    public async Task StartAsync(IDialogContext context)
    {
        switch (param1.Id)
        {
            case 1:
                {
                    var form = new FormDialog<Form1>(new Form1(), Form1.BuildForm, FormOptions.PromptInStart, null);
                    context.Call(form, formComplete);
                    break;
                }
            case 2:
                {
                    var form = new FormDialog<Form2>(new Form2(), Form2.BuildForm, FormOptions.PromptInStart, null);
                    context.Call(form, formComplete);
                    break;
                }
            case 3:
                {
                    var form = new FormDialog<Form3>(new Form3(), Form3.BuildForm, FormOptions.PromptInStart, null);
                    context.Call(form, formComplete);
                    break;
                }
        }

    }

    private async Task formComplete(IDialogContext context, IAwaitable<FormParent> result)
    {
        var ans = await result;
        await context.PostAsync("Result received");
        context.Done(this);
    }
}

Expected behavior

When calling the proactive dialog which calls the form in a different locale, the form should be presented in the locale specified

Screenshots

English formflow triggered through keyword - correct English formflow triggered through keyword

English formflow triggered through API - correct English formflow triggered through API

Mandarin formflow triggered through keyword - correct Mandarin formflow triggered through keyword

Mandarin formflow triggered through API - incorrect Mandarin formflow triggered through API

The error message says

"Yes" is not an option for question 1.

Additional information

I've traced the context.activity object through the various methods, from StartProactiveDialogAsync to CallDialog all the way till the formComplete method. The locale does tend to be correct, its simply the display of the first question of the proactive dialog calling the formflow that happens to be in the wrong language.


Solution

  • Eric from Microsoft helped to resolve this.

    His full answer can be found here: https://github.com/Microsoft/BotBuilder-V3/issues/82

    Simply put the locale needs to be pulled out of context.activity.privateconversationdata and sent to the form itself as it does not pick up the locale on its own when resuming a conversation.