Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public interface IMtTranslationOptions
string JsonFilePath { get; set; }
string ProjectName { get; set; }
string GlossaryPath { get; set; }
string PeUrl { get; set; } // Microsoft private endpont url
string ApiKey { get; set; } //Google Key
string ClientId { get; set; } // Microsoft key
string Region { get; set; } // Microsoft Region
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,273 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IdentityModel.Tokens.Jwt;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Newtonsoft.Json;
using NLog;
using RestSharp;
using Sdl.Community.MtEnhancedProvider.Model;
using Sdl.Community.MtEnhancedProvider.Service;

namespace Sdl.Community.MtEnhancedProvider.MstConnect
{
internal class ApiConnecterWithPe
{
private const string OcpApimSubscriptionKeyHeader = "Ocp-Apim-Subscription-Key";

private List<string> _supportedLangs;

private readonly string _peUrl;
private string _subscriptionKey;
private string _region;
private readonly Logger _logger = LogManager.GetCurrentClassLogger();
private HtmlUtil _htmlUtil;

/// <summary>
/// This class allows connection to the Microsoft Translation API
/// </summary>
/// <param name="subscriptionKey">Microsoft API key</param>
/// <param name="region">Region</param>
internal ApiConnecterWithPe(string peUrl, string subscriptionKey, string region, HtmlUtil htmlUtil)
{
_peUrl = peUrl;
_subscriptionKey = subscriptionKey;
_region = region;
_htmlUtil = htmlUtil;

if (_supportedLangs == null)
{
_supportedLangs = GetSupportedLanguages(); //if the class variable has not been set
}
}

/// <summary>
/// translates the text input
/// </summary>
internal string Translate(string sourceLang, string targetLang, string textToTranslate, string categoryId)
{
//convert our language codes
var sourceLc = ConvertLangCode(sourceLang);
var targetLc = ConvertLangCode(targetLang);

var translatedText = string.Empty;
try
{
//search for words like this <word>
var rgx = new Regex("(\\<\\w+[üäåëöøßşÿÄÅÆĞ]*[^\\d\\W\\\\/\\\\]+\\>)");
var words = rgx.Matches(textToTranslate);
if (words.Count > 0)
{
textToTranslate = ReplaceCharacters(textToTranslate, words);
}

const string path = "/translate?api-version=3.0";
var category = categoryId == "" ? "general" : categoryId;
var languageParams = $"&from={sourceLc}&to={targetLc}&textType=html&category={category}";

var uri = string.Concat(_peUrl, path, languageParams);
var body = new object[]
{
new
{
Text =textToTranslate
}
};
var requestBody = JsonConvert.SerializeObject(body);
using (var httpClient = new HttpClient())
{
using (var httpRequest = new HttpRequestMessage())
{
httpRequest.Method = HttpMethod.Post;
httpRequest.Content = new StringContent(requestBody, Encoding.UTF8, "application/json");
httpRequest.RequestUri = new Uri(uri);
httpRequest.Headers.Add(OcpApimSubscriptionKeyHeader, _subscriptionKey);

var response = httpClient.SendAsync(httpRequest).Result;
var responseBody = response.Content.ReadAsStringAsync().Result;
if (response.IsSuccessStatusCode)
{
var responseTranslation = JsonConvert.DeserializeObject<List<TranslationResponse>>(responseBody);
translatedText = _htmlUtil.HtmlDecode(responseTranslation[0]?.Translations[0]?.Text);
}
else
{
var responseMessage = JsonConvert.DeserializeObject<ResponseMessage>(responseBody);
throw new Exception(responseMessage.Error.Message);
}
}
}
}
catch (WebException exception)
{
var mesg = ProcessWebException(exception, PluginResources.MsApiFailedGetLanguagesMessage);
_logger.Error($"{MethodBase.GetCurrentMethod().Name}\n {exception.Message}\n { exception.StackTrace}");
throw new Exception(mesg);
}
return translatedText;
}

private string ReplaceCharacters(string textToTranslate, MatchCollection matches)
{
var indexes = new List<int>();
foreach (Match match in matches)
{
if (match.Index.Equals(0))
{
indexes.Add(match.Length);
}
else
{
//check if there is any text after PI
var remainingText = textToTranslate.Substring(match.Index + match.Length);
if (!string.IsNullOrEmpty(remainingText))
{
//get the position where PI starts to split before
indexes.Add(match.Index);
//split after PI
indexes.Add(match.Index + match.Length);
}
else
{
indexes.Add(match.Index);
}
}
}
var splitText = textToTranslate.SplitAt(indexes.ToArray()).ToList();
var positions = new List<int>();
for (var i = 0; i < splitText.Count; i++)
{
if (!splitText[i].Contains("tg"))
{
positions.Add(i);
}
}

foreach (var position in positions)
{
var originalString = splitText[position];
var start = Regex.Replace(originalString, "<", "&lt;");
var finalString = Regex.Replace(start, ">", "&gt;");
splitText[position] = finalString;
}
var finalText = string.Empty;
foreach (var text in splitText)
{
finalText += text;
}
return finalText;
}

/// <summary>
/// Checks of lang pair is supported by MS
/// </summary>
internal bool IsSupportedLangPair(string sourceLang, string targetLang)
{
//convert our language codes
var source = ConvertLangCode(sourceLang);
var target = ConvertLangCode(targetLang);

var sourceSupported = false;
var targetSupported = false;

//check to see if both the source and target languages are supported
foreach (var lang in _supportedLangs)
{
if (lang.Equals(source)) sourceSupported = true;
if (lang.Equals(target)) targetSupported = true;
}

if (sourceSupported && targetSupported) return true; //if both are supported return true

//otherwise return false
return false;
}

private List<string> GetSupportedLanguages()
{
var languageCodeList = new List<string>();
try
{
var uri = new Uri(_peUrl);
var client = new RestClient(uri);

var request = new RestRequest("languages", Method.Get);
request.AddParameter("api-version", "3.0");
request.AddParameter("scope", "translation");
request.AddHeader(OcpApimSubscriptionKeyHeader, _subscriptionKey);

var languageResponse = client.ExecuteAsync(request).Result;

if (!languageResponse.IsSuccessful) throw new Exception("Error on connecting to translator. " + languageResponse.Content);

var languages = JsonConvert.DeserializeObject<LanguageResponse>(languageResponse.Content);
if (languages != null)
{
foreach (var language in languages.Translation)
{
languageCodeList.Add(language.Key);
}
}
}
catch (WebException exception)
{
var mesg = ProcessWebException(exception, PluginResources.MsApiFailedGetLanguagesMessage);
_logger.Error($"{MethodBase.GetCurrentMethod().Name}\n{exception.Message}\n { exception.StackTrace}");
throw new Exception(mesg);
}
return languageCodeList;
}

private string ProcessWebException(WebException e, string message)
{
_logger.Error($"{MethodBase.GetCurrentMethod().Name}\n{e.Response}\n {message}");

// Obtain detailed error information
string strResponse;
using (var response = (HttpWebResponse)e.Response)
{
using (var responseStream = response.GetResponseStream())
{
using (var sr = new StreamReader(responseStream, Encoding.ASCII))
{
strResponse = sr.ReadToEnd();
}
}
}
return $"Http status code={e.Status}, error message={strResponse}";
}

internal void EnsureConnectivity()
{
var result = Translate("en", "de", "Hello World!", "");

if (string.IsNullOrEmpty(result))
{
throw new InvalidOperationException("No Connection could be established");
}
}

private string ConvertLangCode(string languageCode)
{
//takes the language code input and converts it to one that MS Translate can use
if (languageCode.Contains("sr-Cyrl")) return "sr-Cyrl";
if (languageCode.Contains("sr-Latn")) return "sr-Latn";

var ci = new CultureInfo(languageCode); //construct a CultureInfo object with the language code

//deal with chinese..MS Translator has different ones
if (new[] { "zh-TW", "zh-HK", "zh-MO", "zh-Hant", "zh-CHT" }.Contains(ci.Name)) return "zh-Hant";
if (new[] { "zh-CN", "zh-SG", "zh-Hans-HK", "zh-Hans-MO", "zh-Hans", "zh-CHS" }.Contains(ci.Name)) return "zh-Hans";

return ci.TwoLetterISOLanguageName;

}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ public class MtTranslationOptions: IMtTranslationOptions
private string _apiKey;
private string _clientid;
const string MsTranslatorString = "Microsoft Translator"; //these strings should not be localized or changed and are therefore hard-coded as constants
const string GTranslateString = "Google Translate"; //these strings should not be localized or changed and are therefore hard-coded as constants
const string MsTranslatorWithPeString = "Microsoft Translator with Private Endpoint"; //these strings should not be localized or changed and are therefore hard-coded as constants
const string GTranslateString = "Google Translate"; //these strings should not be localized or changed and are therefore hard-coded as constants

//The translation method affects when/if the plugin gets called by Studio
public static readonly TranslationMethod ProviderTranslationMethod = TranslationMethod.MachineTranslation;
Expand Down Expand Up @@ -158,6 +159,7 @@ public enum ProviderType
{
GoogleTranslate = 1,
MicrosoftTranslator = 2,
MicrosoftTranslatorWithPe = 3,
None = 0
}

Expand All @@ -169,7 +171,9 @@ public static string GetProviderTypeDescription(ProviderType type)
return GTranslateString; //these strings should not be localized and are therefore hard-coded
case ProviderType.MicrosoftTranslator:
return MsTranslatorString; //these strings should not be localized and are therefore hard-coded
}
case ProviderType.MicrosoftTranslatorWithPe:
return MsTranslatorWithPeString; //these strings should not be localized and are therefore hard-coded
}
return "";
}

