Open cleemullins opened 5 years ago
Just some quick input. There's two routes we can go with this (that I can think of):
AdaptiveCard card = new AdaptiveCard();
card.Body.Add(new AdaptiveTextBlock()
{
Text = translatedString,
Size = AdaptiveTextSize.ExtraLarge
});
or,
I have encountered this in IcM form twice and I wrote a C# function to translate Adaptive Cards so I'll paste it here:
The nested functions may appear a little confusing but I thought it would be better to have one self-contained function rather than have multiple functions.
public async Task<object> TranslateAdaptiveCardAsync(object card, string targetLocale, CancellationToken cancellationToken = default)
{
var propertiesToTranslate = new[] { "text", "altText", "fallbackText", "title", "placeholder", "data" };
var cardJObject = JObject.FromObject(card);
var list = new List<(JContainer Container, object Key, string Text)>();
void recurseThroughJObject(JObject jObject)
{
var type = jObject["type"];
var parent = jObject.Parent;
var grandParent = parent?.Parent;
// value should be translated in facts and Input.Text, and ignored in Input.Date and Input.Time and Input.Toggle and Input.ChoiceSet and Input.Choice
var valueIsTranslatable = type?.Type == JTokenType.String && (string)type == "Input.Text"
|| type == null && parent?.Type == JTokenType.Array && grandParent?.Type == JTokenType.Property && ((JProperty)grandParent)?.Name == "facts";
foreach (var key in ((IDictionary<string, JToken>)jObject).Keys)
{
switchOnJToken(jObject, key, propertiesToTranslate.Contains(key) || (key == "value" && valueIsTranslatable));
}
}
void switchOnJToken(JContainer jContainer, object key, bool shouldTranslate)
{
var jToken = jContainer[key];
switch (jToken.Type)
{
case JTokenType.Object:
recurseThroughJObject((JObject)jToken);
break;
case JTokenType.Array:
var jArray = (JArray)jToken;
var shouldTranslateChild = key as string == "inlines";
for (int i = 0; i < jArray.Count; i++)
{
switchOnJToken(jArray, i, shouldTranslateChild);
}
break;
case JTokenType.String:
if (shouldTranslate)
{
// Store the text to translate as well as the JToken information to apply the translated text to
list.Add((jContainer, key, (string)jToken));
}
break;
}
}
recurseThroughJObject(cardJObject);
// From Cognitive Services translation documentation:
// https://docs.microsoft.com/en-us/azure/cognitive-services/translator/quickstart-csharp-translate
var requestBody = JsonConvert.SerializeObject(list.Select(item => new { item.Text }));
using (var request = new HttpRequestMessage())
{
var uri = $"https://api.cognitive.microsofttranslator.com/translate?api-version=3.0&to={targetLocale}";
request.Method = HttpMethod.Post;
request.RequestUri = new Uri(uri);
request.Content = new StringContent(requestBody, Encoding.UTF8, "application/json");
request.Headers.Add("Ocp-Apim-Subscription-Key", _key);
var response = await _client.SendAsync(request, cancellationToken);
response.EnsureSuccessStatusCode();
var responseBody = await response.Content.ReadAsStringAsync();
var result = JsonConvert.DeserializeObject<TranslatorResponse[]>(responseBody);
if (result == null)
{
return null;
}
for (int i = 0; i < result.Length && i < list.Count; i++)
{
var item = list[i];
var translatedText = result[i]?.Translations?.FirstOrDefault()?.Text;
if (!string.IsNullOrWhiteSpace(translatedText))
{
// Modify each stored JToken with the translated text
item.Container[item.Key] = translatedText;
}
}
// Return the modified JObject representing the Adaptive Card
return cardJObject;
}
}
I would like to extend a word of caution. If you know everything your bot can say ahead of time, which is the case for almost all bots, you should translate that text ahead of time and store the results in resource files. This will reduce the amount of calls you have to make to the translator service and also give you better translations because you will have more control over them. This is especially true if you have any text that is hard for the translator service to interpret, like complicated markdown.
The translator service is best used for incoming messages. If you must use it for outgoing messages, this Adaptive Card translator function should help you. But know that Adaptive Cards are complex by nature, and you may want to tweak this code to suit your own needs if there are some components of Adaptive Cards that you don't want to be translated, or if there are edge cases I haven't considered where my code can't handle certain cards.
I have also included Adaptive Card translations in my Bot Builder Community proposal: https://github.com/BotBuilderCommunity/botbuilder-community-dotnet/issues/137
Can we use the same function to translate hero cards also ?
Thanks..
@madbeher - No, the function is specific to Adaptive Cards. I'd need to make different functions to translate Bot Framework cards. Please comment on the Bot Builder Community issue with any requests.
CSS Sees issues come in related to translation, where cards (adaptive, etc) are not being translated.
Consider updating this sample to include an Adaptive Card translation.