SharePoint y Azure - El Microsoft Translator API

Escrito por  Gustavo Velez

Que es el Microsoft Translator API

El servicio de Traducción de Azure permite, entre otras cosas, traducir textos de un idioma a otro automáticamente, permitiendo integrar textos en múltiples idiomas desde cualquier tipo de aplicación. El servicio forma parte del conjunto de Cognitive Services de Azure, que es una colección de algoritmos de Inteligencia Artificial que se pueden utilizar en la nube de Microsoft.

El servicio de Traducción ha utilizado desde su principio un algoritmo de Traducción Estadística Mecánica (SMT, Statistical Machine Translation), pero como esta plataforma ya ha alcanzado su máximo desarrollo y no es posible mejorarla, Microsoft ha comenzado a utilizar otro tipo de algoritmo basado en Inteligencia Artificial llamado DNN, Deep Neural Networks. El cambio entre los dos algoritmos se esta haciendo incrementalmente, pero los usuarios solamente notaran las mejoras en cuanto a la calidad de las traducciones pues la API de comunicación no cambia. DNN ofrece mejores traducciones porque utiliza el contexto de toda la frase para crear el texto traducido, no solamente el contexto de algunas palabras antes y después de la palabra a traducir.

El Azure Translator API es una herramienta ideal para utilizar en SharePoint: una tarea recurrente en la creación de Portales es poder ofrecer la misma información en varios idiomas simultáneamente. Aunque en este momento la calidad de las traducciones no es totalmente perfecta (y probablemente nunca lo será), el nivel es suficientemente alto como para poder reducir considerablemente los costos de traducción manual ya que los traductores solamente necesitan revisar los textos, no traducirlos desde cero. La calidad de las traducciones varia dependiendo de los idiomas: traducir de un idioma cualquiera al inglés tiene generalmente mucha mas calidad que traducir desde el inglés a otro idioma. También se puede observar que la calidad depende del idioma: lenguajes muy utilizados como el español y francés producen resultados de traducción superiores a idiomas menos usados. En este momento el servicio de traducción soporta 62 idiomas, incluyendo dos dialectos de Klingon, Yucatec Maya y Hmong Daw. Microsoft trabaja constantemente para aumentar el numero de idiomas, pero hay que tener en cuenta que se necesita aproximadamente un millón de palabras en textos originales para entrenar los algoritmos de IA, lo que no facilita el trabajo.

Configuración del Azure Translator API

