前回はリソースファイルを使ってボットの多言語化を始めました。今回はアダプティブカードの多言語化と、前回やり残したダイアログの多言語化を行います。
アダプティブカードの多言語化
今回のボットでは JSON ファイルからアダプティブカードを作っています。アダプティブカードの中身は翻訳しないといけない箇所が多いため、ここでは言語別のアダプティブカードを作って、ユーザーが指定した言語で読み込むように変更します。
1. AdaptiveJsons フォルダ配下に ja-JP フォルダを作成し、既存の JSON を全て移動。
2. 同様に en-US フォルダを作成して、既存の JSON をコピー。英語に翻訳。
3. json ファイルを読み取るコードに言語を追加。ProfileDialog.cs の場合は以下の様に変更。
var userProfile = await accessors.UserProfile.GetAsync(stepContext.Context,
()=> new UserProfile(), cancellationToken);
var adaptiveCardJson = File.ReadAllText(
$"./AdaptiveJsons/{userProfile.Language}/Profile.json");
残りのダイアログの多言語化
前回の記事同様、残りのダイアログも多言語化対応します。
1. LoginDialog 用の RESX ファイルを追加。
<?xml version="1.0" encoding="utf-8"?>
<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="signindialog" xml:space="preserve">
<value>サインイン ダイアログ</value>
</data>
<data name="signin" xml:space="preserve">
<value>サインイン</value>
</data>
<data name="failed" xml:space="preserve">
<value>サインインに失敗しました</value>
</data>
</root>
<?xml version="1.0" encoding="utf-8"?>
<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="signindialog" xml:space="preserve">
<value>Signin Dialog</value>
</data>
<data name="signin" xml:space="preserve">
<value>Signin</value>
</data>
<data name="failed" xml:space="preserve">
<value>Failed to signin</value>
</data>
</root>
2. LoginDialog.cs でリソースを使うように変更。
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Schema;
using Microsoft.Extensions.Localization;
public class LoginDialog : ComponentDialog
{
private const string connectionName = "AzureAdv2";
private IStringLocalizer<LoginDialog> localizer;
public LoginDialog(IStringLocalizer<LoginDialog> localizer) : base(nameof(LoginDialog))
{
this.localizer = localizer;
// ウォーターフォールのステップを定義。処理順にメソッドを追加。
var waterfallSteps = new WaterfallStep[]
{
LoginAsync,
CompleteLoginAsync,
};
// ウォーターフォールダイアログと各種プロンプトを追加
AddDialog(new WaterfallDialog("logindialog", waterfallSteps));
AddDialog(new OAuthPrompt(
"login",
new OAuthPromptSettings
{
ConnectionName = connectionName,
Text = localizer["signindialog"],
Title = localizer["signin"],
Timeout = 300000, // 5分でタイムアウトするように設定
}));
}
private async Task<DialogTurnResult> LoginAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
return await stepContext.BeginDialogAsync("login", cancellationToken: cancellationToken);
}
private async Task<DialogTurnResult> CompleteLoginAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
// ログインの結果よりトークンを取得
var tokenResponse = (TokenResponse)stepContext.Result;
if (string.IsNullOrEmpty(tokenResponse.Token))
{
await stepContext.Context.SendActivityAsync(localizer["failed"], cancellationToken: cancellationToken);
return await stepContext.EndDialogAsync("", cancellationToken);
}
else
// 戻り値としてトークンを返す
return await stepContext.EndDialogAsync(tokenResponse.Token, cancellationToken);
}
}
3. MenuDialog 用の RESX を追加。
<?xml version="1.0" encoding="utf-8"?>
<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="checkweather" xml:space="preserve">
<value>天気を確認</value>
</data>
<data name="checkschedule" xml:space="preserve">
<value>予定を確認。</value>
</data>
<data name="choicemenu" xml:space="preserve">
<value>今日はなにをしますか?</value>
</data>
</root>
<?xml version="1.0" encoding="utf-8"?>
<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="checkweather" xml:space="preserve">
<value>Check weather</value>
</data>
<data name="checkschedule" xml:space="preserve">
<value>Check schedule</value>
</data>
<data name="choicemenu" xml:space="preserve">
<value>What do you want to do today?</value>
</data>
</root>
4. MenuDialog.cs を以下のコードに差し替え。
- リソースを使うため、一部のプロパティを静的から通常に変更
- コンストラクタで初期化
using System.Threading;
using System.Threading.Tasks;
using System.Collections.Generic;
using Microsoft.Bot.Builder;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Builder.Dialogs.Choices;
using System.Linq;
using System;
using Microsoft.Extensions.Localization;
public class MenuDialog : ComponentDialog
{
// メニューの作成。表示される文字と子ダイアログの名称をセットで登録
private Dictionary<string, string> menus;
private IStringLocalizer<MenuDialog> localizer;
// ChoiceFactory で選択肢に設定する IList<Choice> を作成
private IList<Choice> choices;
public MenuDialog(IServiceProvider serviceProvider, IStringLocalizer<MenuDialog> localizer) : base(nameof(MenuDialog))
{
this.localizer = localizer;
menus = new Dictionary<string, string>(){
{ localizer["checkweather"], nameof(WeatherDialog) },
{ localizer["checkschedule"], nameof(ScheduleDialog) }
};
choices = ChoiceFactory.ToChoices(menus.Select(x => x.Key).ToList());
// ウォーターフォールのステップを定義。処理順にメソッドを追加。
var waterfallSteps = new WaterfallStep[]
{
ShowMenuAsync,
ProcessInputAsync,
LoopMenu
};
// ウォーターフォールダイアログと各種プロンプトを追加
AddDialog(new WaterfallDialog("menu", waterfallSteps));
AddDialog(new ChoicePrompt("choice"));
AddDialog((WeatherDialog)serviceProvider.GetService(typeof(WeatherDialog)));
AddDialog((ScheduleDialog)serviceProvider.GetService(typeof(ScheduleDialog)));
}
public async Task<DialogTurnResult> ShowMenuAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
// Choice プロンプトでメニューを表示
return await stepContext.PromptAsync(
"choice",
new PromptOptions
{
Prompt = MessageFactory.Text(localizer["choicemenu"]),
Choices = choices,
},
cancellationToken);
}
private async Task<DialogTurnResult> ProcessInputAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
// 答えを確認して次のダイアログ名を取得
var choice = (FoundChoice)stepContext.Result;
var dialogId = menus[choice.Value];
// 子ダイアログの実行
return await stepContext.BeginDialogAsync(dialogId, null, cancellationToken);
}
private async Task<DialogTurnResult> LoopMenu(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
// スタックに乗せないように、Replace でメニューを再表示
return await stepContext.ReplaceDialogAsync("menu", null, cancellationToken);
}
}
5. PhotoUpdateDialog 用の RESX を追加。
<?xml version="1.0" encoding="utf-8"?>
<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="failed" xml:space="preserve">
<value>サインインに失敗しました。</value>
</data>
</root>
<?xml version="1.0" encoding="utf-8"?>
<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="failed" xml:space="preserve">
<value>Failed to signin</value>
</data>
</root>
6. PhotoUpdateDialog.cs のコードを以下に差し替え。
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Connector;
using Microsoft.Bot.Schema;
using Microsoft.Extensions.Localization;
public class PhotoUpdateDialog : ComponentDialog
{
private IServiceProvider serviceProvider;
private IStringLocalizer<PhotoUpdateDialog> localizer;
public PhotoUpdateDialog(IServiceProvider serviceProvider, IStringLocalizer<PhotoUpdateDialog> localizer) : base(nameof(PhotoUpdateDialog))
{
this.serviceProvider = serviceProvider;
this.localizer = localizer;
// ウォーターフォールのステップを定義。処理順にメソッドを追加。
var waterfallSteps = new WaterfallStep[]
{
LoginAsync,
UpdatePhotoAsync,
GetPhotoAsync,
};
// ウォーターフォールダイアログと各種プロンプトを追加
AddDialog(new WaterfallDialog("updatephoto", waterfallSteps));
AddDialog((LoginDialog)serviceProvider.GetService(typeof(LoginDialog)));
}
private async Task<DialogTurnResult> LoginAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
// 認証ダイアログはテキストがないと落ちるため、ダミーを設定
stepContext.Context.Activity.Text = "dummy";
return await stepContext.BeginDialogAsync(nameof(LoginDialog), cancellationToken: cancellationToken);
}
private async Task<DialogTurnResult> UpdatePhotoAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
// ログインの結果よりトークンを取得
var accessToken = (string)stepContext.Result;
// 親ダイアログより渡されたイメージを取得
// 添付ファイルを取得して MemoryStream に格納
var connector = new ConnectorClient(new Uri(stepContext.Context.Activity.ServiceUrl));
var image = await connector.HttpClient.GetStreamAsync(stepContext.Options.ToString());
if (!string.IsNullOrEmpty(accessToken))
{
var graphClient = new MSGraphService(accessToken);
await graphClient.UpdatePhotoAsync(image);
}
else
await stepContext.Context.SendActivityAsync(localizer["failed"], cancellationToken: cancellationToken);
return await stepContext.NextAsync(accessToken, cancellationToken);
}
private async Task<DialogTurnResult> GetPhotoAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
// 前の処理よりトークンを取得
var accessToken = (string)stepContext.Result;
if (!string.IsNullOrEmpty(accessToken))
{
// 返信の作成
var reply = stepContext.Context.Activity.CreateReply();
// 現在の写真を取得
var graphClient = new MSGraphService(accessToken);
var image = await graphClient.GetPhotoAsync();
byte[] buffer = new byte[16 * 1024];
using (MemoryStream ms = new MemoryStream())
{
int read;
while ((read = image.Read(buffer, 0, buffer.Length)) > 0)
{
ms.Write(buffer, 0, read);
}
var image64 = System.Convert.ToBase64String(ms.ToArray());
// 返信に画像を設定
reply.Attachments.Add(new Attachment(
contentType: "image/png",
contentUrl: $"data:image/png;base64,{image64}"
));
}
await stepContext.Context.SendActivityAsync(reply, cancellationToken);
}
else
await stepContext.Context.SendActivityAsync(localizer["failed"], cancellationToken: cancellationToken);
return await stepContext.EndDialogAsync(true, cancellationToken);
}
}
7. ProfileDialog 用 RESX を追加。
<?xml version="1.0" encoding="utf-8"?>
<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="save" xml:space="preserve">
<value>プロファイルを保存します。</value>
</data>
</root>
<?xml version="1.0" encoding="utf-8"?>
<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="save" xml:space="preserve">
<value>Save profile information</value>
</data>
</root>
8. ProfileDialog.cs のコードを差し替え。
- SummaryStepAsync で言語情報を受け渡し
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Bot.Builder;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Schema;
using Microsoft.Extensions.Localization;
using Newtonsoft.Json;
public class ProfileDialog : ComponentDialog
{
private MyStateAccessors accessors;
private IStringLocalizer<ProfileDialog> localizer;
public ProfileDialog(MyStateAccessors accessors, IStringLocalizer<ProfileDialog> localizer) : base(nameof(ProfileDialog))
{
this.accessors = accessors;
this.localizer = localizer;
// ウォーターフォールのステップを定義。処理順にメソッドを追加。
var waterfallSteps = new WaterfallStep[]
{
//AdaptiveAsync,
ProfileStepAsync,
SummaryStepAsync,
};
// ウォーターフォールダイアログと各種プロンプトを追加
AddDialog(new WaterfallDialog("profile", waterfallSteps));
AddDialog(new TextPrompt("adaptive", AdaptiveCardResponseValidators.ValidateInput));
}
private async Task<DialogTurnResult> ProfileStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
var userProfile = await accessors.UserProfile.GetAsync(stepContext.Context, ()=> new UserProfile(), cancellationToken);
var adaptiveCardJson = File.ReadAllText($"./AdaptiveJsons/{userProfile.Language}/Profile.json");
var adaptiveCardAttachment = new Attachment()
{
ContentType = "application/vnd.microsoft.card.adaptive",
Content = JsonConvert.DeserializeObject(adaptiveCardJson),
};
// 返信をコンテキストより作成
var reply = stepContext.Context.Activity.CreateReply();
reply.Attachments = new List<Attachment>() { adaptiveCardAttachment };
// 戻り値は複雑な型だが一旦 TextPrompt ダイアログを使って送信
return await stepContext.PromptAsync("adaptive", new PromptOptions
{
Prompt = reply,
Validations = new NumberRange() { MinValue = 0, MaxValue = 120 }
},
cancellationToken: cancellationToken);
}
private async Task<DialogTurnResult> SummaryStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
var input = JsonConvert.DeserializeObject<UserProfile>(stepContext.Context.Activity.Value.ToString());
// 使用言語は現在のプロファイルよりコピー
var userProfile = await accessors.UserProfile.GetAsync(stepContext.Context, ()=> new UserProfile(), cancellationToken);
input.Language = userProfile.Language;
await stepContext.Context.SendActivityAsync(MessageFactory.Text(localizer["save"]));
await accessors.UserProfile.SetAsync(stepContext.Context, input, cancellationToken);
return await stepContext.EndDialogAsync(cancellationToken: cancellationToken);
}
}
9. ScheduleDialog 用の RESX を追加。
<?xml version="1.0" encoding="utf-8"?>
<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="failed" xml:space="preserve">
<value>サインインに失敗しました。</value>
</data>
</root>
<?xml version="1.0" encoding="utf-8"?>
<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="failed" xml:space="preserve">
<value>Failed to signin</value>
</data>
</root>
10. ScheduleDialog.cs を以下のコードと差し替え。
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Schema;
using Microsoft.Extensions.Localization;
public class ScheduleDialog : ComponentDialog
{
private IStringLocalizer<ScheduleDialog> localizer;
public ScheduleDialog(IServiceProvider serviceProvider, IStringLocalizer<ScheduleDialog> localizer) : base(nameof(ScheduleDialog))
{
this.localizer = localizer;
// ウォーターフォールのステップを定義。処理順にメソッドを追加。
var waterfallSteps = new WaterfallStep[]
{
LoginAsync,
GetScheduleAsync,
};
// ウォーターフォールダイアログと各種プロンプトを追加
AddDialog(new WaterfallDialog("schedule", waterfallSteps));
AddDialog((LoginDialog)serviceProvider.GetService(typeof(LoginDialog)));
}
private async Task<DialogTurnResult> LoginAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
return await stepContext.BeginDialogAsync(nameof(LoginDialog), cancellationToken: cancellationToken);
}
private async Task<DialogTurnResult> GetScheduleAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
// ログインの結果よりトークンを取得
var accessToken = (string)stepContext.Result;
if (!string.IsNullOrEmpty(accessToken))
{
var graphClient = new MSGraphService(accessToken);
var events = await graphClient.GetScheduleAsync();
events.ForEach(async x =>
{
await stepContext.Context.SendActivityAsync($"{System.DateTime.Parse(x.Start.DateTime).ToString("HH:mm")}-{System.DateTime.Parse(x.End.DateTime).ToString("HH:mm")} : {x.Subject}", cancellationToken: cancellationToken);
});
}
else
await stepContext.Context.SendActivityAsync(localizer["failed"], cancellationToken: cancellationToken);
return await stepContext.EndDialogAsync(true, cancellationToken);
}
}
11. WeatherDialog 用の RESX 追加。
<?xml version="1.0" encoding="utf-8"?>
<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="end" xml:space="preserve">
<value>終了</value>
</data>
<data name="today" xml:space="preserve">
<value>今日</value>
</data>
</root>
<?xml version="1.0" encoding="utf-8"?>
<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="end" xml:space="preserve">
<value>end</value>
</data>
<data name="today" xml:space="preserve">
<value>today</value>
</data>
</root>
12. WeatherDialog.cs を以下のコードと差し替え。
- localizer を使うために、プロパティの位置変更
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Bot.Builder;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Schema;
using Microsoft.Extensions.Localization;
using Newtonsoft.Json;
public class WeatherDialog : ComponentDialog
{
// 既定で今日の天気を表示
private string date;
private MyStateAccessors accessors;
private IStringLocalizer<WeatherDialog> localizer;
public WeatherDialog(MyStateAccessors accessors, IStringLocalizer<WeatherDialog> localizer) : base(nameof(WeatherDialog))
{
this.accessors = accessors;
this.localizer = localizer;
date = localizer["today"];
// ウォーターフォールのステップを定義。処理順にメソッドを追加。
var waterfallSteps = new WaterfallStep[]
{
ShowWeatherAsync,
CheckUserInputAsync,
CheckDateAsync,
};
// ウォーターフォールダイアログと各種プロンプトを追加
AddDialog(new WaterfallDialog("weather", waterfallSteps));
AddDialog(new TextPrompt("date"));
}
private async Task<DialogTurnResult> ShowWeatherAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
var userProfile = await accessors.UserProfile.GetAsync(stepContext.Context, () => new UserProfile(), cancellationToken);
// アダプティブカードの定義を JSON ファイルより読込み、対象日を変更
var adaptiveCardJson = File.ReadAllText($"./AdaptiveJsons/{userProfile.Language}/Weather.json").Replace("{0}", date);
var adaptiveCardAttachment = new Attachment()
{
ContentType = "application/vnd.microsoft.card.adaptive",
Content = JsonConvert.DeserializeObject(adaptiveCardJson),
};
// 返信をコンテキストより作成
var reply = stepContext.Context.Activity.CreateReply();
reply.Attachments = new List<Attachment>() { adaptiveCardAttachment };
// 戻り値は文字型のため、TextPrompt ダイアログを使って送信
return await stepContext.PromptAsync("date", new PromptOptions
{
Prompt = reply
},
cancellationToken: cancellationToken);
}
private async Task<DialogTurnResult> CheckUserInputAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
var input = stepContext.Result.ToString();
if (input == localizer["end"])
return await stepContext.EndDialogAsync();
var userProfile = await accessors.UserProfile.GetAsync(stepContext.Context, () => new UserProfile(), cancellationToken);
// 他の日を選択するようにアダプティブカードを作成
var adaptiveCardJson = File.ReadAllText($"./AdaptiveJsons/{userProfile.Language}/WeatherDateChoice.json");
var adaptiveCardAttachment = new Attachment()
{
ContentType = "application/vnd.microsoft.card.adaptive",
Content = JsonConvert.DeserializeObject(adaptiveCardJson),
};
var reply = stepContext.Context.Activity.CreateReply();
reply.Attachments = new List<Attachment>() { adaptiveCardAttachment };
// 戻り値は文字型のため、TextPrompt ダイアログを使って送信
return await stepContext.PromptAsync("date", new PromptOptions
{
Prompt = reply
},
cancellationToken: cancellationToken);
}
private async Task<DialogTurnResult> CheckDateAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
// 結果を date に指定してダイアログを再利用
date = stepContext.Result.ToString();
return await stepContext.ReplaceDialogAsync("weather");
}
}
13. myfirstbot.csproj にリソースの情報を追加。
<ItemGroup>
<EmbeddedResource Update="Resources\LoginDialog.en.resx">
<Generator>PublicResXFileCodeGenerator</Generator>
</EmbeddedResource>
<EmbeddedResource Update="Resources\LoginDialog.ja.resx">
<Generator>PublicResXFileCodeGenerator</Generator>
</EmbeddedResource>
<EmbeddedResource Update="Resources\MenuDialog.en.resx">
<Generator>PublicResXFileCodeGenerator</Generator>
</EmbeddedResource>
<EmbeddedResource Update="Resources\MenuDialog.ja.resx">
<Generator>PublicResXFileCodeGenerator</Generator>
</EmbeddedResource>
<EmbeddedResource Update="Resources\PhotoUpdateDialog.en.resx">
<Generator>PublicResXFileCodeGenerator</Generator>
</EmbeddedResource>
<EmbeddedResource Update="Resources\PhotoUpdateDialog.ja.resx">
<Generator>PublicResXFileCodeGenerator</Generator>
</EmbeddedResource>
<EmbeddedResource Update="Resources\ProfileDialog.en.resx">
<Generator>PublicResXFileCodeGenerator</Generator>
</EmbeddedResource>
<EmbeddedResource Update="Resources\ProfileDialog.ja.resx">
<Generator>PublicResXFileCodeGenerator</Generator>
</EmbeddedResource>
<EmbeddedResource Update="Resources\ScheduleDialog.en.resx">
<Generator>PublicResXFileCodeGenerator</Generator>
</EmbeddedResource>
<EmbeddedResource Update="Resources\ScheduleDialog.ja.resx">
<Generator>PublicResXFileCodeGenerator</Generator>
</EmbeddedResource>
<EmbeddedResource Update="Resources\WeatherDialog.en.resx">
<Generator>PublicResXFileCodeGenerator</Generator>
</EmbeddedResource>
<EmbeddedResource Update="Resources\WeatherDialog.ja.resx">
<Generator>PublicResXFileCodeGenerator</Generator>
</EmbeddedResource>
<EmbeddedResource Update="Resources\WelcomeDialog.en.resx">
<Generator>PublicResXFileCodeGenerator</Generator>
</EmbeddedResource>
<EmbeddedResource Update="Resources\WelcomeDialog.ja.resx">
<Generator>PublicResXFileCodeGenerator</Generator>
</EmbeddedResource>
</ItemGroup>
テスト
一通りの変更が終わったら、テストをしてみます。
1. F5 キーを押下してデバッグ実行。エミュレーターで接続。言語選択で「English」を選択後、プロファイル登録の確認で「Yes!」を選択。
2. カードが英語で出ることを確認。必要な情報を入力して「Register」をクリック。
3. プロファイル保存後、話かけたらメニューが出ることを確認。
4. Check weather をクリックして、正しく英語で情報が出るか確認。
5. 次に日本でテスト。すると一部英語のままになっていることを確認。
英語は正しく出ましたが、日本語が正しく出ない問題が発生したため、この問題を対策する必要があります。
言語指定のタイミングを検討
現在言語の指定は Bot のミドルウェアで実行されています。この場合以下の順序でコードが実行されるため問題が出ます。
- サーバー起動時に Startup.cs のコードが実行
- Bot にアクセスした際、MyBot のコンストラクタが実行
- 各ダイアログのコンストラクタが実行
- このタイミングでメニューなど一部値がその時点での言語で初期化
- ミドルウェアが実行
- このタイミングで言語が設定
よって、言語が設定された後に必要な初期化をするようにします。
1. MenuDialog.cs のコードを以下に書き換え。
- InitializeAsync に必要な初期化をいれてウォーターフォールで初めに呼び出し
- 後のメソッドで参照する変数を静的に指定
using System.Threading;
using System.Threading.Tasks;
using System.Collections.Generic;
using Microsoft.Bot.Builder;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Builder.Dialogs.Choices;
using System.Linq;
using System;
using Microsoft.Extensions.Localization;
public class MenuDialog : ComponentDialog
{
static private Dictionary<string, string> menus;
static private IList<Choice> choices;
private IStringLocalizer<MenuDialog> localizer;
public MenuDialog(IServiceProvider serviceProvider, IStringLocalizer<MenuDialog> localizer) : base(nameof(MenuDialog))
{
this.localizer = localizer;
// ウォーターフォールのステップを定義。処理順にメソッドを追加。
var waterfallSteps = new WaterfallStep[]
{
InitializeAsync,
ShowMenuAsync,
ProcessInputAsync,
LoopMenu
};
// ウォーターフォールダイアログと各種プロンプトを追加
AddDialog(new WaterfallDialog("menu", waterfallSteps));
AddDialog(new ChoicePrompt("choice"));
AddDialog((WeatherDialog)serviceProvider.GetService(typeof(WeatherDialog)));
AddDialog((ScheduleDialog)serviceProvider.GetService(typeof(ScheduleDialog)));
}
public async Task<DialogTurnResult> InitializeAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
// メニューの作成。表示される文字と子ダイアログの名称をセットで登録
menus = new Dictionary<string, string>(){
{ localizer["checkweather"], nameof(WeatherDialog) },
{ localizer["checkschedule"], nameof(ScheduleDialog) },
};
// ChoiceFactory で選択肢に設定する IList<Choice> を作成
choices = ChoiceFactory.ToChoices(menus.Select(x => x.Key).ToList());
return await stepContext.NextAsync();
}
public async Task<DialogTurnResult> ShowMenuAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
// Choice プロンプトでメニューを表示
return await stepContext.PromptAsync(
"choice",
new PromptOptions
{
Prompt = MessageFactory.Text(localizer["choicemenu"]),
Choices = choices,
},
cancellationToken);
}
private async Task<DialogTurnResult> ProcessInputAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
// 答えを確認して次のダイアログ名を取得
var choice = (FoundChoice)stepContext.Result;
var dialogId = menus[choice.Value];
// 子ダイアログの実行
return await stepContext.BeginDialogAsync(dialogId, null, cancellationToken);
}
private async Task<DialogTurnResult> LoopMenu(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
// スタックに乗せないように、Replace でメニューを再表示
return await stepContext.ReplaceDialogAsync("menu", null, cancellationToken);
}
}
2. F5 キーを押下してデバッグ実行。再度日本語をテスト。
まとめ
今回はアダプティブカードの JSON の多言語化について検討しました。また全てのダイアログのリソースを作りましたが、結構手間です。リソースファイルは ResXManager などツールで自動翻訳もできるものがあるので、大規模環境や複数言語のリソースを使う場合は検討してください。次回はユーザーインプットの翻訳について考えていきます。