Walkthrough: Writing a Windows 8 Translator App

This walkthrough takes you through the steps to build a basic Windows 8 app that searches twitter for a term that you specify, and, when it encounters non-English tweets, will use the Microsoft Translator API to translate them for you.

Before you get started, be sure you have followed the walkthrough for signing up to the Microsoft Translator API service, and have your Client ID and Client Secret ready. You can see that walkthrough here: http://blogs.msdn.com/b/translation/p/gettingstarted1.aspx

You can see the app in action in Figure 1. Here, the term ‘French’ has been searched for, and the app renders the tweets, along with their translations.

clip_image002

Figure 1. The Translating Twitter app for Windows 8.

Figure 2 shows a close-up of one of the tweets, showing the translation.

clip_image003

Figure 2. Close up on a translated tweet.

In this walkthrough, you’ll see how to build this application, step by step, using C# and XAML. You’ll build it using the Model-View-View-Model (MVVM) architecture.

Step 1. Create the App

Using Visual Studio, from the File menu, select New Project.

From the Templates menu, select Visual C#, then select Windows Store. You’ll see a list of installed templates, from these, select ‘Blank App (XAML)’ as shown in Figure 3.

clip_image005

Figure 3. Creating the app in Visual Studio

Visual Studio will then create a solution containing everything you need to get started.

Step 2. Create the Model

When creating an app that uses the MVVM architecture, the first thing you need to build is the Model. This is the class or set of classes that implement the data that your app will use. In the case of the twitter application, this is the class that will represent a tweet.

Using Visual Studio, create a new folder in your solution and call it Models. In this, add a new class and call it Tweet.cs. See Figure 4.

clip_image006

Figure 4. Adding the Tweet Model.

Edit the code in the Tweet.cs class so that it looks like this:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace TwitterView.Models
{
    public class Tweet
    {
        public string Title { get; set; }
        public string Author { get; set; }
        public string ImageUri { get; set; }
        public string currentText { get; set; }
        public string translatedText { get; set; }
        public string AuthorID { get; set; }
        public string pubDate { get; set; }
    }
}

 

This gives you a class that has the data fields that Twitter search exposes, that you’ll be able to use for binding. It adds a string, ‘translatedText’ into which you’ll store the translated data. 

Step 3. Create the ViewModel

The next step in building the app is to create the ViewModel. This is a class that acts as a bridge between your app and the data. It downloads the twitter data, and performs the translations. It exposes data and events that allow the user interface to bind to the data.

First, create a folder in your solution called ‘ViewModels’. In this folder, create a new class called ‘TweetsViewModel.cs’. See Figure 4.

clip_image007

Figure 4. Creating the View Model

This should be a class that implements the INotifyPropertyChanged interface. Here’s the complete code for the class.

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
// Below Added to default Class
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Net.Http;
using System.Xml.Linq;
using TwitterView.Models;
using TwitterView.Common;
//
namespace TwitterView.ViewModels
{
    public class TweetsViewModel : INotifyPropertyChanged
    {
        private List<Tweet> _tweets = new List<Tweet>();
        private string _last_id = "";

        public TweetsViewModel()
        {
           
            getTweets("Microsoft");
        }

        public List<Tweet> tweets
        {
            get {
                RaisePropertyChanged("tweets");
                return _tweets;
            }
            
        }
        
        public event PropertyChangedEventHandler PropertyChanged;