Expand All @@ -185,7 +189,9 @@ public static ProviderType GetProviderType(string typeString)
return ProviderType.GoogleTranslate;
case MsTranslatorString:
return ProviderType.MicrosoftTranslator;
default:
case MsTranslatorWithPeString:
return ProviderType.MicrosoftTranslatorWithPe;
default:
return ProviderType.None;
}
}
Expand Down Expand Up @@ -247,6 +253,13 @@ public string ApiKey
set => _apiKey = value;
}

// The Microsoft private endpoint url
public string PeUrl
{
get => GetStringParameter("peurl");
set => SetStringParameter("peurl", value);
}

[JsonIgnore]
//User for Microsoft authentication
public string ClientId
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public class MtTranslationProvider : ITranslationProvider
private MtTranslationProviderGTApiConnecter _gtConnect;
private GoogleV3Connecter _googleV3Connecter;
private ApiConnecter _mstConnect;
private ApiConnecterWithPe _mstConnectWithPe;
private readonly HtmlUtil _htmlUtil;

public MtTranslationProvider(IMtTranslationOptions options, RegionsProvider regionProvider, HtmlUtil htmlUtil)
Expand Down Expand Up @@ -143,6 +144,16 @@ public bool SupportsLanguageDirection(LanguagePair languageDirection)
return _mstConnect.IsSupportedLangPair(languageDirection.SourceCulture.Name, languageDirection.TargetCulture.Name);
}

