packages feed

microsoft-translator (empty) → 0.1.0.0

raw patch · 10 files changed

+739/−0 lines, 10 filesdep +basedep +bytestringdep +http-api-datasetup-changed

Dependencies added: base, bytestring, http-api-data, http-client, http-client-tls, http-media, mtl, safe, servant, servant-client, text, time, xml

Files

+ ChangeLog.md view
@@ -0,0 +1,5 @@+# Revision history for microsoft-translator++## 0.1.0.0  -- YYYY-mm-dd++* First version. Released on an unsuspecting world.
+ LICENSE view
@@ -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.
+ Setup.hs view
@@ -0,0 +1,2 @@+import           Distribution.Simple+main = defaultMain
+ microsoft-translator.cabal view
@@ -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
+ src/Microsoft/Translator.hs view
@@ -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
+ src/Microsoft/Translator/API.hs view
@@ -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)
+ src/Microsoft/Translator/API/Auth.hs view
@@ -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)
+ src/Microsoft/Translator/Exception.hs view
@@ -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
+ src/Microsoft/Translator/Language.hs view
@@ -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
+ src/Microsoft/TranslatorExample.hs view
@@ -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 ""