✨ Added context menu
This commit is contained in:
318
internal/common/translator/deepl_translator.go
Normal file
318
internal/common/translator/deepl_translator.go
Normal file
@@ -0,0 +1,318 @@
|
||||
// Package translator 提供文本翻译功能
|
||||
package translator
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/text/language"
|
||||
)
|
||||
|
||||
// DeeplTranslator DeepL翻译器结构体
|
||||
type DeeplTranslator struct {
|
||||
DeeplHost string // DeepL服务主机
|
||||
httpClient *http.Client // HTTP客户端
|
||||
Timeout time.Duration // 请求超时时间
|
||||
}
|
||||
|
||||
// 常量定义
|
||||
const (
|
||||
deeplDefaultTimeout = 30 * time.Second
|
||||
defaultDeeplHost = "www2.deepl.com" // 默认DeepL API主机
|
||||
deeplJsonRpcUrl = "https://www2.deepl.com/jsonrpc" // DeepL JSON-RPC API
|
||||
)
|
||||
|
||||
// 错误定义
|
||||
var (
|
||||
ErrDeeplNetworkError = errors.New("deepl translator network error")
|
||||
ErrDeeplUnsupportedLang = errors.New("deepl translator unsupported language")
|
||||
ErrDeeplResponseError = errors.New("deepl translator response error")
|
||||
)
|
||||
|
||||
// 语言映射
|
||||
var deeplLangMap = map[string]string{
|
||||
"auto": "auto",
|
||||
"de": "DE",
|
||||
"en": "EN",
|
||||
"es": "ES",
|
||||
"fr": "FR",
|
||||
"it": "IT",
|
||||
"ja": "JA",
|
||||
"ko": "KO",
|
||||
"nl": "NL",
|
||||
"pl": "PL",
|
||||
"pt": "PT",
|
||||
"ru": "RU",
|
||||
"zh": "ZH",
|
||||
"bg": "BG",
|
||||
"cs": "CS",
|
||||
"da": "DA",
|
||||
"el": "EL",
|
||||
"et": "ET",
|
||||
"fi": "FI",
|
||||
"hu": "HU",
|
||||
"lt": "LT",
|
||||
"lv": "LV",
|
||||
"ro": "RO",
|
||||
"sk": "SK",
|
||||
"sl": "SL",
|
||||
"sv": "SV",
|
||||
}
|
||||
|
||||
// 反向语言映射
|
||||
var deeplLangMapReverse = map[string]string{
|
||||
"auto": "auto",
|
||||
"DE": "de",
|
||||
"EN": "en",
|
||||
"ES": "es",
|
||||
"FR": "fr",
|
||||
"IT": "it",
|
||||
"JA": "ja",
|
||||
"KO": "ko",
|
||||
"NL": "nl",
|
||||
"PL": "pl",
|
||||
"PT": "pt",
|
||||
"RU": "ru",
|
||||
"ZH": "zh",
|
||||
"BG": "bg",
|
||||
"CS": "cs",
|
||||
"DA": "da",
|
||||
"EL": "el",
|
||||
"ET": "et",
|
||||
"FI": "fi",
|
||||
"HU": "hu",
|
||||
"LT": "lt",
|
||||
"LV": "lv",
|
||||
"RO": "ro",
|
||||
"SK": "sk",
|
||||
"SL": "sl",
|
||||
"SV": "sv",
|
||||
}
|
||||
|
||||
// DeeplRequest DeepL请求结构体
|
||||
type DeeplRequest struct {
|
||||
Jsonrpc string `json:"jsonrpc"`
|
||||
Method string `json:"method"`
|
||||
ID int64 `json:"id"`
|
||||
Params DeeplReqParams `json:"params"`
|
||||
}
|
||||
|
||||
// DeeplReqParams DeepL请求参数结构体
|
||||
type DeeplReqParams struct {
|
||||
Texts []DeeplText `json:"texts"`
|
||||
Splitting string `json:"splitting"`
|
||||
Lang DeeplLang `json:"lang"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
}
|
||||
|
||||
// DeeplText DeepL文本结构体
|
||||
type DeeplText struct {
|
||||
Text string `json:"text"`
|
||||
RequestAlternatives int `json:"requestAlternatives"`
|
||||
}
|
||||
|
||||
// DeeplLang DeepL语言结构体
|
||||
type DeeplLang struct {
|
||||
SourceLangUserSelected string `json:"source_lang_user_selected"`
|
||||
TargetLang string `json:"target_lang"`
|
||||
}
|
||||
|
||||
// DeeplResponse DeepL响应结构体
|
||||
type DeeplResponse struct {
|
||||
Jsonrpc string `json:"jsonrpc"`
|
||||
ID int64 `json:"id"`
|
||||
Result DeeplResult `json:"result"`
|
||||
}
|
||||
|
||||
// DeeplResult DeepL结果结构体
|
||||
type DeeplResult struct {
|
||||
Texts []DeeplResultText `json:"texts"`
|
||||
Lang string `json:"lang"`
|
||||
LangIsConfident bool `json:"lang_is_confident"`
|
||||
DetectedLanguages map[string]float64 `json:"detectedLanguages"`
|
||||
}
|
||||
|
||||
// DeeplResultText DeepL结果文本结构体
|
||||
type DeeplResultText struct {
|
||||
Text string `json:"text"`
|
||||
Alternatives []DeeplAlternative `json:"alternatives,omitempty"`
|
||||
}
|
||||
|
||||
// DeeplAlternative DeepL替代翻译结构体
|
||||
type DeeplAlternative struct {
|
||||
Text string `json:"text"`
|
||||
}
|
||||
|
||||
// NewDeeplTranslator 创建一个新的DeepL翻译器实例
|
||||
func NewDeeplTranslator() *DeeplTranslator {
|
||||
translator := &DeeplTranslator{
|
||||
DeeplHost: defaultDeeplHost,
|
||||
Timeout: deeplDefaultTimeout,
|
||||
httpClient: &http.Client{Timeout: deeplDefaultTimeout},
|
||||
}
|
||||
|
||||
return translator
|
||||
}
|
||||
|
||||
// SetTimeout 设置请求超时时间
|
||||
func (t *DeeplTranslator) SetTimeout(timeout time.Duration) {
|
||||
t.Timeout = timeout
|
||||
t.httpClient.Timeout = timeout
|
||||
}
|
||||
|
||||
// SetDeeplHost 设置DeepL主机
|
||||
func (t *DeeplTranslator) SetDeeplHost(host string) {
|
||||
t.DeeplHost = host
|
||||
}
|
||||
|
||||
// Translate 使用标准语言标签进行文本翻译
|
||||
func (t *DeeplTranslator) Translate(text string, from language.Tag, to language.Tag) (string, error) {
|
||||
return t.translate(text, from.String(), to.String())
|
||||
}
|
||||
|
||||
// TranslateWithParams 使用简单字符串参数进行文本翻译
|
||||
func (t *DeeplTranslator) TranslateWithParams(text string, params TranslationParams) (string, error) {
|
||||
tries := params.Tries
|
||||
if tries == 0 {
|
||||
tries = defaultNumberOfRetries
|
||||
}
|
||||
|
||||
var result string
|
||||
var lastError error
|
||||
|
||||
for i := 0; i < tries; i++ {
|
||||
if i > 0 && params.Delay > 0 {
|
||||
time.Sleep(params.Delay)
|
||||
}
|
||||
|
||||
result, lastError = t.translate(text, params.From, params.To)
|
||||
if lastError == nil {
|
||||
return result, nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", lastError
|
||||
}
|
||||
|
||||
// translate 执行实际翻译操作
|
||||
func (t *DeeplTranslator) translate(text, from, to string) (string, error) {
|
||||
// 转换语言代码为DeepL格式
|
||||
sourceLang, ok := deeplLangMap[strings.ToLower(from)]
|
||||
if !ok && from != "auto" {
|
||||
sourceLang = "auto"
|
||||
}
|
||||
|
||||
targetLang, ok := deeplLangMap[strings.ToLower(to)]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("%w: language '%s' not supported by DeepL", ErrDeeplUnsupportedLang, to)
|
||||
}
|
||||
|
||||
// 准备请求数据
|
||||
id := getRandomNumber()
|
||||
iCount := getICount(text)
|
||||
timestamp := getTimeStamp(iCount)
|
||||
|
||||
// 构建请求体
|
||||
reqParams := DeeplReqParams{
|
||||
Texts: []DeeplText{
|
||||
{
|
||||
Text: text,
|
||||
RequestAlternatives: 3,
|
||||
},
|
||||
},
|
||||
Splitting: "newlines",
|
||||
Lang: DeeplLang{
|
||||
SourceLangUserSelected: sourceLang,
|
||||
TargetLang: targetLang,
|
||||
},
|
||||
Timestamp: timestamp,
|
||||
}
|
||||
|
||||
request := DeeplRequest{
|
||||
Jsonrpc: "2.0",
|
||||
Method: "LMT_handle_texts",
|
||||
ID: id,
|
||||
Params: reqParams,
|
||||
}
|
||||
|
||||
// 序列化请求
|
||||
jsonData, err := json.Marshal(request)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to marshal request: %w", err)
|
||||
}
|
||||
|
||||
// 特殊处理method字段格式
|
||||
postStr := string(jsonData)
|
||||
if (id+5)%29 == 0 || (id+3)%13 == 0 {
|
||||
postStr = strings.Replace(postStr, `"method":"`, `"method" : "`, 1)
|
||||
} else {
|
||||
postStr = strings.Replace(postStr, `"method":"`, `"method": "`, 1)
|
||||
}
|
||||
|
||||
// 发送请求
|
||||
req, err := http.NewRequest("POST", deeplJsonRpcUrl, bytes.NewBuffer([]byte(postStr)))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to create request: %w", err)
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36")
|
||||
|
||||
resp, err := t.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("%w: %v", ErrDeeplNetworkError, err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return "", fmt.Errorf("%w: status code %d", ErrDeeplNetworkError, resp.StatusCode)
|
||||
}
|
||||
|
||||
// 读取响应
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to read response: %w", err)
|
||||
}
|
||||
|
||||
// 解析响应
|
||||
var deeplResp DeeplResponse
|
||||
err = json.Unmarshal(body, &deeplResp)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("%w: %v", ErrDeeplResponseError, err)
|
||||
}
|
||||
|
||||
// 检查是否有有效的结果
|
||||
if len(deeplResp.Result.Texts) == 0 {
|
||||
return "", fmt.Errorf("%w: no translation result", ErrDeeplResponseError)
|
||||
}
|
||||
|
||||
// 返回翻译结果
|
||||
return deeplResp.Result.Texts[0].Text, nil
|
||||
}
|
||||
|
||||
// getICount 获取文本中'i'字符的数量
|
||||
func getICount(text string) int {
|
||||
return strings.Count(text, "i")
|
||||
}
|
||||
|
||||
// getRandomNumber 生成随机数
|
||||
func getRandomNumber() int64 {
|
||||
return int64(rand.Intn(99999)+100000) * 1000
|
||||
}
|
||||
|
||||
// getTimeStamp 获取时间戳
|
||||
func getTimeStamp(iCount int) int64 {
|
||||
ts := time.Now().UnixMilli()
|
||||
if iCount != 0 {
|
||||
iCount++
|
||||
return ts - (ts % int64(iCount)) + int64(iCount)
|
||||
}
|
||||
return ts
|
||||
}
|
Reference in New Issue
Block a user