diff --git a/README.md b/README.md index 902a0c2..8cdc7a0 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,19 @@ - 在配置多个 API 时如果调用当前 API 失败自动切换到下一个 - 可以将翻译过的内容写入 `Redis` `Memory` 缓存重复翻译内容降低翻译 API 重复调用 +## 批量翻译支持情况 + +| 平台 | 是否支持批量翻译 | 是否完美支持 | 准确的源语言 | 备注 | +| :-----: | :--------------: | :----------: | :----------: | :----------------------------------------------------------: | +| 百度 | 是 | 否 | 否 | 不支持精确返回具体每条结果的源语言类型 | +| Google | 是 | 是 | 是 | | +| 有道 | 是 | 否 | 否 | 源语言类型识别不准确 | +| 火山 | 是 | 是 | 是 | | +| Deepl | 是 | 否 | 否 | 源语言类型识别不准确 | +| 讯飞 | 是 | 否 | 否 | 官方不支持批量翻译通过特殊字符 № 切割实现 且 可能出现结果非多条 | +| PaPaGo | 是 | 否 | 否 | 基于 \n 切割实现 且不可识别不同的源语言类型 | +| ChatGPT | 是 | 是 | 是 | | + ## 未来支持 (优先级按照顺序,打勾为已实现) ✈️ - [x] 持久化已翻译到 `MySQL` - [x] web 控制页面 diff --git a/README_EN.md b/README_EN.md index acd84e5..e4910dc 100644 --- a/README_EN.md +++ b/README_EN.md @@ -24,6 +24,19 @@ Optional - Automatically switches to the next API if the current API call fails when configuring multiple APIs. - Can write translated content into `Redis` `Memory` cache to reduce repetitive calls to translation APIs. +## Batch Translation Support + +| Platform | Batch Translation Support | Perfect Support | Accurate Source Language | Note | +| :------: | :-----------------------: | :-------------: | :----------------------: | :-------------------------------------------------------------------------------------------------------------------------------------- | +| Baidu | Yes | No | No | Does not support accurate return of specific source language for each result | +| Google | Yes | Yes | Yes | | +| Youdao | Yes | No | No | Source language identification is not accurate | +| Huoshan | Yes | Yes | Yes | | +| Deepl | Yes | No | No | Source language identification is not accurate | +| iFly | Yes | No | No | Officially does not support batch translation, implemented through special character № splitting and may result in non-multiple outputs | +| PaPaGo | Yes | No | No | Implemented based on \n splitting and cannot recognize different source language types | +| ChatGPT | Yes | Yes | Yes | | + ## Future Support (prioritized, checked means implemented) ✈️ - [x] Persist translated content to `MySQL`. - [x] Web control panel. diff --git a/main.go b/main.go index 9bab06c..c0adcda 100644 --- a/main.go +++ b/main.go @@ -46,7 +46,7 @@ func baseInit() { // 开启翻译支持 translate.InitTranslate() // 初始化 chatGPT 需要的数据 - global.ChatGPTLangConfig = gjson.MustEncodeString(translate.BaseTranslateConf[translate.ChatGptTranslateMode]) + translate.ChatGPTLangConfig = gjson.MustEncodeString(translate.BaseTranslateConf[translate.ChatGptTranslateMode]) // 初始化缓冲区 if err := buffer.Buffer.Init(false); err != nil { panic(err) diff --git a/src/buffer/buffer.go b/src/buffer/buffer.go index 649af49..0eab020 100644 --- a/src/buffer/buffer.go +++ b/src/buffer/buffer.go @@ -6,6 +6,7 @@ import ( "sync" "uniTranslate/src/global" queueHandler "uniTranslate/src/service/queue/handler" + "uniTranslate/src/translate" "uniTranslate/src/types" "github.com/gogf/gf/v2/net/ghttp" @@ -36,15 +37,15 @@ func (t *BufferType) GetIdx() [][]int { return t.idx } -func (t *BufferType) Handler(r *ghttp.Request, from, to, text, platform string, fn func(*types.TranslatePlatform, string, string, string) (*types.TranslateData, error)) (s *types.TranslateData, e error) { +func (t *BufferType) Handler(r *ghttp.Request, req *translate.TranslateReq, fn func(config *types.TranslatePlatform, req *translate.TranslateReq) (*types.TranslateData, error)) (s *types.TranslateData, e error) { t.m.Lock() var bufferArr BufferArrInterface - if platform == "" { + if req.Platfrom == "" { bufferArr = new(RandomSortBufferArr) } else { bufferArr = new(PlatformSortBufferArr) } - bufferArr.Init(t, platform) + bufferArr.Init(t, req.Platfrom) t.m.Unlock() // 创建上下文 ctx := gctx.New() @@ -61,7 +62,7 @@ func (t *BufferType) Handler(r *ghttp.Request, from, to, text, platform string, // 释放锁 t.m.Unlock() // 调用处理 - t, err := fn(p, from, to, text) + t, err := fn(p, req) if err != nil { e = fmt.Errorf("调用翻译失败 %s", err) queueHandler.RequestRecordQueue.Push(&types.RequestRecordData{ diff --git a/src/global/system.go b/src/global/system.go index 081b790..6f122bc 100644 --- a/src/global/system.go +++ b/src/global/system.go @@ -81,7 +81,7 @@ var GfCache *gcache.Cache var StatisticalProcess types.StatisticsInterface = new(types.MySqlStatistics) -var ChatGPTLangConfig string + // 是否将缓存写入存储 var CacheWriteToStorage = false diff --git a/src/service/cron/service.go b/src/service/cron/service.go index af15d5b..c447c96 100644 --- a/src/service/cron/service.go +++ b/src/service/cron/service.go @@ -17,7 +17,7 @@ func Service() { g.Log().Error(ctx, "清理请求记录失败", err) } }() - g.Log().Infof(ctx, "每2小时执行一次 清理请求记录") + g.Log().Infof(ctx, "每1小时执行一次 清理请求记录") if err := clearRequestRecord(ctx); err != nil { g.Log().Error(ctx, "清理请求记录失败", err) diff --git a/src/service/web/controller/translate.go b/src/service/web/controller/translate.go index b5b6033..3cdc46d 100644 --- a/src/service/web/controller/translate.go +++ b/src/service/web/controller/translate.go @@ -29,13 +29,12 @@ func Translate(r *ghttp.Request) { toT := r.Get("to") textT := r.Get("text") platform := r.Get("platform").String() - batch := r.Get("batch", false).Bool() x.FastResp(r, fromT.IsEmpty() || toT.IsEmpty() || textT.IsEmpty(), false).Resp("参数错误") x.FastResp(r, platform != "" && !xlib.InArr(platform, translate.TranslateModeList), false).Resp("不支持的平台") from := fromT.String() to := toT.String() x.FastResp(r, to == "auto", false).Resp("转换后语言不支持 auto") - text := textT.String() + text := textT.Strings() // 内容转换为md5 var keyStr string if global.CachePlatform { @@ -43,9 +42,6 @@ func Translate(r *ghttp.Request) { } else { keyStr = fmt.Sprintf("to:%s-text:%s", to, text) } - if batch { - keyStr += "-batch" - } md5 := gmd5.MustEncrypt(keyStr) // 写入到缓存 var ( @@ -54,14 +50,21 @@ func Translate(r *ghttp.Request) { ) // 记录从翻译到获取到结果的时间 startTime := gtime.Now().UnixMilli() + req := &translate.TranslateReq{ + From: from, + To: to, + Platfrom: platform, + Text: text, + TextStr: gstr.Join(text, "\n"), + } // 判断是否进行缓存 if global.CacheMode == "off" { var dataAny any - dataAny, err = t(r, from, to, text, platform) + dataAny, err = t(r, req) data = gvar.New(dataAny) } else { data, err = global.GfCache.GetOrSetFunc(r.GetCtx(), fmt.Sprintf("Translate:%s", md5), func(ctx context.Context) (value any, err error) { - return t(r, from, to, text, platform) + return t(r, req) }, 0) } endTime := gtime.Now().UnixMilli() @@ -132,9 +135,9 @@ func RefreshConfigCache(r *ghttp.Request) { x.FastResp(r).Resp() } -func t(r *ghttp.Request, from, to, text, platform string) (value any, err error) { +func t(r *ghttp.Request, req *translate.TranslateReq) (value any, err error) { var data *types.TranslateData - data, err = buffer.Buffer.Handler(r, from, to, text, platform, handler.Translate) + data, err = buffer.Buffer.Handler(r, req, handler.Translate) value = data if data != nil { diff --git a/src/service/web/handler/translate.go b/src/service/web/handler/translate.go index eca57eb..8d41da9 100644 --- a/src/service/web/handler/translate.go +++ b/src/service/web/handler/translate.go @@ -9,27 +9,25 @@ import ( ) // Translate 翻译 -func Translate(config *types.TranslatePlatform, OriginalFrom, OriginalTo, text string) (data *types.TranslateData, err error) { +func Translate(config *types.TranslatePlatform, req *translate.TranslateReq) (data *types.TranslateData, err error) { // 获取翻译平台 t, err := translate.GetTranslate(config.Type, config.Cfg) if err != nil { return } // 翻译 - translateTextArr, from, err := t.Translate(OriginalFrom, OriginalTo, text) + resp, err := t.Translate(req) if err != nil { return } // 返回数据 data = &types.TranslateData{ - OriginalText: text, - OriginalTextMd5: gmd5.MustEncrypt(text), - TranslateTextArr: translateTextArr, - From: from, - To: OriginalTo, - Platform: config.Type, - OriginalTextLen: gstr.LenRune(text), - TranslationLen: gstr.LenRune(gstr.Join(translateTextArr, "")), + OriginalText: req.TextStr, + OriginalTextMd5: gmd5.MustEncrypt(req.TextStr), + Translate: resp, + To: req.To, + Platform: config.Type, + OriginalTextLen: gstr.LenRune(gstr.Replace(req.TextStr, "\n", "")), } return } diff --git a/src/translate/baidu.go b/src/translate/baidu.go index 65dd11b..6d04778 100644 --- a/src/translate/baidu.go +++ b/src/translate/baidu.go @@ -1,13 +1,12 @@ package translate import ( + "encoding/json" "errors" "fmt" "time" - "github.com/gogf/gf/v2/container/gvar" "github.com/gogf/gf/v2/crypto/gmd5" - "github.com/gogf/gf/v2/encoding/gjson" "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/os/gctx" "github.com/gogf/gf/v2/os/gtime" @@ -21,23 +20,34 @@ type BaiduConfigType struct { Key string `json:"key"` } -func (t *BaiduConfigType) Translate(from, to, text string) (result []string, fromLang string, err error) { +type BaiduHTTPTranslateResp struct { + From string `json:"from"` + To string `json:"to"` + TransResult []TransResult `json:"trans_result"` +} + +type TransResult struct { + Src string `json:"src"` + Dst string `json:"dst"` +} + +func (t *BaiduConfigType) Translate(req *TranslateReq) (resp []*TranslateResp, err error) { if t == nil || t.Url == "" || t.AppId == "" || t.Key == "" { err = errors.New("百度翻译配置异常") return } mode := t.GetMode() // 语言标记转换 - from, err = SafeLangType(from, mode) + from, err := SafeLangType(req.From, mode) if err != nil { return } - to, err = SafeLangType(to, mode) + to, err := SafeLangType(req.To, mode) if err != nil { return } salt := gtime.Now().UnixMilli() - signStr := fmt.Sprintf("%s%s%d%s", t.AppId, text, salt, t.Key) + signStr := fmt.Sprintf("%s%s%d%s", t.AppId, req.TextStr, salt, t.Key) sign, err := gmd5.EncryptString(signStr) // 处理MD5加密失败 if err != nil { @@ -45,7 +55,7 @@ func (t *BaiduConfigType) Translate(from, to, text string) (result []string, fro } // 发起请求 post, err := g.Client().SetTimeout(time.Duration(t.CurlTimeOut)*time.Millisecond).Post(gctx.New(), t.Url, g.Map{ - "q": text, + "q": req.TextStr, "from": from, "to": to, "appid": t.AppId, @@ -59,34 +69,30 @@ func (t *BaiduConfigType) Translate(from, to, text string) (result []string, fro // 推出函数时关闭链接 defer func() { _ = post.Close() }() // 返回的json解析 - respStr := post.ReadAllString() + respByte := post.ReadAll() // 判断状态码 if post.StatusCode != 200 { - err = fmt.Errorf("请求失败 状态码: %d 返回结果: %s", post.StatusCode, respStr) + err = fmt.Errorf("请求失败 状态码: %d 返回结果: %s", post.StatusCode, respByte) return } - json, err := gjson.DecodeToJson(respStr) - // 处理json错误 - if err != nil { + httpResp := new(BaiduHTTPTranslateResp) + if err = json.Unmarshal(respByte, httpResp); err != nil { return } - // 判断获取到的数据是否正常 - if json.Get("trans_result").IsEmpty() { - err = fmt.Errorf("请求数据异常 账号: %s 返回结果: %s", t.AppId, respStr) - return - } - // 循环获取数据 - var arr []string - for _, v := range json.Get("trans_result").Maps() { - arr = append(arr, gvar.New(v["dst"], true).String()) - } - - lang, err := GetYouDaoLang(json.Get("from").String(), mode) - if err != nil { - return + resp = make([]*TranslateResp, 0) + for _, item := range httpResp.TransResult { + lang, err1 := GetYouDaoLang(httpResp.From, mode) + if err1 != nil { + err = err1 + return + } + resp = append(resp, &TranslateResp{ + Text: item.Dst, + FromLang: lang, + }) } - return arr, lang, nil + return } func (t *BaiduConfigType) GetMode() string { diff --git a/src/translate/chatGPT.go b/src/translate/chatGPT.go index de1c5fc..6eef13f 100644 --- a/src/translate/chatGPT.go +++ b/src/translate/chatGPT.go @@ -2,44 +2,68 @@ package translate import ( "context" + "encoding/json" "errors" "fmt" - "uniTranslate/src/global" - "github.com/gogf/gf/v2/container/gvar" "github.com/sashabaranov/go-openai" ) +var ChatGPTLangConfig string + type ChatGptConfigType struct { Key string `json:"key"` } -func (t *ChatGptConfigType) Translate(from, to, text string) (result []string, fromLang string, err error) { +type ChatGPTHTTPTranslateResp []ChatGPTHTTPTranslateRespElement + +type ChatGPTHTTPTranslateRespElement struct { + FromLang string `json:"fromLang"` + Text string `json:"text"` +} + +func (t *ChatGptConfigType) Translate(req *TranslateReq) (resp []*TranslateResp, err error) { if t.Key == "" { - return nil, "", errors.New("chatGPT翻译配置异常") + err = errors.New("chatGPT翻译配置异常") + return } mode := t.GetMode() // 语言标记转换 - from, err = SafeLangType(from, mode) + from, err := SafeLangType(req.From, mode) if err != nil { return } - to, err = SafeLangType(to, mode) + to, err := SafeLangType(req.To, mode) if err != nil { return } - // google auto = "" if from == "auto" { from = "" } - result = make([]string, 0) - gptResp, err := SendToChatGpt(t.Key, fmt.Sprintf("将[%s]翻译成%s按照格式{\"fromLang\":\"源语言\",\"text\":\"翻译结果\"}返回给我fromLang有这几种语言直接给我返回对应的key位置%s不需要其他任何回复严格按照我给你的格式翻译结果不要用[]包着", text, to, global.ChatGPTLangConfig)) + gptResp, err := SendToChatGpt(t.Key, fmt.Sprintf("接下来模拟你是一个批量翻译接口你将 [%s] 数组数据批量翻译为 %s 返回数据结构为[ [{\"fromLang\":\"源语言\",\"text\":\"翻译结果\"}] ]标准的压缩数组json结构返回给我fromLang有这几种语言直接给我返回对应的key位置%s不需要其他任何回复严格按照我给你的格式翻译结果不要用[]包着", func() (str string) { + for k, v := range req.Text { + if k == 0 { + str = fmt.Sprintf("\"%s\"", v) + } else { + str = fmt.Sprintf("%s,\"%s\"", str, v) + } + } + return + }(), to, ChatGPTLangConfig)) if err != nil { return } - respData := gvar.New(gptResp).MapStrVar() - result = append(result, respData["text"].String()) - fromLang, err = GetYouDaoLang(respData["fromLang"].String(), mode) + httpResp := new(ChatGPTHTTPTranslateResp) + if err = json.Unmarshal([]byte(gptResp), httpResp); err != nil { + return + } + resp = make([]*TranslateResp, 0) + for _, item := range *httpResp { + resp = append(resp, &TranslateResp{ + Text: item.Text, + FromLang: item.FromLang, + }) + } return } diff --git a/src/translate/deepl.go b/src/translate/deepl.go index 54cfbf6..6b6cd61 100644 --- a/src/translate/deepl.go +++ b/src/translate/deepl.go @@ -1,11 +1,11 @@ package translate import ( + "encoding/json" "errors" "fmt" "time" - "github.com/gogf/gf/v2/encoding/gjson" "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/os/gctx" ) @@ -17,7 +17,16 @@ type DeeplConfigType struct { Key string `json:"key"` } -func (t *DeeplConfigType) Translate(from, to, text string) (result []string, fromLang string, err error) { +type DeeplHTTPTranslateResp struct { + Translations []DeeplTranslation `json:"translations"` +} + +type DeeplTranslation struct { + DetectedSourceLanguage string `json:"detected_source_language"` + Text string `json:"text"` +} + +func (t *DeeplConfigType) Translate(req *TranslateReq) (resp []*TranslateResp, err error) { if t == nil || t.Url == "" || t.Key == "" { err = errors.New("deepl翻译配置异常") return @@ -25,11 +34,11 @@ func (t *DeeplConfigType) Translate(from, to, text string) (result []string, fro ctx := gctx.New() mode := t.GetMode() // 语言标记转换 - from, err = SafeLangType(from, mode) + from, err := SafeLangType(req.From, mode) if err != nil { return } - to, err = SafeLangType(to, mode) + to, err := SafeLangType(req.To, mode) if err != nil { return } @@ -40,7 +49,7 @@ func (t *DeeplConfigType) Translate(from, to, text string) (result []string, fro HttpResult, err := g.Client().SetTimeout(time.Duration(t.CurlTimeOut)*time.Millisecond).Header(g.MapStrStr{ "Authorization": fmt.Sprintf("DeepL-Auth-Key %s", t.Key), }).Post(ctx, t.Url, g.Map{ - "text": text, + "text": req.Text, "source_lang": from, "target_lang": to, }) @@ -51,33 +60,35 @@ func (t *DeeplConfigType) Translate(from, to, text string) (result []string, fro // 推出函数时关闭链接 defer func() { _ = HttpResult.Close() }() // 判断状态码 - respStr := HttpResult.ReadAllString() + respByte := HttpResult.ReadAll() if HttpResult.StatusCode != 200 { - err = fmt.Errorf("请求失败 状态码: %d 返回结果: %s", HttpResult.StatusCode, respStr) + err = fmt.Errorf("请求失败 状态码: %d 返回结果: %s", HttpResult.StatusCode, respByte) return } - // 返回的json解析 - json, err := gjson.DecodeToJson(respStr) - if err != nil { + fmt.Printf("deepl resp: %s\n", respByte) + httpResp := new(DeeplHTTPTranslateResp) + if err = json.Unmarshal(respByte, httpResp); err != nil { return } - // 获取源语言 - dsl := json.Get("translations.0.detected_source_language") - if dsl.IsEmpty() { - fromLang = from - } else { - fromLang = dsl.String() + if len(httpResp.Translations) == 0 { + return } - // 返回翻译结果 - tr := json.Get("translations.0.text") - if tr.IsEmpty() { - err = errors.New("翻译失败请重试 " + respStr) + respData := httpResp.Translations[0] + + lang, err := GetYouDaoLang(respData.DetectedSourceLanguage, mode) + if err != nil { return - } else { - result = tr.Strings() } - // 将语言种类转换为有道标准 - fromLang, err = GetYouDaoLang(fromLang, mode) + strArr := make([]string, 0) + if err = json.Unmarshal([]byte(respData.Text), &strArr); err != nil { + return + } + for _, v := range strArr { + resp = append(resp, &TranslateResp{ + Text: v, + FromLang: lang, + }) + } return } diff --git a/src/translate/google.go b/src/translate/google.go index 1626958..d9feb99 100644 --- a/src/translate/google.go +++ b/src/translate/google.go @@ -1,12 +1,11 @@ package translate import ( + "encoding/json" "errors" "fmt" - "net/url" "time" - "github.com/gogf/gf/v2/encoding/gjson" "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/os/gctx" ) @@ -18,8 +17,21 @@ type GoogleConfigType struct { Key string `json:"key"` } +type GoogleHTTPTranslateResp struct { + Data GoogleData `json:"data"` +} + +type GoogleData struct { + Translations []GoogleTranslation `json:"translations"` +} + +type GoogleTranslation struct { + TranslatedText string `json:"translatedText"` + DetectedSourceLanguage string `json:"detectedSourceLanguage"` +} + // Translate google 翻译 -func (t *GoogleConfigType) Translate(from, to, text string) (result []string, fromLang string, err error) { +func (t *GoogleConfigType) Translate(req *TranslateReq) (resp []*TranslateResp, err error) { if t == nil || t.Url == "" || t.Key == "" { err = errors.New("google翻译配置异常") return @@ -27,63 +39,52 @@ func (t *GoogleConfigType) Translate(from, to, text string) (result []string, fr mode := t.GetMode() ctx := gctx.New() // 语言标记转换 - from, err = SafeLangType(from, mode) + from, err := SafeLangType(req.From, mode) if err != nil { return } - to, err = SafeLangType(to, mode) + to, err := SafeLangType(req.To, mode) if err != nil { return } - // google auto = "" if from == "auto" { from = "" } - // 调用翻译 - HttpResult, err := g.Client(). - SetTimeout(time.Duration(t.CurlTimeOut)*time.Millisecond). - Get(ctx, fmt.Sprintf( - "%s?key=%s&q=%s&source=%s&target=%s", - t.Url, - t.Key, - url.QueryEscape(text), - from, - to, - )) - // 处理调用接口错误 + // 发起请求翻译 + postResp, err := g.Client().Discovery(nil).ContentJson().SetTimeout(time.Duration(t.CurlTimeOut)*time.Millisecond).Post(ctx, fmt.Sprintf("%s?key=%s", t.Url, t.Key), g.Map{ + "q": req.Text, + "target": to, + "source": from, + }) if err != nil { return } - // 推出函数时关闭链接 - defer func() { _ = HttpResult.Close() }() - // 判断状态码 - respStr := HttpResult.ReadAllString() - if HttpResult.StatusCode != 200 { - err = fmt.Errorf("请求失败 状态码: %d 返回结果: %s", HttpResult.StatusCode, respStr) + defer func() { + _ = postResp.Close() + }() + respByte := postResp.ReadAll() + // 处理HTTP状态 + if postResp.StatusCode != 200 { + err = fmt.Errorf("google翻译请求失败,状态码:%d 返回数据: %s", postResp.StatusCode, respByte) return } - // 返回的json解析 - json, err := gjson.DecodeToJson(respStr) - if err != nil { + // 解析json + httpResp := new(GoogleHTTPTranslateResp) + if err = json.Unmarshal(respByte, httpResp); err != nil { return } - // 获取源语言 - dsl := json.Get("data.translations.0.detectedSourceLanguage") - if dsl.IsEmpty() { - fromLang = from - } else { - fromLang = dsl.String() - } - // 返回翻译结果 - tr := json.Get("data.translations.0.translatedText") - if tr.IsEmpty() { - err = errors.New("翻译失败请重试 " + respStr) - return - } else { - result = tr.Strings() + resp = make([]*TranslateResp, 0) + for _, item := range httpResp.Data.Translations { + lang, err1 := GetYouDaoLang(item.DetectedSourceLanguage, mode) + if err1 != nil { + err = err1 + return + } + resp = append(resp, &TranslateResp{ + Text: item.TranslatedText, + FromLang: lang, + }) } - // 将语言种类转换为有道标准 - fromLang, err = GetYouDaoLang(fromLang, mode) return } diff --git a/src/translate/huoShan.go b/src/translate/huoShan.go index 0d899cd..b4f917d 100644 --- a/src/translate/huoShan.go +++ b/src/translate/huoShan.go @@ -3,12 +3,11 @@ package translate import ( "encoding/json" baseErr "errors" + "fmt" "net/http" "net/url" "time" - "github.com/gogf/gf/v2/encoding/gjson" - "github.com/pkg/errors" "github.com/volcengine/volc-sdk-golang/base" ) @@ -45,13 +44,34 @@ type HuoShanConfigType struct { SecretKey string } +type HuoShanHTTPTranslateResp struct { + TranslationList []TranslationList `json:"TranslationList"` + ResponseMetadata ResponseMetadata `json:"ResponseMetadata"` + ResponseMetaData ResponseMetadata `json:"ResponseMetaData"` +} + +type ResponseMetadata struct { + RequestID string `json:"RequestId"` + Action string `json:"Action"` + Version string `json:"Version"` + Service string `json:"Service"` + Region string `json:"Region"` +} + +type TranslationList struct { + Translation string `json:"Translation"` + DetectedSourceLanguage string `json:"DetectedSourceLanguage"` + Extra interface{} `json:"Extra"` +} + // Translate 火山翻译 -func (t *HuoShanConfigType) Translate(from, to, text string) (result []string, fromLang string, err error) { +func (t *HuoShanConfigType) Translate(req *TranslateReq) (resp []*TranslateResp, err error) { if t == nil || t.SecretKey == "" || t.AccessKey == "" { err = baseErr.New("火山翻译配置异常") return } mode := t.GetMode() + from := req.From // 处理目标语言 if from == "auto" { from = "" @@ -61,7 +81,7 @@ func (t *HuoShanConfigType) Translate(from, to, text string) (result []string, f return } } - to, err = SafeLangType(to, mode) + to, err := SafeLangType(req.To, mode) if err != nil { return } @@ -70,29 +90,38 @@ func (t *HuoShanConfigType) Translate(from, to, text string) (result []string, f client.SetAccessKey(t.AccessKey) client.SetSecretKey(t.SecretKey) - req := Req{ + body, err := json.Marshal(Req{ SourceLanguage: from, TargetLanguage: to, - TextList: []string{text}, - } - body, err := json.Marshal(req) + TextList: req.Text, + }) if err != nil { return } - resp, code, err := client.Json("TranslateText", nil, string(body)) + respByte, code, err := client.Json("TranslateText", nil, string(body)) if err != nil { return } if code != 200 { - err = errors.New(string(resp)) + err = fmt.Errorf("火山翻译请求失败,状态码:%d 返回数据: %s", code, respByte) return } - jsonData, err := gjson.DecodeToJson(resp) - if err != nil { + httpResp := new(HuoShanHTTPTranslateResp) + if err = json.Unmarshal(respByte, httpResp); err != nil { return } - result = jsonData.Get("TranslationList.0.Translation").Strings() - fromLang, err = GetYouDaoLang(jsonData.Get("TranslationList.0.DetectedSourceLanguage").String(), mode) + for _, item := range httpResp.TranslationList { + lang, err1 := GetYouDaoLang(item.DetectedSourceLanguage, mode) + if err1 != nil { + err = err1 + return + } + resp = append(resp, &TranslateResp{ + Text: item.Translation, + FromLang: lang, + }) + + } return } diff --git a/src/translate/papago.go b/src/translate/papago.go index 5c778c6..0850f6f 100644 --- a/src/translate/papago.go +++ b/src/translate/papago.go @@ -2,11 +2,12 @@ package translate import ( "context" + "encoding/json" "errors" "fmt" + "strings" "time" - "github.com/gogf/gf/v2/encoding/gjson" "github.com/gogf/gf/v2/frame/g" ) @@ -18,58 +19,71 @@ type PaPaGoConfigType struct { Url string `json:"url"` } -func (t *PaPaGoConfigType) Translate(from, to, text string) (result []string, fromLang string, err error) { +type PaPaGoHTTPTranslateResp struct { + Message Message `json:"message"` +} + +type Message struct { + Result Result `json:"result"` +} + +type Result struct { + SrcLangType string `json:"srcLangType"` + TarLangType string `json:"tarLangType"` + TranslatedText string `json:"translatedText"` +} + +func (t *PaPaGoConfigType) Translate(req *TranslateReq) (resp []*TranslateResp, err error) { if t == nil || t.Key == "" || t.KeyId == "" || t.Url == "" { err = errors.New("PaPaGo配置异常") return } mode := t.GetMode() // 处理目标语言 - from, err = SafeLangType(from, mode) + from, err := SafeLangType(req.From, mode) if err != nil { return } - to, err = SafeLangType(to, mode) + to, err := SafeLangType(req.To, mode) if err != nil { return } // 发起请求 - resp, err := g.Client(). + postResp, err := g.Client(). SetTimeout(time.Duration(t.CurlTimeOut)*time.Millisecond). ContentJson(). Header(g.MapStrStr{ "X-NCP-APIGW-API-KEY-ID": t.KeyId, "X-NCP-APIGW-API-KEY": t.Key, }). - Post(context.Background(), t.Url, g.Map{"source": from, "target": to, "text": text}) + Post(context.Background(), t.Url, g.Map{"source": from, "target": to, "text": req.TextStr}) if err != nil { return } defer func() { - _ = resp.Close() + _ = postResp.Close() }() - bodyStr := resp.ReadAllString() + bodyByte := postResp.ReadAll() // 判断请求状态 - if resp.StatusCode != 200 { - err = fmt.Errorf("PaPaGo 请求失败 %d %s", resp.StatusCode, bodyStr) + if postResp.StatusCode != 200 { + err = fmt.Errorf("PaPaGo 请求失败 %d %s", postResp.StatusCode, bodyByte) return } - // 转换json - jsonData, err := gjson.DecodeToJson(bodyStr) - if err != nil { + httpResp := new(PaPaGoHTTPTranslateResp) + if err = json.Unmarshal(bodyByte, httpResp); err != nil { return } - if !jsonData.Get("error").IsEmpty() { - err = errors.New(jsonData.Get("error.message").String()) + lang, err := GetYouDaoLang(httpResp.Message.Result.TarLangType, mode) + if err != nil { return } - respTextT := jsonData.Get("message.result.translatedText") - if respTextT.IsEmpty() { - err = fmt.Errorf("PaPaGo 返回结果错误 %s", bodyStr) - return + for _, v := range strings.Split(httpResp.Message.Result.TranslatedText, "\n") { + resp = append(resp, &TranslateResp{ + Text: v, + FromLang: lang, + }) } - result = []string{respTextT.String()} - fromLang, err = GetYouDaoLang(jsonData.Get("message.result.srcLangType").String(), mode) + return } diff --git a/src/translate/tencent.go b/src/translate/tencent.go index 834287e..93b1a9f 100644 --- a/src/translate/tencent.go +++ b/src/translate/tencent.go @@ -4,7 +4,6 @@ import ( baseErr "errors" "fmt" - "github.com/gogf/gf/v2/encoding/gjson" "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common" "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/errors" "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile" @@ -18,17 +17,17 @@ type TencentConfigType struct { Region string `json:"region"` } -func (t *TencentConfigType) Translate(from, to, text string) (result []string, fromLang string, err error) { +func (t *TencentConfigType) Translate(req *TranslateReq) (resp []*TranslateResp, err error) { if t == nil || t.SecretId == "" || t.SecretKey == "" || t.Region == "" || t.Url == "" { err = baseErr.New("腾讯翻译配置异常") return } mode := t.GetMode() - from, err = SafeLangType(from, mode) + from, err := SafeLangType(req.From, mode) if err != nil { return } - to, err = SafeLangType(to, mode) + to, err := SafeLangType(req.To, mode) if err != nil { return } @@ -45,28 +44,32 @@ func (t *TencentConfigType) Translate(from, to, text string) (result []string, f if err != nil { return } - request := tmt.NewTextTranslateRequest() + request := tmt.NewTextTranslateBatchRequest() - request.SourceText = common.StringPtr(text) + request.SourceTextList = common.StringPtrs(req.Text) request.Source = common.StringPtr(from) request.Target = common.StringPtr(to) request.ProjectId = common.Int64Ptr(0) - response, err := client.TextTranslate(request) + response, err := client.TextTranslateBatch(request) + if err != nil { + return + } var tencentCloudSDKError *errors.TencentCloudSDKError if baseErr.As(err, &tencentCloudSDKError) { err = fmt.Errorf("an API error has returned: %s", tencentCloudSDKError.Error()) return } + lang, err := GetYouDaoLang(*response.Response.Source, mode) if err != nil { return } - jsonData, err := gjson.DecodeToJson(response.ToJsonString()) - if err != nil { - return + for _, item := range response.Response.TargetTextList { + resp = append(resp, &TranslateResp{ + Text: *item, + FromLang: lang, + }) } - result = jsonData.Get("Response.TargetText").Strings() - fromLang, err = GetYouDaoLang(jsonData.Get("Response.Source").String(), mode) return } diff --git a/src/translate/translate.go b/src/translate/translate.go index c49d826..4d42fdf 100644 --- a/src/translate/translate.go +++ b/src/translate/translate.go @@ -25,11 +25,24 @@ var TranslateModeList = []string{ // ITranslate 翻译接口 type ITranslate interface { // Translate 翻译 - Translate(from, to, text string) (result []string, fromLang string, err error) + Translate(req *TranslateReq) (resp []*TranslateResp, err error) // GetMode 获取模式 GetMode() (mode string) } +type TranslateResp struct { + Text string `json:"text"` + FromLang string `json:"fromLang"` +} + +type TranslateReq struct { + From string `json:"from"` + To string `json:"to"` + Platfrom string `json:"platform"` + Text []string `json:"text"` + TextStr string `json:"textStr"` +} + func GetTranslate(mode string, config map[string]any) (t ITranslate, err error) { // 调用对应平台 switch mode { diff --git a/src/translate/xunFei.go b/src/translate/xunFei.go index dc6d660..f6c5ba5 100644 --- a/src/translate/xunFei.go +++ b/src/translate/xunFei.go @@ -4,16 +4,17 @@ import ( "crypto/hmac" "crypto/sha256" "encoding/base64" + "encoding/json" "errors" "fmt" "net/url" + "strings" "time" "github.com/gogf/gf/v2/encoding/gjson" "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/net/gclient" "github.com/gogf/gf/v2/os/gctx" - "github.com/gogf/gf/v2/text/gstr" ) type xunFeiHttpConfigType struct { @@ -44,12 +45,12 @@ type XunFeiNiuConfigType struct { } // XunFeiNiuTranslate 讯飞新翻译引擎 -func (t *XunFeiNiuConfigType) Translate(from, to, text string) (result []string, fromLang string, err error) { +func (t *XunFeiNiuConfigType) Translate(req *TranslateReq) (resp []*TranslateResp, err error) { return xunFeiBaseTranslate(xunFeiNiuHttpConfig, t.GetMode(), &XunFeiConfigType{ AppId: t.AppId, Secret: t.Secret, ApiKey: t.ApiKey, - }, from, to, text) + }, req.From, req.To, req.Text) } func (t *XunFeiNiuConfigType) GetMode() string { @@ -62,20 +63,43 @@ type XunFeiConfigType struct { ApiKey string `json:"apiKey"` } -func (t *XunFeiConfigType) Translate(from, to, text string) (result []string, fromLang string, err error) { - return xunFeiBaseTranslate(xunFeiHttpConfig, t.GetMode(), t, from, to, text) +func (t *XunFeiConfigType) Translate(req *TranslateReq) (resp []*TranslateResp, err error) { + return xunFeiBaseTranslate(xunFeiHttpConfig, t.GetMode(), t, req.From, req.To, req.Text) } func (t *XunFeiConfigType) GetMode() string { return XunFeiTranslateMode } +type XunFeiHTTPTranslateResp struct { + Code int64 `json:"code"` + Data XunFeiData `json:"data"` + Message string `json:"message"` + Sid string `json:"sid"` +} + +type XunFeiData struct { + Result XunFeiResult `json:"result"` +} + +type XunFeiResult struct { + From string `json:"from"` + To string `json:"to"` + TransResult XunFeiTransResult `json:"trans_result"` +} + +type XunFeiTransResult struct { + Dst string `json:"dst"` + Src string `json:"src"` +} + // xunFeiBaseTranslate 讯飞基础翻译实现 -func xunFeiBaseTranslate(baseConfig *xunFeiHttpConfigType, mode string, config *XunFeiConfigType, from, to, text string) (result []string, fromLang string, err error) { +func xunFeiBaseTranslate(baseConfig *xunFeiHttpConfigType, mode string, config *XunFeiConfigType, from, to string, text []string) (resp []*TranslateResp, err error) { if config.AppId == "" || config.ApiKey == "" || config.Secret == "" { - return nil, "", errors.New("讯飞翻译配置异常") + err = errors.New("讯飞翻译配置异常") + return } - oFrom := from + // oFrom := from // 语言标记转换 from, err = SafeLangType(from, mode) if err != nil { @@ -99,7 +123,7 @@ func xunFeiBaseTranslate(baseConfig *xunFeiHttpConfigType, mode string, config * "to": to, }, "data": map[string]interface{}{ - "text": base64.StdEncoding.EncodeToString([]byte(text)), + "text": base64.StdEncoding.EncodeToString([]byte(strings.Join(text, "№"))), }, } currentTime := time.Now().UTC().Format(time.RFC1123) @@ -117,19 +141,29 @@ func xunFeiBaseTranslate(baseConfig *xunFeiHttpConfigType, mode string, config * if err != nil { return } - // to json - jsonData, err := gjson.DecodeToJson(xunFeiResp.ReadAllString()) - if err != nil { + defer func() { _ = xunFeiResp.Close() }() + if xunFeiResp.StatusCode != 200 { + err = fmt.Errorf("请求失败 状态码: %d 返回结果: %s", xunFeiResp.StatusCode, xunFeiResp.ReadAllString()) + return + } + respByte := xunFeiResp.ReadAll() + httpResp := new(XunFeiHTTPTranslateResp) + if err = json.Unmarshal(respByte, httpResp); err != nil { return } - if jsonData.Get("code").Int() != 0 { - err = errors.New(jsonData.Get("message").String()) + if httpResp.Code != 0 { + err = fmt.Errorf("讯飞翻译请求失败 %d %s", httpResp.Code, respByte) + return + } + lang, err := GetYouDaoLang(httpResp.Data.Result.To, mode) + if err != nil { return } - result = []string{gstr.Trim(jsonData.Get("data.result.trans_result.dst").String())} - fromLang, err = GetYouDaoLang(jsonData.Get("data.result.from", "").String(), XunFeiNiuTranslateMode) - if fromLang == "" { - fromLang = oFrom + for _, item := range strings.Split(httpResp.Data.Result.TransResult.Dst, "№") { + resp = append(resp, &TranslateResp{ + Text: item, + FromLang: lang, + }) } return } diff --git a/src/translate/youdao.go b/src/translate/youdao.go index d58b1a8..a620e90 100644 --- a/src/translate/youdao.go +++ b/src/translate/youdao.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "math" + "net/url" "time" "github.com/gogf/gf/v2/encoding/gjson" @@ -22,19 +23,25 @@ type YouDaoConfigType struct { SecKey string `json:"secKey"` } +type YouDAOHTTPTranslateResp struct { + Query string `json:"query"` + Translation string `json:"translation"` + Type string `json:"type"` +} + // Translate 有道翻译 -func (t *YouDaoConfigType) Translate(from, to, text string) (result []string, fromLang string, err error) { +func (t *YouDaoConfigType) Translate(req *TranslateReq) (resp []*TranslateResp, err error) { if t == nil || t.AppKey == "" || t.Url == "" || t.SecKey == "" { err = errors.New("有道翻译配置异常") return } mode := t.GetMode() // 语言标记转换 - from, err = SafeLangType(from, mode) + from, err := SafeLangType(req.From, mode) if err != nil { return } - if to == "auto" { + if req.To == "auto" { err = errors.New("转换后语言不能为auto") return } @@ -49,15 +56,23 @@ func (t *YouDaoConfigType) Translate(from, to, text string) (result []string, fr salt := gtime.Now().UnixMilli() curTime := int(math.Round(float64(salt / 1000))) - signStr := fmt.Sprintf("%s%s%d%d%s", t.AppKey, truncate(text), salt, curTime, t.SecKey) + signStr := fmt.Sprintf("%s%s%d%d%s", t.AppKey, truncate(gstr.Join(req.Text, "")), salt, curTime, t.SecKey) sign := xlib.Sha256(signStr) - post, err := g.Client().SetTimeout(time.Duration(t.CurlTimeOut)*time.Millisecond).Post(gctx.New(), t.Url, g.Map{ - "q": text, + post, err := g.Client().SetTimeout(time.Duration(t.CurlTimeOut)*time.Millisecond).Post(gctx.New(), fmt.Sprintf("%s?%s", t.Url, func() (str string) { + for k, item := range req.Text { + if k == 0 { + str = fmt.Sprintf("q=%s", url.QueryEscape(item)) + } else { + str = fmt.Sprintf("%s&q=%s", str, url.QueryEscape(item)) + } + } + return + }()), g.Map{ "appKey": t.AppKey, "salt": salt, "from": from, - "to": to, + "to": req.To, "sign": sign, "signType": "v3", "curtime": curTime, @@ -79,11 +94,20 @@ func (t *YouDaoConfigType) Translate(from, to, text string) (result []string, fr err = fmt.Errorf("请求失败errorCode: %d err: %s", json.Get("errorCode").Int(), postResp) return } - // 获取 from - returnFrom := gstr.Split(json.Get("l").String(), "2")[0] - return json.Get("translation").Strings(), returnFrom, nil + resp = make([]*TranslateResp, 0) + for _, item := range json.Get("translateResults").Vars() { + itemResp := new(TranslateResp) + httpResp := new(YouDAOHTTPTranslateResp) + if err = item.Scan(httpResp); err != nil { + return + } + itemResp.FromLang = gstr.Split(httpResp.Type, "2")[0] + itemResp.Text = httpResp.Translation + resp = append(resp, itemResp) + } + return } func (t *YouDaoConfigType) GetMode() string { return YouDaoTranslateMode -} \ No newline at end of file +} diff --git a/src/types/translate.go b/src/types/translate.go index b4cfa79..95205aa 100644 --- a/src/types/translate.go +++ b/src/types/translate.go @@ -1,17 +1,19 @@ package types -import "github.com/gogf/gf/v2/os/gcache" +import ( + "uniTranslate/src/translate" + + "github.com/gogf/gf/v2/os/gcache" +) type TranslateData struct { - Md5 string `json:"-"` - TranslateTextArr []string `json:"translate" orm:"translate"` - From string `json:"from" orm:"fromLang"` - To string `json:"to" orm:"toLang"` - Platform string `json:"platform" orm:"platform"` - OriginalText string `json:"originalText" orm:"text"` - OriginalTextMd5 string `json:"-" orm:"textMd5"` - OriginalTextLen int `json:"originalTextLen" orm:"textLen"` - TranslationLen int `json:"translationLen" orm:"translationLen"` + Md5 string `json:"-"` + Translate []*translate.TranslateResp `json:"translate"` + To string `json:"to" orm:"toLang"` + Platform string `json:"platform" orm:"platform"` + OriginalText string `json:"originalText" orm:"text"` + OriginalTextMd5 string `json:"-" orm:"textMd5"` + OriginalTextLen int `json:"originalTextLen" orm:"textLen"` } type StatisticsInterface interface {