        private void RaisePropertyChanged(string propertyName)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null)
            {
                handler(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        private string getTwitterID(string strLink)
        {
            int lastSlash = strLink.LastIndexOf("/");
            int firstSlash = strLink.LastIndexOf("/", lastSlash - 1);
            string strLinkChunk = strLink.Substring(firstSlash);
            string str_TwitterID = strLinkChunk.Substring(1, strLinkChunk.Length - 8);
            return str_TwitterID;
        }

        private async Task<bool> doTranslation(List<Tweet> _rawtweets)
        {
            bool bReturn = true;
            string clientID = "LaurenceTest";
            string clientSecret = "ekRK0D0JYy7iy1oavWsWpAIQd795qh3Er/IrvqQWDXA=";
            var _Authentication = new AzureDataMarket(clientID, clientSecret);
            AzureDataMarket.Token m_Token = await _Authentication.GetTokenAsync();
            string auth = m_Token.Header;
            foreach (Tweet t in _rawtweets)
            {
                HttpClient client = new HttpClient();
                string strSource = t.currentText;
                client.DefaultRequestHeaders.Add("Authorization", auth);
                string RequestUri = "http://api.microsofttranslator.com/v2/Http.svc/Translate?text=" + System.Net.WebUtility.UrlEncode(strSource) + "&to=en";
                try
                {
                    string strTranslated = await client.GetStringAsync(RequestUri);
                    System.Xml.Linq.XDocument xTranslation = System.Xml.Linq.XDocument.Parse(strTranslated);
                    string strTransText = xTranslation.Root.FirstNode.ToString();
                    if (strTransText == strSource)
                        t.translatedText = "";
                    else
                        t.translatedText = strTransText;
                }
                catch (Exception ex)
                {
                    t.translatedText = t.currentText;
                    bReturn = false;
                }
            }

            return bReturn;
        }

        public async void getTweets(string _searchTerm)
        {
            try
            {
                var httpClient = new HttpClient();
                var result = httpClient.GetStringAsync("http://search.twitter.com/search.rss?rpp=24&q=" + _searchTerm);

                var document = XDocument.Parse(result.Result);
                var ns = (XNamespace)"http://base.google.com/ns/1.0";
                string strLink = document.Descendants("item").First().Descendants("link").First().ToString();
                string id = getTwitterID(strLink);
                List<Tweet> rawtweets = new List<Tweet>();
                if (id != _last_id)
                {
                    rawtweets = (from item in document.Descendants("item")
                                          select new Tweet()
                                          {
                                              Title = (string)item.Descendants("title").First(),
                                              currentText = (string)item.Descendants("title").First(),
                                              Author = (string)item.Descendants("author").First(),
                                              pubDate = (string)item.Descendants("pubDate").First(),
                                              ImageUri = (string)item.Descendants(ns + "image_link").First()
                                          }).ToList();


                    _last_id = id;
                }
                bool x = await doTranslation(rawtweets);
                _tweets = rawtweets;
            }
            catch (Exception ex)
            {
            }

            RaisePropertyChanged("tweets");
            //return true;
        }

    }

}
 

This View Model exposes a List<Tweet> which it creates by calling the Twitter search API and loading the results. The doTranslation function uses Microsoft Translator to translate the tweets.

Step 3.1 Understanding the Translation code.

The translation is handled by the doTranslation function.

private async Task<bool> doTranslation(List<Tweet> _rawtweets)
{
  bool bReturn = true;
  string clientID = "<YourClientID>";
  string clientSecret = "<YourClientSecret>";
  var _Authentication = new AzureDataMarket(clientID, clientSecret);
  AzureDataMarket.Token m_Token = await _Authentication.GetTokenAsync();
  string auth = m_Token.Header;
  foreach (Tweet t in _rawtweets)
  {
    HttpClient client = new HttpClient();
    string strSource = t.currentText;
    client.DefaultRequestHeaders.Add("Authorization", auth);
    string RequestUri = "http://api.microsofttranslator.com/v2/Http.svc/Translate?text=" 
                        + System.Net.WebUtility.UrlEncode(strSource) + "&to=en";
    try
    {
      string strTranslated = await client.GetStringAsync(RequestUri);
      System.Xml.Linq.XDocument xTranslation = 
            System.Xml.Linq.XDocument.Parse(strTranslated);
      string strTransText = xTranslation.Root.FirstNode.ToString();
      if (strTransText == strSource)
        t.translatedText = "";
      else
        t.translatedText = strTransText;
    }
    catch (Exception ex)
    {
      t.translatedText = t.currentText;
      bReturn = false;
    }
  }
  return bReturn;
}

What this function does is two-fold. First, it gets an authentication token for the Microsoft Translation service from the Windows Azure Marketplace. It does this by using the AzureDataMarket class. The full listing for this class is in Appendix 1. 

When it has the token, it uses it to call the Translator API at http://api.microsofttranslator.com/v2/Http.svc passing it the original tweet. The service returns a string encoded in XML, so it is loaded into an XDocument for easy parsing. It’s as simple as that!

Step 4. Creating the View.

Windows 8 provides a XAML control called a ‘GridView’ that provides a template-driven way to render data. This can be bound to a data source. Here’s an example:

<GridView ItemTemplate="{StaticResource TweetTemplate}" SelectionMode="None" ItemsSource="{Binding tweets}"></GridView>

 

The ItemTemplate attributes define what the data should look like. It’s a static resource called ‘TweetTemplate’, and it’s defined here:

<DataTemplate x:Key="TweetTemplate">
 <Grid>
  <Rectangle Fill="#FFDA713F" HorizontalAlignment="Left" Height="115" Margin="10,11,0,0" 
             VerticalAlignment="Top" Width="455" RadiusX="20" RadiusY="20"/>
  <TextBlock Foreground="White" HorizontalAlignment="Left" Height="50" 
             Margin="176,12,0,0" TextWrapping="Wrap" x:Name="txtTweet" 
             Text="{Binding Title}" VerticalAlignment="Top" Width="277" FontSize="12"/>
  <TextBlock Foreground="White" HorizontalAlignment="Left" Height="50" 
             Margin="176,72,0,0" TextWrapping="Wrap" x:Name="txtTrans" 
             Text="{Binding translatedText}" VerticalAlignment="Top" Width="277" 
             FontSize="12"/>
  <Image Source="{Binding ImageUri}" HorizontalAlignment="Left" Height="89" 
         Margin="20,20,0,0" VerticalAlignment="Top" Width="116"/>
  <TextBlock Foreground="White" HorizontalAlignment="Left" Height="17" 
             Margin="24,109,0,0" TextWrapping="Wrap" Text="{Binding Author}" 
             VerticalAlignment="Top" Width="150" FontSize="10"/>

