diff --git a/engines/reverso.go b/engines/reverso.go new file mode 100644 index 0000000..db1cebc --- /dev/null +++ b/engines/reverso.go @@ -0,0 +1,160 @@ +package engines + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" +) + +// Reverso is an engine that fetches data from https://reverso.net. +type Reverso struct{} + +func (_ *Reverso) InternalName() string { return "reverso" } + +func (_ *Reverso) DisplayName() string { return "Reverso" } + +var reversoLangs = []Language{ + {Name: "Arabic", Code: "ara"}, + {Name: "Chinese (Simplified)", Code: "chi"}, // marketed as just "Chinese" + {Name: "Czech", Code: "cze"}, + {Name: "Danish", Code: "dan"}, + {Name: "Dutch", Code: "dut"}, + {Name: "English", Code: "eng"}, + {Name: "French", Code: "fra"}, + {Name: "German", Code: "ger"}, + {Name: "Hebrew", Code: "heb"}, + {Name: "Hindi", Code: "hin"}, + {Name: "Hungarian", Code: "hun"}, + {Name: "Italian", Code: "ita"}, + {Name: "Japanese", Code: "jpn"}, + {Name: "Korean", Code: "kor"}, + {Name: "Persian", Code: "per"}, + {Name: "Polish", Code: "pol"}, + {Name: "Portuguese", Code: "por"}, + {Name: "Romanian", Code: "rum"}, + {Name: "Russian", Code: "rus"}, + {Name: "Slovak", Code: "slo"}, + {Name: "Spanish", Code: "spa"}, + {Name: "Swedish", Code: "swe"}, + {Name: "Thai", Code: "tha"}, + {Name: "Turkish", Code: "tur"}, + {Name: "Ukrainian", Code: "ukr"}, +} + +func (_ *Reverso) SourceLanguages() ([]Language, error) { return reversoLangs, nil } + +func (_ *Reverso) TargetLanguages() ([]Language, error) { return reversoLangs, nil } + +func (_ *Reverso) SupportsAutodetect() bool { return true } + +type reversoAPIResponse struct { + LanguageDetection struct { + DetectedLanguage string `json:"detectedLanguage"` + } `json:"languageDetection"` + Translation []string `json:"translation"` +} + +func (e *Reverso) callAPI(text string, from, to Language) (reversoAPIResponse, error) { + // `contextResults` must be false for language detection + formData := map[string]interface{}{ + "format": "text", + "from": from.Code, + "to": to.Code, + "input": text, + "options": map[string]interface{}{ + "sentenceSplitter": false, + "origin": "translation.web", + "contextResults": false, + "languageDetection": true, + }, + } + + formDataJSON, err := json.Marshal(formData) + + if err != nil { + return reversoAPIResponse{}, err + } + + request, err := http.NewRequest("POST", "https://api.reverso.net/translate/v1/translation", bytes.NewBuffer(formDataJSON)) + + if err != nil { + return reversoAPIResponse{}, err + } + + request.Header.Set("Content-Type", "application/json") + // Returns 403 with empty or no user agent. + request.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; rv:110.0) Gecko/20100101 Firefox/110.0") + + client := &http.Client{} + + response, err := client.Do(request) + + if err != nil { + return reversoAPIResponse{}, err + } + + defer response.Body.Close() + + if response.StatusCode != 200 { + return reversoAPIResponse{}, fmt.Errorf("got status code %d from Reverso API", response.StatusCode) + } + + var result reversoAPIResponse + + if err := json.NewDecoder(response.Body).Decode(&result); err != nil { + return reversoAPIResponse{}, err + } + + return result, nil +} + +func (e *Reverso) DetectLanguage(text string) (Language, error) { + // Any language pair works here, does not affect result + r, err := e.callAPI(text, reversoLangs[0], reversoLangs[1]) + + if err != nil { + return Language{}, err + } + + langCode := r.LanguageDetection.DetectedLanguage + + for _, lang := range reversoLangs { + if lang.Code == langCode { + return lang, nil + } + } + + return Language{}, fmt.Errorf("language code \"%s\" is not in Reverso's language list", langCode) +} + +func (e *Reverso) Translate(text string, from, to Language) (TranslationResult, error) { + if from.Code == "auto" { + from_, err := e.DetectLanguage(text) + + if err != nil { + return TranslationResult{}, err + } + + from = from_ + } + + var translation string + + if from == to { + translation = text + } else { + r, err := e.callAPI(text, from, to) + + if err != nil { + return TranslationResult{}, err + } + + translation = r.Translation[0] + } + + return TranslationResult{ + SourceLanguage: from, + TranslatedText: translation, + }, nil +}