Localize form content

A form's localization language is determined by the current thread's CurrentUICulture and CurrentCulture. By default, the culture derives from the Locale field of the current message, but you can override that default behavior. Depending on how your bot is constructed, localized information may come from up to three different sources:

  • the built-in localization for PromptDialog and FormFlow
  • a resource file that you generate for the static strings in your form
  • a resource file that you create with strings for dynamically-computed fields, messages or confirmations

Generate a resource file for the static strings in your form

Static strings in a form include the strings that the form generates from the information in your C# class and the strings that you specify as prompts, templates, messages or confirmations. Strings that are generated from built-in templates are not considered static strings, since those strings are already localized. Since many of the strings in a form are automatically generated, it is not feasible to use normal C# resource strings directly. Instead, you can generate a resource file for the static strings in your form either by calling IFormBuilder.SaveResources or by using the RView tool that is included with the BotBuilder SDK for .NET.

Use IFormBuilder.SaveResources

You can generate a resource file by calling IFormBuilder.SaveResources on your form to save the strings to a .resx file.

Use RView

Alternatively, you can generate a resource file that is based upon your .dll or .exe by using the RView tool that is included in the BotBuilder SDK for .NET. To generate the .resx file, execute rview and specify the assembly that contains your static form-building method and the path to that method. This snippet shows how to generate the Microsoft.Bot.Sample.AnnotatedSandwichBot.SandwichOrder.resx resource file using RView.

rview -g Microsoft.Bot.Sample.AnnotatedSandwichBot.dll Microsoft.Bot.Sample.AnnotatedSandwichBot.SandwichOrder.BuildForm

This excerpt shows part of the .resx file that is generated by executing this rview command.

<data name="Specials_description;VALUE" xml:space="preserve">
<value>Specials</value>
</data>
<data name="DeliveryAddress_description;VALUE" xml:space="preserve">
<value>Delivery Address</value>
</data>
<data name="DeliveryTime_description;VALUE" xml:space="preserve">
<value>Delivery Time</value>
</data>
<data name="PhoneNumber_description;VALUE" xml:space="preserve">
<value>Phone Number</value>
</data>
<data name="Rating_description;VALUE" xml:space="preserve">
<value>your experience today</value>
</data>
<data name="message0;LIST" xml:space="preserve">
<value>Welcome to the sandwich order bot!</value>
</data>
<data name="Sandwich_terms;LIST" xml:space="preserve">
<value>sandwichs?</value>
</data>

Configure your project

After you have generated a resource file, add it to your project and then set the neutral language by completing these steps:

  1. Right-click on your project and select Application.
  2. Click Assembly Information.
  3. Select the Neutral Language value that corresponds to the language in which you developed your bot.

When your form is created, the IFormBuilder.Build method will automatically look for resources that contain your form type name and use them to localize the static strings in your form.

Note

Dynamically-computed fields that are defined using Advanced.Field.SetDefine (as described in Using Dynamic Fields) cannot be localized in the same manner as static fields, since strings for dynamically-computed fields are constructed at the time the form is populated. However, you can localize dynamically-computed fields by using normal C# localization mechanisms.

Localize resource files

After you have added resource files to your project, you can localize them by using the Multilingual App Toolkit (MAT). Install MAT, then enable it for your project by completing these steps:

  1. Select your project in the Visual Studio Solution Explorer.
  2. Click Tools, Multilingual App Toolkit, and Enable.
  3. Right-click the project and select Multilingual App Toolkit, Add Translations to select the translations. This will create industry-standard XLF files that you can automatically or manually translate.
Note

Although this article describes how to use the Multilingual App Toolkit to localize content, you may implement localization via a variety of other means.

See it in action

This code example builds upon the one in Customize a form using FormBuilder to implement localization as described above. In this example, the DynamicSandwich class (not shown here) contains localization information for dynamically-computed fields, messages and confirmations.