 </Grid>
</DataTemplate>
 

Note the properties that use Binding in their values. This denotes that they are bound to a value in the model. If you look back to the Model class, you’ll see where these were defined. The template defines a rectangle, as the background, text blocks for the title (original tweet), translated text and the author, and an image which is bound to the image of the tweet author. It will end up looking like Figure 5. Note the three text blocks, the background rectangle, with rounded corners, and the image.

clip_image003[1]

Figure 5. The Templated Tweet

Looking back at the GridView definition again, you can then see that the ItemsSource property is set to bind to the tweets. This is the list of tweets that is exposed by the ViewModel.

<GridView ItemTemplate="{StaticResource TweetTemplate}" SelectionMode="None" ItemsSource="{Binding tweets}"></GridView>

That’s about it. There are a few more pieces of functionality such as the bottom AppBar that allows you to change the default search terms. You can see this in the full app code download which is available here: <TODO>

Summary

In this article you saw how to create a full Windows 8 app that searched twitter for tweets matching a word, and when those tweets were in a different language, it uses the Microsoft Translator API to translate the text into English.

 

Appendix 1. The AzureMarket Class

This class calls the Windows Azure Marketplace, passing it your Client ID and Client Secret, and getting an access token back. It then creates the ‘bearer’ payload required by the Microsoft Translator API to use this token.

Here’s the code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace TwitterView.Common
{
    class AzureDataMarket
    {
        readonly string m_ClientId;
        readonly string m_ClientSecret;
        readonly string m_Request;
        readonly string DATAMARKET_ACCESS_URI = "https://datamarket.accesscontrol.windows.net/v2/OAuth2-13";

        public AzureDataMarket(string clientId, string clientSecret)
        {
            this.m_ClientId = Uri.EscapeDataString(clientId);
            this.m_ClientSecret = Uri.EscapeDataString(clientSecret);
            this.m_Request = string.Format("grant_type=client_credentials&client_id={0}&client_secret={1}&scope=http://api.microsofttranslator.com", m_ClientId, m_ClientSecret);
        }

        public async Task<Token> GetTokenAsync()
        {
            // build request
            var _Request = System.Net.WebRequest.Create(DATAMARKET_ACCESS_URI);
            _Request.ContentType = "application/x-www-form-urlencoded";
            _Request.Method = "POST";

            // make request
            var _Bytes = Encoding.UTF8.GetBytes(this.m_Request);
            using (var _Stream = await _Request.GetRequestStreamAsync())
                _Stream.Write(_Bytes, 0, _Bytes.Length);

            // deserialize response
            try
            {
                using (var _Response = await _Request.GetResponseAsync())
                {
                    var _Serializer = new System.Runtime.Serialization.Json.DataContractJsonSerializer(typeof(Token));
                    var _Token = await Task.Run(() => (Token)_Serializer.ReadObject(_Response.GetResponseStream()));
                    return _Token;
                }
            }
            catch (Exception)
            {
                System.Diagnostics.Debugger.Break();
                throw;
            }
        }

        [System.Runtime.Serialization.DataContract]
        public class Token
        {
            [System.Runtime.Serialization.DataMember]
            public string access_token { get; set; }

            [System.Runtime.Serialization.DataMember]
            public string token_type { get; set; }

            private int m_expires_in;
            [System.Runtime.Serialization.DataMember]
            public int expires_in
            {
                get { return m_expires_in; }
                set
                {
                    m_expires_in = value;
                    ExpirationDate = DateTime.Now.AddSeconds(value);
                }
            }

            [System.Runtime.Serialization.DataMember]
            public string scope { get; set; }

            private DateTime ExpirationDate = DateTime.MinValue;
            public bool IsExpired { get { return ExpirationDate < DateTime.Now; } }

            public string Header
            {
                get { return string.Format("Bearer {0}", access_token); }
            }
        }
    }
}