fix: unable to translate (switch to DeepL iOS)

This commit is contained in:
Vincent Yang 2025-03-01 04:24:53 -05:00
parent 26a9003b13
commit 2400139a8d
No known key found for this signature in database
GPG Key ID: 281CF11A76870183
2 changed files with 91 additions and 123 deletions

View File

@ -2,7 +2,7 @@
* @Author: Vincent Young * @Author: Vincent Young
* @Date: 2024-09-16 11:59:24 * @Date: 2024-09-16 11:59:24
* @LastEditors: Vincent Yang * @LastEditors: Vincent Yang
* @LastEditTime: 2025-01-20 17:09:59 * @LastEditTime: 2025-03-01 04:23:49
* @FilePath: /DeepLX/translate/translate.go * @FilePath: /DeepLX/translate/translate.go
* @Telegram: https://t.me/missuo * @Telegram: https://t.me/missuo
* @GitHub: https://github.com/missuo * @GitHub: https://github.com/missuo
@ -14,6 +14,8 @@ package translate
import ( import (
"bytes" "bytes"
"compress/flate"
"compress/gzip"
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
@ -27,13 +29,9 @@ import (
"github.com/tidwall/gjson" "github.com/tidwall/gjson"
) )
const baseURL = "https://www2.deepl.com/jsonrpc"
// makeRequest makes an HTTP request to DeepL API // makeRequest makes an HTTP request to DeepL API
func makeRequest(postData *PostData, proxyURL string, dlSession string) (gjson.Result, error) {
func makeRequest(postData *PostData, urlMethod string, proxyURL string, dlSession string) (gjson.Result, error) { urlFull := "https://www2.deepl.com/jsonrpc"
urlFull := fmt.Sprintf("%s?client=chrome-extension,1.28.0&method=%s", baseURL, urlMethod)
postStr := formatPostString(postData) postStr := formatPostString(postData)
// Create a new req client // Create a new req client
@ -41,21 +39,18 @@ func makeRequest(postData *PostData, urlMethod string, proxyURL string, dlSessio
// Set headers // Set headers
headers := http.Header{ headers := http.Header{
"Accept": []string{"*/*"},
"Accept-Language": []string{"en-US,en;q=0.9,zh-CN;q=0.8,zh-TW;q=0.7,zh-HK;q=0.6,zh;q=0.5"},
"Authorization": []string{"None"},
"Cache-Control": []string{"no-cache"},
"Content-Type": []string{"application/json"}, "Content-Type": []string{"application/json"},
"DNT": []string{"1"}, "User-Agent": []string{"DeepL/1627620 CFNetwork/3826.500.62.2.1 Darwin/24.4.0"},
"Origin": []string{"chrome-extension://cofdbpoegempjloogbagkncekinflcnj"}, "Accept": []string{"*/*"},
"Pragma": []string{"no-cache"}, "X-App-Os-Name": []string{"iOS"},
"Priority": []string{"u=1, i"}, "X-App-Os-Version": []string{"18.4.0"},
"Accept-Language": []string{"en-US,en;q=0.9"},
"Accept-Encoding": []string{"gzip, deflate, br"}, // Keep this!
"X-App-Device": []string{"iPhone16,2"},
"Referer": []string{"https://www.deepl.com/"}, "Referer": []string{"https://www.deepl.com/"},
"Sec-Fetch-Dest": []string{"empty"}, "X-Product": []string{"translator"},
"Sec-Fetch-Mode": []string{"cors"}, "X-App-Build": []string{"1627620"},
"Sec-Fetch-Site": []string{"none"}, "X-App-Version": []string{"25.1"},
"Sec-GPC": []string{"1"},
"User-Agent": []string{"DeepLBrowserExtension/1.28.0 Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36"},
} }
if dlSession != "" { if dlSession != "" {
@ -83,40 +78,28 @@ func makeRequest(postData *PostData, urlMethod string, proxyURL string, dlSessio
} }
var bodyReader io.Reader var bodyReader io.Reader
if resp.Header.Get("Content-Encoding") == "br" { contentEncoding := resp.Header.Get("Content-Encoding")
switch contentEncoding {
case "br":
bodyReader = brotli.NewReader(resp.Body) bodyReader = brotli.NewReader(resp.Body)
} else { case "gzip":
bodyReader, err = gzip.NewReader(resp.Body) // Use gzip.NewReader
if err != nil {
return gjson.Result{}, fmt.Errorf("failed to create gzip reader: %w", err)
}
case "deflate": // Less common, but good to handle
bodyReader = flate.NewReader(resp.Body)
default:
bodyReader = resp.Body bodyReader = resp.Body
} }
body, err := io.ReadAll(bodyReader) body, err := io.ReadAll(bodyReader)
if err != nil { if err != nil {
return gjson.Result{}, err return gjson.Result{}, fmt.Errorf("failed to read response body: %w", err)
} }
return gjson.ParseBytes(body), nil return gjson.ParseBytes(body), nil
} }
// splitText splits the input text for translation
func splitText(text string, tagHandling bool, proxyURL string, dlSession string) (gjson.Result, error) {
postData := &PostData{
Jsonrpc: "2.0",
Method: "LMT_split_text",
ID: getRandomNumber(),
Params: Params{
CommonJobParams: CommonJobParams{
Mode: "translate",
},
Lang: Lang{
LangUserSelected: "auto",
},
Texts: []string{text},
TextType: map[bool]string{true: "richtext", false: "plaintext"}[tagHandling || isRichText(text)],
},
}
return makeRequest(postData, "LMT_split_text", proxyURL, dlSession)
}
// TranslateByDeepLX performs translation using DeepL API // TranslateByDeepLX performs translation using DeepL API
func TranslateByDeepLX(sourceLang, targetLang, text string, tagHandling string, proxyURL string, dlSession string) (DeepLXTranslationResult, error) { func TranslateByDeepLX(sourceLang, targetLang, text string, tagHandling string, proxyURL string, dlSession string) (DeepLXTranslationResult, error) {
if text == "" { if text == "" {
@ -126,6 +109,10 @@ func TranslateByDeepLX(sourceLang, targetLang, text string, tagHandling string,
}, nil }, nil
} }
if tagHandling == "" {
tagHandling = "plaintext"
}
// Split text by newlines and store them for later reconstruction // Split text by newlines and store them for later reconstruction
textParts := strings.Split(text, "\n") textParts := strings.Split(text, "\n")
var translatedParts []string var translatedParts []string
@ -138,15 +125,6 @@ func TranslateByDeepLX(sourceLang, targetLang, text string, tagHandling string,
continue continue
} }
// Split text first
splitResult, err := splitText(part, tagHandling == "html" || tagHandling == "xml", proxyURL, dlSession)
if err != nil {
return DeepLXTranslationResult{
Code: http.StatusServiceUnavailable,
Message: err.Error(),
}, nil
}
// Get detected language if source language is auto // Get detected language if source language is auto
if sourceLang == "auto" || sourceLang == "" { if sourceLang == "auto" || sourceLang == "" {
sourceLang = strings.ToUpper(whatlanggo.DetectLang(part).Iso6391()) sourceLang = strings.ToUpper(whatlanggo.DetectLang(part).Iso6391())
@ -154,32 +132,18 @@ func TranslateByDeepLX(sourceLang, targetLang, text string, tagHandling string,
// Prepare jobs from split result // Prepare jobs from split result
var jobs []Job var jobs []Job
chunks := splitResult.Get("result.texts.0.chunks").Array()
for idx, chunk := range chunks {
sentence := chunk.Get("sentences.0")
// Handle context
contextBefore := []string{}
contextAfter := []string{}
if idx > 0 {
contextBefore = []string{chunks[idx-1].Get("sentences.0.text").String()}
}
if idx < len(chunks)-1 {
contextAfter = []string{chunks[idx+1].Get("sentences.0.text").String()}
}
jobs = append(jobs, Job{ jobs = append(jobs, Job{
Kind: "default", Kind: "default",
PreferredNumBeams: 4, PreferredNumBeams: 4,
RawEnContextBefore: contextBefore, RawEnContextBefore: []string{},
RawEnContextAfter: contextAfter, RawEnContextAfter: []string{},
Sentences: []Sentence{{ Sentences: []Sentence{{
Prefix: sentence.Get("prefix").String(), Prefix: "",
Text: sentence.Get("text").String(), Text: text,
ID: idx + 1, ID: 0,
}}, }},
}) })
}
hasRegionalVariant := false hasRegionalVariant := false
targetLangCode := targetLang targetLangCode := targetLang
@ -199,13 +163,18 @@ func TranslateByDeepLX(sourceLang, targetLang, text string, tagHandling string,
Params: Params{ Params: Params{
CommonJobParams: CommonJobParams{ CommonJobParams: CommonJobParams{
Mode: "translate", Mode: "translate",
Formality: "undefined",
TranscribeAs: "romanize",
AdvancedMode: false,
TextType: tagHandling,
WasSpoken: false,
}, },
Lang: Lang{ Lang: Lang{
SourceLangComputed: strings.ToUpper(sourceLang), SourceLangUserSelected: "auto",
TargetLang: strings.ToUpper(targetLangCode), TargetLang: strings.ToUpper(targetLangCode),
SourceLangComputed: strings.ToUpper(sourceLang),
}, },
Jobs: jobs, Jobs: jobs,
Priority: 1,
Timestamp: getTimeStamp(getICount(part)), Timestamp: getTimeStamp(getICount(part)),
}, },
} }
@ -218,21 +187,26 @@ func TranslateByDeepLX(sourceLang, targetLang, text string, tagHandling string,
Params: Params{ Params: Params{
CommonJobParams: CommonJobParams{ CommonJobParams: CommonJobParams{
Mode: "translate", Mode: "translate",
RegionalVariant: map[bool]string{true: targetLang, false: ""}[hasRegionalVariant], Formality: "undefined",
TranscribeAs: "romanize",
AdvancedMode: false,
TextType: tagHandling,
WasSpoken: false,
RegionalVariant: targetLang,
}, },
Lang: Lang{ Lang: Lang{
SourceLangComputed: strings.ToUpper(sourceLang), SourceLangUserSelected: "auto",
TargetLang: strings.ToUpper(targetLangCode), TargetLang: strings.ToUpper(targetLangCode),
SourceLangComputed: strings.ToUpper(sourceLang),
}, },
Jobs: jobs, Jobs: jobs,
Priority: 1,
Timestamp: getTimeStamp(getICount(part)), Timestamp: getTimeStamp(getICount(part)),
}, },
} }
} }
// Make translation request // Make translation request
result, err := makeRequest(postData, "LMT_handle_jobs", proxyURL, dlSession) result, err := makeRequest(postData, proxyURL, dlSession)
if err != nil { if err != nil {
return DeepLXTranslationResult{ return DeepLXTranslationResult{
Code: http.StatusServiceUnavailable, Code: http.StatusServiceUnavailable,

View File

@ -2,7 +2,7 @@
* @Author: Vincent Young * @Author: Vincent Young
* @Date: 2024-09-16 11:59:24 * @Date: 2024-09-16 11:59:24
* @LastEditors: Vincent Yang * @LastEditors: Vincent Yang
* @LastEditTime: 2024-11-01 23:18:56 * @LastEditTime: 2025-03-01 04:16:07
* @FilePath: /DeepLX/translate/types.go * @FilePath: /DeepLX/translate/types.go
* @Telegram: https://t.me/missuo * @Telegram: https://t.me/missuo
* @GitHub: https://github.com/missuo * @GitHub: https://github.com/missuo
@ -14,14 +14,19 @@ package translate
// Lang represents the language settings for translation // Lang represents the language settings for translation
type Lang struct { type Lang struct {
SourceLangComputed string `json:"source_lang_computed,omitempty"` SourceLangUserSelected string `json:"source_lang_user_selected"` // Can be "auto"
TargetLang string `json:"target_lang"` TargetLang string `json:"target_lang"`
LangUserSelected string `json:"lang_user_selected,omitempty"` SourceLangComputed string `json:"source_lang_computed,omitempty"`
} }
// CommonJobParams represents common parameters for translation jobs // CommonJobParams represents common parameters for translation jobs
type CommonJobParams struct { type CommonJobParams struct {
Formality string `json:"formality"` // Can be "undefined"
TranscribeAs string `json:"transcribe_as"`
Mode string `json:"mode"` Mode string `json:"mode"`
WasSpoken bool `json:"wasSpoken"`
AdvancedMode bool `json:"advancedMode"`
TextType string `json:"textType"`
RegionalVariant string `json:"regionalVariant,omitempty"` RegionalVariant string `json:"regionalVariant,omitempty"`
} }
@ -45,10 +50,7 @@ type Job struct {
type Params struct { type Params struct {
CommonJobParams CommonJobParams `json:"commonJobParams"` CommonJobParams CommonJobParams `json:"commonJobParams"`
Lang Lang `json:"lang"` Lang Lang `json:"lang"`
Texts []string `json:"texts,omitempty"` Jobs []Job `json:"jobs"`
TextType string `json:"textType,omitempty"`
Jobs []Job `json:"jobs,omitempty"`
Priority int `json:"priority,omitempty"`
Timestamp int64 `json:"timestamp"` Timestamp int64 `json:"timestamp"`
} }
@ -60,25 +62,6 @@ type PostData struct {
Params Params `json:"params"` Params Params `json:"params"`
} }
// SplitTextResponse represents the response from text splitting
type SplitTextResponse struct {
Jsonrpc string `json:"jsonrpc"`
ID int64 `json:"id"`
Result struct {
Lang struct {
Detected string `json:"detected"`
} `json:"lang"`
Texts []struct {
Chunks []struct {
Sentences []struct {
Prefix string `json:"prefix"`
Text string `json:"text"`
} `json:"sentences"`
} `json:"chunks"`
} `json:"texts"`
} `json:"result"`
}
// TranslationResponse represents the response from translation // TranslationResponse represents the response from translation
type TranslationResponse struct { type TranslationResponse struct {
Jsonrpc string `json:"jsonrpc"` Jsonrpc string `json:"jsonrpc"`
@ -86,23 +69,34 @@ type TranslationResponse struct {
Result struct { Result struct {
Translations []struct { Translations []struct {
Beams []struct { Beams []struct {
Sentences []struct { Sentences []SentenceResponse `json:"sentences"`
Text string `json:"text"` NumSymbols int `json:"num_symbols"`
} `json:"sentences"` RephraseVariant struct { // Added rephrase_variant
Name string `json:"name"`
} `json:"rephrase_variant"`
} `json:"beams"` } `json:"beams"`
Quality string `json:"quality"` // Added quality
} `json:"translations"` } `json:"translations"`
SourceLang string `json:"source_lang"`
TargetLang string `json:"target_lang"` TargetLang string `json:"target_lang"`
SourceLang string `json:"source_lang"`
SourceLangIsConfident bool `json:"source_lang_is_confident"`
DetectedLanguages map[string]interface{} `json:"detectedLanguages"` // Use interface{} for now
} `json:"result"` } `json:"result"`
} }
// SentenceResponse is a helper struct for the response sentences
type SentenceResponse struct {
Text string `json:"text"`
IDS []int `json:"ids"` // Added IDS
}
// DeepLXTranslationResult represents the final translation result // DeepLXTranslationResult represents the final translation result
type DeepLXTranslationResult struct { type DeepLXTranslationResult struct {
Code int `json:"code"` Code int `json:"code"`
ID int64 `json:"id"` ID int64 `json:"id"`
Message string `json:"message,omitempty"` Message string `json:"message,omitempty"`
Data string `json:"data"` Data string `json:"data"` // The primary translated text
Alternatives []string `json:"alternatives"` Alternatives []string `json:"alternatives"` // Other possible translations
SourceLang string `json:"source_lang"` SourceLang string `json:"source_lang"`
TargetLang string `json:"target_lang"` TargetLang string `json:"target_lang"`
Method string `json:"method"` Method string `json:"method"`