diff --git a/Makefile b/Makefile index be90cce..db6ff69 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ tidy: go mod tidy -BinName=anto-v3.4.7-windows.exe +BinName=anto-v3.4.9-windows.exe deploy: rs build diff --git a/bootstrap/boot.go b/bootstrap/boot.go index f32b097..82ab75d 100644 --- a/bootstrap/boot.go +++ b/bootstrap/boot.go @@ -7,6 +7,8 @@ import ( "anto/domain/service/translator/ali_cloud_mt" "anto/domain/service/translator/baidu" "anto/domain/service/translator/caiyunai" + "anto/domain/service/translator/deepl" + "anto/domain/service/translator/deepl_pro" "anto/domain/service/translator/g_deepl_x" "anto/domain/service/translator/google_cloud" "anto/domain/service/translator/huawei_cloud_nlp" @@ -43,12 +45,14 @@ func Boot(_ context.Context) { g_deepl_x.API().Init(cfg.Singleton().YouDao) google_cloud.API().Init(cfg.Singleton().GoogleCloud) openai.API().Init(cfg.Singleton().OpenAI) + deepl.API().Init(cfg.Singleton().DeepL) + deepl_pro.API().Init(cfg.Singleton().DeepLPro) repository.GetTranslators().Register( huawei_cloud_nlp.API(), baidu.API(), tencent_cloud_mt.API(), openapi_youdao.API(), ali_cloud_mt.API(), caiyunai.API(), niutrans.API(), volcengine.API(), g_deepl_x.API(), - google_cloud.API(), openai.API(), + google_cloud.API(), openai.API(), deepl.API(), deepl_pro.API(), ) } diff --git a/cfg/cfg.go b/cfg/cfg.go index 8f7801f..695c050 100644 --- a/cfg/cfg.go +++ b/cfg/cfg.go @@ -4,6 +4,8 @@ import ( "anto/domain/service/translator/ali_cloud_mt" "anto/domain/service/translator/baidu" "anto/domain/service/translator/caiyunai" + "anto/domain/service/translator/deepl" + "anto/domain/service/translator/deepl_pro" "anto/domain/service/translator/g_deepl_x" "anto/domain/service/translator/google_cloud" "anto/domain/service/translator/huawei_cloud_nlp" @@ -42,6 +44,8 @@ func Singleton() *Cfg { apiSingleton.YouDao = new(youdao.Config).Default().(*youdao.Config) apiSingleton.GoogleCloud = new(google_cloud.Config).Default().(*google_cloud.Config) apiSingleton.OpenAI = new(openai.Config).Default().(*openai.Config) + apiSingleton.DeepL = new(deepl.Config).Default().(*deepl.Config) + apiSingleton.DeepLPro = new(deepl_pro.Config).Default().(*deepl_pro.Config) }) return apiSingleton } @@ -62,6 +66,8 @@ type Cfg struct { GDeeplX *g_deepl_x.Config `mapstructure:"g_deepl_x"` GoogleCloud *google_cloud.Config `mapstructure:"google_cloud"` OpenAI *openai.Config `mapstructure:"openai"` + DeepL *deepl.Config `mapstructure:"deepl"` + DeepLPro *deepl_pro.Config `mapstructure:"deepl_pro"` currentViper *viper.Viper `mapstructure:"-"` } diff --git a/common/const.go b/common/const.go index cc54bae..588d836 100644 --- a/common/const.go +++ b/common/const.go @@ -3,7 +3,7 @@ package common const ( AppName = "anto" Author = "speauty" - Version = "v3.4.7" + Version = "v3.4.9" DownloadLatestVersionUrl = "http://kodo.app.speauty.cn/anto-latest-windows.exe" GoUidLen = 8 diff --git a/domain/service/translator/deepl/config.go b/domain/service/translator/deepl/config.go new file mode 100644 index 0000000..408dfbf --- /dev/null +++ b/domain/service/translator/deepl/config.go @@ -0,0 +1,67 @@ +package deepl + +import ( + "anto/domain/service/translator" + "github.com/spf13/viper" +) + +type Config struct { + *translator.DefaultConfig + ApiKey string `mapstructure:"api_key"` + QPS int `mapstructure:"qps"` + MaxCharNum int `mapstructure:"max_single_text_length"` + MaxCoroutineNum int `mapstructure:"max_coroutine_num"` +} + +func (config *Config) Default() translator.ImplConfig { + return &Config{ + ApiKey: "", + MaxCharNum: 1000, QPS: 10, MaxCoroutineNum: 5, + } +} + +func (config *Config) SyncDisk(currentViper *viper.Viper) error { + tagAndVal := config.JoinAllTagAndValue(API(), config, "mapstructure") + + for tag, val := range tagAndVal { + currentViper.Set(tag, val) + } + return nil +} + +func (config *Config) GetAK() string { return config.ApiKey } +func (config *Config) GetQPS() int { return config.QPS } +func (config *Config) GetMaxCharNum() int { return config.MaxCharNum } +func (config *Config) GetMaxCoroutineNum() int { return config.MaxCoroutineNum } + +func (config *Config) SetAK(str string) error { + if err := config.ValidatorStr(str); err != nil { + return err + } + config.ApiKey = str + return nil +} + +func (config *Config) SetQPS(num int) error { + if err := config.ValidatorNum(num); err != nil { + return err + } + config.QPS = num + return nil +} + +func (config *Config) SetMaxCharNum(num int) error { + if err := config.ValidatorNum(num); err != nil { + return err + } + config.MaxCharNum = num + return nil +} + +func (config *Config) SetMaxCoroutineNum(num int) error { + if err := config.ValidatorNum(num); err != nil { + return err + } + config.MaxCoroutineNum = num + return nil +} diff --git a/domain/service/translator/deepl/lang.go b/domain/service/translator/deepl/lang.go new file mode 100644 index 0000000..339c27e --- /dev/null +++ b/domain/service/translator/deepl/lang.go @@ -0,0 +1,31 @@ +package deepl + +import ( + "anto/domain/service/translator" +) + +var langSupported = []translator.LangPair{ + {"ZH", "中文"}, + {"EN", "英语"}, + {"DE", "德语"}, + {"ES", "西班牙语"}, + {"FR", "法语"}, + {"IT", "意大利语"}, + {"PL", "波兰语"}, + {"PT", "葡萄牙语"}, + {"RU", "俄语"}, + {"BG", "保加利亚语"}, + {"CS", "捷克语"}, + {"DA", "丹麦语"}, + {"EL", "希腊语"}, + {"EL", "丹麦语"}, + {"ET", "爱沙尼亚语"}, + {"FI", "芬兰语"}, + {"HU", "匈牙利语"}, + {"LT", "立陶宛语"}, + {"LV", "拉脱维亚语"}, + {"RO", "罗马尼亚语"}, + {"SK", "斯洛伐克语"}, + {"SL", "斯洛文尼亚语"}, + {"SV", "瑞典语"}, +} diff --git a/domain/service/translator/deepl/translator.go b/domain/service/translator/deepl/translator.go new file mode 100644 index 0000000..6c2df71 --- /dev/null +++ b/domain/service/translator/deepl/translator.go @@ -0,0 +1,104 @@ +package deepl + +import ( + "anto/domain/service/translator" + "anto/lib/log" + "context" + "encoding/json" + "fmt" + "github.com/golang-module/carbon" + "strings" + "sync" +) + +var ( + apiTranslator *Translator + onceTranslator sync.Once +) + +const ( + TRANSLATE_API_FREE string = "https://api-free.deepl.com/v2/translate" + TRANSLATE_API_PRO string = "https://api.deepl.com/v2/translate" +) + +func API() *Translator { + onceTranslator.Do(func() { + apiTranslator = New() + }) + return apiTranslator +} + +func New() *Translator { + return &Translator{ + id: "deepl", + name: "DeepL", + sep: "\n", + langSupported: langSupported, + } +} + +type Translator struct { + id string + name string + cfg translator.ImplConfig + langSupported []translator.LangPair + sep string +} + +func (customT *Translator) Init(cfg translator.ImplConfig) { customT.cfg = cfg } + +func (customT *Translator) GetId() string { return customT.id } +func (customT *Translator) GetShortId() string { return "dl" } +func (customT *Translator) GetName() string { return customT.name } +func (customT *Translator) GetCfg() translator.ImplConfig { return customT.cfg } +func (customT *Translator) GetLangSupported() []translator.LangPair { return customT.langSupported } +func (customT *Translator) GetSep() string { return customT.sep } +func (customT *Translator) IsValid() bool { return true } + +func (customT *Translator) Translate(ctx context.Context, args *translator.TranslateArgs) (*translator.TranslateRes, error) { + timeStart := carbon.Now() + texts := strings.Split(args.TextContent, customT.GetSep()) + req := &translateReq{ + Text: texts, SourceLang: args.FromLang, TargetLang: args.ToLang, SplitSentences: "0", + } + + reqBytes, _ := json.Marshal(req) + respBytes, err := translator.RequestSimpleHttp(ctx, customT, TRANSLATE_API_FREE, true, reqBytes, map[string]string{ + "Authorization": fmt.Sprintf("DeepL-Auth-Key %s", customT.cfg.GetAK()), + }) + if err != nil { + return nil, err + } + + resp := new(translateResp) + if err = json.Unmarshal(respBytes, resp); err != nil { + log.Singleton().ErrorF("解析报文异常, 引擎: %s, 错误: %s", customT.GetName(), err) + return nil, fmt.Errorf("解析报文出现异常, 错误: %s", err.Error()) + } + + ret := new(translator.TranslateRes) + + for textIdx, textTarget := range resp.Translations { + ret.Results = append(ret.Results, &translator.TranslateResBlock{ + Id: texts[textIdx], + TextTranslated: textTarget.Text, + }) + } + + ret.TimeUsed = int(carbon.Now().DiffAbsInSeconds(timeStart)) + return ret, nil +} + +type translateReq struct { + Text []string `json:"text"` + SourceLang string `json:"source_lang"` + TargetLang string `json:"target_lang"` + SplitSentences string `json:"split_sentences"` +} + +type translateResp struct { + Translations []struct { + DetectedSourceLanguage string `json:"detected_source_language"` + Text string `json:"text"` + } `json:"translations"` +} diff --git a/domain/service/translator/deepl_pro/config.go b/domain/service/translator/deepl_pro/config.go new file mode 100644 index 0000000..fc59889 --- /dev/null +++ b/domain/service/translator/deepl_pro/config.go @@ -0,0 +1,67 @@ +package deepl_pro + +import ( + "anto/domain/service/translator" + "github.com/spf13/viper" +) + +type Config struct { + *translator.DefaultConfig + ApiKey string `mapstructure:"api_key"` + QPS int `mapstructure:"qps"` + MaxCharNum int `mapstructure:"max_single_text_length"` + MaxCoroutineNum int `mapstructure:"max_coroutine_num"` +} + +func (config *Config) Default() translator.ImplConfig { + return &Config{ + ApiKey: "", + MaxCharNum: 1000, QPS: 10, MaxCoroutineNum: 5, + } +} + +func (config *Config) SyncDisk(currentViper *viper.Viper) error { + tagAndVal := config.JoinAllTagAndValue(API(), config, "mapstructure") + + for tag, val := range tagAndVal { + currentViper.Set(tag, val) + } + return nil +} + +func (config *Config) GetAK() string { return config.ApiKey } +func (config *Config) GetQPS() int { return config.QPS } +func (config *Config) GetMaxCharNum() int { return config.MaxCharNum } +func (config *Config) GetMaxCoroutineNum() int { return config.MaxCoroutineNum } + +func (config *Config) SetAK(str string) error { + if err := config.ValidatorStr(str); err != nil { + return err + } + config.ApiKey = str + return nil +} + +func (config *Config) SetQPS(num int) error { + if err := config.ValidatorNum(num); err != nil { + return err + } + config.QPS = num + return nil +} + +func (config *Config) SetMaxCharNum(num int) error { + if err := config.ValidatorNum(num); err != nil { + return err + } + config.MaxCharNum = num + return nil +} + +func (config *Config) SetMaxCoroutineNum(num int) error { + if err := config.ValidatorNum(num); err != nil { + return err + } + config.MaxCoroutineNum = num + return nil +} diff --git a/domain/service/translator/deepl_pro/lang.go b/domain/service/translator/deepl_pro/lang.go new file mode 100644 index 0000000..0228347 --- /dev/null +++ b/domain/service/translator/deepl_pro/lang.go @@ -0,0 +1,31 @@ +package deepl_pro + +import ( + "anto/domain/service/translator" +) + +var langSupported = []translator.LangPair{ + {"ZH", "中文"}, + {"EN", "英语"}, + {"DE", "德语"}, + {"ES", "西班牙语"}, + {"FR", "法语"}, + {"IT", "意大利语"}, + {"PL", "波兰语"}, + {"PT", "葡萄牙语"}, + {"RU", "俄语"}, + {"BG", "保加利亚语"}, + {"CS", "捷克语"}, + {"DA", "丹麦语"}, + {"EL", "希腊语"}, + {"EL", "丹麦语"}, + {"ET", "爱沙尼亚语"}, + {"FI", "芬兰语"}, + {"HU", "匈牙利语"}, + {"LT", "立陶宛语"}, + {"LV", "拉脱维亚语"}, + {"RO", "罗马尼亚语"}, + {"SK", "斯洛伐克语"}, + {"SL", "斯洛文尼亚语"}, + {"SV", "瑞典语"}, +} diff --git a/domain/service/translator/deepl_pro/translator.go b/domain/service/translator/deepl_pro/translator.go new file mode 100644 index 0000000..288b2b3 --- /dev/null +++ b/domain/service/translator/deepl_pro/translator.go @@ -0,0 +1,104 @@ +package deepl_pro + +import ( + "anto/domain/service/translator" + "anto/lib/log" + "context" + "encoding/json" + "fmt" + "github.com/golang-module/carbon" + "strings" + "sync" +) + +var ( + apiTranslator *Translator + onceTranslator sync.Once +) + +const ( + TRANSLATE_API_FREE string = "https://api-free.deepl.com/v2/translate" + TRANSLATE_API_PRO string = "https://api.deepl.com/v2/translate" +) + +func API() *Translator { + onceTranslator.Do(func() { + apiTranslator = New() + }) + return apiTranslator +} + +func New() *Translator { + return &Translator{ + id: "deepl_pro", + name: "DeepL_PRO", + sep: "\n", + langSupported: langSupported, + } +} + +type Translator struct { + id string + name string + cfg translator.ImplConfig + langSupported []translator.LangPair + sep string +} + +func (customT *Translator) Init(cfg translator.ImplConfig) { customT.cfg = cfg } + +func (customT *Translator) GetId() string { return customT.id } +func (customT *Translator) GetShortId() string { return "dlp" } +func (customT *Translator) GetName() string { return customT.name } +func (customT *Translator) GetCfg() translator.ImplConfig { return customT.cfg } +func (customT *Translator) GetLangSupported() []translator.LangPair { return customT.langSupported } +func (customT *Translator) GetSep() string { return customT.sep } +func (customT *Translator) IsValid() bool { return true } + +func (customT *Translator) Translate(ctx context.Context, args *translator.TranslateArgs) (*translator.TranslateRes, error) { + timeStart := carbon.Now() + texts := strings.Split(args.TextContent, customT.GetSep()) + req := &translateReq{ + Text: texts, SourceLang: args.FromLang, TargetLang: args.ToLang, SplitSentences: "0", + } + + reqBytes, _ := json.Marshal(req) + respBytes, err := translator.RequestSimpleHttp(ctx, customT, TRANSLATE_API_PRO, true, reqBytes, map[string]string{ + "Authorization": fmt.Sprintf("DeepL-Auth-Key %s", customT.cfg.GetAK()), + }) + if err != nil { + return nil, err + } + + resp := new(translateResp) + if err = json.Unmarshal(respBytes, resp); err != nil { + log.Singleton().ErrorF("解析报文异常, 引擎: %s, 错误: %s", customT.GetName(), err) + return nil, fmt.Errorf("解析报文出现异常, 错误: %s", err.Error()) + } + + ret := new(translator.TranslateRes) + + for textIdx, textTarget := range resp.Translations { + ret.Results = append(ret.Results, &translator.TranslateResBlock{ + Id: texts[textIdx], + TextTranslated: textTarget.Text, + }) + } + + ret.TimeUsed = int(carbon.Now().DiffAbsInSeconds(timeStart)) + return ret, nil +} + +type translateReq struct { + Text []string `json:"text"` + SourceLang string `json:"source_lang"` + TargetLang string `json:"target_lang"` + SplitSentences string `json:"split_sentences"` +} + +type translateResp struct { + Translations []struct { + DetectedSourceLanguage string `json:"detected_source_language"` + Text string `json:"text"` + } `json:"translations"` +}