diff --git a/Bot.Builder.Community.sln b/Bot.Builder.Community.sln index 0e3583bc..c4b28cf5 100644 --- a/Bot.Builder.Community.sln +++ b/Bot.Builder.Community.sln @@ -183,6 +183,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Bot.Builder.Community.Adapt EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Bot.Builder.Community.Adapters.Facebook.Tests", "tests\Bot.Builder.Community.Adapters.Facebook.Tests\Bot.Builder.Community.Adapters.Facebook.Tests.csproj", "{8201DC48-763A-4534-9E51-466E15DF01D8}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Bot.Builder.Community.Components.Middleware.Multilingual", "libraries\Bot.Builder.Community.Components.Middleware.Multilingual\Bot.Builder.Community.Components.Middleware.Multilingual.csproj", "{0541C224-70D5-4184-AF25-7E658298BFAF}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Bot.Builder.Community.Components.Azure.BlobUpload", "libraries\Bot.Builder.Community.Components.Azure.BlobUpload\Bot.Builder.Community.Components.Azure.BlobUpload.csproj", "{C57D2BF6-5589-4F20-97E9-F225AD361239}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug - NuGet Packages|Any CPU = Debug - NuGet Packages|Any CPU @@ -805,6 +809,22 @@ Global {8201DC48-763A-4534-9E51-466E15DF01D8}.Documentation|Any CPU.Build.0 = Debug|Any CPU {8201DC48-763A-4534-9E51-466E15DF01D8}.Release|Any CPU.ActiveCfg = Release|Any CPU {8201DC48-763A-4534-9E51-466E15DF01D8}.Release|Any CPU.Build.0 = Release|Any CPU + {0541C224-70D5-4184-AF25-7E658298BFAF}.Debug - NuGet Packages|Any CPU.ActiveCfg = Debug|Any CPU + {0541C224-70D5-4184-AF25-7E658298BFAF}.Debug - NuGet Packages|Any CPU.Build.0 = Debug|Any CPU + {0541C224-70D5-4184-AF25-7E658298BFAF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0541C224-70D5-4184-AF25-7E658298BFAF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0541C224-70D5-4184-AF25-7E658298BFAF}.Documentation|Any CPU.ActiveCfg = Debug|Any CPU + {0541C224-70D5-4184-AF25-7E658298BFAF}.Documentation|Any CPU.Build.0 = Debug|Any CPU + {0541C224-70D5-4184-AF25-7E658298BFAF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0541C224-70D5-4184-AF25-7E658298BFAF}.Release|Any CPU.Build.0 = Release|Any CPU + {C57D2BF6-5589-4F20-97E9-F225AD361239}.Debug - NuGet Packages|Any CPU.ActiveCfg = Debug|Any CPU + {C57D2BF6-5589-4F20-97E9-F225AD361239}.Debug - NuGet Packages|Any CPU.Build.0 = Debug|Any CPU + {C57D2BF6-5589-4F20-97E9-F225AD361239}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C57D2BF6-5589-4F20-97E9-F225AD361239}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C57D2BF6-5589-4F20-97E9-F225AD361239}.Documentation|Any CPU.ActiveCfg = Debug|Any CPU + {C57D2BF6-5589-4F20-97E9-F225AD361239}.Documentation|Any CPU.Build.0 = Debug|Any CPU + {C57D2BF6-5589-4F20-97E9-F225AD361239}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C57D2BF6-5589-4F20-97E9-F225AD361239}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -893,6 +913,8 @@ Global {428AD1B4-DF58-4D21-9C19-AB4AB6001A90} = {840D4038-9AB8-4750-9FFE-365386CE47E2} {3348B9A5-E3CE-4AF8-B059-8B4D7971C25A} = {840D4038-9AB8-4750-9FFE-365386CE47E2} {8201DC48-763A-4534-9E51-466E15DF01D8} = {840D4038-9AB8-4750-9FFE-365386CE47E2} + {0541C224-70D5-4184-AF25-7E658298BFAF} = {1F7F4CAF-CF22-491F-840D-A63835726C12} + {C57D2BF6-5589-4F20-97E9-F225AD361239} = {1F7F4CAF-CF22-491F-840D-A63835726C12} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {9FE3B75E-BA2B-45BC-BBF0-DDA8BA10C4F0} diff --git a/libraries/Bot.Builder.Community.Components.Azure.BlobUpload/AzureBlobUpload.cs b/libraries/Bot.Builder.Community.Components.Azure.BlobUpload/AzureBlobUpload.cs new file mode 100644 index 00000000..07e05dce --- /dev/null +++ b/libraries/Bot.Builder.Community.Components.Azure.BlobUpload/AzureBlobUpload.cs @@ -0,0 +1,131 @@ +using System; +using AdaptiveExpressions.Properties; +using Azure.Storage.Blobs.Models; +using Bot.Builder.Community.Components.Azure.BlobUpload.Upload; +using Microsoft.Bot.Builder.Dialogs; +using Newtonsoft.Json; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; + +namespace Bot.Builder.Community.Components.Azure.BlobUpload +{ + + public class AzureBlobUpload : Dialog + { + [JsonProperty("$Kind")] + public const string Kind = "AzureBlobUpload"; + + [JsonProperty("FileUrl")] + public StringExpression FileUrl { get; set; } + + [JsonProperty("FileName")] + public StringExpression FileName { get; set; } + + [JsonProperty("PublicAccessType")] + public EnumExpression PublicAccessType { get; set; } + + [JsonProperty("DeleteSnapshotsOption")] + public EnumExpression DeleteSnapshotsOption { get; set; } + + [JsonProperty("Containers")] + public StringExpression Containers { get; set; } + + [JsonProperty("ConnectionString")] + public StringExpression ConnectionString { get; set; } + + [JsonProperty("resultProperty")] + public StringExpression ResultProperty { get; set; } + + IAzureUpload _dataUpload; + + public AzureBlobUpload([CallerFilePath] string sourceFilePath = "", + [CallerLineNumber] int sourceLineNumber = 0) : base() + { + RegisterSourceLocation(sourceFilePath, sourceLineNumber); + + } + + public override async Task BeginDialogAsync(DialogContext dc, object options = null, + CancellationToken cancellationToken = new CancellationToken()) + { + + IsPropertyIsValid(dc); + + CreateDataStorage(dc); + + var getUrl = FileUrl?.GetValue(dc.State); + + var fileName = FileName?.GetValue(dc.State); + + var result = await _dataUpload.UploadAsync(getUrl, fileName); + + if (ResultProperty != null) + { + dc.State.SetValue(this.ResultProperty.GetValue(dc.State), result); + } + + return await dc.EndDialogAsync(result: result, cancellationToken: cancellationToken); + } + + + + private void CreateDataStorage(DialogContext dc) + { + if (_dataUpload == null) + return; + + var containerName = Containers?.GetValue(dc.State); + + var accessType = PublicAccessType?.GetValue(dc.State); + + var deleteSnapshotsOption = DeleteSnapshotsOption?.GetValue(dc.State); + + Enum.TryParse(accessType.ToString(), out PublicAccessType publicAccessType); + + Enum.TryParse(deleteSnapshotsOption.ToString(), out DeleteSnapshotsOption deleteSnapshotsType); + + var connectionString = ConnectionString?.GetValue(dc.State); + + _dataUpload = new AzureUpload(connectionString, containerName, publicAccessType, deleteSnapshotsType); + + } + + + private void IsPropertyIsValid(DialogContext dc) + { + + var getUrl = FileUrl?.GetValue(dc.State); + + + if (string.IsNullOrEmpty(getUrl)) + { + throw new Exception($"{nameof(AzureBlobUpload)} : FileUrl is required"); + } + + var fileName = FileName?.GetValue(dc.State); + + if (string.IsNullOrEmpty(fileName)) + { + throw new Exception($"{nameof(AzureBlobUpload)} : FileName is required"); + } + + + var container = Containers?.GetValue(dc.State); + + if (string.IsNullOrEmpty(container)) + { + throw new Exception($"{nameof(AzureBlobUpload)} : Container name is required"); + } + + var connectionString = ConnectionString?.GetValue(dc.State); + + if (string.IsNullOrEmpty(connectionString)) + { + throw new Exception($"{nameof(AzureBlobUpload)} : connection string is required"); + } + + } + } + +} \ No newline at end of file diff --git a/libraries/Bot.Builder.Community.Components.Azure.BlobUpload/AzureDataStorageBotComponent.cs b/libraries/Bot.Builder.Community.Components.Azure.BlobUpload/AzureDataStorageBotComponent.cs new file mode 100644 index 00000000..3ddd7f14 --- /dev/null +++ b/libraries/Bot.Builder.Community.Components.Azure.BlobUpload/AzureDataStorageBotComponent.cs @@ -0,0 +1,16 @@ +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Dialogs.Declarative; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace Bot.Builder.Community.Components.Azure.BlobUpload +{ + public class AzureDataStorageBotComponent : BotComponent + { + public override void ConfigureServices(IServiceCollection services, IConfiguration configuration) + { + services.AddSingleton(sp => + new DeclarativeType(AzureBlobUpload.Kind)); + } + } +} diff --git a/libraries/Bot.Builder.Community.Components.Azure.BlobUpload/Bot.Builder.Community.Components.Azure.BlobUpload.csproj b/libraries/Bot.Builder.Community.Components.Azure.BlobUpload/Bot.Builder.Community.Components.Azure.BlobUpload.csproj new file mode 100644 index 00000000..f9102f2c --- /dev/null +++ b/libraries/Bot.Builder.Community.Components.Azure.BlobUpload/Bot.Builder.Community.Components.Azure.BlobUpload.csproj @@ -0,0 +1,45 @@ + + + + + + netstandard2.0 + Upload file to Azure Blob Storage component for Bot Framework Composer + Bot Builder Community + https://github.com/BotBuilderCommunity/botbuilder-community-dotnet/blob/master/LICENSE + https://github.com/BotBuilderCommunity/botbuilder-community-dotnet/tree/master/libraries/Bot.Builder.Community.Components.Dialogs.Input + 0.0.5 + 1.0.0 + 1.0.0 + package-icon.png + https://github.com/BotBuilderCommunity/botbuilder-community-dotnet/ + true + content + composer;botframework;botbuilder;msbot-component;msbot-action + $(NoWarn);NU5104 + + + + + + + + + + + + + + + + + + + + + + True + + + + diff --git a/libraries/Bot.Builder.Community.Components.Azure.BlobUpload/Upload/AzureUpload.cs b/libraries/Bot.Builder.Community.Components.Azure.BlobUpload/Upload/AzureUpload.cs new file mode 100644 index 00000000..9021e31f --- /dev/null +++ b/libraries/Bot.Builder.Community.Components.Azure.BlobUpload/Upload/AzureUpload.cs @@ -0,0 +1,73 @@ +using System; +using System.IO; +using Azure.Storage.Blobs.Models; +using Azure.Storage.Blobs; +using Azure; +using System.Net; +using System.Threading.Tasks; + +namespace Bot.Builder.Community.Components.Azure.BlobUpload.Upload +{ + internal class AzureUpload : IAzureUpload + { + readonly BlobServiceClient _blobServiceClient; + BlobContainerClient _blobContainerClient; + private readonly PublicAccessType _publicAccessType; + private readonly DeleteSnapshotsOption _deleteSnapshotsOption; + + public AzureUpload(string connectionString, string containerName, + PublicAccessType publicAccessType = PublicAccessType.None, DeleteSnapshotsOption deleteSnapshotsOption = DeleteSnapshotsOption.None) + { + _blobServiceClient = new BlobServiceClient(connectionString); + _blobContainerClient = _blobServiceClient.GetBlobContainerClient(containerName); + this._publicAccessType = publicAccessType; + this._deleteSnapshotsOption = deleteSnapshotsOption; + + _blobServiceClient = new BlobServiceClient(connectionString); + + CreateContainer(containerName); + + if (_blobContainerClient == null) + throw new Exception("Create Container is failed"); + } + private async void CreateContainer(string containerName) + { + try + { + _blobContainerClient = _blobServiceClient.GetBlobContainerClient(containerName); + + if (_blobContainerClient != null) + { + bool isExits = await _blobContainerClient.ExistsAsync(); + + if (isExits) + return; + + await _blobContainerClient.CreateIfNotExistsAsync(publicAccessType: _publicAccessType, cancellationToken: default); + } + + } + catch (RequestFailedException) + { + + } + } + + public async Task UploadAsync(string fileUrl, string fileName) + { + var wc = new WebClient(); + var stream = new MemoryStream(wc.DownloadData(fileUrl)); + + var blob = _blobContainerClient.GetBlobClient(fileName); + + if (_deleteSnapshotsOption == DeleteSnapshotsOption.None) + return blob.Uri.AbsoluteUri; + + await blob.DeleteIfExistsAsync(DeleteSnapshotsOption.IncludeSnapshots); + + var result = await blob.UploadAsync(stream); + + return blob.Uri.AbsoluteUri; + } + } +} diff --git a/libraries/Bot.Builder.Community.Components.Azure.BlobUpload/Upload/IAzureUpload.cs b/libraries/Bot.Builder.Community.Components.Azure.BlobUpload/Upload/IAzureUpload.cs new file mode 100644 index 00000000..27b17dee --- /dev/null +++ b/libraries/Bot.Builder.Community.Components.Azure.BlobUpload/Upload/IAzureUpload.cs @@ -0,0 +1,11 @@ +using System.Threading.Tasks; +using Azure.Storage.Blobs; +using Azure.Storage.Blobs.Models; + +namespace Bot.Builder.Community.Components.Azure.BlobUpload.Upload +{ + public interface IAzureUpload + { + Task UploadAsync(string fileUrl, string fileName); + } +} \ No newline at end of file diff --git a/libraries/Bot.Builder.Community.Components.Azure.BlobUpload/schema/AzureBlobUpload.schema b/libraries/Bot.Builder.Community.Components.Azure.BlobUpload/schema/AzureBlobUpload.schema new file mode 100644 index 00000000..83f328ee --- /dev/null +++ b/libraries/Bot.Builder.Community.Components.Azure.BlobUpload/schema/AzureBlobUpload.schema @@ -0,0 +1,87 @@ +{ + "$schema": "https://schemas.botframework.com/schemas/component/v1.0/component.schema", + "$role": "implements(Microsoft.IDialog)", + "title": "Upload file to Azure Blob Storage", + "description": "This component uploads a file to Azure Blob Storage", + "type": "object", + "additionalProperties": false, + "properties": { + "FileUrl": { + "$ref": "schema:#/definitions/stringExpression", + "title": "File or attachment Url", + "description": "Pass file or attachment url to upload to Azure Blob Storage", + "examples": [ + "https://www.example.com" + ] + }, + "FileName": { + "$ref": "schema:#/definitions/stringExpression", + "title": "File Name", + "description": "Pass file name to upload to Azure Blob Storage", + "examples": [ + "test.png" + ] + }, + + "ConnectionString": { + "$ref": "schema:#/definitions/stringExpression", + "title": "Connection String", + "description": "Pass Azure Blob Storage connection string", + "examples": [ + "DefaultEndpoints..." + ] + }, + + "Containers": { + "$ref": "schema:#/definitions/stringExpression", + "title": "Containers Name", + "description": "Pass container name to upload to Azure Blob Storage", + "examples": [ + "testcontainer" + ] + }, + "PublicAccessType": { + "title": "Public Access Type", + "description": "Specifies the level of public access that is allowed on the share", + "oneOf": [ + { + "type": "object", + "title": "Public Access Type", + "description": " public access", + "enum": [ + "None", + "BlobContainer", + "Blob" + ], + "default": "None" + } + ] + }, + "DeleteSnapshotsOption": { + "title": "Delete Snapshots Option", + "description": "The set of options describing delete operation", + "oneOf": [ + { + "type": "object", + "title": "Delete Snapshots Option", + "description": "Choose the SnapshotsOption", + "enum": [ + "None", + "IncludeSnapshots", + "OnlySnapshots" + ], + "default": "None" + } + ] + }, + "resultProperty": { + "$ref": "schema:#/definitions/stringExpression", + "title": "Result(Blob Url)", + "description": "Result property to store the azure blob url", + "examples": [ + "user.blobUrl" + ] + } + + } +} \ No newline at end of file diff --git a/libraries/Bot.Builder.Community.Components.Azure.BlobUpload/schema/AzureBlobUpload.uischema b/libraries/Bot.Builder.Community.Components.Azure.BlobUpload/schema/AzureBlobUpload.uischema new file mode 100644 index 00000000..e8c68e8d --- /dev/null +++ b/libraries/Bot.Builder.Community.Components.Azure.BlobUpload/schema/AzureBlobUpload.uischema @@ -0,0 +1,7 @@ +{ + "$schema": "https://schemas.botframework.com/schemas/component/v1.0/component.schema", + "menu": { + "label": "Upload file to Azure Blob Storage", + "submenu": [ "Azure" ] + } +} \ No newline at end of file diff --git a/libraries/Bot.Builder.Community.Components.Middleware.Multilingual/AzureTranslateService/ITranslateService.cs b/libraries/Bot.Builder.Community.Components.Middleware.Multilingual/AzureTranslateService/ITranslateService.cs new file mode 100644 index 00000000..dd279b9e --- /dev/null +++ b/libraries/Bot.Builder.Community.Components.Middleware.Multilingual/AzureTranslateService/ITranslateService.cs @@ -0,0 +1,16 @@ +using Bot.Builder.Community.Recognizers.Fuzzy; +using System.Threading.Tasks; + +namespace Bot.Builder.Community.Components.Middleware.Multilingual.AzureTranslateService +{ + public interface ITranslateService + { + LanguageInformation GetLanguageInformation(string text); + Task IsLanguageAvailable(string text); + Task TranslateText(string sourceLanguage, string targetLanguage, string text); + + string DefaultLanguageCode { get; } + double ScoreThreshold { get; } + bool IsMultilingualEnabled { get;} + } +} diff --git a/libraries/Bot.Builder.Community.Components.Middleware.Multilingual/AzureTranslateService/LanguageInformation.cs b/libraries/Bot.Builder.Community.Components.Middleware.Multilingual/AzureTranslateService/LanguageInformation.cs new file mode 100644 index 00000000..aec890de --- /dev/null +++ b/libraries/Bot.Builder.Community.Components.Middleware.Multilingual/AzureTranslateService/LanguageInformation.cs @@ -0,0 +1,8 @@ +namespace Bot.Builder.Community.Components.Middleware.Multilingual.AzureTranslateService +{ + public class LanguageInformation + { + public string Language { get; set; } + public string LanguageCode { get; set; } + } +} diff --git a/libraries/Bot.Builder.Community.Components.Middleware.Multilingual/AzureTranslateService/TranslateService.cs b/libraries/Bot.Builder.Community.Components.Middleware.Multilingual/AzureTranslateService/TranslateService.cs new file mode 100644 index 00000000..fb98d1c8 --- /dev/null +++ b/libraries/Bot.Builder.Community.Components.Middleware.Multilingual/AzureTranslateService/TranslateService.cs @@ -0,0 +1,206 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; +using Bot.Builder.Community.Recognizers.Fuzzy; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Bot.Builder.Community.Components.Middleware.Multilingual.AzureTranslateService +{ + public class TranslateService : ITranslateService + { + private IList _languageInformation; + private IList _languageList; + + private readonly ISetting _setting; + + readonly FuzzyRecognizer _fuzzyRecognizer; + + public string DefaultLanguageCode => _setting.DefaultLanguageCode; + public double ScoreThreshold => _setting.ScoreThreshold; + public bool IsMultilingualEnabled => _setting.IsMultilingualEnabled; + + public TranslateService(ISetting setting) + { + LoadLanguageInformation(); + this._setting = setting; + _fuzzyRecognizer = new FuzzyRecognizer(); + } + + public async Task IsLanguageAvailable(string text) + { + var result = await _fuzzyRecognizer.Recognize(_languageList, text); + if (result?.Matches?.Count > 0) + { + return result.Matches[0]; + } + return null; + } + + public LanguageInformation GetLanguageInformation(string languageName) + { + return _languageInformation.FirstOrDefault(item => string.Compare(item.Language, languageName, StringComparison.OrdinalIgnoreCase) == 0); + } + + public async Task TranslateText(string sourceLanguage, string targetLanguage, string text) + { + + var translateText = text; + + var route = "/translate?api-version=3.0&from=" + sourceLanguage + "&to=" + targetLanguage; + + var body = new object[] { new { Text = text } }; + var requestBody = JsonConvert.SerializeObject(body); + + using (var client = new HttpClient()) + { + using (var request = new HttpRequestMessage()) + { + request.Method = HttpMethod.Post; + request.RequestUri = new Uri(_setting.Endpoint + route); + request.Content = new StringContent(requestBody, Encoding.UTF8, "application/json"); + request.Headers.Add("Ocp-Apim-Subscription-Key", _setting.Key); + request.Headers.Add("Ocp-Apim-Subscription-Region", _setting.Location); + + var response = await client.SendAsync(request).ConfigureAwait(false); + var result = await response.Content.ReadAsStringAsync(); + var result1 = JArray.Parse(result); + + if (result1?.Count > 0) + { + var jToken = result1[0]["translations"]; + if (jToken != null) + { + translateText = jToken[0]?["text"]?.ToString(); + } + } + } + } + + return translateText; + } + + private void LoadLanguageInformation() + { + _languageInformation = new List + { + new LanguageInformation() { Language = "Afrikaans", LanguageCode = "af" }, + new LanguageInformation() { Language = "Albanian", LanguageCode = "sq" }, + new LanguageInformation() { Language = "Amharic", LanguageCode = "am" }, + new LanguageInformation() { Language = "Arabic", LanguageCode = "ar" }, + new LanguageInformation() { Language = "Armenian", LanguageCode = "hy" }, + new LanguageInformation() { Language = "Assamese", LanguageCode = "as" }, + new LanguageInformation() { Language = "Azerbaijani (Latin)", LanguageCode = "az" }, + new LanguageInformation() { Language = "Bangla", LanguageCode = "bn" }, + new LanguageInformation() { Language = "Bashkir", LanguageCode = "ba" }, + new LanguageInformation() { Language = "Basque", LanguageCode = "eu" }, + new LanguageInformation() { Language = "Bosnian (Latin)", LanguageCode = "bs" }, + new LanguageInformation() { Language = "Bulgarian", LanguageCode = "bg" }, + new LanguageInformation() { Language = "Cantonese (Traditional)", LanguageCode = "yue" }, + new LanguageInformation() { Language = "Catalan", LanguageCode = "ca" }, + new LanguageInformation() { Language = "Chinese (Literary)", LanguageCode = "lzh" }, + new LanguageInformation() { Language = "Chinese Simplified", LanguageCode = "zh-Hans" }, + new LanguageInformation() { Language = "Chinese Traditional", LanguageCode = "zh-Hant" }, + new LanguageInformation() { Language = "Croatian", LanguageCode = "hr" }, + new LanguageInformation() { Language = "Czech", LanguageCode = "cs" }, + new LanguageInformation() { Language = "Danish", LanguageCode = "da" }, + new LanguageInformation() { Language = "Dari", LanguageCode = "prs" }, + new LanguageInformation() { Language = "Divehi", LanguageCode = "dv" }, + new LanguageInformation() { Language = "Dutch", LanguageCode = "nl" }, + new LanguageInformation() { Language = "English", LanguageCode = "en" }, + new LanguageInformation() { Language = "Estonian", LanguageCode = "et" }, + new LanguageInformation() { Language = "Faroese", LanguageCode = "fo" }, + new LanguageInformation() { Language = "Fijian", LanguageCode = "fj" }, + new LanguageInformation() { Language = "Filipino", LanguageCode = "fil" }, + new LanguageInformation() { Language = "Finnish", LanguageCode = "fi" }, + new LanguageInformation() { Language = "French", LanguageCode = "fr" }, + new LanguageInformation() { Language = "French (Canada)", LanguageCode = "fr-ca" }, + new LanguageInformation() { Language = "Galician", LanguageCode = "gl" }, + new LanguageInformation() { Language = "Georgian", LanguageCode = "ka" }, + new LanguageInformation() { Language = "German", LanguageCode = "de" }, + new LanguageInformation() { Language = "Greek", LanguageCode = "el" }, + new LanguageInformation() { Language = "Gujarati", LanguageCode = "gu" }, + new LanguageInformation() { Language = "Haitian Creole", LanguageCode = "ht" }, + new LanguageInformation() { Language = "Hebrew", LanguageCode = "he" }, + new LanguageInformation() { Language = "Hindi", LanguageCode = "hi" }, + new LanguageInformation() { Language = "Hmong Daw (Latin)", LanguageCode = "mww" }, + new LanguageInformation() { Language = "Hungarian", LanguageCode = "hu" }, + new LanguageInformation() { Language = "Icelandic", LanguageCode = "is" }, + new LanguageInformation() { Language = "Indonesian", LanguageCode = "id" }, + new LanguageInformation() { Language = "Inuinnaqtun", LanguageCode = "ikt" }, + new LanguageInformation() { Language = "Inuktitut", LanguageCode = "iu" }, + new LanguageInformation() { Language = "Inuktitut (Latin)", LanguageCode = "iu-Latn" }, + new LanguageInformation() { Language = "Irish", LanguageCode = "ga" }, + new LanguageInformation() { Language = "Italian", LanguageCode = "it" }, + new LanguageInformation() { Language = "Japanese", LanguageCode = "ja" }, + new LanguageInformation() { Language = "Kannada", LanguageCode = "kn" }, + new LanguageInformation() { Language = "Kazakh", LanguageCode = "kk" }, + new LanguageInformation() { Language = "Khmer", LanguageCode = "km" }, + new LanguageInformation() { Language = "Klingon", LanguageCode = "tlh-Latn" }, + new LanguageInformation() { Language = "Klingon(plqaD)", LanguageCode = "tlh-Piqd" }, + new LanguageInformation() { Language = "Korean", LanguageCode = "ko" }, + new LanguageInformation() { Language = "Kurdish (Central)", LanguageCode = "ku" }, + new LanguageInformation() { Language = "Kurdish (Northern)", LanguageCode = "kmr" }, + new LanguageInformation() { Language = "Kyrgyz (Cyrillic)", LanguageCode = "ky" }, + new LanguageInformation() { Language = "Lao", LanguageCode = "lo" }, + new LanguageInformation() { Language = "Latvian", LanguageCode = "lv" }, + new LanguageInformation() { Language = "Lithuanian", LanguageCode = "lt" }, + new LanguageInformation() { Language = "Macedonian", LanguageCode = "mk" }, + new LanguageInformation() { Language = "Malagasy", LanguageCode = "mg" }, + new LanguageInformation() { Language = "Malay (Latin)", LanguageCode = "ms" }, + new LanguageInformation() { Language = "Malayalam", LanguageCode = "ml" }, + new LanguageInformation() { Language = "Maltese", LanguageCode = "mt" }, + new LanguageInformation() { Language = "Maori", LanguageCode = "mi" }, + new LanguageInformation() { Language = "Marathi", LanguageCode = "mr" }, + new LanguageInformation() { Language = "Mongolian (Cyrillic)", LanguageCode = "mn-Cyrl" }, + new LanguageInformation() { Language = "Mongolian (Traditional)", LanguageCode = "mn-Mong" }, + new LanguageInformation() { Language = "Myanmar", LanguageCode = "my" }, + new LanguageInformation() { Language = "Nepali", LanguageCode = "ne" }, + new LanguageInformation() { Language = "Norwegian", LanguageCode = "nb" }, + new LanguageInformation() { Language = "Odia", LanguageCode = "or" }, + new LanguageInformation() { Language = "Pashto", LanguageCode = "ps" }, + new LanguageInformation() { Language = "Persian", LanguageCode = "fa" }, + new LanguageInformation() { Language = "Polish", LanguageCode = "pl" }, + new LanguageInformation() { Language = "Portuguese (Brazil)", LanguageCode = "pt" }, + new LanguageInformation() { Language = "Portuguese (Portugal)", LanguageCode = "pt-pt" }, + new LanguageInformation() { Language = "Punjabi", LanguageCode = "pa" }, + new LanguageInformation() { Language = "Queretaro Otomi", LanguageCode = "otq" }, + new LanguageInformation() { Language = "Romanian", LanguageCode = "ro" }, + new LanguageInformation() { Language = "Russian", LanguageCode = "ru" }, + new LanguageInformation() { Language = "Samoan (Latin)", LanguageCode = "sm" }, + new LanguageInformation() { Language = "Serbian (Cyrillic)", LanguageCode = "sr-Cyrl" }, + new LanguageInformation() { Language = "Serbian (Latin)", LanguageCode = "sr-Latn" }, + new LanguageInformation() { Language = "Slovak", LanguageCode = "sk" }, + new LanguageInformation() { Language = "Slovenian", LanguageCode = "sl" }, + new LanguageInformation() { Language = "Somali (Arabic)", LanguageCode = "so" }, + new LanguageInformation() { Language = "Spanish", LanguageCode = "es" }, + new LanguageInformation() { Language = "Swahili (Latin)", LanguageCode = "sw" }, + new LanguageInformation() { Language = "Swedish", LanguageCode = "sv" }, + new LanguageInformation() { Language = "Tahitian", LanguageCode = "ty" }, + new LanguageInformation() { Language = "Tamil", LanguageCode = "ta" }, + new LanguageInformation() { Language = "Tatar (Latin)", LanguageCode = "tt" }, + new LanguageInformation() { Language = "Telugu", LanguageCode = "te" }, + new LanguageInformation() { Language = "Thai", LanguageCode = "th" }, + new LanguageInformation() { Language = "Tibetan", LanguageCode = "bo" }, + new LanguageInformation() { Language = "Tigrinya", LanguageCode = "ti" }, + new LanguageInformation() { Language = "Tongan", LanguageCode = "to" }, + new LanguageInformation() { Language = "Turkish", LanguageCode = "tr" }, + new LanguageInformation() { Language = "Turkmen (Latin)", LanguageCode = "tk" }, + new LanguageInformation() { Language = "Ukrainian", LanguageCode = "uk" }, + new LanguageInformation() { Language = "Upper Sorbian", LanguageCode = "hsb" }, + new LanguageInformation() { Language = "Urdu", LanguageCode = "ur" }, + new LanguageInformation() { Language = "Uyghur (Arabic)", LanguageCode = "ug" }, + new LanguageInformation() { Language = "Uzbek (Latin)", LanguageCode = "uz" }, + new LanguageInformation() { Language = "Vietnamese", LanguageCode = "vi" }, + new LanguageInformation() { Language = "Welsh", LanguageCode = "cy" }, + new LanguageInformation() { Language = "Yucatec Maya", LanguageCode = "yua" }, + new LanguageInformation() { Language = "Zulu", LanguageCode = "zu" } + }; + + _languageList = _languageInformation.Select(item => item.Language).ToList(); + } + } +} diff --git a/libraries/Bot.Builder.Community.Components.Middleware.Multilingual/Bot.Builder.Community.Components.Middleware.Multilingual.csproj b/libraries/Bot.Builder.Community.Components.Middleware.Multilingual/Bot.Builder.Community.Components.Middleware.Multilingual.csproj new file mode 100644 index 00000000..e1a72e06 --- /dev/null +++ b/libraries/Bot.Builder.Community.Components.Middleware.Multilingual/Bot.Builder.Community.Components.Middleware.Multilingual.csproj @@ -0,0 +1,36 @@ + + + + + netstandard2.0 + package-icon.png + + bots;botframework;botbuilder;msbot-component;msbot-middleware; + http://www.github.com/botbuildercommunity/botbuildercommunity-dotnet + + + LICENSE + Middlware component for Multilingual bot using Azure Translator Service + https://github.com/BotBuilderCommunity/botbuilder-community-dotnet/tree/develop/libraries/Bot.Builder.Community.Components.Middleware.Multilingual + true + + + + + + + + + + + + True + + + + True + + + + + diff --git a/libraries/Bot.Builder.Community.Components.Middleware.Multilingual/Middleware/MiddlewareHelper.cs b/libraries/Bot.Builder.Community.Components.Middleware.Multilingual/Middleware/MiddlewareHelper.cs new file mode 100644 index 00000000..d3dcd507 --- /dev/null +++ b/libraries/Bot.Builder.Community.Components.Middleware.Multilingual/Middleware/MiddlewareHelper.cs @@ -0,0 +1,82 @@ +using Bot.Builder.Community.Recognizers.Fuzzy; +using Microsoft.Bot.Builder.Dialogs; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Schema; +using Newtonsoft.Json.Linq; +using Newtonsoft.Json; +using System.Threading.Tasks; +using System.Threading; +using Bot.Builder.Community.Components.Middleware.Multilingual.AzureTranslateService; + +namespace Bot.Builder.Community.Components.Middleware.Multilingual.Middleware +{ + public partial class MultilingualMiddleware + { + private const string Property = "turn.Multilingual"; + + private async Task FindLanguage(ITurnContext turnContext) + { + var userLanguage = _translateService.DefaultLanguageCode; + + var defaultLanguageInformation = new LanguageInformation() + { + Language = "English", + LanguageCode = _translateService.DefaultLanguageCode, + }; + + var score = await GetLanguageScore(turnContext.Activity.Text); + var isMultilingual = false; + + if (score?.Score > _translateService.ScoreThreshold) + { + defaultLanguageInformation = _translateService.GetLanguageInformation(score.Choice); + userLanguage = defaultLanguageInformation.LanguageCode; + isMultilingual = true; + } + + if (score == null) return userLanguage; + + var conversationExpire = new + { + LanguageChoice = score.Choice, + LanguageScore = score.Score, + UserText = turnContext.Activity.Text, + LanguageCode = defaultLanguageInformation.LanguageCode, + ConversationLanguage = defaultLanguageInformation.Language, + IsEnabled = isMultilingual, + SupportedListUrl = "https://learn.microsoft.com/en-us/azure/cognitive-services/translator/language-support#translation" + }; + + var languageInfo = JsonConvert.SerializeObject(conversationExpire); + + ObjectPath.SetPathValue(turnContext.TurnState, Property, JObject.Parse(languageInfo)); + + return userLanguage; + } + + private async Task GetLanguageScore(string language) + { + var score = await _translateService.IsLanguageAvailable(language) ?? new FuzzyMatch + { + Score = 0, + Choice = string.Empty + }; + + return score; + } + + private async Task GetCurrentLanguage(ITurnContext turnContext, CancellationToken cancellationToken) + { + return await _languageStateProperty.GetAsync(turnContext, () => string.Empty, cancellationToken) + .ConfigureAwait(false); + } + + private async Task TranslateMessageActivityAsync(IMessageActivity activity, string targetLocale) + { + if (activity.Type == ActivityTypes.Message) + { + activity.Text = await _translateService.TranslateText(_translateService.DefaultLanguageCode, targetLocale, activity.Text); + } + } + } +} \ No newline at end of file diff --git a/libraries/Bot.Builder.Community.Components.Middleware.Multilingual/Middleware/MultilingualMiddleware.cs b/libraries/Bot.Builder.Community.Components.Middleware.Multilingual/Middleware/MultilingualMiddleware.cs new file mode 100644 index 00000000..69c127db --- /dev/null +++ b/libraries/Bot.Builder.Community.Components.Middleware.Multilingual/Middleware/MultilingualMiddleware.cs @@ -0,0 +1,94 @@ +using Microsoft.Bot.Builder; +using Microsoft.Bot.Schema; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Bot.Builder.Community.Components.Middleware.Multilingual.AzureTranslateService; + +namespace Bot.Builder.Community.Components.Middleware.Multilingual.Middleware +{ + public partial class MultilingualMiddleware : IMiddleware + { + private UserState _userState; + private readonly IStatePropertyAccessor _languageStateProperty; + private readonly ITranslateService _translateService; + + public MultilingualMiddleware(UserState userState, ITranslateService translateService) + { + this._userState = userState; + this._languageStateProperty = userState.CreateProperty(nameof(_languageStateProperty)); + this._translateService = translateService; + } + + public async Task OnTurnAsync(ITurnContext turnContext, NextDelegate next, + CancellationToken cancellationToken = new CancellationToken()) + { + if (_translateService.IsMultilingualEnabled) + { + var userLanguage = await GetCurrentLanguage(turnContext, cancellationToken); + + if (turnContext.Activity.Type == ActivityTypes.Message) + { + if (string.IsNullOrEmpty(userLanguage)) + { + userLanguage = await FindLanguage(turnContext); + } + + if (string.CompareOrdinal(userLanguage, _translateService.DefaultLanguageCode) != 0) + { + turnContext.Activity.Text = await _translateService.TranslateText(userLanguage, + _translateService.DefaultLanguageCode, turnContext.Activity.Text); + } + + await _languageStateProperty.SetAsync(turnContext, userLanguage, cancellationToken) + .ConfigureAwait(false); + + } + + + turnContext.OnSendActivities(async (ctx, activities, nextSend) => + { + userLanguage = await GetCurrentLanguage(turnContext, cancellationToken); + + if (string.IsNullOrEmpty(userLanguage) || + string.CompareOrdinal(userLanguage, _translateService.DefaultLanguageCode) == 0) + { + return await nextSend().ConfigureAwait(false); + } + + var tasks = activities.Where(a => a.Type == ActivityTypes.Message) + .Select(activity => + TranslateMessageActivityAsync(activity.AsMessageActivity(), userLanguage)).ToList(); + + if (tasks.Any()) + { + await Task.WhenAll(tasks).ConfigureAwait(false); + } + + return await nextSend(); + }); + + turnContext.OnUpdateActivity(async (ctx, activities, nextUpdate) => + { + if (activities.Type == ActivityTypes.Message) + { + userLanguage = await GetCurrentLanguage(turnContext, cancellationToken); + + if (string.IsNullOrEmpty(userLanguage) || + string.CompareOrdinal(userLanguage, _translateService.DefaultLanguageCode) == 0) + { + return await nextUpdate().ConfigureAwait(false); + } + + await TranslateMessageActivityAsync(activities.AsMessageActivity(), userLanguage); + } + + return await nextUpdate(); + }); + } + + await next(cancellationToken).ConfigureAwait(false); + } + + } +} diff --git a/libraries/Bot.Builder.Community.Components.Middleware.Multilingual/MultilingualMiddlewareComponent.cs b/libraries/Bot.Builder.Community.Components.Middleware.Multilingual/MultilingualMiddlewareComponent.cs new file mode 100644 index 00000000..1246f2f0 --- /dev/null +++ b/libraries/Bot.Builder.Community.Components.Middleware.Multilingual/MultilingualMiddlewareComponent.cs @@ -0,0 +1,30 @@ +using System; +using Bot.Builder.Community.Components.Middleware.Multilingual.AzureTranslateService; +using Bot.Builder.Community.Components.Middleware.Multilingual.Middleware; +using Bot.Builder.Community.Components.Middleware.Multilingual.Setting; +using Microsoft.Bot.Builder; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace Bot.Builder.Community.Components.Middleware.Multilingual +{ + public class MultilingualMiddlewareComponent : BotComponent + { + public override void ConfigureServices(IServiceCollection services, IConfiguration configuration) + { + + if (services == null) + throw new ArgumentNullException(nameof(services)); + + if (configuration == null) + throw new ArgumentNullException(nameof(configuration)); + + services.AddSingleton(sp => new Settings(configuration)); + + services.AddSingleton(); + + services.AddSingleton(); + + } +} +} diff --git a/libraries/Bot.Builder.Community.Components.Middleware.Multilingual/Setting/ISetting.cs b/libraries/Bot.Builder.Community.Components.Middleware.Multilingual/Setting/ISetting.cs new file mode 100644 index 00000000..5e0d4dfa --- /dev/null +++ b/libraries/Bot.Builder.Community.Components.Middleware.Multilingual/Setting/ISetting.cs @@ -0,0 +1,13 @@ +namespace Bot.Builder.Community.Components.Middleware.Multilingual.AzureTranslateService +{ + public interface ISetting + { + string Key { get; set; } + string Endpoint { get; set; } + string Location { get; set; } + + string DefaultLanguageCode { get; set; } + double ScoreThreshold { get; set; } + bool IsMultilingualEnabled { get; set; } + } +} \ No newline at end of file diff --git a/libraries/Bot.Builder.Community.Components.Middleware.Multilingual/Setting/Settings.cs b/libraries/Bot.Builder.Community.Components.Middleware.Multilingual/Setting/Settings.cs new file mode 100644 index 00000000..41fbed38 --- /dev/null +++ b/libraries/Bot.Builder.Community.Components.Middleware.Multilingual/Setting/Settings.cs @@ -0,0 +1,64 @@ +using System; +using Bot.Builder.Community.Components.Middleware.Multilingual.AzureTranslateService; +using Microsoft.Extensions.Configuration; + +namespace Bot.Builder.Community.Components.Middleware.Multilingual.Setting +{ + internal class Settings : ISetting + { + public string Key { get; set; } + public string Endpoint { get; set; } + public string Location { get; set; } + public string DefaultLanguageCode { get; set; } + public double ScoreThreshold { get; set; } + public bool IsMultilingualEnabled { get; set; } + + public Settings(IConfiguration configuration) + { + + IsMultilingualEnabled = string.IsNullOrEmpty(configuration[nameof(IsMultilingualEnabled)]) + || Convert.ToBoolean(configuration[nameof(IsMultilingualEnabled)]); + + if (!IsMultilingualEnabled) + return; + + IsMultilingualEnabled = true; + + Key = configuration[nameof(Key)]; + + if (string.IsNullOrEmpty(Key)) + { + throw new Exception("Multilingual Key is not set"); + } + + Endpoint = configuration[nameof(Endpoint)]; + + if (string.IsNullOrEmpty(Endpoint)) + { + throw new Exception("Multilingual Endpoint is not set"); + } + + Location = configuration[nameof(Location)]; + + if (string.IsNullOrEmpty(Location)) + { + throw new Exception("Multilingual Location is not set"); + } + + DefaultLanguageCode = configuration[nameof(DefaultLanguageCode)]; + + if (string.IsNullOrEmpty(DefaultLanguageCode)) + { + DefaultLanguageCode = "en"; + } + + ScoreThreshold = Convert.ToDouble(configuration[nameof(ScoreThreshold)]); + + if (ScoreThreshold > 0.0 && ScoreThreshold <= 1.0) + return; + + ScoreThreshold = 0.5; + + } + } +}