public static IForm<SandwichOrder> BuildLocalizedForm()
{
    var culture = Thread.CurrentThread.CurrentUICulture;
    IForm<SandwichOrder> form;
    if (!_forms.TryGetValue(culture, out form))
    {
        OnCompletionAsyncDelegate<SandwichOrder> processOrder = async (context, state) =>
                {
                    await context.PostAsync(DynamicSandwich.Processing);
                }; 
        // FormBuilder uses the thread culture to automatically switch framework strings and static strings.
        // Dynamically defined fields must do their own localization.
        var builder = new FormBuilder<SandwichOrder>()
                .Message("Welcome to the sandwich order bot!")
                .Field(nameof(Sandwich))
                .Field(nameof(Length))
                .Field(nameof(Bread))
                .Field(nameof(Cheese))
                .Field(nameof(Toppings),
                    validate: async (state, value) =>
                    {
                        var values = ((List<object>)value).OfType<ToppingOptions>();
                        var result = new ValidateResult { IsValid = true, Value = values };
                        if (values != null && values.Contains(ToppingOptions.Everything))
                        {
                            result.Value = (from ToppingOptions topping in Enum.GetValues(typeof(ToppingOptions))
                                            where topping != ToppingOptions.Everything && !values.Contains(topping)
                                            select topping).ToList();
                        }
                        return result;
                    })
                .Message("For sandwich toppings you have selected {Toppings}.")
                .Field(nameof(SandwichOrder.Sauces))
                .Field(new FieldReflector<SandwichOrder>(nameof(Specials))
                    .SetType(null)
                    .SetActive((state) => state.Length == LengthOptions.FootLong)
                    .SetDefine(async (state, field) =>
                        {
                            field
                                .AddDescription("cookie", DynamicSandwich.FreeCookie)
                                .AddTerms("cookie", Language.GenerateTerms(DynamicSandwich.FreeCookie, 2))
                                .AddDescription("drink", DynamicSandwich.FreeDrink)
                                .AddTerms("drink", Language.GenerateTerms(DynamicSandwich.FreeDrink, 2));
                            return true;
                        }))
                .Confirm(async (state) =>
                    {
                        var cost = 0.0;
                        switch (state.Length)
                        {
                            case LengthOptions.SixInch: cost = 5.0; break;
                            case LengthOptions.FootLong: cost = 6.50; break;
                        }
                        return new PromptAttribute(string.Format(DynamicSandwich.Cost, cost) + "{||}");
                    })
                .Field(nameof(SandwichOrder.DeliveryAddress),
                    validate: async (state, response) =>
                    {
                        var result = new ValidateResult { IsValid = true, Value = response };
                        var address = (response as string).Trim();
                        if (address.Length > 0 && address[0] < '0' || address[0] > '9')
                        {
                            result.Feedback = DynamicSandwich.BadAddress;
                            result.IsValid = false;
                        }
                        return result;
                    })
                .Field(nameof(SandwichOrder.DeliveryTime), "What time do you want your sandwich delivered? {||}")
                .Confirm("Do you want to order your {Length} {Sandwich} on {Bread} {&Bread} with {[{Cheese} {Toppings} {Sauces}]} to be sent to {DeliveryAddress} {?at {DeliveryTime:t}}?")
                .AddRemainingFields()
                .Message("Thanks for ordering a sandwich!")
                .OnCompletion(processOrder);
		builder.Configuration.DefaultPrompt.ChoiceStyle = ChoiceStyleOptions.Auto;
		form = builder.Build();
		_forms[culture] = form;
	}
	return form;
}

This snippet shows the resulting interaction between bot and user when CurrentUICulture is French.

Bienvenue sur le bot d'ordre "sandwich" !
Quel genre de "sandwich" vous souhaitez sur votre "sandwich"?
 1. BLT
 2. Jambon Forêt Noire
 3. Poulet Buffalo
 4. Faire fondre le poulet et Bacon Ranch
 5. Combo de coupe à froid
 6. Boulette de viande Marinara
 7. Poulet rôti au four
 8. Rôti de boeuf
 9. Rotisserie poulet
 10. Italienne piquante
 11. Bifteck et fromage
 12. Oignon doux Teriyaki
 13. Thon
 14. Poitrine de dinde
 15. Veggie
> 2

Quel genre de longueur vous souhaitez sur votre "sandwich"?
 1. Six pouces
 2. Pied Long
> ?
* Vous renseignez le champ longueur.Réponses possibles:
* Vous pouvez saisir un numéro 1-2 ou des mots de la description. (Six pouces, ou Pied Long)
* Retourner à la question précédente.
* Assistance: Montrez les réponses possibles.
* Abandonner: Abandonner sans finir
* Recommencer remplir le formulaire. (Vos réponses précédentes sont enregistrées.)
* Statut: Montrer le progrès en remplissant le formulaire jusqu'à présent.
* Vous pouvez passer à un autre champ en entrant son nom. ("Sandwich", Longueur, Pain, Fromage, Nappages, Sauces, Adresse de remise, Délai de livraison, ou votre expérience aujourd'hui).
Quel genre de longueur vous souhaitez sur votre "sandwich"?
 1. Six pouces
 2. Pied Long
> 1

Quel genre de pain vous souhaitez sur votre "sandwich"?
 1. Neuf grains de blé
 2. Neuf grains miel avoine
 3. Italien
 4. Fromage et herbes italiennes
 5. Pain plat
> neuf
Par pain "neuf" vouliez-vous dire (1. Neuf grains miel avoine, ou 2. Neuf grains de blé)

Sample code

For complete samples that show how to implement FormFlow using the Bot Builder SDK for .NET, see the Multi-Dialog Bot sample and the Contoso Flowers Bot sample in GitHub.

Additional resources