diff --git a/ChangeLog.md b/ChangeLog.md
new file mode 100644
--- /dev/null
+++ b/ChangeLog.md
@@ -0,0 +1,5 @@
+# Revision history for microsoft-translator
+
+## 0.1.0.0  -- YYYY-mm-dd
+
+* First version. Released on an unsuspecting world.
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2017 Cliff Harvey
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import           Distribution.Simple
+main = defaultMain
diff --git a/microsoft-translator.cabal b/microsoft-translator.cabal
new file mode 100644
--- /dev/null
+++ b/microsoft-translator.cabal
@@ -0,0 +1,46 @@
+name:                microsoft-translator
+version:             0.1.0.0
+synopsis:            Bindings to the Microsoft Translator API
+description:         Bindings to the Microsoft Translator API
+license:             MIT
+license-file:        LICENSE
+author:              Cliff Harvey
+maintainer:          cs.hbar@gmail.com
+copyright:           Cliff Harvey 2017
+category:            Natural Language
+build-type:          Simple
+extra-source-files:  ChangeLog.md
+cabal-version:       >=1.10
+
+library
+
+  build-depends:         base            >= 4.10 && < 4.11
+                       , servant         >= 0.11 && < 0.12
+                       , servant-client  >= 0.11 && < 0.12
+                       , text            >= 1.2  && < 1.3
+                       , http-client     >= 0.5  && < 0.6
+                       , http-client-tls >= 0.3  && < 0.4
+                       , http-media      >= 0.7  && < 0.8
+                       , bytestring      >= 0.10 && < 0.11
+                       , http-api-data   >= 0.3  && < 0.4
+                       , mtl             >= 2.2  && < 2.3
+                       , time            >= 1.8  && < 1.9
+                       , xml             >= 1.3  && < 1.4
+                       , safe            >= 0.3  && < 0.4
+
+  hs-source-dirs:      src
+  default-language:    Haskell2010
+
+  exposed-modules:       Microsoft.Translator.API.Auth
+                       , Microsoft.Translator.API
+                       , Microsoft.Translator
+                       , Microsoft.TranslatorExample
+
+  other-modules:         Microsoft.Translator.Exception
+                       , Microsoft.Translator.Language
+
+  ghc-options:         -Wall
+
+source-repository head
+    type: git
+    location: https://github.com/BlackBrane/microsoft-translator
diff --git a/src/Microsoft/Translator.hs b/src/Microsoft/Translator.hs
new file mode 100644
--- /dev/null
+++ b/src/Microsoft/Translator.hs
@@ -0,0 +1,253 @@
+{-# LANGUAGE DeriveGeneric #-}
+
+module Microsoft.Translator (
+
+    -- * Basic Types
+      SubscriptionKey (..)
+    , AuthToken
+    , AuthData (..)
+    , TransData
+
+    , Language (..)
+    , TranslatorException
+
+    , ArrayResponse (..)
+    , TransItem (..)
+    , Sentence (..)
+
+    -- * API functions
+    -- ** Authorization
+    , lookupSubKey
+    , issueToken
+    , issueAuth
+    , refresh
+    , initTransData
+    , initTransDataWith
+    , checkAuth
+    , keepFreshAuth
+
+    -- ** Translation
+    -- *** ExceptT variants
+    , translate
+    , translateArray
+    , translateArrayText
+    , translateArraySentences
+
+    -- *** IO variants
+    , lookupSubKeyIO
+    , issueAuthIO
+    , initTransDataIO
+    , checkAuthIO
+    , translateIO
+    , translateArrayIO
+    , translateArrayTextIO
+    , translateArraySentencesIO
+
+    -- *** Minimalistic variants
+    , simpleTranslate
+    , basicTranslate
+    , basicTranslateArray
+
+    -- * Pure functions
+    , mkSentences
+
+) where
+
+import           Microsoft.Translator.API
+import           Microsoft.Translator.API.Auth
+import           Microsoft.Translator.Exception
+
+import           Control.Concurrent      (forkIO, threadDelay)
+import           Control.Monad.Except
+import           Data.Char               (isSpace)
+import           Data.IORef
+import           Data.Monoid             ((<>))
+import           Data.String             (fromString)
+import           Data.Text               as T (Text, all, splitAt)
+import           Data.Time
+import           GHC.Generics            (Generic)
+import           Network.HTTP.Client
+import           Network.HTTP.Client.TLS
+import           System.Environment      (lookupEnv)
+
+
+-- | Simplest possible translation function.
+--   Always needs to make a request for the JWT token first.
+simpleTranslate :: SubscriptionKey -> Manager
+                -> Maybe Language -> Language
+                -> Text -> IO (Either TranslatorException Text)
+simpleTranslate key man from to txt = runExceptT $ do
+        tok <- ExceptT $ issueToken man key
+        ExceptT $ basicTranslate man tok from to txt
+
+-- | Retrieve your subscription key from the TRANSLATOR_SUBSCRIPTION_KEY environment
+--   variable.
+lookupSubKey :: ExceptT TranslatorException IO SubscriptionKey
+lookupSubKey = ExceptT $
+    maybe (Left MissingSubscriptionKey) (Right . SubKey . fromString) <$>
+        lookupEnv "TRANSLATOR_SUBSCRIPTION_KEY"
+
+-- | Retrieve your subscription key from the TRANSLATOR_SUBSCRIPTION_KEY environment
+--   variable.
+lookupSubKeyIO :: IO (Either TranslatorException SubscriptionKey)
+lookupSubKeyIO = runExceptT lookupSubKey
+
+
+-- | An 'AuthToken' together with the time it was recieved.
+--   Each token is valid for 10 minutes.
+data AuthData = AuthData
+    { timeStamp :: UTCTime
+    , authToken :: AuthToken
+    } deriving Show
+
+
+-- | Retrieve a token, via 'issueToken', and save it together with a timestamp.
+issueAuth :: Manager -> SubscriptionKey -> ExceptT TranslatorException IO AuthData
+issueAuth man key = do
+    tok <- ExceptT $ issueToken man key
+    now <- liftIO getCurrentTime
+    pure $ AuthData now tok
+
+
+-- | The data to hold onto for making translation requests.
+--   Includes your 'SubscriptionKey', an `AuthData` and an HTTPS 'Manager'.
+data TransData = TransData
+    { subKey      :: SubscriptionKey
+    , manager     :: Manager
+    , authDataRef :: IORef AuthData }
+
+-- | Retrieve an 'AuthData' token and hold on to the new HTTPS manager.
+initTransData :: SubscriptionKey -> ExceptT TranslatorException IO TransData
+initTransData key =
+    liftIO (newManager tlsManagerSettings) >>= initTransDataWith key
+
+-- | Retrieve an 'AuthData' token and hold on to the HTTPS manager.
+--   For when you want to supply a particular manager. Otherwise use 'initTransData'.
+initTransDataWith :: SubscriptionKey -> Manager -> ExceptT TranslatorException IO TransData
+initTransDataWith key man =
+    TransData key man <$> (issueAuth man key >>= liftIO . newIORef)
+
+
+refresh :: TransData -> ExceptT TranslatorException IO AuthData
+refresh tdata = do
+    auth <- issueAuth (manager tdata) (subKey tdata)
+    liftIO $ writeIORef (authDataRef tdata) auth
+    pure auth
+
+-- | If a token contained in a 'TransData' is expired or about to expire, refresh it.
+checkAuth :: TransData -> ExceptT TranslatorException IO AuthData
+checkAuth tdata = do
+    now <- liftIO getCurrentTime
+    auth <- liftIO . readIORef $ authDataRef tdata
+    if (diffUTCTime now (timeStamp auth) > 9*60+30)
+        then refresh tdata
+        else pure auth
+
+-- | Create a 'TransData' with a new auth token and fork a thread to refresh it every
+--   9 minutes.
+--   This is mostly a quick-and-dirty function for demo purposes and one-off projects.
+--   You'll want to roll something more robust for production applications.
+keepFreshAuth :: SubscriptionKey -> ExceptT TranslatorException IO TransData
+keepFreshAuth key = do
+    tdata <- initTransData key
+    _ <- liftIO . forkIO $ loop tdata
+    pure tdata
+
+    where
+        loop :: TransData -> IO ()
+        loop td = do
+            threadDelay $ 10^(6::Int) * 9 * 60
+            _ <- runExceptT $ refresh td
+            loop td
+
+
+-- | Translate text
+translate :: TransData -> Maybe Language -> Language -> Text
+          -> ExceptT TranslatorException IO Text
+translate tdata from to txt = do
+     tok <- authToken <$> checkAuth tdata
+     ExceptT $ basicTranslate (manager tdata) tok from to txt
+
+-- | Translate a text array.
+--   The 'ArrayResponse' you get back includes sentence break information.
+translateArray :: TransData -> Language -> Language -> [Text]
+               -> ExceptT TranslatorException IO ArrayResponse
+translateArray tdata from to txts = do
+     tok <- authToken <$> checkAuth tdata
+     ExceptT $ basicTranslateArray (manager tdata) tok from to txts
+
+-- | Translate a text array, and just return the list of texts.
+translateArrayText :: TransData -> Language -> Language -> [Text]
+                   -> ExceptT TranslatorException IO [Text]
+translateArrayText tdata from to txts =
+    map transText . getArrayResponse <$> translateArray tdata from to txts
+
+-- | Translate a text array, and split all the texts into constituent sentences,
+--   paired with the originals.
+translateArraySentences :: TransData -> Language -> Language -> [Text]
+                        -> ExceptT TranslatorException IO [[Sentence]]
+translateArraySentences tdata from to txts =
+    mkSentences txts <$> translateArray tdata from to txts
+
+-- | An original/translated sentence pair.
+data Sentence = Sentence
+    { fromText :: Text
+    , toText   :: Text
+    } deriving (Show, Eq, Generic)
+
+extractSentences :: [Int] -> Text -> [Text]
+extractSentences []     txt = [txt]
+extractSentences (n:ns) txt = headTxt : extractSentences ns tailTxt
+    where (headTxt, tailTxt) = T.splitAt n txt
+
+-- | Take the original texts and the ArrayResponse object, and apply the sentence break
+--   information to pair each sentence in the request to the translated text.
+mkSentences :: [Text] -> ArrayResponse -> [[Sentence]]
+mkSentences origTxts (ArrayResponse tItems) =
+    uncurry formSentenceSet <$> zip origTxts tItems
+    where
+        formSentenceSet :: Text -> TransItem -> [Sentence]
+        formSentenceSet origTxt (TransItem transTxt origBreaks transBreaks) =
+            filter notBlank $ zipWith Sentence
+                (extractSentences origBreaks  origTxt)
+                (extractSentences transBreaks transTxt)
+
+        notBlank :: Sentence -> Bool
+        notBlank (Sentence orig trans) = not . T.all isSpace $ orig <> trans
+
+
+
+-- | Retrieve a token, via 'issueToken', and save it together with a timestamp.
+issueAuthIO :: Manager -> SubscriptionKey -> IO (Either TranslatorException AuthData)
+issueAuthIO man = runExceptT . issueAuth man
+
+-- | Retrieve an 'AuthData' token and start up an HTTPS manager.
+initTransDataIO :: SubscriptionKey -> IO (Either TranslatorException TransData)
+initTransDataIO = runExceptT . initTransData
+
+-- | If a token contained in a 'TransData' is expired or about to expire, refresh it.
+checkAuthIO :: TransData -> IO (Either TranslatorException AuthData)
+checkAuthIO = runExceptT . checkAuth
+
+-- | Translate text.
+translateIO :: TransData -> Maybe Language -> Language -> Text
+            -> IO (Either TranslatorException Text)
+translateIO tdata from to = runExceptT . translate tdata from to
+
+-- | Translate a text array.
+translateArrayIO :: TransData -> Language -> Language -> [Text]
+                 -> IO (Either TranslatorException ArrayResponse)
+translateArrayIO tdata from to = runExceptT . translateArray tdata from to
+
+-- | Translate a text array, and just return the list of texts.
+translateArrayTextIO :: TransData -> Language -> Language -> [Text]
+                     -> IO (Either TranslatorException [Text])
+translateArrayTextIO tdata from to =
+    runExceptT . translateArrayText tdata from to
+
+-- | Translate a text array, and split all the texts into constituent sentences
+--   paired with the originals.
+translateArraySentencesIO :: TransData -> Language -> Language -> [Text]
+                          -> IO (Either TranslatorException [[Sentence]])
+translateArraySentencesIO tdata from to =
+    runExceptT . translateArraySentences tdata from to
diff --git a/src/Microsoft/Translator/API.hs b/src/Microsoft/Translator/API.hs
new file mode 100644
--- /dev/null
+++ b/src/Microsoft/Translator/API.hs
@@ -0,0 +1,173 @@
+{-# LANGUAGE DataKinds             #-}
+{-# LANGUAGE DeriveGeneric         #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE OverloadedStrings     #-}
+{-# LANGUAGE TypeApplications      #-}
+{-# LANGUAGE TypeOperators         #-}
+{-# LANGUAGE ViewPatterns          #-}
+
+module Microsoft.Translator.API (
+
+      basicTranslate
+    , basicTranslateArray
+    , TranslatorException
+    , Language (..)
+    , ArrayRequest (..)
+    , ArrayResponse (..)
+    , TransItem (..)
+
+) where
+
+import           Microsoft.Translator.API.Auth
+import           Microsoft.Translator.Exception
+import           Microsoft.Translator.Language
+
+import           Data.Bifunctor
+import           Data.ByteString.Lazy (fromStrict, toStrict)
+import           Data.Monoid
+import           Data.Proxy
+import           Data.Text            as T (Text, pack, unlines)
+import           Data.Text.Encoding   (decodeUtf8', encodeUtf8)
+import           Data.Typeable
+import           GHC.Generics         (Generic)
+import           Network.HTTP.Client  hiding (Proxy)
+import qualified Network.HTTP.Media   as M
+import           Safe                 (headMay, readMay)
+import           Servant.API
+import           Servant.Client
+import           Text.XML.Light.Input
+import           Text.XML.Light.Proc
+import           Text.XML.Light.Types
+
+
+baseUrl :: BaseUrl
+baseUrl = BaseUrl Https "api.microsofttranslator.com" 443 "/V2/Http.svc"
+
+-- | MS Microsoft.Translator API
+--   http://docs.microsofttranslator.com/text-translate.html#!/default/get_Translate
+type API =
+    "Translate"
+        :> Header "authorization" AuthToken
+        :> QueryParam "text"  Text
+        :> QueryParam "from"  Language
+        :> QueryParam "to"    Language
+        :> Get '[XML] TransText
+    :<|> "TranslateArray"
+        :> Header "authorization" AuthToken
+        :> ReqBody '[XML] ArrayRequest
+        :> Post    '[XML] ArrayResponse
+
+
+newtype TransText = TransText { getTransText :: Text }
+    deriving Generic
+
+data ArrayRequest = ArrayRequest
+    { fromLang :: Language
+    , toLang   :: Language
+    , texts    :: [Text]
+    }
+
+newtype ArrayResponse = ArrayResponse
+    { getArrayResponse :: [TransItem] }
+    deriving (Show,Generic)
+
+data TransItem = TransItem
+    { transText        :: Text
+    , originalBreaks   :: [Int]
+    , translatedBreaks :: [Int]
+    } deriving (Show, Generic)
+
+
+-- | JSON Web Token content type
+data XML
+    deriving Typeable
+
+instance Accept XML where
+    contentType _ = "application" M.// "xml" M./: ("charset", "utf-8")
+
+instance MimeUnrender XML TransText where
+    mimeUnrender _ bs = first show $ do
+        txt <- first InvalidUTF8 . decodeUtf8' $ toStrict bs
+        el <- maybe (Left $ InvalidXML txt) Right $ parseXMLDoc txt
+        maybe (Left $ UnexpectedXMLLayout el) Right $ extractTransResponse el
+
+extractTransResponse :: Element -> Maybe TransText
+extractTransResponse
+    = fmap (TransText . pack . cdData)
+    . headMay
+    . onlyText
+    . elContent
+
+encodeRequestXML :: ArrayRequest -> Text
+encodeRequestXML (ArrayRequest from to txts) = T.unlines $
+    [ "<TranslateArrayRequest>"
+    , "  <AppId />"
+    , "  <From>" <> toLangCode from <> "</From>"
+    , "  <Texts>"
+    ] <> fmap xmlString txts <>
+    [ "  </Texts>"
+    , "  <To>" <> toLangCode to <> "</To>"
+    , "</TranslateArrayRequest>"
+    ]
+
+    where
+        xmlString str =
+            "<string xmlns=\"http://schemas.microsoft.com/2003/10/Serialization/Arrays\">" <>
+            str <> "</string>"
+
+instance MimeRender XML ArrayRequest where
+    mimeRender _ = fromStrict . encodeUtf8 . encodeRequestXML
+
+instance MimeUnrender XML ArrayResponse where
+    mimeUnrender _ bs = first show $ do
+        txt <- first InvalidUTF8 . decodeUtf8' $ toStrict bs
+        el <- maybe (Left $ InvalidXML txt) Right $ parseXMLDoc txt
+        extractArrayResponse el
+
+extractArrayResponse :: Element -> Either TranslatorException ArrayResponse
+extractArrayResponse
+    = fmap ArrayResponse
+    . traverse extractItem
+    . onlyElems
+    . elContent
+
+extractItem :: Element -> Either TranslatorException TransItem
+extractItem el@(onlyElems . elContent -> l) =
+    maybe (Left $ UnexpectedXMLLayout el) Right $ do
+        txtElem <- headMay [ x | x <- l, qName (elName x) == "TranslatedText" ]
+        origSep <- extractBreaks =<<
+            headMay [ x | x <- l, qName (elName x) == "OriginalTextSentenceLengths" ]
+        transSep <- extractBreaks =<<
+            headMay [ x | x <- l, qName (elName x) == "TranslatedTextSentenceLengths" ]
+        cd <- headMay . onlyText  $ elContent txtElem
+        Just $ TransItem (pack $ cdData cd) origSep transSep
+
+extractBreaks :: Element -> Maybe [Int]
+extractBreaks
+    = traverse readMay
+    . fmap cdData
+    . onlyText
+    . concatMap elContent
+    . onlyElems
+    . elContent
+
+
+transClient :: Maybe AuthToken -> Maybe Text -> Maybe Language -> Maybe Language -> ClientM TransText
+arrayClient :: Maybe AuthToken -> ArrayRequest -> ClientM ArrayResponse
+transClient :<|> arrayClient = client (Proxy @ API)
+
+basicTranslate :: Manager -> AuthToken -> Maybe Language -> Language -> Text
+               -> IO (Either TranslatorException Text)
+basicTranslate man tok from to txt =
+    bimap APIException getTransText <$>
+        runClientM
+            (transClient (Just tok) (Just txt) from (Just to))
+            (ClientEnv man baseUrl)
+
+basicTranslateArray :: Manager -> AuthToken -> Language -> Language -> [Text]
+                    -> IO (Either TranslatorException ArrayResponse)
+basicTranslateArray man tok from to txts =
+    bimap APIException id <$>
+        runClientM
+            (arrayClient (Just tok) (ArrayRequest from to txts))
+            (ClientEnv man baseUrl)
diff --git a/src/Microsoft/Translator/API/Auth.hs b/src/Microsoft/Translator/API/Auth.hs
new file mode 100644
--- /dev/null
+++ b/src/Microsoft/Translator/API/Auth.hs
@@ -0,0 +1,77 @@
+{-# LANGUAGE DataKinds                  #-}
+{-# LANGUAGE DeriveDataTypeable         #-}
+{-# LANGUAGE DeriveGeneric              #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE MultiParamTypeClasses      #-}
+{-# LANGUAGE OverloadedStrings          #-}
+{-# LANGUAGE TypeApplications           #-}
+{-# LANGUAGE TypeOperators              #-}
+
+module Microsoft.Translator.API.Auth (
+
+      SubscriptionKey (..)
+    , AuthToken
+    , TranslatorException
+    , issueToken
+
+) where
+
+import           Microsoft.Translator.Exception
+
+import           Control.Arrow        (left)
+import           Data.Bifunctor
+import           Data.ByteString.Lazy (toStrict)
+import           Data.Monoid
+import           Data.String
+import           Data.Text            (Text)
+import           Data.Text.Encoding   (decodeUtf8')
+import           Data.Typeable
+import           GHC.Generics         (Generic)
+import           Network.HTTP.Client  hiding (Proxy)
+import qualified Network.HTTP.Media   as M
+import           Servant.API
+import           Servant.Client
+
+
+authUrl :: BaseUrl
+authUrl = BaseUrl Https "api.cognitive.microsoft.com" 443 "/sts/v1.0"
+
+-- | MS Microsoft.Translator token service API
+--   http://docs.microsofttranslator.com/oauth-token.html
+type AuthAPI =
+    "issueToken"
+        :> QueryParam "Subscription-Key" SubscriptionKey
+        :> Post '[JWT] AuthToken
+
+-- | A key to your subscription to the service. Used to retrieve an 'AuthToken'.
+newtype SubscriptionKey
+    = SubKey Text
+    deriving (Show, ToHttpApiData, IsString)
+
+-- | The JSON Web Token issued by MS Microsoft.Translator token service. Consists of wrapped text.
+--   Valid for ten minutes.
+newtype AuthToken
+    = AuthToken Text
+    deriving (Show, Generic)
+
+-- | JSON Web Token content type
+data JWT
+    deriving Typeable
+
+instance Accept JWT where
+    contentType _ = "application" M.// "jwt" M./: ("charset", "us-ascii")
+
+instance MimeUnrender JWT AuthToken where
+    mimeUnrender _ = fmap AuthToken . left show . decodeUtf8' . toStrict
+
+instance ToHttpApiData AuthToken where
+    toUrlPiece (AuthToken txt) = "Bearer " <> txt
+
+
+authClient :: Maybe SubscriptionKey -> ClientM AuthToken
+authClient = client (Proxy @ AuthAPI)
+
+-- | Retrieve a token from the API. It will be valid for 10 minutes.
+issueToken :: Manager -> SubscriptionKey -> IO (Either TranslatorException AuthToken)
+issueToken man key = first APIException <$>
+    runClientM (authClient $ Just key) (ClientEnv man authUrl)
diff --git a/src/Microsoft/Translator/Exception.hs b/src/Microsoft/Translator/Exception.hs
new file mode 100644
--- /dev/null
+++ b/src/Microsoft/Translator/Exception.hs
@@ -0,0 +1,21 @@
+{-# LANGUAGE DeriveDataTypeable #-}
+
+module Microsoft.Translator.Exception where
+
+import           Control.Exception
+import           Data.Text
+import           Data.Text.Encoding.Error
+import           Data.Typeable
+import           Servant.Client
+import           Text.XML.Light.Types
+
+
+data TranslatorException
+    = APIException ServantError
+    | InvalidXML Text
+    | UnexpectedXMLLayout Element
+    | InvalidUTF8 UnicodeException
+    | MissingSubscriptionKey
+    deriving (Show, Typeable)
+
+instance Exception TranslatorException
diff --git a/src/Microsoft/Translator/Language.hs b/src/Microsoft/Translator/Language.hs
new file mode 100644
--- /dev/null
+++ b/src/Microsoft/Translator/Language.hs
@@ -0,0 +1,122 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+module Microsoft.Translator.Language where
+
+import           Data.Text
+import           Web.HttpApiData
+
+
+-- | Languages supported by MS Microsoft.Translator
+data Language
+    = Afrikaans
+    | Arabic
+    | Bosnian
+    | Bulgarian
+    | Catalan
+    | ChineseSimplified
+    | ChineseTraditional
+    | Croatian
+    | Czech
+    | Danish
+    | Dutch
+    | English
+    | Estonian
+    | Finnish
+    | French
+    | German
+    | Greek
+    | HaitianCreole
+    | Hebrew
+    | Hindi
+    | HmongDaw
+    | Hungarian
+    | Indonesian
+    | Italian
+    | Japanese
+    | Kiswahili
+    | Klingon
+    | KlingonPIqaD
+    | Korean
+    | Latvian
+    | Lithuanian
+    | Malay
+    | Maltese
+    | Norwegian
+    | Persian
+    | Polish
+    | Portuguese
+    | QueretaroOtomi
+    | Romanian
+    | Russian
+    | SerbianCyrillic
+    | SerbianLatin
+    | Slovak
+    | Slovenian
+    | Spanish
+    | Swedish
+    | Thai
+    | Turkish
+    | Ukrainian
+    | Urdu
+    | Vietnamese
+    | Welsh
+    | YucatecMaya
+
+
+toLangCode :: Language -> Text
+toLangCode Afrikaans          = "af"
+toLangCode Arabic             = "ar"
+toLangCode Bosnian            = "bs-Latn"
+toLangCode Bulgarian          = "bg"
+toLangCode Catalan            = "ca"
+toLangCode ChineseSimplified  = "zh-CHS"
+toLangCode ChineseTraditional = "zh-CHT"
+toLangCode Croatian           = "hr"
+toLangCode Czech              = "cs"
+toLangCode Danish             = "da"
+toLangCode Dutch              = "nl"
+toLangCode English            = "en"
+toLangCode Estonian           = "et"
+toLangCode Finnish            = "fi"
+toLangCode French             = "fr"
+toLangCode German             = "de"
+toLangCode Greek              = "el"
+toLangCode HaitianCreole      = "ht"
+toLangCode Hebrew             = "he"
+toLangCode Hindi              = "hi"
+toLangCode HmongDaw           = "mww"
+toLangCode Hungarian          = "hu"
+toLangCode Indonesian         = "id"
+toLangCode Italian            = "it"
+toLangCode Japanese           = "ja"
+toLangCode Kiswahili          = "sw"
+toLangCode Klingon            = "tlh"
+toLangCode KlingonPIqaD       = "tlh-Qaak"
+toLangCode Korean             = "ko"
+toLangCode Latvian            = "lv"
+toLangCode Lithuanian         = "lt"
+toLangCode Malay              = "ms"
+toLangCode Maltese            = "mt"
+toLangCode Norwegian          = "no"
+toLangCode Persian            = "fa"
+toLangCode Polish             = "pl"
+toLangCode Portuguese         = "pt"
+toLangCode QueretaroOtomi     = "otq"
+toLangCode Romanian           = "ro"
+toLangCode Russian            = "ru"
+toLangCode SerbianCyrillic    = "sr-Cyrl"
+toLangCode SerbianLatin       = "sr-Latn"
+toLangCode Slovak             = "sk"
+toLangCode Slovenian          = "sl"
+toLangCode Spanish            = "es"
+toLangCode Swedish            = "sv"
+toLangCode Thai               = "th"
+toLangCode Turkish            = "tr"
+toLangCode Ukrainian          = "uk"
+toLangCode Urdu               = "ur"
+toLangCode Vietnamese         = "vi"
+toLangCode Welsh              = "cy"
+toLangCode YucatecMaya        = "yua"
+
+instance ToHttpApiData Language where
+    toUrlPiece = toLangCode
diff --git a/src/Microsoft/TranslatorExample.hs b/src/Microsoft/TranslatorExample.hs
new file mode 100644
--- /dev/null
+++ b/src/Microsoft/TranslatorExample.hs
@@ -0,0 +1,20 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+module Microsoft.TranslatorExample where
+
+import           Microsoft.Translator
+
+import           Control.Monad.Except
+import qualified Data.Text.IO as T
+
+
+main :: IO ()
+main = do
+    -- set your subscription key in the TRANSLATOR_SUBSCRIPTION_KEY environment var
+    Right transData <- runExceptT (lookupSubKey >>= initTransData)
+    forever $ do
+        T.putStr "> "
+        str <- T.getLine
+        Right txt <- translateIO transData Nothing ChineseTraditional str
+        T.putStrLn txt
+        T.putStrLn ""
