Skip to content

Commit

Permalink
Merge e781c1d into dacb035
Browse files Browse the repository at this point in the history
  • Loading branch information
mdrichardson committed Sep 30, 2019
2 parents dacb035 + e781c1d commit 35318d7
Show file tree
Hide file tree
Showing 7 changed files with 593 additions and 34 deletions.
57 changes: 40 additions & 17 deletions libraries/Microsoft.Bot.Builder.Dialogs/Prompts/ChoicePrompt.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@

using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Bot.Builder.Dialogs.Choices;
using Microsoft.Bot.Schema;
using static Microsoft.Recognizers.Text.Culture;
using static Microsoft.Bot.Builder.Dialogs.Prompts.PromptCultureModels;

namespace Microsoft.Bot.Builder.Dialogs
{
Expand All @@ -16,17 +18,15 @@ namespace Microsoft.Bot.Builder.Dialogs
/// </summary>
public class ChoicePrompt : Prompt<FoundChoice>
{
private static readonly Dictionary<string, ChoiceFactoryOptions> DefaultChoiceOptions = new Dictionary<string, ChoiceFactoryOptions>()
{
{ Spanish, new ChoiceFactoryOptions { InlineSeparator = ", ", InlineOr = " o ", InlineOrMore = ", o ", IncludeNumbers = true } },
{ Dutch, new ChoiceFactoryOptions { InlineSeparator = ", ", InlineOr = " of ", InlineOrMore = ", of ", IncludeNumbers = true } },
{ English, new ChoiceFactoryOptions { InlineSeparator = ", ", InlineOr = " or ", InlineOrMore = ", or ", IncludeNumbers = true } },
{ French, new ChoiceFactoryOptions { InlineSeparator = ", ", InlineOr = " ou ", InlineOrMore = ", ou ", IncludeNumbers = true } },
{ German, new ChoiceFactoryOptions { InlineSeparator = ", ", InlineOr = " oder ", InlineOrMore = ", oder ", IncludeNumbers = true } },
{ Japanese, new ChoiceFactoryOptions { InlineSeparator = "", InlineOr = " または ", InlineOrMore = "、 または ", IncludeNumbers = true } },
{ Portuguese, new ChoiceFactoryOptions { InlineSeparator = ", ", InlineOr = " ou ", InlineOrMore = ", ou ", IncludeNumbers = true } },
{ Chinese, new ChoiceFactoryOptions { InlineSeparator = "", InlineOr = " 要么 ", InlineOrMore = ", 要么 ", IncludeNumbers = true } },
};
/// <summary>
/// A dictionary of Default Choices based on <seealso cref="GetSupportedCultures"/>.
/// Can be replaced by user using the constructor that contains choiceDefaults.
/// </summary>
private readonly Dictionary<string, ChoiceFactoryOptions> _choiceDefaults =
new Dictionary<string, ChoiceFactoryOptions>(
GetSupportedCultures().ToDictionary(
culture => culture.Locale, culture =>
new ChoiceFactoryOptions { InlineSeparator = culture.Separator, InlineOr = culture.InlineOr, InlineOrMore = culture.InlineOrMore, IncludeNumbers = true }));

/// <summary>
/// Initializes a new instance of the <see cref="ChoicePrompt"/> class.
Expand All @@ -50,6 +50,29 @@ public ChoicePrompt(string dialogId, PromptValidator<FoundChoice> validator = nu
DefaultLocale = defaultLocale;
}

/// <summary>
/// Initializes a new instance of the <see cref="ChoicePrompt"/> class.
/// </summary>
/// <param name="dialogId">The ID to assign to this prompt.</param>
/// <param name="validator">Optional, a <see cref="PromptValidator{FoundChoice}"/> that contains additional,
/// custom validation for this prompt.</param>
/// <param name="defaultLocale">Optional, the default locale used to determine language-specific behavior of the prompt.
/// The locale is a 2, 3, or 4 character ISO 639 code that represents a language or language family.</param>
/// <param name="choiceDefaults">Overrides the dictionary of Bot Framework SDK-supported _choiceDefaults (for prompt localization).
/// Must be passed in to each ConfirmPrompt that needs the custom choice defaults.</param>
/// <remarks>The value of <paramref name="dialogId"/> must be unique within the
/// <see cref="DialogSet"/> or <see cref="ComponentDialog"/> to which the prompt is added.
/// <para>If the <see cref="Activity.Locale"/>
/// of the <see cref="DialogContext"/>.<see cref="DialogContext.Context"/>.<see cref="ITurnContext.Activity"/>
/// is specified, then that local is used to determine language specific behavior; otherwise
/// the <paramref name="defaultLocale"/> is used. US-English is the used if no language or
/// default locale is available, or if the language or locale is not otherwise supported.</para></remarks>
public ChoicePrompt(string dialogId, Dictionary<string, ChoiceFactoryOptions> choiceDefaults, PromptValidator<FoundChoice> validator = null, string defaultLocale = null)
: this(dialogId, validator, defaultLocale)
{
_choiceDefaults = choiceDefaults;
}

/// <summary>
/// Gets or sets the style to use when presenting the prompt to the user.
/// </summary>
Expand Down Expand Up @@ -101,17 +124,17 @@ protected override async Task OnPromptAsync(ITurnContext turnContext, IDictionar
}

// Determine culture
var culture = turnContext.Activity.Locale ?? DefaultLocale;
if (string.IsNullOrEmpty(culture) || !DefaultChoiceOptions.ContainsKey(culture))
var culture = MapToNearestLanguage(turnContext.Activity.Locale ?? DefaultLocale);
if (string.IsNullOrEmpty(culture) || !_choiceDefaults.ContainsKey(culture))
{
culture = English;
culture = English.Locale;
}

// Format prompt to send
IMessageActivity prompt;
var choices = options.Choices ?? new List<Choice>();
var channelId = turnContext.Activity.ChannelId;
var choiceOptions = ChoiceOptions ?? DefaultChoiceOptions[culture];
var choiceOptions = ChoiceOptions ?? _choiceDefaults[culture];
var choiceStyle = options.Style ?? Style;
if (isRetry && options.RetryPrompt != null)
{
Expand Down Expand Up @@ -152,7 +175,7 @@ protected override Task<PromptRecognizerResult<FoundChoice>> OnRecognizeAsync(IT
var activity = turnContext.Activity;
var utterance = activity.Text;
var opt = RecognizerOptions ?? new FindChoicesOptions();
opt.Locale = activity.Locale ?? opt.Locale ?? DefaultLocale ?? English;
opt.Locale = MapToNearestLanguage(activity.Locale ?? opt.Locale ?? DefaultLocale ?? English.Locale);
var results = ChoiceRecognizers.RecognizeChoices(utterance, choices, opt);
if (results != null && results.Count > 0)
{
Expand Down
58 changes: 41 additions & 17 deletions libraries/Microsoft.Bot.Builder.Dialogs/Prompts/ConfirmPrompt.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Bot.Builder.Dialogs.Choices;
using Microsoft.Bot.Schema;
using Microsoft.Recognizers.Text.Choice;
using static Microsoft.Recognizers.Text.Culture;
using static Microsoft.Bot.Builder.Dialogs.Prompts.PromptCultureModels;

namespace Microsoft.Bot.Builder.Dialogs
{
Expand All @@ -17,17 +18,17 @@ namespace Microsoft.Bot.Builder.Dialogs
/// </summary>
public class ConfirmPrompt : Prompt<bool>
{
private static readonly Dictionary<string, (Choice, Choice, ChoiceFactoryOptions)> ChoiceDefaults = new Dictionary<string, (Choice, Choice, ChoiceFactoryOptions)>()
{
{ Spanish, (new Choice(""), new Choice("No"), new ChoiceFactoryOptions(", ", " o ", ", o ", true)) },
{ Dutch, (new Choice("Ja"), new Choice("Nee"), new ChoiceFactoryOptions(", ", " of ", ", of ", true)) },
{ English, (new Choice("Yes"), new Choice("No"), new ChoiceFactoryOptions(", ", " or ", ", or ", true)) },
{ French, (new Choice("Oui"), new Choice("Non"), new ChoiceFactoryOptions(", ", " ou ", ", ou ", true)) },
{ German, (new Choice("Ja"), new Choice("Nein"), new ChoiceFactoryOptions(", ", " oder ", ", oder ", true)) },
{ Japanese, (new Choice("はい"), new Choice("いいえ"), new ChoiceFactoryOptions("", " または ", "、 または ", true)) },
{ Portuguese, (new Choice("Sim"), new Choice("Não"), new ChoiceFactoryOptions(", ", " ou ", ", ou ", true)) },
{ Chinese, (new Choice("是的"), new Choice(""), new ChoiceFactoryOptions("", " 要么 ", ", 要么 ", true)) },
};
/// <summary>
/// A dictionary of Default Choices based on <seealso cref="GetSupportedCultures"/>.
/// Can be replaced by user using the constructor that contains choiceDefaults.
/// </summary>
private readonly Dictionary<string, (Choice, Choice, ChoiceFactoryOptions)> _choiceDefaults =
new Dictionary<string, (Choice, Choice, ChoiceFactoryOptions)>(
GetSupportedCultures().ToDictionary(
culture => culture.Locale, culture =>
(new Choice(culture.YesInLanguage),
new Choice(culture.NoInLanguage),
new ChoiceFactoryOptions(culture.Separator, culture.InlineOr, culture.InlineOrMore, true))));

/// <summary>
/// Initializes a new instance of the <see cref="ConfirmPrompt"/> class.
Expand All @@ -51,6 +52,29 @@ public ConfirmPrompt(string dialogId, PromptValidator<bool> validator = null, st
DefaultLocale = defaultLocale;
}

/// <summary>
/// Initializes a new instance of the <see cref="ConfirmPrompt"/> class.
/// </summary>
/// <param name="dialogId">The ID to assign to this prompt.</param>
/// <param name="validator">Optional, a <see cref="PromptValidator{FoundChoice}"/> that contains additional,
/// custom validation for this prompt.</param>
/// <param name="defaultLocale">Optional, the default locale used to determine language-specific behavior of the prompt.
/// The locale is a 2, 3, or 4 character ISO 639 code that represents a language or language family.</param>
/// <param name="choiceDefaults">Overrides the dictionary of Bot Framework SDK-supported _choiceDefaults (for prompt localization).
/// Must be passed in to each ConfirmPrompt that needs the custom choice defaults.</param>
/// <remarks>The value of <paramref name="dialogId"/> must be unique within the
/// <see cref="DialogSet"/> or <see cref="ComponentDialog"/> to which the prompt is added.
/// <para>If the <see cref="Activity.Locale"/>
/// of the <see cref="DialogContext"/>.<see cref="DialogContext.Context"/>.<see cref="ITurnContext.Activity"/>
/// is specified, then that local is used to determine language specific behavior; otherwise
/// the <paramref name="defaultLocale"/> is used. US-English is the used if no language or
/// default locale is available, or if the language or locale is not otherwise supported.</para></remarks>
public ConfirmPrompt(string dialogId, Dictionary<string, (Choice, Choice, ChoiceFactoryOptions)> choiceDefaults, PromptValidator<bool> validator = null, string defaultLocale = null)
: this(dialogId, validator, defaultLocale)
{
_choiceDefaults = choiceDefaults;
}

/// <summary>
/// Gets or sets the style of the yes/no choices rendered to the user when prompting.
/// </summary>
Expand Down Expand Up @@ -106,7 +130,7 @@ protected override async Task OnPromptAsync(ITurnContext turnContext, IDictionar
IMessageActivity prompt;
var channelId = turnContext.Activity.ChannelId;
var culture = DetermineCulture(turnContext.Activity);
var defaults = ChoiceDefaults[culture];
var defaults = _choiceDefaults[culture];
var choiceOptions = ChoiceOptions ?? defaults.Item3;
var confirmChoices = ConfirmChoices ?? Tuple.Create(defaults.Item1, defaults.Item2);
var choices = new List<Choice> { confirmChoices.Item1, confirmChoices.Item2 };
Expand Down Expand Up @@ -160,7 +184,7 @@ protected override Task<PromptRecognizerResult<bool>> OnRecognizeAsync(ITurnCont
else
{
// First check whether the prompt was sent to the user with numbers - if it was we should recognize numbers
var defaults = ChoiceDefaults[culture];
var defaults = _choiceDefaults[culture];
var choiceOptions = ChoiceOptions ?? defaults.Item3;

// This logic reflects the fact that IncludeNumbers is nullable and True is the default set in Inline style
Expand All @@ -184,10 +208,10 @@ protected override Task<PromptRecognizerResult<bool>> OnRecognizeAsync(ITurnCont

private string DetermineCulture(Activity activity)
{
var culture = activity.Locale ?? DefaultLocale;
if (string.IsNullOrEmpty(culture) || !ChoiceDefaults.ContainsKey(culture))
var culture = MapToNearestLanguage(activity.Locale ?? DefaultLocale ?? English.Locale);
if (string.IsNullOrEmpty(culture) || !_choiceDefaults.ContainsKey(culture))
{
culture = English;
culture = English.Locale;
}

return culture;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
namespace Microsoft.Bot.Builder.Dialogs.Prompts
{
/// <summary>
/// Culture model used in Choice and Confirm Prompts.
/// </summary>
public class PromptCultureModel
{
/// <summary>
/// Gets or Sets Culture Model's Locale.
/// </summary>
/// <value>
/// Ex: Locale. Example: "en-US".
/// </value>
public string Locale { get; set; }

/// <summary>
/// Gets or Sets Culture Model's InlineSeparator.
/// </summary>
/// <value>
/// Ex: Locale. Example: ", ".
/// </value>
public string Separator { get; set; }

/// <summary>
/// Gets or Sets Culture Model's InlineOr.
/// </summary>
/// <value>
/// Ex: Locale. Example: " or ".
/// </value>
public string InlineOr { get; set; }

/// <summary>
/// Gets or Sets Culture Model's InlineOrMore.
/// </summary>
/// <value>
/// Ex: Locale. Example: ", or ".
/// </value>
public string InlineOrMore { get; set; }

/// <summary>
/// Gets or Sets Equivalent of "Yes" in Culture Model's Language.
/// </summary>
/// <value>
/// Ex: Locale. Example: "Yes".
/// </value>
public string YesInLanguage { get; set; }

/// <summary>
/// Gets or Sets Equivalent of "No" in Culture Model's Language.
/// </summary>
/// <value>
/// Ex: Locale. Example: "No".
/// </value>
public string NoInLanguage { get; set; }
}
}
122 changes: 122 additions & 0 deletions libraries/Microsoft.Bot.Builder.Dialogs/Prompts/PromptCultureModels.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
using System;
using System.Globalization;
using System.Linq;
using Microsoft.Recognizers.Text;

namespace Microsoft.Bot.Builder.Dialogs.Prompts
{
/// <summary>
/// Class container for currently-supported Culture Models in Confirm and Choice Prompt.
/// </summary>
public static class PromptCultureModels
{
public static PromptCultureModel Chinese =>
new PromptCultureModel
{
InlineOr = " 要么 ",
InlineOrMore = ", 要么 ",
Locale = Culture.Chinese,
NoInLanguage = "",
Separator = "",
YesInLanguage = "是的",
};

public static PromptCultureModel Dutch =>
new PromptCultureModel
{
InlineOr = " of ",
InlineOrMore = ", of ",
Locale = Culture.Dutch,
NoInLanguage = "Nee",
Separator = ", ",
YesInLanguage = "Ja",
};

public static PromptCultureModel English =>
new PromptCultureModel
{
InlineOr = " or ",
InlineOrMore = ", or ",
Locale = Culture.English,
NoInLanguage = "No",
Separator = ", ",
YesInLanguage = "Yes",
};

public static PromptCultureModel French =>
new PromptCultureModel
{
InlineOr = " ou ",
InlineOrMore = ", ou ",
Locale = Culture.French,
NoInLanguage = "Non",
Separator = ", ",
YesInLanguage = "Oui",
};

public static PromptCultureModel German =>
new PromptCultureModel
{
InlineOr = " oder ",
InlineOrMore = ", oder ",
Locale = Culture.German,
NoInLanguage = "Nein",
Separator = ", ",
YesInLanguage = "Ja",
};

public static PromptCultureModel Japanese =>
new PromptCultureModel
{
InlineOr = " または ",
InlineOrMore = "、 または ",
Locale = Culture.Japanese,
NoInLanguage = "いいえ",
Separator = "",
YesInLanguage = "はい",
};

public static PromptCultureModel Portuguese =>
new PromptCultureModel
{
InlineOr = " ou ",
InlineOrMore = ", ou ",
Locale = Culture.Portuguese,
NoInLanguage = "Não",
Separator = ", ",
YesInLanguage = "Sim",
};

public static PromptCultureModel Spanish =>
new PromptCultureModel
{
InlineOr = " o ",
InlineOrMore = ", o ",
Locale = Culture.Spanish,
NoInLanguage = "No",
Separator = ", ",
YesInLanguage = "",
};

/// <summary>
/// Use Recognizers-Text to normalize various potential Locale strings to a standard.
/// This is more or less a copy/paste of Recognizers-Text.Culture's MapToNearestLanguage, but needed as an override
/// here so that we can support additional languages.
/// </summary>
/// <param name="cultureCode">Represents locale. Examples: "en-US, en-us, EN".</param>
/// <returns>Normalized locale.</returns>
public static string MapToNearestLanguage(string cultureCode) => Culture.MapToNearestLanguage(cultureCode);

public static PromptCultureModel[] GetSupportedCultures() => new PromptCultureModel[]
{
Chinese,
Dutch,
English,
French,
German,
Japanese,
Portuguese,
Spanish,
};
}
}

0 comments on commit 35318d7

Please sign in to comment.