Last week I spent quite a bit of time trying to set up the Bing Translate API service. I can honestly say this was one of the most screwed up developer experiences I've had in a long while - specifically related to the byzantine sign up process that Microsoft has in place. Not only is it nearly impossible to find decent documentation on the required signup process, some of the links in the docs are just plain wrong, and some of the account pages you need to access the actual account information once signed up are not linked anywhere from the administration UI. To make things even harder is the fact that the APIs changed a while back, with a completely new authentication scheme that's described and not directly linked documentation topic also made for a very frustrating search experience. It's a bummer that this is the case too, because the actual API itself is easy to use and works very well - fast and reasonably accurate (as accurate as you can expect machine translation to be). But the sign up process is a pain in the ass doubtlessly leaving many people giving up in frustration. In this post I'll try to hit all the points needed to set up to use the Bing Translate API in one place since such a document seems to be missing from Microsoft. Hopefully the API folks at Microsoft will get their shit together and actually provide this sort of info on their site… Signing Up The first step required is to create a Windows Azure MarketPlace account. Go to: https://datamarket.azure.com/ Sign in with your Windows Live Id If you don't have an account you will be taken to a registration page which you have to fill out. Follow the links and complete the registration. Once you're signed in you can start adding services. Click on the Data Link on the main page Select Microsoft Translator from the list This adds the Microsoft Bing Translator to your services. Pricing The page shows the pricing matrix and the free service which provides 2 megabytes for translations a month for free. Prices go up steeply from there. Pricing is determined by actual bytes of the result translations used. Max translations are 1000 characters so at minimum this means you get around 2000 translations a month for free. However most translations are probable much less so you can expect larger number of translations to go through. For testing or low volume translations this should be just fine. Once signed up there are no further instructions and you're left in limbo on the MS site. Register your Application Once you've created the Data association with Translator the next step is registering your application. To do this you need to access your developer account. Go to https://datamarket.azure.com/developer/applications/register Provide a ClientId, which is effectively the unique string identifier for your application (not your customer id!) Provide your name The client secret was auto-created and this becomes your 'password' For the redirect url provide any https url: https://microsoft.com works Give this application a description of your choice so you can identify it in the list of apps Now, once you've registered your application, keep track of the ClientId and ClientSecret - those are the two keys you need to authenticate before you can call the Translate API. Oddly the applications page is hidden from the Azure Portal UI. I couldn't find a direct link from anywhere on the site back to this page where I can examine my developer application keys. To find them you can go to: https://datamarket.azure.com/developer/applications You can come back here to look at your registered applications and pick up the ClientID and ClientSecret. Fun eh? But we're now ready to actually call the API and do some translating. Using the Bing Translate API The good news is that after this signup hell, using the API is pretty straightforward. To use the translation API you'll need to actually use two services: You need to call an authentication API service first, before you can call the actual translator API. These two APIs live on different domains, and the authentication API returns JSON data while the translator service returns XML. So much for consistency. Authentication The first step is authentication. The service uses oAuth authentication with a bearer token that has to be passed to the translator API. The authentication call retrieves the oAuth token that you can then use with the translate API call. The bearer token has a short 10 minute life time, so while you can cache it for successive calls, the token can't be cached for long periods. This means for Web backend requests you typically will have to authenticate each time unless you build a more elaborate caching scheme that takes the timeout into account (perhaps using the ASP.NET Cache object). For low volume operations you can probably get away with simply calling the auth API for every translation you do. To call the Authentication API use code like this:///
/// Retrieves an oAuth authentication token to be used on the translate
/// API request. The result string needs to be passed as a bearer token
/// to the translate API.
///
/// You can find client ID and Secret (or register a new one) at:
/// https://datamarket.azure.com/developer/applications/
///
/// The client ID of your application
/// The client secret or password
///
public string GetBingAuthToken(string clientId = null, string clientSecret = null)
{
string authBaseUrl = https://datamarket.accesscontrol.windows.net/v2/OAuth2-13;
if (string.IsNullOrEmpty(clientId) || string.IsNullOrEmpty(clientSecret))
{
ErrorMessage = Resources.Resources.Client_Id_and_Client_Secret_must_be_provided;
return null;
}
var postData = string.Format("grant_type=client_credentials&client_id={0}" +
"&client_secret={1}" +
"&scope=http://api.microsofttranslator.com",
HttpUtility.UrlEncode(clientId),
HttpUtility.UrlEncode(clientSecret));
// POST Auth data to the oauth API
string res, token;
try
{
var web = new WebClient();
web.Encoding = Encoding.UTF8;
res = web.UploadString(authBaseUrl, postData);
}
catch (Exception ex)
{
ErrorMessage = ex.GetBaseException().Message;
return null;
}
var ser = new JavaScriptSerializer();
var auth = ser.Deserialize<BingAuth>(res);
if (auth == null)
return null;
token = auth.access_token;
return token;
}
private class BingAuth
{
public string token_type { get; set; }
public string access_token { get; set; }
}
This code basically takes the client id and secret and posts it at the oAuth endpoint which returns a JSON string. Here I use the JavaScript serializer to deserialize the JSON into a custom object I created just for deserialization. You can also use JSON.NET and dynamic deserialization if you are already using JSON.NET in your app in which case you don't need the extra type. In my library that houses this component I don't, so I just rely on the built in serializer.
The auth method returns a long base64 encoded string which can be used as a bearer token in the translate API call.
Translation
Once you have the authentication token you can use it to pass to the translate API. The auth token is passed as an Authorization header and the value is prefixed with a 'Bearer ' prefix for the string.
Here's what the simple Translate API call looks like:///
/// Uses the Bing API service to perform translation
/// Bing can translate up to 1000 characters.
///
/// Requires that you provide a CLientId and ClientSecret
/// or set the configuration values for these two.
///
/// More info on setup:
/// http://www.west-wind.com/weblog/
///
/// Text to translate
/// Two letter culture name
/// Two letter culture name
/// Pass an access token retrieved with GetBingAuthToken.
/// If not passed the default keys from .config file are used if any
///
public string TranslateBing(string text, string fromCulture, string toCulture,
string accessToken = null)
{
string serviceUrl = "http://api.microsofttranslator.com/V2/Http.svc/Translate";
if (accessToken == null)
{
accessToken = GetBingAuthToken();
if (accessToken == null)
return null;
}
string res;
try
{
var web = new WebClient();
web.Headers.Add("Authorization", "Bearer " + accessToken);
string ct = "text/plain";
string postData = string.Format("?text={0}&from={1}&to={2}&contentType={3}",
HttpUtility.UrlEncode(text),
fromCulture, toCulture,
HttpUtility.UrlEncode(ct));
web.Encoding = Encoding.UTF8;
res = web.DownloadString(serviceUrl + postData);
}
catch (Exception e)
{
ErrorMessage = e.GetBaseException().Message;
return null;
}
// result is a single XML Element fragment
var doc = new XmlDocument();
doc.LoadXml(res);
return doc.DocumentElement.InnerText;
}
The first of this code deals with ensuring the auth token exists. You can either pass the token into the method manually or let the method automatically retrieve the auth code on its own. In my case I'm using this inside of a Web application and in that situation I simply need to re-authenticate every time as there's no convenient way to manage the lifetime of the auth cookie.
The auth token is added as an Authorization HTTP header prefixed with 'Bearer ' and attached to the request. The text to translate, the from and to language codes and a result format are passed on the query string of this HTTP GET request against the Translate API.
The translate API returns an XML string which contains a single element with the translated string.
Using the Wrapper Methods
It should be pretty obvious how to use these two methods but here are a couple of test methods that demonstrate the two usage scenarios:[TestMethod]
public void TranslateBingWithAuthTest()
{
var translate = new TranslationServices();
string clientId = DbResourceConfiguration.Current.BingClientId;
string clientSecret = DbResourceConfiguration.Current.BingClientSecret;
string auth = translate.GetBingAuthToken(clientId, clientSecret);
Assert.IsNotNull(auth);
string text = translate.TranslateBing("Hello World we're back home!", "en", "de",auth);
Assert.IsNotNull(text, translate.ErrorMessage);
Console.WriteLine(text);
}
[TestMethod]
public void TranslateBingIntegratedTest()
{
var translate = new TranslationServices();
string text = translate.TranslateBing("Hello World we're back home!","en","de");
Assert.IsNotNull(text, translate.ErrorMessage);
Console.WriteLine(text);
}
Other API Methods
The Translate API has a number of methods available and this one is the simplest one but probably also the most common one that translates a single string.
You can find additional methods for this API here:
http://msdn.microsoft.com/en-us/library/ff512419.aspx
Soap and AJAX APIs are also available and documented on MSDN:
http://msdn.microsoft.com/en-us/library/dd576287.aspx
These links will be your starting points for calling other methods in this API.
Dual Interface
I've talked about my database driven localization provider here in the past, and it's for this tool that I added the Bing localization support. Basically I have a localization administration form that allows me to translate individual strings right out of the UI, using both Google and Bing APIs:
As you can see in this example, the results from Google and Bing can vary quite a bit - in this case Google is stumped while Bing actually generated a valid translation. At other times it's the other way around - it's pretty useful to see multiple translations at the same time. Here I can choose from one of the values and driectly embed them into the translated text field.
Lost in Translation
There you have it. As I mentioned using the API once you have all the bureaucratic crap out of the way calling the APIs is fairly straight forward and reasonably fast, even if you have to call the Auth API for every call.
Hopefully this post will help out a few of you trying to navigate the Microsoft bureaucracy, at least until next time Microsoft upends everything and introduces new ways to sign up again. Until then - happy translating…
Related Posts
Translation method Source on Github
Translating with Google Translate without Google API Keys
Creating a data-driven ASP.NET Resource Provider© Rick Strahl, West Wind Technologies, 2005-2013Posted in Localization ASP.NET .NET
Tweet
!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0];if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src="//platform.twitter.com/widgets.js";fjs.parentNode.insertBefore(js,fjs);}}(document,"script","twitter-wjs");
(function() {
var po = document.createElement('script'); po.type = 'text/javascript'; po.async = true;
po.src = 'https://apis.google.com/js/plusone.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(po, s);
})();