diff --git a/lib/csharp/microsoft.bot.builder.skills/Microsoft.Bot.Builder.Skills.Tests/ManifestTests.cs b/lib/csharp/microsoft.bot.builder.skills/Microsoft.Bot.Builder.Skills.Tests/ManifestTests.cs index 0da0086a82..c89236694e 100644 --- a/lib/csharp/microsoft.bot.builder.skills/Microsoft.Bot.Builder.Skills.Tests/ManifestTests.cs +++ b/lib/csharp/microsoft.bot.builder.skills/Microsoft.Bot.Builder.Skills.Tests/ManifestTests.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; @@ -47,6 +48,8 @@ public void TestInitialize() cogModelConfig.LanguageModels.Add(luisModel); _botSettings.CognitiveModels.Add("en", cogModelConfig); + _botSettings.CognitiveModels.Add("de", cogModelConfig); + _botSettings.CognitiveModels.Add("fr", cogModelConfig); _services = new ServiceCollection(); @@ -180,6 +183,15 @@ public async Task SkillControllerManifestRequestInlineTriggerUtterances() Assert.IsTrue( skillManifest.Actions[i].Definition.Triggers.Utterances[0].Text.Length > 0, $"The {skillManifest.Actions[i].Id} action has no LUIS utterances added as part of manifest generation."); + + // Validate DE, FR has been added too. + Assert.IsTrue( + skillManifest.Actions[i].Definition.Triggers.Utterances.Exists(u => u.Locale.ToLower() == "de"), + $"The {skillManifest.Actions[i].Id} action has no LUIS utterances added as part of manifest generation for the DE locale."); + + Assert.IsTrue( + skillManifest.Actions[i].Definition.Triggers.Utterances.Exists(u => u.Locale.ToLower() == "fr"), + $"The {skillManifest.Actions[i].Id} action has no LUIS utterances added as part of manifest generation for the FR locale."); } } } diff --git a/lib/csharp/microsoft.bot.builder.skills/Microsoft.Bot.Builder.Skills.Tests/manifestTemplate.json b/lib/csharp/microsoft.bot.builder.skills/Microsoft.Bot.Builder.Skills.Tests/manifestTemplate.json index fff5cd6ca8..66188c511a 100644 --- a/lib/csharp/microsoft.bot.builder.skills/Microsoft.Bot.Builder.Skills.Tests/manifestTemplate.json +++ b/lib/csharp/microsoft.bot.builder.skills/Microsoft.Bot.Builder.Skills.Tests/manifestTemplate.json @@ -58,6 +58,20 @@ "Calendar#CreateCalendarEntry", "Calendar#FindMeetingRoom" ] + }, + { + "locale": "de", + "source": [ + "Calendar#CreateCalendarEntry", + "Calendar#FindMeetingRoom" + ] + }, + { + "locale": "fr", + "source": [ + "Calendar#CreateCalendarEntry", + "Calendar#FindMeetingRoom" + ] } ] } @@ -85,6 +99,20 @@ "Calendar#AcceptEventEntry", "Calendar#DeleteCalendarEntry" ] + }, + { + "locale": "de", + "source": [ + "Calendar#AcceptEventEntry", + "Calendar#DeleteCalendarEntry" + ] + }, + { + "locale": "fr", + "source": [ + "Calendar#AcceptEventEntry", + "Calendar#DeleteCalendarEntry" + ] } ] } @@ -102,6 +130,18 @@ "source": [ "Calendar#ConnectToMeeting" ] + }, + { + "locale": "de", + "source": [ + "Calendar#ConnectToMeeting" + ] + }, + { + "locale": "fr", + "source": [ + "Calendar#ConnectToMeeting" + ] } ] } @@ -133,6 +173,18 @@ "source": [ "Calendar#TimeRemaining" ] + }, + { + "locale": "de", + "source": [ + "Calendar#TimeRemaining" + ] + }, + { + "locale": "fr", + "source": [ + "Calendar#TimeRemaining" + ] } ] } @@ -172,6 +224,28 @@ "Calendar#FindCalendarWho", "Calendar#FindDuration" ] + }, + { + "locale": "de", + "source": [ + "Calendar#FindCalendarDetail", + "Calendar#FindCalendarEntry", + "Calendar#FindCalendarWhen", + "Calendar#FindCalendarWhere", + "Calendar#FindCalendarWho", + "Calendar#FindDuration" + ] + }, + { + "locale": "fr", + "source": [ + "Calendar#FindCalendarDetail", + "Calendar#FindCalendarEntry", + "Calendar#FindCalendarWhen", + "Calendar#FindCalendarWhere", + "Calendar#FindCalendarWho", + "Calendar#FindDuration" + ] } ] } @@ -214,6 +288,18 @@ "source": [ "Calendar#ChangeCalendarEntry" ] + }, + { + "locale": "de", + "source": [ + "Calendar#ChangeCalendarEntry" + ] + }, + { + "locale": "fr", + "source": [ + "Calendar#ChangeCalendarEntry" + ] } ] } diff --git a/lib/csharp/microsoft.bot.builder.skills/Microsoft.Bot.Builder.Skills/Manifest/SkillManifestGenerator.cs b/lib/csharp/microsoft.bot.builder.skills/Microsoft.Bot.Builder.Skills/Manifest/SkillManifestGenerator.cs index e40311d033..497e4364d4 100644 --- a/lib/csharp/microsoft.bot.builder.skills/Microsoft.Bot.Builder.Skills/Manifest/SkillManifestGenerator.cs +++ b/lib/csharp/microsoft.bot.builder.skills/Microsoft.Bot.Builder.Skills/Manifest/SkillManifestGenerator.cs @@ -73,11 +73,15 @@ public async Task GenerateManifest(string manifestFile, string ap // If the developer has requested inline, we need to go through all utteranceSource references and retrieve the utterances and insert inline if (inlineTriggerUtterances) { - // Retrieve all of the LUIS model definitions deployed and configured for the skill - // These are used to match the model name and intent so we can retrieve the utterances + Dictionary localeLuisModels = new Dictionary(); - // TODO - Multi-locale support - var modelCache = await PreFetchLuisModelContents(cognitiveModels["en"].LanguageModels); + // Retrieve all of the LUIS model definitions deployed and configured for the skill which could have multiple locales + // These are used to match the model name and intent so we can retrieve the utterances + foreach (var localeSet in cognitiveModels) + { + // Download/cache all the LUIS models configured for this locale (key is the locale name) + await PreFetchLuisModelContents(localeLuisModels, localeSet.Key, localeSet.Value.LanguageModels); + } foreach (var action in skillManifest.Actions) { @@ -105,12 +109,11 @@ public async Task GenerateManifest(string manifestFile, string ap var modelName = source.Substring(0, intentIndex); string intentToMatch = source.Substring(intentIndex + 1); - // Find the LUIS model from our cache by matching on the luis model ID - var model = modelCache.SingleOrDefault(m => string.Equals(m.Key, modelName, StringComparison.CurrentCultureIgnoreCase)).Value; - + // Find the LUIS model from our cache by matching on the locale/modelname + var model = localeLuisModels.SingleOrDefault(m => string.Equals(m.Key, $"{utteranceSource.Locale}_{modelName}", StringComparison.CurrentCultureIgnoreCase)).Value; if (model == null) { - throw new Exception($"Utterance source for action: '{action.Id}' references the '{modelName}' model which cannot be found in the currently deployed configuration."); + throw new Exception($"Utterance source (locale: {utteranceSource.Locale}) for action: '{action.Id}' references the '{modelName}' model which cannot be found in the currently deployed configuration."); } // Validate that the intent in the manifest exists in this LUIS model @@ -151,10 +154,8 @@ public async Task GenerateManifest(string manifestFile, string ap /// /// List of LuisServices. /// Collection of LUIS model definitions grouped by model name. - private async Task> PreFetchLuisModelContents(List luisServices) + private async Task PreFetchLuisModelContents(Dictionary localeModelUtteranceCache, string locale, List luisServices) { - Dictionary utteranceCache = new Dictionary(); - // For each luisSource we identify the Intent and match with available luisServices to identify the LuisAppId which we update foreach (LuisService luisService in luisServices) { @@ -167,11 +168,9 @@ private async Task> PreFetchLuisModelContents(List(json); - utteranceCache.Add(luisService.Id, luisApp); + localeModelUtteranceCache.Add($"{locale}_{luisService.Id}", luisApp); } } - - return utteranceCache; } } } \ No newline at end of file