Skip to content
This repository was archived by the owner on Jan 5, 2026. It is now read-only.

Commit 3afe849

Browse files
mdrichardsonMichael Richardsontracyboehrer
authored
Add PromptCultureModels, rework prompt locales (#1557)
* add PromptCultureModels, rework prompt locales * add description back to inspector Co-authored-by: Michael Richardson <v-micric@microsoft.com> Co-authored-by: tracyboehrer <tracyboehrer@users.noreply.github.com>
1 parent 885d3d1 commit 3afe849

File tree

6 files changed

+707
-119
lines changed

6 files changed

+707
-119
lines changed

libraries/botbuilder-dialogs/botbuilder/dialogs/prompts/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from .number_prompt import NumberPrompt
1515
from .oauth_prompt import OAuthPrompt
1616
from .oauth_prompt_settings import OAuthPromptSettings
17+
from .prompt_culture_models import PromptCultureModel, PromptCultureModels
1718
from .prompt_options import PromptOptions
1819
from .prompt_recognizer_result import PromptRecognizerResult
1920
from .prompt_validator_context import PromptValidatorContext
@@ -30,6 +31,8 @@
3031
"NumberPrompt",
3132
"OAuthPrompt",
3233
"OAuthPromptSettings",
34+
"PromptCultureModel",
35+
"PromptCultureModels",
3336
"PromptOptions",
3437
"PromptRecognizerResult",
3538
"PromptValidatorContext",
Lines changed: 40 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
# Copyright (c) Microsoft Corporation. All rights reserved.
22
# Licensed under the MIT License.
33

4-
from typing import Callable, Dict, List, Union
4+
from typing import Callable, Dict, List
55

6-
from recognizers_text import Culture
76
from botbuilder.core import TurnContext
87
from botbuilder.dialogs.choices import (
98
Choice,
@@ -15,6 +14,7 @@
1514
from botbuilder.schema import Activity, ActivityTypes
1615

1716
from .prompt import Prompt
17+
from .prompt_culture_models import PromptCultureModels
1818
from .prompt_options import PromptOptions
1919
from .prompt_validator_context import PromptValidatorContext
2020
from .prompt_recognizer_result import PromptRecognizerResult
@@ -29,69 +29,43 @@ class ChoicePrompt(Prompt):
2929
"""
3030

3131
_default_choice_options: Dict[str, ChoiceFactoryOptions] = {
32-
Culture.Spanish: ChoiceFactoryOptions(
33-
inline_separator=", ",
34-
inline_or=" o ",
35-
inline_or_more=", o ",
32+
c.locale: ChoiceFactoryOptions(
33+
inline_separator=c.separator,
34+
inline_or=c.inline_or_more,
35+
inline_or_more=c.inline_or_more,
3636
include_numbers=True,
37-
),
38-
Culture.Dutch: ChoiceFactoryOptions(
39-
inline_separator=", ",
40-
inline_or=" of ",
41-
inline_or_more=", of ",
42-
include_numbers=True,
43-
),
44-
Culture.English: ChoiceFactoryOptions(
45-
inline_separator=", ",
46-
inline_or=" or ",
47-
inline_or_more=", or ",
48-
include_numbers=True,
49-
),
50-
Culture.French: ChoiceFactoryOptions(
51-
inline_separator=", ",
52-
inline_or=" ou ",
53-
inline_or_more=", ou ",
54-
include_numbers=True,
55-
),
56-
"de-de": ChoiceFactoryOptions(
57-
inline_separator=", ",
58-
inline_or=" oder ",
59-
inline_or_more=", oder ",
60-
include_numbers=True,
61-
),
62-
Culture.Japanese: ChoiceFactoryOptions(
63-
inline_separator="、 ",
64-
inline_or=" または ",
65-
inline_or_more="、 または ",
66-
include_numbers=True,
67-
),
68-
Culture.Portuguese: ChoiceFactoryOptions(
69-
inline_separator=", ",
70-
inline_or=" ou ",
71-
inline_or_more=", ou ",
72-
include_numbers=True,
73-
),
74-
Culture.Chinese: ChoiceFactoryOptions(
75-
inline_separator=", ",
76-
inline_or=" 要么 ",
77-
inline_or_more=", 要么 ",
78-
include_numbers=True,
79-
),
37+
)
38+
for c in PromptCultureModels.get_supported_cultures()
8039
}
8140

8241
def __init__(
8342
self,
8443
dialog_id: str,
8544
validator: Callable[[PromptValidatorContext], bool] = None,
8645
default_locale: str = None,
46+
choice_defaults: Dict[str, ChoiceFactoryOptions] = None,
8747
):
48+
"""
49+
:param dialog_id: Unique ID of the dialog within its parent `DialogSet`.
50+
:param validator: (Optional) validator that will be called each time the user responds to the prompt.
51+
If the validator replies with a message no additional retry prompt will be sent.
52+
:param default_locale: (Optional) locale to use if `dc.context.activity.locale` not specified.
53+
Defaults to a value of `en-us`.
54+
:param choice_defaults: (Optional) Overrides the dictionary of
55+
Bot Framework SDK-supported _default_choice_options.
56+
As type Dict[str, ChoiceFactoryOptions], the key is a string of the locale, such as "en-us".
57+
* Must be passed in to each ConfirmPrompt that needs the custom choice defaults.
58+
"""
8859
super().__init__(dialog_id, validator)
8960

9061
self.style = ListStyle.auto
9162
self.default_locale = default_locale
9263
self.choice_options: ChoiceFactoryOptions = None
9364
self.recognizer_options: FindChoicesOptions = None
9465

66+
if choice_defaults is not None:
67+
self._default_choice_options = choice_defaults
68+
9569
async def on_prompt(
9670
self,
9771
turn_context: TurnContext,
@@ -106,20 +80,15 @@ async def on_prompt(
10680
raise TypeError("ChoicePrompt.on_prompt(): options cannot be None.")
10781

10882
# Determine culture
109-
culture: Union[
110-
str, None
111-
] = turn_context.activity.locale if turn_context.activity.locale else self.default_locale
112-
113-
if not culture or culture not in ChoicePrompt._default_choice_options:
114-
culture = Culture.English
83+
culture = self._determine_culture(turn_context.activity)
11584

11685
# Format prompt to send
11786
choices: List[Choice] = options.choices if options.choices else []
11887
channel_id: str = turn_context.activity.channel_id
11988
choice_options: ChoiceFactoryOptions = (
12089
self.choice_options
12190
if self.choice_options
122-
else ChoicePrompt._default_choice_options[culture]
91+
else self._default_choice_options[culture]
12392
)
12493
choice_style = (
12594
0 if options.style == 0 else options.style if options.style else self.style
@@ -155,15 +124,25 @@ async def on_recognize(
155124
if not utterance:
156125
return result
157126
opt: FindChoicesOptions = self.recognizer_options if self.recognizer_options else FindChoicesOptions()
158-
opt.locale = (
159-
activity.locale
160-
if activity.locale
161-
else (self.default_locale or Culture.English)
162-
)
127+
opt.locale = self._determine_culture(turn_context.activity, opt)
163128
results = ChoiceRecognizers.recognize_choices(utterance, choices, opt)
164129

165130
if results is not None and results:
166131
result.succeeded = True
167132
result.value = results[0].resolution
168133

169134
return result
135+
136+
def _determine_culture(
137+
self, activity: Activity, opt: FindChoicesOptions = FindChoicesOptions()
138+
) -> str:
139+
culture = (
140+
PromptCultureModels.map_to_nearest_language(activity.locale)
141+
or opt.locale
142+
or self.default_locale
143+
or PromptCultureModels.English.locale
144+
)
145+
if not culture or not self._default_choice_options.get(culture):
146+
culture = PromptCultureModels.English.locale
147+
148+
return culture

libraries/botbuilder-dialogs/botbuilder/dialogs/prompts/confirm_prompt.py

Lines changed: 28 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -12,60 +12,30 @@
1212
ListStyle,
1313
)
1414
from .prompt import Prompt
15+
from .prompt_culture_models import PromptCultureModels
1516
from .prompt_options import PromptOptions
1617
from .prompt_recognizer_result import PromptRecognizerResult
1718

1819

1920
class ConfirmPrompt(Prompt):
20-
# TODO: Fix to reference recognizer to use proper constants
21-
choice_defaults: Dict[str, object] = {
22-
"Spanish": (
23-
Choice("Si"),
24-
Choice("No"),
25-
ChoiceFactoryOptions(", ", " o ", ", o ", True),
26-
),
27-
"Dutch": (
28-
Choice("Ja"),
29-
Choice("Nee"),
30-
ChoiceFactoryOptions(", ", " of ", ", of ", True),
31-
),
32-
"English": (
33-
Choice("Yes"),
34-
Choice("No"),
35-
ChoiceFactoryOptions(", ", " or ", ", or ", True),
36-
),
37-
"French": (
38-
Choice("Oui"),
39-
Choice("Non"),
40-
ChoiceFactoryOptions(", ", " ou ", ", ou ", True),
41-
),
42-
"German": (
43-
Choice("Ja"),
44-
Choice("Nein"),
45-
ChoiceFactoryOptions(", ", " oder ", ", oder ", True),
46-
),
47-
"Japanese": (
48-
Choice("はい"),
49-
Choice("いいえ"),
50-
ChoiceFactoryOptions("、 ", " または ", "、 または ", True),
51-
),
52-
"Portuguese": (
53-
Choice("Sim"),
54-
Choice("Não"),
55-
ChoiceFactoryOptions(", ", " ou ", ", ou ", True),
56-
),
57-
"Chinese": (
58-
Choice("是的"),
59-
Choice("不"),
60-
ChoiceFactoryOptions(", ", " 要么 ", ", 要么 ", True),
61-
),
21+
_default_choice_options: Dict[str, object] = {
22+
c.locale: (
23+
Choice(c.yes_in_language),
24+
Choice(c.no_in_language),
25+
ChoiceFactoryOptions(c.separator, c.inline_or, c.inline_or_more, True),
26+
)
27+
for c in PromptCultureModels.get_supported_cultures()
6228
}
6329

6430
# TODO: PromptValidator
6531
def __init__(
66-
self, dialog_id: str, validator: object = None, default_locale: str = None
32+
self,
33+
dialog_id: str,
34+
validator: object = None,
35+
default_locale: str = None,
36+
choice_defaults: Dict[str, object] = None,
6737
):
68-
super(ConfirmPrompt, self).__init__(dialog_id, validator)
38+
super().__init__(dialog_id, validator)
6939
if dialog_id is None:
7040
raise TypeError("ConfirmPrompt(): dialog_id cannot be None.")
7141
# TODO: Port ListStyle
@@ -75,6 +45,9 @@ def __init__(
7545
self.choice_options = None
7646
self.confirm_choices = None
7747

48+
if choice_defaults is not None:
49+
self._default_choice_options = choice_defaults
50+
7851
async def on_prompt(
7952
self,
8053
turn_context: TurnContext,
@@ -89,8 +62,8 @@ async def on_prompt(
8962

9063
# Format prompt to send
9164
channel_id = turn_context.activity.channel_id
92-
culture = self.determine_culture(turn_context.activity)
93-
defaults = self.choice_defaults[culture]
65+
culture = self._determine_culture(turn_context.activity)
66+
defaults = self._default_choice_options[culture]
9467
choice_opts = (
9568
self.choice_options if self.choice_options is not None else defaults[2]
9669
)
@@ -125,7 +98,7 @@ async def on_recognize(
12598
utterance = turn_context.activity.text
12699
if not utterance:
127100
return result
128-
culture = self.determine_culture(turn_context.activity)
101+
culture = self._determine_culture(turn_context.activity)
129102
results = recognize_boolean(utterance, culture)
130103
if results:
131104
first = results[0]
@@ -135,7 +108,7 @@ async def on_recognize(
135108
else:
136109
# First check whether the prompt was sent to the user with numbers
137110
# if it was we should recognize numbers
138-
defaults = self.choice_defaults[culture]
111+
defaults = self._default_choice_options[culture]
139112
opts = (
140113
self.choice_options
141114
if self.choice_options is not None
@@ -161,12 +134,13 @@ async def on_recognize(
161134

162135
return result
163136

164-
def determine_culture(self, activity: Activity) -> str:
137+
def _determine_culture(self, activity: Activity) -> str:
165138
culture = (
166-
activity.locale if activity.locale is not None else self.default_locale
139+
PromptCultureModels.map_to_nearest_language(activity.locale)
140+
or self.default_locale
141+
or PromptCultureModels.English.locale
167142
)
168-
if not culture or culture not in self.choice_defaults:
169-
culture = (
170-
"English" # TODO: Fix to reference recognizer to use proper constants
171-
)
143+
if not culture or not self._default_choice_options.get(culture):
144+
culture = PromptCultureModels.English.locale
145+
172146
return culture

0 commit comments

Comments
 (0)