diff --git a/acceptance_test.go b/acceptance_test.go index 9e3393d..ce932e5 100644 --- a/acceptance_test.go +++ b/acceptance_test.go @@ -54,7 +54,7 @@ func microsoftTranslator(t *testing.T) translator.Translator { } func testTranslate(t *testing.T, translator translator.Translator) { - translation, err := translator.Translate("Hello World!", "en", "de") + translation, err := translator.Translate("Hello World!", "en", "de", "3.0") if err != nil { t.Errorf("Unexpected error: %s", err.Error()) @@ -72,7 +72,7 @@ func testTranslate(t *testing.T, translator translator.Translator) { } func testDetect(t *testing.T, translator translator.Translator) { - languageCode, err := translator.Detect("¿cómo está?") + languageCode, err := translator.Detect("¿cómo está?", "3.0") if err != nil { t.Errorf("Unexpected error: %s", err.Error()) diff --git a/example/main.go b/example/main.go index cb3a252..38053a0 100644 --- a/example/main.go +++ b/example/main.go @@ -29,7 +29,7 @@ func helloWorld(t translator.Translator) { translations := make([]<-chan string, len(languages)) for i, language := range languages { - translations[i] = translate(t, "Hello World!", "en", language) + translations[i] = translate(t, "Hello World!", "en", language, "") } for n := range mergeChannels(translations) { @@ -39,10 +39,10 @@ func helloWorld(t translator.Translator) { // Starts a go routine to translate text for a particular language. Returns a channel that will be // used to send either the translation or an error string if something went wrong. -func translate(t translator.Translator, text, from string, to translator.Language) <-chan string { +func translate(t translator.Translator, text, from string, to translator.Language, version string) <-chan string { out := make(chan string) go func() { - translation, err := t.Translate(text, from, to.Code) + translation, err := t.Translate(text, from, to.Code, version) if err != nil { out <- fmt.Sprintf("Error during translation for %s: %s", to.Name, err.Error()) } else { diff --git a/google/api.go b/google/api.go index efbe3cc..3eb5315 100644 --- a/google/api.go +++ b/google/api.go @@ -22,10 +22,10 @@ func (a *api) Languages() ([]translator.Language, error) { return a.lp.languages() } -func (a *api) Detect(text string) (string, error) { +func (a *api) Detect(text, version string) (string, error) { return a.lp.detect(text) } -func (a *api) Translate(text, from, to string) (string, error) { +func (a *api) Translate(text, from, to, version string) (string, error) { return a.tp.translate(text, from, to) } diff --git a/google/api_test.go b/google/api_test.go index 1509eae..7f08f30 100644 --- a/google/api_test.go +++ b/google/api_test.go @@ -4,21 +4,21 @@ import "github.com/st3v/translator" type mockLanguageProvider struct { languagesFunc func() ([]translator.Language, error) - detectFunc func(text string) (string, error) + detectFunc func(text, version string) (string, error) } func (m *mockLanguageProvider) languages() ([]translator.Language, error) { return m.languagesFunc() } -func (m *mockLanguageProvider) detect(text string) (string, error) { - return m.detectFunc(text) +func (m *mockLanguageProvider) detect(text, version string) (string, error) { + return m.detectFunc(text, version) } type mockTranslationProvider struct { - translateFunc func(text, from, to string) (string, error) + translateFunc func(text, from, to, version string) (string, error) } -func (m *mockTranslationProvider) translate(text, from, to string) (string, error) { - return m.translateFunc(text, from, to) +func (m *mockTranslationProvider) translate(text, from, to, version string) (string, error) { + return m.translateFunc(text, from, to, version) } diff --git a/microsoft/api.go b/microsoft/api.go index 8dc1879..9a98c93 100644 --- a/microsoft/api.go +++ b/microsoft/api.go @@ -25,14 +25,14 @@ func NewTranslator(subscriptionKey string) translator.Translator { } } -func (a *api) Translate(text, from, to string) (string, error) { - return a.translationProvider.Translate(text, from, to) +func (a *api) Translate(text, from, to, version string) (string, error) { + return a.translationProvider.Translate(text, from, to, version) } func (a *api) Languages() ([]translator.Language, error) { return a.languageCatalog.Languages() } -func (a *api) Detect(text string) (string, error) { - return a.translationProvider.Detect(text) +func (a *api) Detect(text, version string) (string, error) { + return a.translationProvider.Detect(text, version) } diff --git a/microsoft/api_test.go b/microsoft/api_test.go index 00a6113..7b630a7 100644 --- a/microsoft/api_test.go +++ b/microsoft/api_test.go @@ -11,12 +11,13 @@ func TestAPITranslate(t *testing.T) { expectedTranslation := "My English is under all pig." from := "de" to := "en" + version := "3.0" api := &api{ translationProvider: newMockTranslationProvider(original, from, to, expectedTranslation, t), } - actualTranslation, err := api.Translate(original, from, to) + actualTranslation, err := api.Translate(original, from, to, version) if err != nil { t.Errorf("Unexpected error: %s", err.Error()) } @@ -72,12 +73,13 @@ func TestAPIDetect(t *testing.T) { text := "Mein Englisch ist unter aller Sau." expectedLanguage := "de" from := "de" + version := "3.0" api := &api{ translationProvider: newMockTranslationProvider(text, from, "", "", t), } - actualLanguage, err := api.Detect(text) + actualLanguage, err := api.Detect(text, version) if err != nil { t.Errorf("Unexpected error: %s", err.Error()) } diff --git a/microsoft/router.go b/microsoft/router.go index 97aef28..61f7050 100644 --- a/microsoft/router.go +++ b/microsoft/router.go @@ -2,11 +2,12 @@ package microsoft const ( authURL = "https://api.cognitive.microsoft.com/sts/v1.0/issueToken" - serviceURL = "https://api.microsofttranslator.com/v2/Http.svc/" + serviceURL = "https://api.cognitive.microsofttranslator.com/" translationURL = serviceURL + "Translate" detectURL = serviceURL + "Detect" - languageNamesURL = serviceURL + "GetLanguageNames" - languageCodesURL = serviceURL + "GetLanguagesForTranslate" + languageNamesURL = serviceURL + "Languages" + languageCodesURL = serviceURL + "Languages" + apiVersion = "3.0" ) // The Router provides necessary URLs to communicate with @@ -17,6 +18,7 @@ type Router interface { DetectURL() string LanguageNamesURL() string LanguageCodesURL() string + ApiVersion() string } type router struct{} @@ -44,3 +46,7 @@ func (r *router) LanguageNamesURL() string { func (r *router) LanguageCodesURL() string { return languageCodesURL } + +func (r *router) ApiVersion() string { + return apiVersion +} diff --git a/microsoft/router_test.go b/microsoft/router_test.go index 3aa7b0d..f8f1b89 100644 --- a/microsoft/router_test.go +++ b/microsoft/router_test.go @@ -17,7 +17,7 @@ func TestRouterAuthURL(t *testing.T) { func TestRouterTranslationURL(t *testing.T) { router := newRouter() - expectedURL := "https://api.microsofttranslator.com/v2/Http.svc/Translate" + expectedURL := "https://api.cognitive.microsofttranslator.com/Translate" actualURL := router.TranslationURL() @@ -29,7 +29,7 @@ func TestRouterTranslationURL(t *testing.T) { func TestRouterLanguageNamesURL(t *testing.T) { router := newRouter() - expectedURL := "https://api.microsofttranslator.com/v2/Http.svc/GetLanguageNames" + expectedURL := "https://api.cognitive.microsofttranslator.com/Languages" actualURL := router.LanguageNamesURL() @@ -41,7 +41,7 @@ func TestRouterLanguageNamesURL(t *testing.T) { func TestRouterLanguageCodesURL(t *testing.T) { router := newRouter() - expectedURL := "https://api.microsofttranslator.com/v2/Http.svc/GetLanguagesForTranslate" + expectedURL := "https://api.cognitive.microsofttranslator.com/Languages" actualURL := router.LanguageCodesURL() @@ -66,6 +66,7 @@ type mockRouter struct { languageNamesURL string languageCodesURL string detectURL string + apiVersion string } func (m *mockRouter) AuthURL() string { @@ -87,3 +88,7 @@ func (m *mockRouter) LanguageCodesURL() string { func (m *mockRouter) DetectURL() string { return m.detectURL } + +func (m *mockRouter) ApiVersion() string { + return m.apiVersion +} diff --git a/microsoft/translation_provider.go b/microsoft/translation_provider.go index 418ace3..52df742 100644 --- a/microsoft/translation_provider.go +++ b/microsoft/translation_provider.go @@ -1,20 +1,21 @@ package microsoft import ( - "encoding/xml" + "bytes" + "encoding/json" + "errors" "fmt" - "io/ioutil" - "net/url" - "github.com/st3v/tracerr" "github.com/st3v/translator/http" + "io/ioutil" + "net/url" ) // The TranslationProvider communicates with Microsoft's // API to provide a translation for a given text. type TranslationProvider interface { - Translate(text, from, to string) (string, error) - Detect(text string) (string, error) + Translate(text, from, to, version string) (string, error) + Detect(text, version string) (string, error) } type translationProvider struct { @@ -22,6 +23,41 @@ type translationProvider struct { httpClient http.Client } +type Request []struct { + Text string `json:"Text"` +} + +type Translate []struct { + Translations []Translations `json:"translations"` +} +type Translations struct { + Text string `json:"text"` + To string `json:"to"` +} + +type Detect []struct { + Language string `json:"language"` + Score float64 `json:"score"` + IsTranslationSupported bool `json:"isTranslationSupported"` + IsTransliterationSupported bool `json:"isTransliterationSupported"` + Alternatives []Alternatives `json:"alternatives"` +} + +type Alternatives struct { + Language string `json:"language"` + Score float64 `json:"score"` + IsTranslationSupported bool `json:"isTranslationSupported"` + IsTransliterationSupported bool `json:"isTransliterationSupported"` +} + +type Errors struct { + Error Error `json:"error"` +} +type Error struct { + Code int `json:"code"` + Message string `json:"message"` +} + func newTranslationProvider(authenticator http.Authenticator, router Router) TranslationProvider { return &translationProvider{ router: router, @@ -29,56 +65,81 @@ func newTranslationProvider(authenticator http.Authenticator, router Router) Tra } } -func (p *translationProvider) Translate(text, from, to string) (string, error) { +func (p *translationProvider) Translate(text, from, to, version string) (string, error) { + apiVer := version + + if apiVer == "" { + apiVer = p.router.ApiVersion() + } + request := make(Request, 1) + request[0].Text = text + b, _ := json.Marshal(&request) uri := fmt.Sprintf( - "%s?text=%s&from=%s&to=%s", + "%s?api-version=%s&from=%s&to=%s&textType=html", p.router.TranslationURL(), - url.QueryEscape(text), + apiVer, url.QueryEscape(from), url.QueryEscape(to)) - response, err := p.httpClient.SendRequest("GET", uri, nil, "text/plain") + response, err := p.httpClient.SendRequest("POST", uri, bytes.NewBuffer(b), "application/json") if err != nil { return "", tracerr.Wrap(err) } body, err := ioutil.ReadAll(response.Body) + defer response.Body.Close() if err != nil { return "", tracerr.Wrap(err) } - translation := &xmlString{} - err = xml.Unmarshal(body, &translation) + errMsg := Errors{} + err = json.Unmarshal(body, &errMsg) + translation := Translate{} + if err != nil { - return "", tracerr.Wrap(err) + err = json.Unmarshal(body, &translation) + if err != nil { + return "", tracerr.Wrap(err) + } + } else { + return "", errors.New(errMsg.Error.Message) } - return translation.Value, nil + return translation[0].Translations[0].Text, nil } -func (p *translationProvider) Detect(text string) (string, error) { +func (p *translationProvider) Detect(text, version string) (string, error) { + apiVer := version + + if apiVer == "" { + apiVer = p.router.ApiVersion() + } + request := make(Request, 1) + request[0].Text = text + b, _ := json.Marshal(&request) uri := fmt.Sprintf( - "%s?text=%s", + "%s?api-version=%s", p.router.DetectURL(), - url.QueryEscape(text)) + apiVer) - response, err := p.httpClient.SendRequest("GET", uri, nil, "text/plain") + response, err := p.httpClient.SendRequest("POST", uri, bytes.NewBuffer(b), "application/json") if err != nil { return "", tracerr.Wrap(err) } body, err := ioutil.ReadAll(response.Body) + defer response.Body.Close() if err != nil { return "", tracerr.Wrap(err) } - detect := &xmlString{} - err = xml.Unmarshal(body, &detect) + detect := Detect{} + err = json.Unmarshal(body, &detect) if err != nil { return "", tracerr.Wrap(err) } - return detect.Value, nil + return detect[0].Language, nil } diff --git a/microsoft/translation_provider_test.go b/microsoft/translation_provider_test.go index 0d186a3..7243669 100644 --- a/microsoft/translation_provider_test.go +++ b/microsoft/translation_provider_test.go @@ -1,7 +1,7 @@ package microsoft import ( - "encoding/xml" + "encoding/json" "fmt" "net/http" "net/http/httptest" @@ -15,20 +15,17 @@ func TestTranslationProviderTranslate(t *testing.T) { expectedTranslation := "I only understand train station." expectedFrom := "de" expectedTo := "en" + expectedVersion := "3.0" server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.Method != "GET" { + if r.Method != "POST" { t.Fatalf("Unexpected request method: %s", r.Method) } - if r.Header.Get("Content-Type") != "text/plain" { + if r.Header.Get("Content-Type") != "application/json" { t.Fatalf("Unexpected content type in request header: %s", r.Header.Get("Content-Type")) } - if r.FormValue("text") != expectedOriginal { - t.Fatalf("Unexpected `text` param in request: %s", r.FormValue("text")) - } - if r.FormValue("to") != expectedTo { t.Fatalf("Unexpected `to` param in request: %s", r.FormValue("to")) } @@ -36,13 +33,18 @@ func TestTranslationProviderTranslate(t *testing.T) { if r.FormValue("from") != expectedFrom { t.Fatalf("Unexpected `from` param in request: %s", r.FormValue("from")) } - - response, err := xml.Marshal(newXMLString(expectedTranslation)) + var request interface{} + tr := []byte(`[{"detectedLanguage":{"language": "en","score": 1.0},"translations":[{"text":"I only understand train station.","to": "en"},{"text": "Salve, mondo!","to": "it"}]}]`) + err := json.Unmarshal(tr, &request) if err != nil { - t.Fatalf("Unexpected error marshalling xml repsonse: %s", err.Error()) + t.Fatalf("Unexpected error marshalling json response: %s", err.Error()) + } + response, err := json.Marshal(&request) + if err != nil { + t.Fatalf("Unexpected error marshalling json response: %s", err.Error()) } - w.Header().Set("Content-Type", "text/xml") + w.Header().Set("Content-Type", "application/json") fmt.Fprint(w, string(response)) return @@ -57,7 +59,7 @@ func TestTranslationProviderTranslate(t *testing.T) { httpClient: _http.NewAuthenticatedClient(), } - actualTranslation, err := translationProvider.Translate(expectedOriginal, expectedFrom, expectedTo) + actualTranslation, err := translationProvider.Translate(expectedOriginal, expectedFrom, expectedTo, expectedVersion) if err != nil { t.Fatalf("Unexpected error: %s", err.Error()) } @@ -70,26 +72,30 @@ func TestTranslationProviderTranslate(t *testing.T) { func TestTranslationProviderDetect(t *testing.T) { text := "Ich verstehe nur Bahnhof." expectedLanguage := "de" + version := "3.0" server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.Method != "GET" { + if r.Method != "POST" { t.Fatalf("Unexpected request method: %s", r.Method) } - if r.Header.Get("Content-Type") != "text/plain" { + if r.Header.Get("Content-Type") != "application/json" { t.Fatalf("Unexpected content type in request header: %s", r.Header.Get("Content-Type")) } - if r.FormValue("text") != text { - t.Fatalf("Unexpected `text` param in request: %s", r.FormValue("text")) + var request interface{} + tr := []byte(`[{"language":"de","score":1.0,"isTranslationSupported":true,"isTransliterationSupported":false,"alternatives":[{"language":"en","score":0.75,"isTranslationSupported":false,"isTransliterationSupported":false},{"language":"pl","score":0.75,"isTranslationSupported":true,"isTransliterationSupported":false}]}]`) + err := json.Unmarshal(tr, &request) + if err != nil { + t.Fatalf("Unexpected error marshalling json response: %s", err.Error()) } - response, err := xml.Marshal(newXMLString(expectedLanguage)) + response, err := json.Marshal(&request) if err != nil { - t.Fatalf("Unexpected error marshalling xml repsonse: %s", err.Error()) + t.Fatalf("Unexpected error marshalling json response: %s", err.Error()) } - w.Header().Set("Content-Type", "text/xml") + w.Header().Set("Content-Type", "application/json") fmt.Fprint(w, string(response)) return @@ -104,7 +110,7 @@ func TestTranslationProviderDetect(t *testing.T) { httpClient: _http.NewAuthenticatedClient(), } - actualLanguage, err := translationProvider.Detect(text) + actualLanguage, err := translationProvider.Detect(text, version) if err != nil { t.Fatalf("Unexpected error: %s", err.Error()) } @@ -132,7 +138,7 @@ type mockTranslationProvider struct { t *testing.T } -func (p *mockTranslationProvider) Translate(text, from, to string) (string, error) { +func (p *mockTranslationProvider) Translate(text, from, to, version string) (string, error) { if p.text != text { p.t.Fatalf("Unexpected text value: `%s`", text) } @@ -147,6 +153,6 @@ func (p *mockTranslationProvider) Translate(text, from, to string) (string, erro return p.translation, nil } -func (p *mockTranslationProvider) Detect(text string) (string, error) { +func (p *mockTranslationProvider) Detect(text, version string) (string, error) { return p.from, nil } diff --git a/translator.go b/translator.go index a09f8f6..d30c56b 100644 --- a/translator.go +++ b/translator.go @@ -16,9 +16,9 @@ type Translator interface { // Translate takes a string in a given language and returns its translation // to another language. Source and destination languages are specified by their // corresponding language codes. - Translate(text, from, to string) (string, error) + Translate(text, from, to, version string) (string, error) // Detect identifies the language of the given text and returns the // corresponding language code. - Detect(text string) (string, error) + Detect(text, version string) (string, error) } diff --git a/translator_test.go b/translator_test.go index 0189bfb..e080b45 100644 --- a/translator_test.go +++ b/translator_test.go @@ -5,7 +5,7 @@ import "testing" // Make sure nobody breaks the interface. func TestTranslatorInterface(t *testing.T) { var translator Translator = &testTranslator{} - translator.Translate("", "", "") + translator.Translate("", "", "", "") } type testTranslator struct{} @@ -14,10 +14,10 @@ func (t *testTranslator) Languages() ([]Language, error) { return nil, nil } -func (t *testTranslator) Translate(text, from, to string) (string, error) { +func (t *testTranslator) Translate(text, from, to, version string) (string, error) { return "", nil } -func (t *testTranslator) Detect(text string) (string, error) { +func (t *testTranslator) Detect(text, version string) (string, error) { return "", nil }