Para utilizar el Translator API es necesario crear primero el servicio en Azure:

  1.        Entre al portal de manejo de Azure ( https://portal.azure.com ) utilizando sus credenciales.

  2.        Vaya a la sección de "Resource Groups" y cree un nuevo Grupo de Recursos (también es posible reutilizar un grupo ya existente).

  3.        Cree un servicio de "Translator Text API":

·        En el Resource Group, utilice el botón de "+Add" para crear un recurso, busque por "translator" en la casilla de búsqueda y seleccione "Translator Text API" en los resultados.

·        Asígnele un nombre al servicio y utilice el Grupo de Recursos deseado. En la casilla de "Pricing tier" seleccione un servicio dependiendo de la cantidad de traducciones a esperar por mes, lo que determina el precio del servicio (gratis si se utilizan menos de 2.000.000 de caracteres por mes). Acepte el anuncio de privacidad que aparece en la configuración (Microsoft utilizara los datos enviados para mejorar automáticamente los algoritmos de traducción).​

Imagen 1.- Creación del servicio de Traducción API.

4 .        Una vez creado el servicio, haga clic sobre su nombre en la lista de recursos del Resource Group, vaya a "Keys" y copie el valor de "Key 1".

Utilizando el Azure Translation API con SharePoint

En el siguiente ejemplo se van a utilizar dos Listas de Anuncios de SharePoint en donde el campo de "Body" de un elemento nuevo creado en la Lista en Español será traducido al inglés y utilizado para crear otro anuncio en la Lista de Anuncios en inglés. Cuando se introduce un nuevo elemento en la Lista de Anuncios en español, un WebHook hace que una Función de Azure comience a funcionar, utilice el texto del "Body" para hacer una llamada al Azure Translation API y cree el elemento en la Lista en Ingles.

Nota: la creación y configuración de una Función de Azure se puede encontrar el en artículo "SharePoint y Azure – Azure Functions" ( http://www.compartimoss.com/revistas/numero-30/sharepoint-y-azure-azure-functions ). La configuración y utilización de WebHooks de SharePoint se puede encontrar en el artículo "Eventos sobre SharePoint Online con Webhooks" ( http://www.compartimoss.com/revistas/numero-32/eventos-sobre-sharepoint-online-con-webhooks ).

5 .        Cree una cuenta de Funciones básica en el Grupo de Recursos, asignándole un nombre, Plan de Servicios y cuenta de Azure Storage.

6 .        Utilizando Visual Studio 2017 (o Visual Studio 2016 con el AddIn para programar Funciones de Azure), cree una nueva solución del tipo "Azure Function". Una vez creada la solución, agréguele una Función del tipo "Http Trigger" con derechos de acceso anónimos.

7 .        Agregue a la solución los paquetes NuGet, "AppForSharePointOnlineWebToolkit" y "Newtonsoft.Json".

8 .        Reemplace toda la rutina "Run" con el siguiente código:

1[FunctionName("TranslationFunction")]
2
3public static async Task<HttpResponseMessage> Run([HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = null)]HttpRequestMessage req, TraceWriter log)
4
5{
6
7// Registration
8
9string validationToken = req.GetQueryNameValuePairs()
10
11.FirstOrDefault(q => string.Compare(q.Key, "validationtoken", true) == 0)
12
13.Value;
14
15if (validationToken != null)
16
17{
18
19var myResponse = req.CreateResponse(HttpStatusCode.OK);
20
21myResponse.Content = new StringContent(validationToken);
22
23return myResponse;
24
25}
26
27
28
29// Changes
30
31var myContent = await req.Content.ReadAsStringAsync();
32
33var allNotifications = JsonConvert.DeserializeObject<ResponseModel<NotificationModel>>(myContent).Value;
34
35
36
37if (allNotifications.Count > 0)
38
39{
40
41foreach (var oneNotification in allNotifications)
42
43{
44
45// Login in SharePoint
46
47string baseUrl = "https://gustavo.sharepoint.com";
48
49string myUserName = "gustavo@gustavo.onmicrosoft.com";
50
51string myPassword = ConfigurationManager.AppSettings["gustavoPW"];
52
53
54
55SecureString securePassword = new SecureString();
56
57foreach (char oneChar in myPassword) securePassword.AppendChar(oneChar);
58
59SharePointOnlineCredentials myCredentials = new SharePointOnlineCredentials(myUserName, securePassword);
60
61
62
63ClientContext SPClientContext = new ClientContext(baseUrl + oneNotification.SiteUrl);
64
65SPClientContext.Credentials = myCredentials;
66
67
68
69// Get the Changes
70
71GetChanges(SPClientContext, oneNotification.Resource, log);
72
73}
74
75}
76
77
78
79return new HttpResponseMessage(HttpStatusCode.OK);
80
81}
82

Esta rutina primero se encarga de hacer el registro del WebHook (si la consulta contiene un parámetro "validationtoken" en el Query String). Después de registrado el WebHook, cada consulta es procesada para extraer las notificaciones que contiene. En cada notificación de la colección de notificaciones se hace un logeo en SharePoint para obtener los cambios detectados en la Lista (por medio de la rutina "GetChanges").

9 .        La rutina "GetChanges" recibe el contexto de SharePoint y el identificador de la Lista, y tiene la forma:

1static void GetChanges(ClientContext SPClientContext, string ListId, TraceWriter log)
2
3{
4
5// Get the List
6
7Web spWeb = SPClientContext.Site.RootWeb;
8
9List listaAnunciosES = spWeb.Lists.GetByTitle("AnnouncementsES");
10
11SPClientContext.Load(listaAnunciosES);
12
13List listaAnunciosEN = spWeb.Lists.GetByTitle("AnnouncementsEN");
14
15SPClientContext.Load(listaAnunciosEN);
16
17SPClientContext.ExecuteQuery();
18
19
20
21// Create the ChangeToken and Change Query
22
23ChangeToken lastChangeToken = new ChangeToken();
24
25lastChangeToken.StringValue = string.Format("1;3;{0};{1};-1", ListId, DateTime.Now.AddMinutes(-1).ToUniversalTime().Ticks.ToString());
26
27ChangeToken newChangeToken = new ChangeToken();
28
29newChangeToken.StringValue = string.Format("1;3;{0};{1};-1", ListId, DateTime.Now.ToUniversalTime().Ticks.ToString());
30
31ChangeQuery myChangeQuery = new ChangeQuery(false, false);
32
33myChangeQuery.Item = true;  // Get only Item changes
34
35myChangeQuery.Add = true;   // Get only the new Items
36
37myChangeQuery.ChangeTokenStart = lastChangeToken;
38
39myChangeQuery.ChangeTokenEnd = newChangeToken;
40
41
42
43// Get all the Changes
44
45var allChanges = listaAnunciosES.GetChanges(myChangeQuery);
46
47SPClientContext.Load(allChanges);
48
49SPClientContext.ExecuteQuery();
50
51
52
53foreach (Change oneChange in allChanges)
54
55{
56
57if (oneChange is ChangeItem)
58
59{
60
61// Get what is changed
62
63ListItem anuncioES = listaAnunciosES.GetItemById((oneChange as ChangeItem).ItemId);
64
65SPClientContext.Load(anuncioES);
66
67SPClientContext.ExecuteQuery();
68
69
70
71string textoParaTraducir = anuncioES["Body"].ToString().Replace("\"", "'");
72
73string lenguajeDe = "es";
74
75string lenguajeA = "en";
76
77string apiKey = ConfigurationManager.AppSettings["apiKey"];
78
79string textoTraducido = getTraduccion(textoParaTraducir, lenguajeDe, lenguajeA, apiKey);
80
81
82
83// Insert the values in the EN List
84
85ListItemCreationInformation newAnuncioENInfo = new ListItemCreationInformation();
86
87ListItem anuncioEN = listaAnunciosEN.AddItem(newAnuncioENInfo);
88
89anuncioEN["Title"] = anuncioES["Title"];
90
91anuncioEN["Body"] = textoTraducido;
92
93anuncioEN.Update();
94
95SPClientContext.ExecuteQuery();
96
97}
98
99}
100
101}
102

Primero se crean dos objetos que contienen las Listas de Anuncios de SharePoint. Luego se crea una consulta de cambio (variable "myChangeQuery") que especifica que se requieren los cambios ocurridos en el ultimo minuto, que ocurren en elementos de la Lista y que sean del tipo "Add", es decir, elementos nuevos. Luego de ejecutar la consulta, se examina cada uno de los cambios y se obtiene un objeto con el Anuncio agregado (el que contiene, a su vez, el texto del "Body").

En la misma rutina se llama a la rutina "getTraduccion", la que se encarga de hacer la consulta en Azure para traducir el texto, utilizando como parámetros de entrada el texto a traducir, el idioma de origen, el idioma de destino y la llave obtenida en el punto 4. Cuando ya se ha obtenido el texto traducido, se crea un nuevo elemento en la Lista de Anuncios en inglés.

10 .        La rutina "getTraduccion" recibe como parámetros de entrada el texto a traducir, los idiomas de origen y de destino y la clave del servicio de Traducción:

1static string getTraduccion(string TextoParaTraducir, string LenguajeDe, string LenguajeA, string ApiKey)
2
3{
4
5string txtTraducido = string.Empty;
6
7
8
9AzureAuthToken authTokenSource = new AzureAuthToken(ApiKey.Trim());
10
11string authToken = authTokenSource.GetAccessTokenAsync().Result;
12
13
14
15string uriTraduccion = "https://api.microsofttranslator.com/v2/Http.svc/Translate?text=" +
16
17HttpUtility.UrlEncode(TextoParaTraducir) +
18
19"&from=" + LenguajeDe + "&to=" + LenguajeA;
20
21HttpWebRequest httpWebRequest = (HttpWebRequest)WebRequest.Create(uriTraduccion);
22
23httpWebRequest.Headers.Add("Authorization", authToken);
24
25using (WebResponse myResponse = httpWebRequest.GetResponse())
26
27using (Stream stream = myResponse.GetResponseStream())
28
29{
30
31DataContractSerializer mySerializer = new DataContractSerializer(Type.GetType("System.String"));
32
33txtTraducido = (string)mySerializer.ReadObject(stream);
34
35}
36
37
38
39return txtTraducido;
40
41}
42

Cada consulta al Translation API se realiza por medio de una llamada REST a un URL pre-especificado del servicio de Traducción, utilizando como parámetros los dos valores de los idiomas y el texto a traducir. En este caso se está utilizando el método "Translate" en la llamada REST, pero existen otros 12 métodos que se pueden utilizar, incluyendo métodos para traducir no un texto solo sino también un array de textos en una sola llamada, detectar el idioma original, enumerar los idiomas que se pueden utilizar y separar palabras en un texto completo.

Esta rutina llama inicialmente el método "GetAccessTokenAsync" de la clase "AzureAuthToken" para obtener el token de acceso al servicio de traducción utilizando la llave obtenida al momento de configuración descrito en el punto 4. Esta clase tiene un método sincrónico y otro asincrónico para obtener el token, pero Microsoft especifica que es preferible utilizar el método asincrónico. La clase ha sido creada por Microsoft, se puede encontrar en la información de Azure y tiene la forma:

1using System;
2
3using System.Net;
4
5using System.Net.Http;
6
7using System.Threading.Tasks;
8
9
10
11namespace TranslationFunction
12
13{
14
15/// <summary>
16
17/// Client to call Cognitive Services Azure Auth Token service in order to get an access token.
18
19/// Exposes asynchronous as well as synchronous methods.
20
21/// </summary>
22
23public class AzureAuthToken
24
25{
26
27/// URL of the token service
28
29private static readonly Uri ServiceUrl = new Uri("https://api.cognitive.microsoft.com/sts/v1.0/issueToken");
30
31
32
33/// Name of header used to pass the subscription key to the token service
34
35private const string OcpApimSubscriptionKeyHeader = "Ocp-Apim-Subscription-Key";
36
37
38
39/// After obtaining a valid token, this class will cache it for this duration.
40
41/// Use a duration of 5 minutes, which is less than the actual token lifetime of 10 minutes.
42
43private static readonly TimeSpan TokenCacheDuration = new TimeSpan(0, 5, 0);
44
45
46
47/// Cache the value of the last valid token obtained from the token service.
48
49private string \_storedTokenValue = string.Empty;
50
51
52
53/// When the last valid token was obtained.
54
55private DateTime \_storedTokenTime = DateTime.MinValue;
56
57
58
59/// Gets the subscription key.
60
61public string SubscriptionKey { get; }
62
63
64
65/// Gets the HTTP status code for the most recent request to the token service.
66
67public HttpStatusCode RequestStatusCode { get; private set; }
68
69
70
71/// <summary>
72
73/// Creates a client to obtain an access token.
74
75/// </summary>
76
77/// <param name="key">Subscription key to use to get an authentication token.</param>
78
79public AzureAuthToken(string key)
80
81{
82
83if (string.IsNullOrEmpty(key))
84
85{
86
87throw new ArgumentNullException(nameof(key), "A subscription key is required");
88
89}
90
91
92
93this.SubscriptionKey = key;
94
95this.RequestStatusCode = HttpStatusCode.InternalServerError;
96
97}
98
99
100
101/// <summary>
102
103/// Gets a token for the specified subscription.
104
105/// </summary>
106
107/// <returns>The encoded JWT token prefixed with the string "Bearer ".</returns>
108
109/// <remarks>
110
111/// This method uses a cache to limit the number of request to the token service.
112
113/// A fresh token can be re-used during its lifetime of 10 minutes. After a successful
114
115/// request to the token service, this method caches the access token. Subsequent
116
117/// invocations of the method return the cached token for the next 5 minutes. After
118
119/// 5 minutes, a new token is fetched from the token service and the cache is updated.
120
121/// </remarks>
122
123public async Task<string> GetAccessTokenAsync()
124
125{
126
127if (string.IsNullOrWhiteSpace(this.SubscriptionKey))
128
129{
130
131return string.Empty;
132
133}
134
135
136
137// Re-use the cached token if there is one.
138
139if ((DateTime.Now - \_storedTokenTime) < TokenCacheDuration)
140
141{
142
143return \_storedTokenValue;
144
145}
146
147
148
149using (var client = new HttpClient())
150
151using (var request = new HttpRequestMessage())
152
153{
154
155request.Method = HttpMethod.Post;
156
157request.RequestUri = ServiceUrl;
158
159request.Content = new StringContent(string.Empty);
160
161request.Headers.TryAddWithoutValidation(OcpApimSubscriptionKeyHeader, this.SubscriptionKey);
162
163client.Timeout = TimeSpan.FromSeconds(2);
164
165var response = await client.SendAsync(request);
166
167this.RequestStatusCode = response.StatusCode;
168
169response.EnsureSuccessStatusCode();
170
171var token = await response.Content.ReadAsStringAsync();
172
173\_storedTokenTime = DateTime.Now;
174
175\_storedTokenValue = "Bearer " + token;
176
177return \_storedTokenValue;
178
179}
180
181}
182
183
184
185/// <summary>
186
187/// Gets a token for the specified subscription. Synchronous version.
188
189/// Use of async version preferred
190
191/// </summary>
192
193/// <returns>The encoded JWT token prefixed with the string "Bearer ".</returns>
194
195/// <remarks>
196
197/// This method uses a cache to limit the number of request to the token service.
198
199/// A fresh token can be re-used during its lifetime of 10 minutes. After a successful
200
201/// request to the token service, this method caches the access token. Subsequent
202
203/// invocations of the method return the cached token for the next 5 minutes. After
204
205/// 5 minutes, a new token is fetched from the token service and the cache is updated.
206
207/// </remarks>
208
209public string GetAccessToken()
210
211{
212
213// Re-use the cached token if there is one.
214
215if ((DateTime.Now - \_storedTokenTime) < TokenCacheDuration)
216
217{
218
219return \_storedTokenValue;
220
221}
222
223
224
225string accessToken = null;
226
227var task = Task.Run(async () =>
228
229{
230
231accessToken = await this.GetAccessTokenAsync();
232
233});
234
235
236
237while (!task.IsCompleted)
238
239{
240
241System.Threading.Thread.Yield();
242
243}
244
245if (task.IsFaulted)
246
247{
248
249throw task.Exception;
250
251}
252
253if (task.IsCanceled)
254
255{
256
257throw new Exception("Timeout obtaining access token.");
258
259}
260
261return accessToken;
262
263}
264
265
266
267}
268
269}
270
27111 .         Otras tres clases definen objetos utilizados por el WebHook:
272
273public class ResponseModel<T>
274
275{
276
277[JsonProperty(PropertyName = "value")]
278
279public List<T> Value { get; set; }
280
281}
282
283
284
285public class NotificationModel
286
287{
288
289[JsonProperty(PropertyName = "subscriptionId")]
290
291public string SubscriptionId { get; set; }
292
293
294
295[JsonProperty(PropertyName = "clientState")]
296
297public string ClientState { get; set; }
298
299
300
301[JsonProperty(PropertyName = "expirationDateTime")]
302
303public DateTime ExpirationDateTime { get; set; }
304
305
306
307[JsonProperty(PropertyName = "resource")]
308
309public string Resource { get; set; }
310
311
312
313[JsonProperty(PropertyName = "tenantId")]
314
315public string TenantId { get; set; }
316
317
318
319[JsonProperty(PropertyName = "siteUrl")]
320
321public string SiteUrl { get; set; }
322
323
324
325[JsonProperty(PropertyName = "webId")]
326
327public string WebId { get; set; }
328
329}
330
331
332
333public class SubscriptionModel
334
335{
336
337[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
338
339public string Id { get; set; }
340
341
342
343[JsonProperty(PropertyName = "clientState", NullValueHandling = NullValueHandling.Ignore)]
344
345public string ClientState { get; set; }
346
347
348
349[JsonProperty(PropertyName = "expirationDateTime")]
350
351public DateTime ExpirationDateTime { get; set; }
352
353
354
355[JsonProperty(PropertyName = "notificationUrl")]
356
357public string NotificationUrl { get; set; }
358
359
360
361[JsonProperty(PropertyName = "resource", NullValueHandling = NullValueHandling.Ignore)]
362
363public string Resource { get; set; }
364
365}​
366

12 .        Registre el WebHook en la Lista de Anuncios en español (utilizando Postman, por ejemplo, como se indica en el artículo mencionado al principio del artículo) y cree un anuncio nuevo en la Lista. El WebHook hará que la Función realice su trabajo, traduzca el texto, y cree un nuevo Anuncio en la Lista de Anuncios en inglés:

Imagen 2.- Anuncio original y traducido utilizando el servicio de Traducción API. Imagen 2.- Anuncio original y traducido utilizando el servicio de Traducción API.

Conclusiones

El servicio de Traducción de Texto de Azure puede llegar a ser una herramienta muy valiosa en SharePoint, especialmente en lo que se refiere a la creación de contenido traducido automáticamente a diferentes idiomas. El Azure Translation API es fácil de utilizar desde cualquiera lenguaje de programación, y produce resultados confiables rápida y seguramente. El API utiliza algoritmos de Inteligencia Artificial que se mejoran con el uso, por lo no es necesario crear ni entrenar algoritmos propios.

Gustavo Velez MVP Office Servers and Services gustavo@gavd.net http://www.gavd.net

Siguemos en LinkedInSiguemos en Twitter
Powered by  ENCAMINA