if (Options.SelectedProvider == MtTranslationOptions.ProviderType.MicrosoftTranslatorWithPe)
{
if (_mstConnectWithPe == null) //construct ApiConnecter if necessary
{
_mstConnectWithPe = new ApiConnecterWithPe(Options.PeUrl, Options.ClientId, Options.Region, _htmlUtil);
}

return _mstConnectWithPe.IsSupportedLangPair(languageDirection.SourceCulture.Name, languageDirection.TargetCulture.Name);
}

if (Options.SelectedGoogleVersion == Enums.GoogleApiVersion.V2)
{
if (_gtConnect == null) //instantiate GtApiConnecter if necessary
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public ITranslationProvider CreateTranslationProvider(Uri translationProviderUri
var htmlUtil = new HtmlUtil();

//start with MT...check if we are using MT
if (loadOptions.SelectedProvider == MtTranslationOptions.ProviderType.MicrosoftTranslator)
if (loadOptions.SelectedProvider == MtTranslationOptions.ProviderType.MicrosoftTranslator || loadOptions.SelectedProvider == MtTranslationOptions.ProviderType.MicrosoftTranslatorWithPe)
{
// The credential is saved with a different URI scheme than that of the plugin!
// We will need to make this known and/or provide a workaround in identifying the credentials
Expand Down
Loading