├── .gitignore
├── .run
└── anto.run.xml
├── LICENSE
├── Makefile
├── README.md
├── assets
└── images
│ ├── settings.jpg
│ └── startup.jpg
├── bootstrap
├── boot.go
└── resource_builder.go
├── cfg.yml
├── cfg
├── app.go
├── cfg.go
└── opts.go
├── cmd
├── anto
│ ├── anto.manifest
│ ├── app.go
│ └── rsrc.syso
└── sandbox
│ └── main.go
├── common
├── combo_box.go
├── const.go
└── type.go
├── cron
├── detector
│ ├── data.go
│ └── srt_detector.go
├── func.go
├── reader
│ ├── data.go
│ └── srt_reader.go
├── translate
│ ├── data.go
│ └── srt_translator.go
└── writer
│ ├── data.go
│ └── srt_writer.go
├── domain
├── repository
│ └── translator.go
└── service
│ └── translator
│ ├── ai_baidu
│ ├── config.go
│ ├── lang.go
│ └── translator.go
│ ├── ali_cloud_mt
│ ├── config.go
│ ├── lang.go
│ └── translator.go
│ ├── baidu
│ ├── config.go
│ ├── lang.go
│ └── translator.go
│ ├── caiyunai
│ ├── config.go
│ ├── lang.go
│ └── translator.go
│ ├── config.go
│ ├── deepl
│ ├── config.go
│ ├── lang.go
│ └── translator.go
│ ├── deepl_pro
│ ├── config.go
│ ├── lang.go
│ └── translator.go
│ ├── errors.go
│ ├── g_deepl_x
│ ├── config.go
│ ├── lang.go
│ └── translator.go
│ ├── google_cloud
│ ├── config.go
│ ├── lang.go
│ └── translator.go
│ ├── huawei_cloud_nlp
│ ├── config.go
│ ├── lang.go
│ └── translator.go
│ ├── ling_va
│ ├── config.go
│ ├── lang.go
│ └── translator.go
│ ├── microsoft_edge
│ ├── config.go
│ ├── lang.go
│ └── translator.go
│ ├── niutrans
│ ├── config.go
│ ├── lang.go
│ └── translator.go
│ ├── openai
│ ├── config.go
│ ├── lang.go
│ └── translator.go
│ ├── openai_sweet
│ ├── config.go
│ ├── lang.go
│ └── translator.go
│ ├── openapi_youdao
│ ├── config.go
│ ├── error.go
│ ├── lang.go
│ └── translator.go
│ ├── request.go
│ ├── tencent_cloud_mt
│ ├── config.go
│ ├── lang.go
│ └── translator.go
│ ├── translator.go
│ ├── volcengine
│ ├── config.go
│ ├── lang.go
│ └── translator.go
│ ├── xfyun
│ ├── config.go
│ ├── lang.go
│ └── translator.go
│ └── youdao
│ ├── config.go
│ ├── lang.go
│ └── translator.go
├── go.mod
├── go.sum
├── lib
├── log
│ └── log.go
├── nohup
│ └── resident.go
├── restrictor
│ └── restrictor.go
├── srt
│ ├── block.go
│ ├── decode.go
│ ├── encode.go
│ └── srt.go
└── util
│ ├── rand.go
│ ├── time.go
│ └── util.go
├── logo.png
├── platform
└── win
│ ├── menu.go
│ ├── page
│ ├── page.go
│ ├── settings.go
│ └── subtitle_translate.go
│ ├── ui
│ ├── cfg.go
│ ├── event.go
│ ├── handle
│ │ └── file_dialog.go
│ ├── msg
│ │ └── msg.go
│ ├── notification.go
│ ├── pack
│ │ ├── ui_checkbox.go
│ │ ├── ui_combo_box.go
│ │ ├── ui_composite.go
│ │ ├── ui_group_box.go
│ │ ├── ui_image_view.go
│ │ ├── ui_label.go
│ │ ├── ui_line_edit.go
│ │ ├── ui_push_btn.go
│ │ ├── ui_scroll_view.go
│ │ ├── ui_text_edit.go
│ │ ├── ui_text_label.go
│ │ └── widget_group.go
│ ├── page.go
│ ├── sets.go
│ └── ui.go
│ └── win.go
└── resource
├── favicon.ico
└── resource.go
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | .vscode
3 | bin
4 | logs
--------------------------------------------------------------------------------
/.run/anto.run.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 speauty
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | .PHONY: tidy deploy_gui_win rs build compress
2 |
3 | tidy:
4 | go mod tidy
5 |
6 | BinName=anto-v3.7.0-windows.exe
7 |
8 | deploy: rs build
9 |
10 | rs:
11 | rsrc -manifest ./cmd/anto/anto.manifest -ico ./resource/favicon.ico -o ./cmd/anto/rsrc.syso
12 |
13 | build:
14 | go build -gcflags='-l -N' -ldflags='-w -s -H=windowsgui' -o "./bin/${BinName}" anto/cmd/anto
15 |
16 | compress:
17 | upx -9 "./bin/${BinName}"
18 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## ANTO-不专业字幕翻译的Windows桌面应用
2 |
3 | > 作为一名临时搬运工,搞个字幕翻译工具,一点也不过分~是吧
4 | >
5 |
6 |
7 |
8 | ### 写在前面
9 |
10 | 我是21年底才接触到油管搬运。不知出于什么原因,看了UP主[@神经元猫](https://space.bilibili.com/364152971/?spm_id_from=333.999.0.0)搬运翻译的[Cherno C++ 中文](https://space.bilibili.com/364152971/channel/collectiondetail?sid=13909),感觉[TheCherno](https://github.com/TheCherno)挺有趣的,就萌生了搬运他的OpenGL教程,然后就是游戏引擎等。刚开始,特别费人,使用剪映的智能字幕进行音频识别并导出SRT字幕。
11 |
12 | 由于分段很乱,需要人工调整,然后挨个翻译。遇到陌生单词,还要去查一下。但是吧,后来觉得效率低得令人发指。既然有智能字幕,那么就有智能翻译,然后,就了解到了各种机器翻译。看着每月的免费额度,完全可以操作一下。那么,就开始着手这款应用的研发。并不复杂,初版几天就搞定了,主要还是对框架不太熟悉,而且有些场景不太了解,大多都是后面慢慢完善。
13 |
14 | 用,是能用了,但是,应该还有很大提升的空间。比如,有朋友提到的AI翻译,以及通知和托盘等。
15 |
16 | 加油~
17 |
18 | ### 任务列表
19 |
20 | - [x] 集成lxn/walk桌面GUI库(才把fyne清理了,那个编译运行太慢了,而且Win下,依赖环境有点。。。),这个要抽一波,不然用起来,小难受;
21 | - [x] 实现静态界面(还是不大会多窗口模式,估计会照旧采用Widget的显示来模拟多页面,也可能还是单页);
22 | - [x] 常用配置本地缓存(文件),就我最近的使用体验来看,不想再输密钥了;
23 | - [x] 增加全量和增量翻译模式区分,防止重复翻译,浪费资源;
24 | - [x] 增加批量翻译模式(挂机,一直跑),那个列表有点小烦,后面没有单独放置操作的列;
25 | - [x] 实现srt文件解析和压缩(这块好搞,我感觉都写了好几次了);
26 | - [x] 接入上游翻译服务(如果有什么好的免费的服务,也可以给我说);
27 |
28 | ### 应用预览
29 |
30 | 
31 |
32 | 
33 |
34 |
--------------------------------------------------------------------------------
/assets/images/settings.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/speauty/anto/d537a1ad2b935ec8c6973912e1a5ed919d932ea7/assets/images/settings.jpg
--------------------------------------------------------------------------------
/assets/images/startup.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/speauty/anto/d537a1ad2b935ec8c6973912e1a5ed919d932ea7/assets/images/startup.jpg
--------------------------------------------------------------------------------
/bootstrap/boot.go:
--------------------------------------------------------------------------------
1 | package bootstrap
2 |
3 | import (
4 | "anto/cfg"
5 | "anto/common"
6 | "anto/domain/repository"
7 | "anto/domain/service/translator/ai_baidu"
8 | "anto/domain/service/translator/ali_cloud_mt"
9 | "anto/domain/service/translator/baidu"
10 | "anto/domain/service/translator/caiyunai"
11 | "anto/domain/service/translator/deepl"
12 | "anto/domain/service/translator/deepl_pro"
13 | "anto/domain/service/translator/g_deepl_x"
14 | "anto/domain/service/translator/google_cloud"
15 | "anto/domain/service/translator/huawei_cloud_nlp"
16 | "anto/domain/service/translator/microsoft_edge"
17 | "anto/domain/service/translator/niutrans"
18 | "anto/domain/service/translator/openai"
19 | "anto/domain/service/translator/openai_sweet"
20 | "anto/domain/service/translator/openapi_youdao"
21 | "anto/domain/service/translator/tencent_cloud_mt"
22 | "anto/domain/service/translator/volcengine"
23 | "anto/domain/service/translator/xfyun"
24 | "anto/lib/log"
25 | "context"
26 | )
27 |
28 | func Boot(_ context.Context) {
29 | new(ResourceBuilder).Install()
30 |
31 | if err := cfg.Singleton().Load(); err != nil {
32 | panic(err)
33 | }
34 |
35 | cfg.Singleton().App.Author = common.Author
36 | cfg.Singleton().App.Version = common.Version
37 | log.Singleton()
38 |
39 | cfg.Singleton().UI.Title = cfg.Singleton().NewUITitle()
40 |
41 | huawei_cloud_nlp.API().Init(cfg.Singleton().HuaweiCloudNlp)
42 | baidu.API().Init(cfg.Singleton().Baidu)
43 | tencent_cloud_mt.API().Init(cfg.Singleton().TencentCloudMT)
44 | openapi_youdao.API().Init(cfg.Singleton().OpenAPIYouDao)
45 | ali_cloud_mt.API().Init(cfg.Singleton().AliCloudMT)
46 | caiyunai.API().Init(cfg.Singleton().CaiYunAI)
47 | niutrans.API().Init(cfg.Singleton().Niutrans)
48 | volcengine.API().Init(cfg.Singleton().VolcEngine)
49 | g_deepl_x.API().Init(cfg.Singleton().YouDao)
50 | google_cloud.API().Init(cfg.Singleton().GoogleCloud)
51 | openai.API().Init(cfg.Singleton().OpenAI)
52 | openai_sweet.API().Init(cfg.Singleton().OpenAISweet)
53 | deepl.API().Init(cfg.Singleton().DeepL)
54 | deepl_pro.API().Init(cfg.Singleton().DeepLPro)
55 | ai_baidu.API().Init(cfg.Singleton().AiBaidu)
56 | microsoft_edge.API().Init(cfg.Singleton().MicrosoftEdge)
57 | xfyun.API().Init(cfg.Singleton().XFYun)
58 |
59 | repository.GetTranslators().Register(
60 | huawei_cloud_nlp.API(), baidu.API(),
61 | tencent_cloud_mt.API(), openapi_youdao.API(),
62 | ali_cloud_mt.API(), caiyunai.API(), niutrans.API(),
63 | volcengine.API(), g_deepl_x.API(),
64 | google_cloud.API(), openai.API(), deepl.API(), deepl_pro.API(),
65 | openai_sweet.API(), ai_baidu.API(), microsoft_edge.API(),
66 | xfyun.API(),
67 | )
68 | }
69 |
--------------------------------------------------------------------------------
/bootstrap/resource_builder.go:
--------------------------------------------------------------------------------
1 | package bootstrap
2 |
3 | import (
4 | "anto/cfg"
5 | "anto/resource"
6 | "fmt"
7 | "os"
8 | )
9 |
10 | type ResourceBuilder struct {
11 | }
12 |
13 | func (customRB *ResourceBuilder) Install() {
14 | customRB.installICO()
15 | customRB.installDirLogs()
16 | customRB.installCfgYml()
17 | }
18 |
19 | func (customRB *ResourceBuilder) installDirLogs() {
20 | dirname := "logs"
21 | fd, err := os.Stat(dirname)
22 | if err == nil && (fd != nil && fd.IsDir()) {
23 | return
24 | }
25 | if err := os.Mkdir(dirname, os.ModePerm); err != nil {
26 | panic(fmt.Errorf("创建日志目录失败(如果存在相应logs文件, 请手动处理), 错误: %s", err))
27 | }
28 | }
29 |
30 | func (customRB *ResourceBuilder) installCfgYml() {
31 | filename := "cfg.yml"
32 | _, err := os.Stat(filename)
33 | if err == nil {
34 | return
35 | }
36 | fd, err := os.Create(filename)
37 | if err != nil {
38 | panic(fmt.Errorf("创建配置文件失败, 错误: %s", err))
39 | }
40 | fd.Close()
41 |
42 | if err := cfg.Singleton().InitConfig(); err != nil {
43 | panic(fmt.Errorf("写入默认配置失败, 错误: %s", err))
44 | }
45 | }
46 |
47 | func (customRB *ResourceBuilder) installICO() {
48 | filename := "favicon.ico"
49 | _, err := os.Stat(filename)
50 | if err == nil {
51 | return
52 | }
53 | fd, err := os.Create(filename)
54 | if err != nil {
55 | panic(fmt.Errorf("创建图标文件失败, 错误: %s", err))
56 | }
57 |
58 | if _, err = fd.Write(resource.Favicon); err != nil {
59 | panic(fmt.Errorf("写入图标文件失败, 错误: %s", err))
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/cfg.yml:
--------------------------------------------------------------------------------
1 | huawei_cloud_nlp: # 华为云机器翻译
2 | ak_id: "" # Access Key
3 | sk_key: "" # Secret Access Key
4 | region: "cn-north-4" # 当前接口开发的区域, 目前仅支持华北-北京四终端节点
5 | project_id: "" # 项目ID
6 | max_single_text_length: 2000 # 单次翻译最大长度
7 |
8 | ling_va:
9 | data_id: "3qnDcUVykFKnSC3cdRX2t" # 数据ID
10 | max_single_text_length: 1000 # 单次翻译最大长度
11 |
12 | baidu:
13 | app_id: "" # 应用ID
14 | app_key: "" # 应用密钥
15 | max_single_text_length: 1000 # 单次翻译最大长度
16 |
17 | tencent_cloud_mt: # 腾讯云机器翻译
18 | secret_id: "" # 密钥ID 用于标识接口调用者身份
19 | secret_key: "" # 密钥关键字 用于验证接口调用者的身份
20 | max_single_text_length: 2000 # 单次翻译最大长度
21 |
22 | openapi_youdao: # 有道智云翻译
23 | app_key: "" # 应用ID
24 | app_secret: "" # 应用密钥
25 | max_single_text_length: 5000 # 单次翻译最大长度
26 |
27 | ali_cloud_mt: # 阿里云翻译
28 | ak_id: "" # 应用ID
29 | ak_secret: "" # 应用密钥
30 | region: "" # 区域
31 | max_single_text_length: 3000 # 单次翻译最大长度
32 |
33 | caiyun_ai: # 彩云小翻译
34 | token: "" # 密钥
35 | max_single_text_length: 5000 # 单次翻译最大长度
36 |
37 | niutrans:
38 | app_key:
39 |
40 | volc_engine:
41 | access_key:
42 | secret_key:
--------------------------------------------------------------------------------
/cfg/app.go:
--------------------------------------------------------------------------------
1 | package cfg
2 |
3 | import (
4 | _const "anto/common"
5 | )
6 |
7 | type App struct {
8 | Author string `mapstructure:"author"`
9 | Version string `mapstructure:"version"`
10 | }
11 |
12 | func (customA App) Default() *App {
13 | return &App{
14 | Author: _const.Author,
15 | Version: _const.Version,
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/cfg/cfg.go:
--------------------------------------------------------------------------------
1 | package cfg
2 |
3 | import (
4 | "anto/domain/service/translator/ai_baidu"
5 | "anto/domain/service/translator/ali_cloud_mt"
6 | "anto/domain/service/translator/baidu"
7 | "anto/domain/service/translator/caiyunai"
8 | "anto/domain/service/translator/deepl"
9 | "anto/domain/service/translator/deepl_pro"
10 | "anto/domain/service/translator/g_deepl_x"
11 | "anto/domain/service/translator/google_cloud"
12 | "anto/domain/service/translator/huawei_cloud_nlp"
13 | "anto/domain/service/translator/ling_va"
14 | "anto/domain/service/translator/microsoft_edge"
15 | "anto/domain/service/translator/niutrans"
16 | "anto/domain/service/translator/openai"
17 | "anto/domain/service/translator/openai_sweet"
18 | "anto/domain/service/translator/openapi_youdao"
19 | "anto/domain/service/translator/tencent_cloud_mt"
20 | "anto/domain/service/translator/volcengine"
21 | "anto/domain/service/translator/xfyun"
22 | "anto/domain/service/translator/youdao"
23 | "anto/platform/win/ui"
24 | "fmt"
25 | "github.com/spf13/viper"
26 | "sync"
27 | )
28 |
29 | var (
30 | apiSingleton *Cfg
31 | onceSingleton sync.Once
32 | )
33 |
34 | func Singleton() *Cfg {
35 | onceSingleton.Do(func() {
36 | apiSingleton = new(Cfg)
37 | apiSingleton.App = App{}.Default()
38 | apiSingleton.UI = ui.Cfg{}.Default()
39 | apiSingleton.HuaweiCloudNlp = new(huawei_cloud_nlp.Config).Default().(*huawei_cloud_nlp.Config)
40 | apiSingleton.LingVA = new(ling_va.Config).Default().(*ling_va.Config)
41 | apiSingleton.Baidu = new(baidu.Config).Default().(*baidu.Config)
42 | apiSingleton.TencentCloudMT = new(tencent_cloud_mt.Config).Default().(*tencent_cloud_mt.Config)
43 | apiSingleton.OpenAPIYouDao = new(openapi_youdao.Config).Default().(*openapi_youdao.Config)
44 | apiSingleton.AliCloudMT = new(ali_cloud_mt.Config).Default().(*ali_cloud_mt.Config)
45 | apiSingleton.CaiYunAI = new(caiyunai.Config).Default().(*caiyunai.Config)
46 | apiSingleton.Niutrans = new(niutrans.Config).Default().(*niutrans.Config)
47 | apiSingleton.VolcEngine = new(volcengine.Config).Default().(*volcengine.Config)
48 | apiSingleton.YouDao = new(youdao.Config).Default().(*youdao.Config)
49 | apiSingleton.GoogleCloud = new(google_cloud.Config).Default().(*google_cloud.Config)
50 | apiSingleton.OpenAI = new(openai.Config).Default().(*openai.Config)
51 | apiSingleton.OpenAISweet = new(openai_sweet.Config).Default().(*openai_sweet.Config)
52 | apiSingleton.DeepL = new(deepl.Config).Default().(*deepl.Config)
53 | apiSingleton.DeepLPro = new(deepl_pro.Config).Default().(*deepl_pro.Config)
54 | apiSingleton.AiBaidu = new(ai_baidu.Config).Default().(*ai_baidu.Config)
55 | apiSingleton.MicrosoftEdge = new(microsoft_edge.Config).Default().(*microsoft_edge.Config)
56 | apiSingleton.XFYun = new(xfyun.Config).Default().(*xfyun.Config)
57 | })
58 | return apiSingleton
59 | }
60 |
61 | type Cfg struct {
62 | App *App `mapstructure:"-"`
63 | UI *ui.Cfg `mapstructure:"-"`
64 | HuaweiCloudNlp *huawei_cloud_nlp.Config `mapstructure:"huawei_cloud_nlp"`
65 | LingVA *ling_va.Config `mapstructure:"ling_va"`
66 | Baidu *baidu.Config `mapstructure:"baidu"`
67 | TencentCloudMT *tencent_cloud_mt.Config `mapstructure:"tencent_cloud_mt"`
68 | OpenAPIYouDao *openapi_youdao.Config `mapstructure:"openapi_youdao"`
69 | AliCloudMT *ali_cloud_mt.Config `mapstructure:"ali_cloud_mt"`
70 | CaiYunAI *caiyunai.Config `mapstructure:"caiyun_ai"`
71 | Niutrans *niutrans.Config `mapstructure:"niutrans"`
72 | VolcEngine *volcengine.Config `mapstructure:"volc_engine"`
73 | YouDao *youdao.Config `mapstructure:"youdao"`
74 | GDeeplX *g_deepl_x.Config `mapstructure:"g_deepl_x"`
75 | GoogleCloud *google_cloud.Config `mapstructure:"google_cloud"`
76 | OpenAI *openai.Config `mapstructure:"openai"`
77 | OpenAISweet *openai_sweet.Config `mapstructure:"openai_sweet"`
78 | DeepL *deepl.Config `mapstructure:"deepl"`
79 | DeepLPro *deepl_pro.Config `mapstructure:"deepl_pro"`
80 | AiBaidu *ai_baidu.Config `mapstructure:"ai_baidu"`
81 | MicrosoftEdge *microsoft_edge.Config `mapstructure:"microsoft_edge"`
82 | XFYun *xfyun.Config `mapstructure:"xfyun"`
83 |
84 | currentViper *viper.Viper `mapstructure:"-"`
85 | }
86 |
87 | func (customC *Cfg) NewUITitle() string {
88 | return fmt.Sprintf(
89 | "%s-%s speauty@163.com",
90 | customC.UI.Title, customC.App.Version,
91 | )
92 | }
93 |
--------------------------------------------------------------------------------
/cfg/opts.go:
--------------------------------------------------------------------------------
1 | package cfg
2 |
3 | import (
4 | "fmt"
5 | "github.com/spf13/viper"
6 | )
7 |
8 | func (customC *Cfg) GetViper() *viper.Viper {
9 | if customC.currentViper == nil {
10 | customC.currentViper = viper.New()
11 | customC.currentViper.AddConfigPath("./")
12 | customC.currentViper.SetConfigType("yml")
13 | customC.currentViper.SetConfigName("cfg")
14 | }
15 | return customC.currentViper
16 | }
17 |
18 | func (customC *Cfg) Load() error {
19 | currentViper := customC.GetViper()
20 | currentViper.AutomaticEnv()
21 |
22 | if err := currentViper.ReadInConfig(); err != nil {
23 | return fmt.Errorf("当前配置加载失败, 错误: %s", err)
24 | }
25 |
26 | if err := currentViper.Unmarshal(customC); err != nil {
27 | return fmt.Errorf("当前配置解析失败, 错误: %s", err)
28 | }
29 | return nil
30 | }
31 |
32 | func (customC *Cfg) InitConfig() error {
33 | currentViper := customC.GetViper()
34 |
35 | _ = customC.HuaweiCloudNlp.SyncDisk(currentViper)
36 | _ = customC.LingVA.SyncDisk(currentViper)
37 | _ = customC.Baidu.SyncDisk(currentViper)
38 | _ = customC.TencentCloudMT.SyncDisk(currentViper)
39 | _ = customC.OpenAPIYouDao.SyncDisk(currentViper)
40 | _ = customC.AliCloudMT.SyncDisk(currentViper)
41 | _ = customC.CaiYunAI.SyncDisk(currentViper)
42 | _ = customC.Niutrans.SyncDisk(currentViper)
43 | _ = customC.VolcEngine.SyncDisk(currentViper)
44 | _ = customC.YouDao.SyncDisk(currentViper)
45 | _ = customC.AiBaidu.SyncDisk(currentViper)
46 | _ = customC.MicrosoftEdge.SyncDisk(currentViper)
47 | _ = customC.XFYun.SyncDisk(currentViper)
48 |
49 | return customC.Sync()
50 | }
51 |
52 | func (customC *Cfg) Sync() error {
53 | return customC.GetViper().WriteConfig()
54 | }
55 |
--------------------------------------------------------------------------------
/cmd/anto/anto.manifest:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | PerMonitorV2, PerMonitor
12 | True
13 |
14 |
15 |
--------------------------------------------------------------------------------
/cmd/anto/app.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "anto/bootstrap"
5 | "anto/platform/win"
6 | "context"
7 | )
8 |
9 | func main() {
10 | ctx := context.Background()
11 | bootstrap.Boot(ctx)
12 | win.Run(ctx)
13 | }
14 |
--------------------------------------------------------------------------------
/cmd/anto/rsrc.syso:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/speauty/anto/d537a1ad2b935ec8c6973912e1a5ed919d932ea7/cmd/anto/rsrc.syso
--------------------------------------------------------------------------------
/cmd/sandbox/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/imroc/req/v3"
5 | )
6 |
7 | func main() {
8 | testClient := req.C()
9 | testClient.SetProxyURL("sock5://hello:1415456@165.154.3.235:4532")
10 |
11 | res, err := testClient.R().Get("https://www.google.com")
12 | if err != nil {
13 | panic(err)
14 | }
15 | print(res.Status)
16 | }
17 |
--------------------------------------------------------------------------------
/common/combo_box.go:
--------------------------------------------------------------------------------
1 | package common
2 |
3 | type StdComboBoxModel struct {
4 | Key string
5 | Name string
6 | }
7 |
8 | func (customSTD *StdComboBoxModel) BindKey() string {
9 | return "Key"
10 | }
11 |
12 | func (customSTD *StdComboBoxModel) DisplayKey() string {
13 | return "Name"
14 | }
15 |
--------------------------------------------------------------------------------
/common/const.go:
--------------------------------------------------------------------------------
1 | package common
2 |
3 | const (
4 | AppName = "anto"
5 | Author = "speauty"
6 | Version = "v3.7.0"
7 | DownloadLatestVersionUrl = "http://kodo.app.speauty.cn/anto-latest-windows.exe"
8 |
9 | GoUidLen = 8
10 |
11 | UITitle = "anto"
12 | UIIcon = "favicon.ico"
13 | UIResourceDir = "./"
14 | )
15 |
--------------------------------------------------------------------------------
/common/type.go:
--------------------------------------------------------------------------------
1 | package common
2 |
3 | const (
4 | ModeFull TranslateMode = "全量翻译"
5 | ModeDelta TranslateMode = "增量翻译"
6 | )
7 |
8 | var modes = []TranslateMode{ModeFull, ModeDelta}
9 |
10 | type TranslateMode string
11 |
12 | func (customType TranslateMode) String() string {
13 | return string(customType)
14 | }
15 |
16 | func (customType TranslateMode) GetModes() []string { // ComboBox不支持typedef么?
17 | var strModes []string
18 | for _, mode := range modes {
19 | strModes = append(strModes, mode.String())
20 | }
21 | return strModes
22 | }
23 |
24 | func (customType TranslateMode) GetIdx() int {
25 | for idx, mode := range modes {
26 | if customType == mode {
27 | return idx
28 | }
29 | }
30 | return 0
31 | }
32 |
33 | const (
34 | LangDirectionFrom LangDirection = "来源语种"
35 | LangDirectionTo LangDirection = "目标语种"
36 | )
37 |
38 | var langDirectionTypes = []LangDirection{LangDirectionFrom, LangDirectionTo}
39 |
40 | type LangDirection string
41 |
42 | func (customType LangDirection) String() string {
43 | return string(customType)
44 | }
45 |
46 | func (customType LangDirection) GetDirections() []string { // ComboBox不支持typedef么?
47 | var strLangDirections []string
48 | for _, langDirection := range langDirectionTypes {
49 | strLangDirections = append(strLangDirections, langDirection.String())
50 | }
51 | return strLangDirections
52 | }
53 |
54 | func (customType LangDirection) GetIdx() int {
55 | for idx, langDirection := range langDirectionTypes {
56 | if customType == langDirection {
57 | return idx
58 | }
59 | }
60 | return 0
61 | }
62 |
--------------------------------------------------------------------------------
/cron/detector/data.go:
--------------------------------------------------------------------------------
1 | package detector
2 |
3 | import (
4 | "anto/common"
5 | "anto/cron/reader"
6 | "anto/cron/translate"
7 | "anto/domain/service/translator"
8 | )
9 |
10 | type StrDetectorData struct {
11 | Translator translator.ImplTranslator
12 | FromLang string
13 | ToLang string
14 | TranslateMode common.TranslateMode
15 | MainTrackReport common.LangDirection
16 | SrtFile string
17 | SrtDir string
18 | FlagTrackExport int
19 | }
20 |
21 | func (customData StrDetectorData) toReaderData(filePath string) *reader.SrtReaderData {
22 | return &reader.SrtReaderData{
23 | FilePath: filePath,
24 | PtrTranslatorOpts: &translate.SrtTranslateOpts{
25 | Translator: customData.Translator,
26 | FromLang: customData.FromLang, ToLang: customData.ToLang,
27 | TranslateMode: customData.TranslateMode, MainTrackReport: customData.MainTrackReport,
28 | FlagTrackExport: customData.FlagTrackExport,
29 | },
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/cron/detector/srt_detector.go:
--------------------------------------------------------------------------------
1 | // Package detector 检测器
2 | package detector
3 |
4 | import (
5 | _type "anto/common"
6 | "anto/cron"
7 | "anto/cron/reader"
8 | "anto/lib/log"
9 | "anto/lib/util"
10 | "context"
11 | "fmt"
12 | "io/fs"
13 | "path/filepath"
14 | "runtime"
15 | "strings"
16 | "sync"
17 |
18 | "github.com/golang-module/carbon"
19 | )
20 |
21 | const (
22 | cronName = "SRT检测程序"
23 | numChanData = 10
24 | numChanMsg = 20
25 | numCoroutine = 5
26 | )
27 |
28 | var (
29 | apiSingleton *SrtDetector
30 | onceSingleton sync.Once
31 | )
32 |
33 | func Singleton() *SrtDetector {
34 | onceSingleton.Do(func() {
35 | apiSingleton = new(SrtDetector)
36 | apiSingleton.init()
37 | })
38 | return apiSingleton
39 | }
40 |
41 | type SrtDetector struct {
42 | ctx context.Context
43 | ctxFnCancel context.CancelFunc
44 | chanData chan *StrDetectorData
45 | chanMsg chan string
46 | chanMsgRedirect chan string
47 | numCoroutine int
48 | }
49 |
50 | func (customCron *SrtDetector) Push(data *StrDetectorData) {
51 | customCron.chanData <- data
52 | }
53 |
54 | func (customCron *SrtDetector) Run(ctx context.Context, fnCancel context.CancelFunc) {
55 | customCron.ctx = ctx
56 | customCron.ctxFnCancel = fnCancel
57 |
58 | customCron.jobDetector()
59 | customCron.jobMsg()
60 | }
61 |
62 | func (customCron *SrtDetector) Close() {}
63 |
64 | func (customCron *SrtDetector) SetMsgRedirect(chanMsg chan string) {
65 | customCron.chanMsgRedirect = chanMsg
66 | }
67 |
68 | func (customCron *SrtDetector) jobDetector() {
69 | if customCron.numCoroutine <= 0 {
70 | customCron.log().WarnF("%s-%s通道的最大数量(%d)无效, 重置为5", cronName, "chanData", customCron.numCoroutine)
71 | customCron.numCoroutine = 5
72 | }
73 |
74 | for idx := 0; idx < customCron.numCoroutine; idx++ {
75 | go func(ctx context.Context, chanDetector chan *StrDetectorData, chanMsg chan string, idx int) {
76 | coroutineName := fmt.Sprintf("检测协程(%d)", idx)
77 | for {
78 | select {
79 | case <-ctx.Done():
80 | customCron.log().WarnF("%s关闭(ctx.done), %s被迫退出", cronName, coroutineName)
81 | runtime.Goexit()
82 | case currentData, isOpen := <-chanDetector:
83 | timeStart := carbon.Now()
84 | if !isOpen {
85 | customCron.log().WarnF("%s-通道关闭, %s被迫退出", cronName, coroutineName)
86 | runtime.Goexit()
87 | }
88 | if currentData.SrtFile != "" {
89 | if !strings.Contains(currentData.SrtFile, _type.AppName) && len(currentData.SrtFile) > 4 && currentData.SrtFile[len(currentData.SrtFile)-4:] == ".srt" {
90 | reader.Singleton().Push(currentData.toReaderData(currentData.SrtFile))
91 | chanMsg <- fmt.Sprintf("检测到文件(%s), 耗时(s): %d", currentData.SrtFile, util.GetSecondsFromTime(timeStart))
92 | }
93 | }
94 | if currentData.SrtDir == "" {
95 | continue
96 | }
97 | _ = filepath.Walk(currentData.SrtDir, func(path string, info fs.FileInfo, err error) error {
98 | if info.IsDir() {
99 | if path != currentData.SrtDir {
100 | return filepath.SkipDir
101 | }
102 | return nil
103 | }
104 | if !util.IsSrtFile(path) || strings.Contains(path, _type.AppName) || info.Size() == 0 {
105 | return nil
106 | }
107 | reader.Singleton().Push(currentData.toReaderData(path))
108 | chanMsg <- fmt.Sprintf("检测到文件(%s), 耗时(s): %d", path, util.GetSecondsFromTime(timeStart))
109 | return nil
110 | })
111 | }
112 | }
113 | }(customCron.ctx, customCron.chanData, customCron.chanMsg, idx)
114 | }
115 | }
116 |
117 | func (customCron *SrtDetector) jobMsg() {
118 | cron.FuncSrtCronMsgRedirect(customCron.ctx, cronName, customCron.log(), customCron.chanMsg, customCron.chanMsgRedirect)
119 | }
120 |
121 | func (customCron *SrtDetector) init() {
122 | customCron.chanData = make(chan *StrDetectorData, numChanData)
123 | customCron.chanMsg = make(chan string, numChanMsg)
124 | customCron.numCoroutine = numCoroutine
125 | }
126 |
127 | func (customCron *SrtDetector) log() *log.Log {
128 | return log.Singleton()
129 | }
130 |
--------------------------------------------------------------------------------
/cron/func.go:
--------------------------------------------------------------------------------
1 | package cron
2 |
3 | import (
4 | "anto/lib/log"
5 | "anto/lib/util"
6 | "context"
7 | "fmt"
8 | "runtime"
9 | )
10 |
11 | func FuncSrtCronMsgRedirect(ctx context.Context, cronName string, ptrLog *log.Log, chanMsg, chanMsgRedirect chan string) {
12 | go func(ctx context.Context, localChanMsg, localChanMsgRedirect chan string) {
13 | coroutineName := "消息协程"
14 | chanName := "chanMsg"
15 | for true {
16 | select {
17 | case <-ctx.Done():
18 | ptrLog.WarnF("%s关闭(ctx.done), %s被迫退出", cronName, coroutineName)
19 | runtime.Goexit()
20 | case currentMsg, isOpen := <-localChanMsg:
21 | if isOpen == false && currentMsg == "" {
22 | ptrLog.WarnF("%s-%s通道关闭, %s被迫退出", cronName, chanName, coroutineName)
23 | runtime.Goexit()
24 | }
25 | if localChanMsgRedirect != nil {
26 | localChanMsgRedirect <- fmt.Sprintf("时间: %s, 来源: %s, 信息: %s", util.GetDateTime(), cronName, currentMsg)
27 | }
28 | }
29 | }
30 | }(ctx, chanMsg, chanMsgRedirect)
31 | }
32 |
--------------------------------------------------------------------------------
/cron/reader/data.go:
--------------------------------------------------------------------------------
1 | package reader
2 |
3 | import (
4 | "anto/cron/translate"
5 | "anto/lib/srt"
6 | )
7 |
8 | type SrtReaderData struct {
9 | FilePath string
10 | PrtSrt *srt.Srt
11 | PtrTranslatorOpts *translate.SrtTranslateOpts
12 | }
13 |
14 | func (customData *SrtReaderData) toTranslateData() *translate.SrtTranslateData {
15 | return &translate.SrtTranslateData{
16 | PrtSrt: customData.PrtSrt,
17 | PtrOpts: customData.PtrTranslatorOpts,
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/cron/reader/srt_reader.go:
--------------------------------------------------------------------------------
1 | package reader
2 |
3 | import (
4 | "anto/cron"
5 | "anto/cron/translate"
6 | "anto/lib/log"
7 | "anto/lib/srt"
8 | "anto/lib/util"
9 | "bytes"
10 | "context"
11 | "fmt"
12 | "github.com/golang-module/carbon"
13 | "io"
14 | "os"
15 | "runtime"
16 | "sync"
17 | )
18 |
19 | const (
20 | cronName = "SRT读取程序"
21 | numChanData = 10
22 | numChanMsg = 20
23 | numCoroutine = 10
24 | )
25 |
26 | var (
27 | apiSingleton *SrtReader
28 | onceSingleton sync.Once
29 | )
30 |
31 | func Singleton() *SrtReader {
32 | onceSingleton.Do(func() {
33 | apiSingleton = new(SrtReader)
34 | apiSingleton.init()
35 | })
36 | return apiSingleton
37 | }
38 |
39 | type SrtReader struct {
40 | ctx context.Context
41 | ctxFnCancel context.CancelFunc
42 | chanData chan *SrtReaderData
43 | chanMsg chan string
44 | chanMsgRedirect chan string
45 | numCoroutine int
46 | }
47 |
48 | func (customCron *SrtReader) Push(data *SrtReaderData) {
49 | customCron.chanData <- data
50 | }
51 |
52 | func (customCron *SrtReader) Run(ctx context.Context, fnCancel context.CancelFunc) {
53 | customCron.ctx = ctx
54 | customCron.ctxFnCancel = fnCancel
55 |
56 | customCron.jobReader()
57 | customCron.jobMsg()
58 | }
59 |
60 | func (customCron *SrtReader) Close() {}
61 |
62 | func (customCron *SrtReader) SetMsgRedirect(chanMsg chan string) {
63 | customCron.chanMsgRedirect = chanMsg
64 | }
65 |
66 | func (customCron *SrtReader) jobReader() {
67 | if customCron.numCoroutine <= 0 {
68 | customCron.log().WarnF("%s-%s通道的最大数量(%d)无效, 重置为5", cronName, "chanData", customCron.numCoroutine)
69 | customCron.numCoroutine = 5
70 | }
71 |
72 | for idx := 0; idx < customCron.numCoroutine; idx++ {
73 | go func(ctx context.Context, chanReader chan *SrtReaderData, chanMsg chan string, idx int) {
74 | coroutineName := fmt.Sprintf("读取协程(%d)", idx)
75 | for true {
76 | select {
77 | case <-ctx.Done():
78 | customCron.log().WarnF("%s关闭(ctx.done), %s被迫退出", cronName, coroutineName)
79 | runtime.Goexit()
80 | case currentData, isOpen := <-chanReader:
81 | timeStart := carbon.Now()
82 | if isOpen == false && currentData == nil {
83 | customCron.log().WarnF("%s-通道关闭, %s被迫退出", cronName, coroutineName)
84 | runtime.Goexit()
85 | }
86 |
87 | if currentData.FilePath == "" || currentData.PtrTranslatorOpts == nil {
88 | chanMsg <- "当前数据包无效, 即将丢弃"
89 | continue
90 | }
91 | fileFd, err := os.Open(currentData.FilePath)
92 | if err != nil {
93 | msg := fmt.Sprintf("打开文件(%s)异常, 错误: %s, 即将丢弃", currentData.FilePath, err)
94 | customCron.log().Error(msg)
95 | chanMsg <- msg
96 | continue
97 | }
98 | bytesRead, err := io.ReadAll(fileFd)
99 | if err != nil {
100 | _ = fileFd.Close()
101 | msg := fmt.Sprintf("读取文件(%s)异常, 错误: %s, 即将丢弃", currentData.FilePath, err)
102 | customCron.log().Error(msg)
103 | chanMsg <- msg
104 | continue
105 | }
106 | _ = fileFd.Close()
107 |
108 | currentData.PrtSrt = new(srt.Srt)
109 | currentData.PrtSrt.FilePath = currentData.FilePath
110 | currentData.PrtSrt.FileSize = len(bytesRead)
111 | currentData.PrtSrt.FileNameSync()
112 |
113 | if err = currentData.PrtSrt.Decode(bytes.NewReader(bytesRead)); err != nil {
114 | msg := fmt.Sprintf("解析文件(%s)异常, 错误: %s, 即将丢弃", currentData.FilePath, err)
115 | customCron.log().Error(msg)
116 | chanMsg <- msg
117 | continue
118 | }
119 |
120 | currentData.PrtSrt.CntBlock = len(currentData.PrtSrt.Blocks)
121 | translate.Singleton().Push(currentData.toTranslateData())
122 |
123 | chanMsg <- fmt.Sprintf(
124 | "读取文件(%s)成功, 文件名: %s, 字幕块: %d, 文件大小: %d, 耗时: %d",
125 | currentData.FilePath, currentData.PrtSrt.FileName, currentData.PrtSrt.CntBlock,
126 | currentData.PrtSrt.FileSize, util.GetSecondsFromTime(timeStart),
127 | )
128 | }
129 | }
130 | }(customCron.ctx, customCron.chanData, customCron.chanMsg, idx)
131 | }
132 | }
133 |
134 | func (customCron *SrtReader) jobMsg() {
135 | cron.FuncSrtCronMsgRedirect(customCron.ctx, cronName, customCron.log(), customCron.chanMsg, customCron.chanMsgRedirect)
136 | }
137 |
138 | func (customCron *SrtReader) init() {
139 | customCron.chanData = make(chan *SrtReaderData, numChanData)
140 | customCron.chanMsg = make(chan string, numChanMsg)
141 | customCron.numCoroutine = numCoroutine
142 | }
143 |
144 | func (customCron *SrtReader) log() *log.Log {
145 | return log.Singleton()
146 | }
147 |
--------------------------------------------------------------------------------
/cron/translate/data.go:
--------------------------------------------------------------------------------
1 | package translate
2 |
3 | import (
4 | _const "anto/common"
5 | "anto/cron/writer"
6 | "anto/domain/service/translator"
7 | "anto/lib/srt"
8 | "fmt"
9 | "path/filepath"
10 | )
11 |
12 | type SrtTranslateData struct {
13 | PrtSrt *srt.Srt
14 | PtrOpts *SrtTranslateOpts
15 | }
16 |
17 | func (customData *SrtTranslateData) toSrtWriterData() *writer.SrtWriterData {
18 | tmpData := &writer.SrtWriterData{
19 | FileNameSaved: customData.fileNameSavedBuilder(),
20 | PrtSrt: customData.PrtSrt,
21 | PtrOpts: &srt.EncodeOpt{
22 | FlagIsInverse: customData.PtrOpts.MainTrackReport == _const.LangDirectionTo,
23 | FlagTrackExport: customData.PtrOpts.FlagTrackExport,
24 | },
25 | }
26 | return tmpData
27 | }
28 |
29 | func (customData *SrtTranslateData) fileNameSavedBuilder() string {
30 | newFileName := customData.PrtSrt.FilePath[0 : len(customData.PrtSrt.FilePath)-4]
31 | newFileName = fmt.Sprintf(
32 | "%s/%s/%s.srt", filepath.Dir(newFileName), _const.AppName, filepath.Base(newFileName),
33 | )
34 | return newFileName
35 | }
36 |
37 | type SrtTranslateOpts struct {
38 | Translator translator.ImplTranslator
39 | FromLang string
40 | ToLang string
41 | TranslateMode _const.TranslateMode
42 | MainTrackReport _const.LangDirection
43 | FlagTrackExport int
44 | }
45 |
--------------------------------------------------------------------------------
/cron/writer/data.go:
--------------------------------------------------------------------------------
1 | package writer
2 |
3 | import "anto/lib/srt"
4 |
5 | type SrtWriterData struct {
6 | FileNameSaved string
7 | PrtSrt *srt.Srt
8 | PtrOpts *srt.EncodeOpt
9 | }
10 |
--------------------------------------------------------------------------------
/cron/writer/srt_writer.go:
--------------------------------------------------------------------------------
1 | package writer
2 |
3 | import (
4 | "anto/cron"
5 | "anto/lib/log"
6 | "anto/lib/util"
7 | "context"
8 | "fmt"
9 | "github.com/golang-module/carbon"
10 | "os"
11 | "path/filepath"
12 | "runtime"
13 | "sync"
14 | )
15 |
16 | const (
17 | cronName = "SRT写入程序"
18 | numChanData = 10
19 | numChanMsg = 20
20 | numCoroutine = 10
21 | )
22 |
23 | var (
24 | apiSingleton *SrtWriter
25 | onceSingleton sync.Once
26 | )
27 |
28 | func Singleton() *SrtWriter {
29 | onceSingleton.Do(func() {
30 | apiSingleton = new(SrtWriter)
31 | apiSingleton.init()
32 | })
33 | return apiSingleton
34 | }
35 |
36 | type SrtWriter struct {
37 | ctx context.Context
38 | ctxFnCancel context.CancelFunc
39 | chanData chan *SrtWriterData
40 | chanMsg chan string
41 | chanMsgRedirect chan string
42 | numCoroutine int
43 | }
44 |
45 | func (customCron *SrtWriter) Push(data *SrtWriterData) {
46 | customCron.chanData <- data
47 | }
48 |
49 | func (customCron *SrtWriter) Run(ctx context.Context, fnCancel context.CancelFunc) {
50 | customCron.ctx = ctx
51 | customCron.ctxFnCancel = fnCancel
52 |
53 | customCron.jobWriter()
54 | customCron.jobMsg()
55 | }
56 |
57 | func (customCron *SrtWriter) Close() {}
58 |
59 | func (customCron *SrtWriter) SetMsgRedirect(chanMsg chan string) {
60 | customCron.chanMsgRedirect = chanMsg
61 | }
62 |
63 | func (customCron *SrtWriter) jobWriter() {
64 | if customCron.numCoroutine <= 0 {
65 | customCron.log().WarnF("%s-%s通道的最大数量(%d)无效, 重置为5", cronName, "chanData", customCron.numCoroutine)
66 | customCron.numCoroutine = 5
67 | }
68 |
69 | for idx := 0; idx < customCron.numCoroutine; idx++ {
70 | go func(ctx context.Context, chanWriter chan *SrtWriterData, chanMsg chan string, idx int) {
71 | coroutineName := fmt.Sprintf("写入协程(%d)", idx)
72 | for true {
73 | select {
74 | case <-ctx.Done():
75 | customCron.log().WarnF("%s关闭(ctx.done), %s被迫退出", cronName, coroutineName)
76 | runtime.Goexit()
77 | case currentData, isOpen := <-chanWriter:
78 | timeStart := carbon.Now()
79 | if isOpen == false && currentData == nil {
80 | customCron.log().WarnF("%s-通道关闭, %s被迫退出", cronName, coroutineName)
81 | runtime.Goexit()
82 | }
83 | if currentData.FileNameSaved == "" {
84 | chanMsg <- fmt.Sprintf("%s未检测到保存文件名称", currentData.PrtSrt.FileName)
85 | continue
86 | }
87 | if currentData.PrtSrt.FlagTranslated == false {
88 | chanMsg <- fmt.Sprintf("%s未进行翻译, 可能选择了增量翻译模式, 源文已全部翻译", currentData.PrtSrt.FileName)
89 | continue
90 | }
91 | bytesEncoded, err := currentData.PrtSrt.Encode(currentData.PtrOpts)
92 | if err != nil {
93 | customCron.log().ErrorF("%s编码失败, 错误: %s", currentData.PrtSrt.FileName, err)
94 | chanMsg <- fmt.Sprintf("%s编码失败", currentData.PrtSrt.FileName)
95 | continue
96 | }
97 |
98 | dirSrt := filepath.Dir(currentData.FileNameSaved)
99 | if err = os.MkdirAll(dirSrt, os.ModePerm); err != nil {
100 | customCron.log().ErrorF("创建目录[%s]失败, 错误: %s", dirSrt, err)
101 | chanMsg <- fmt.Sprintf("%s创建失败", dirSrt)
102 | continue
103 | }
104 |
105 | fd, err := os.OpenFile(currentData.FileNameSaved, os.O_CREATE|os.O_WRONLY, os.ModePerm)
106 | if err != nil {
107 | customCron.log().ErrorF("创建文件[%s]失败, 错误: %s", currentData.FileNameSaved, err)
108 | chanMsg <- fmt.Sprintf("%s创建失败", currentData.FileNameSaved)
109 | _ = fd.Close()
110 | continue
111 | }
112 | if _, err = fd.Write(bytesEncoded); err != nil {
113 | customCron.log().ErrorF("写入文件失败(%s => %s), 错误: %s", currentData.PrtSrt.FileName, currentData.FileNameSaved, err)
114 | chanMsg <- fmt.Sprintf("写入文件失败(%s => %s)", currentData.PrtSrt.FileName, currentData.FileNameSaved)
115 | _ = fd.Close()
116 | continue
117 | }
118 | _ = fd.Close()
119 | chanMsg <- fmt.Sprintf(
120 | "写入文件成功, 源件: %s, 目标文件: %s, 写入字节数: %d, 耗时(s): %d",
121 | currentData.PrtSrt.FileName, currentData.FileNameSaved, len(bytesEncoded),
122 | util.GetSecondsFromTime(timeStart),
123 | )
124 | }
125 | }
126 | }(customCron.ctx, customCron.chanData, customCron.chanMsg, idx)
127 | }
128 | }
129 |
130 | func (customCron *SrtWriter) jobMsg() {
131 | cron.FuncSrtCronMsgRedirect(customCron.ctx, cronName, customCron.log(), customCron.chanMsg, customCron.chanMsgRedirect)
132 | }
133 |
134 | func (customCron *SrtWriter) init() {
135 | customCron.chanData = make(chan *SrtWriterData, numChanData)
136 | customCron.chanMsg = make(chan string, numChanMsg)
137 | customCron.numCoroutine = numCoroutine
138 | }
139 |
140 | func (customCron *SrtWriter) log() *log.Log {
141 | return log.Singleton()
142 | }
143 |
--------------------------------------------------------------------------------
/domain/repository/translator.go:
--------------------------------------------------------------------------------
1 | package repository
2 |
3 | import (
4 | _type "anto/common"
5 | serviceTranslator "anto/domain/service/translator"
6 | "anto/lib/restrictor"
7 | "sort"
8 | "sync"
9 | )
10 |
11 | var (
12 | apiTranslators *Translators
13 | onceTranslators sync.Once
14 | )
15 |
16 | func GetTranslators() *Translators {
17 | onceTranslators.Do(func() {
18 | apiTranslators = new(Translators)
19 | })
20 | return apiTranslators
21 | }
22 |
23 | type Translators struct {
24 | list sync.Map
25 | names []*_type.StdComboBoxModel
26 | namesAll []*_type.StdComboBoxModel
27 | }
28 |
29 | func (customT *Translators) Register(translators ...serviceTranslator.ImplTranslator) {
30 | tmpRestrictor := restrictor.Singleton()
31 | for _, translator := range translators {
32 | if _, isExisted := customT.list.Load(translator.GetId()); isExisted {
33 | continue
34 | }
35 | customT.list.Store(translator.GetId(), translator)
36 | tmpLimiter := tmpRestrictor.Get(translator.GetId())
37 | limited := translator.GetCfg().GetQPS() / 4 * 3 // 缓冲
38 | if limited < 1 {
39 | limited = 1
40 | }
41 | tmpLimiter.SetLimit(1)
42 | tmpLimiter.SetBurst(limited)
43 |
44 | tmpRestrictor.Set(translator.GetId(), tmpLimiter)
45 | }
46 | customT.genNames2ComboBox()
47 | }
48 |
49 | func (customT *Translators) GetById(id string) serviceTranslator.ImplTranslator {
50 | obj, isExisted := customT.list.Load(id)
51 | if !isExisted {
52 | return nil
53 | }
54 | return obj.(serviceTranslator.ImplTranslator)
55 | }
56 |
57 | func (customT *Translators) GetNames() []*_type.StdComboBoxModel {
58 | return customT.names
59 | }
60 |
61 | func (customT *Translators) GetNamesAll() []*_type.StdComboBoxModel {
62 | return customT.namesAll
63 | }
64 |
65 | func (customT *Translators) genNames2ComboBox() {
66 | customT.names = []*_type.StdComboBoxModel{}
67 | customT.namesAll = []*_type.StdComboBoxModel{}
68 | customT.list.Range(func(idx, translator any) bool {
69 | customT.namesAll = append(customT.namesAll, &_type.StdComboBoxModel{
70 | Key: translator.(serviceTranslator.ImplTranslator).GetId(),
71 | Name: translator.(serviceTranslator.ImplTranslator).GetName(),
72 | })
73 | if translator.(serviceTranslator.ImplTranslator).IsValid() {
74 | customT.names = append(customT.names, &_type.StdComboBoxModel{
75 | Key: translator.(serviceTranslator.ImplTranslator).GetId(),
76 | Name: translator.(serviceTranslator.ImplTranslator).GetName(),
77 | })
78 | }
79 | return true
80 | })
81 | sort.Slice(customT.namesAll, func(i, j int) bool {
82 | return customT.namesAll[i].Key < customT.namesAll[j].Key
83 | })
84 | if len(customT.names) > 1 {
85 | sort.Slice(customT.names, func(i, j int) bool {
86 | return customT.names[i].Key < customT.names[j].Key
87 | })
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/domain/service/translator/ai_baidu/config.go:
--------------------------------------------------------------------------------
1 | package ai_baidu
2 |
3 | import (
4 | "anto/domain/service/translator"
5 | "encoding/json"
6 | "fmt"
7 | "github.com/imroc/req/v3"
8 | "github.com/spf13/viper"
9 | "io"
10 | "time"
11 | )
12 |
13 | type Config struct {
14 | *translator.DefaultConfig
15 | ApiKey string `mapstructure:"api_key"` // 应用关键字
16 | SecretKey string `mapstructure:"secret_key"` // 应用密钥
17 | QPS int `mapstructure:"qps"`
18 | MaxCharNum int `mapstructure:"max_single_text_length"`
19 | MaxCoroutineNum int `mapstructure:"max_coroutine_num"`
20 | AccessToken string `mapstructure:"access_token"`
21 | ExpiredAt int64 `mapstructure:"expired_at"`
22 | }
23 |
24 | func (config *Config) Default() translator.ImplConfig {
25 | return &Config{
26 | ApiKey: "", SecretKey: "",
27 | MaxCharNum: 6000, QPS: 10, MaxCoroutineNum: 5,
28 | }
29 | }
30 |
31 | func (config *Config) SyncDisk(currentViper *viper.Viper) error {
32 | tagAndVal := config.JoinAllTagAndValue(API(), config, "mapstructure")
33 |
34 | for tag, val := range tagAndVal {
35 | currentViper.Set(tag, val)
36 | }
37 | return nil
38 | }
39 |
40 | func (config *Config) GetAK() string { return config.ApiKey }
41 | func (config *Config) GetSK() string { return config.SecretKey }
42 |
43 | func (config *Config) GetQPS() int { return config.QPS }
44 | func (config *Config) GetMaxCharNum() int { return config.MaxCharNum }
45 | func (config *Config) GetMaxCoroutineNum() int { return config.MaxCoroutineNum }
46 |
47 | func (config *Config) SetAK(str string) error {
48 | if err := config.ValidatorStr(str); err != nil {
49 | return err
50 | }
51 | config.ApiKey = str
52 | return nil
53 | }
54 | func (config *Config) SetSK(str string) error {
55 | if err := config.ValidatorStr(str); err != nil {
56 | return err
57 | }
58 | config.SecretKey = str
59 | return nil
60 | }
61 |
62 | func (config *Config) SetQPS(num int) error {
63 | if err := config.ValidatorNum(num); err != nil {
64 | return err
65 | }
66 | config.QPS = num
67 | return nil
68 | }
69 |
70 | func (config *Config) SetMaxCharNum(num int) error {
71 | if err := config.ValidatorNum(num); err != nil {
72 | return err
73 | }
74 | config.MaxCharNum = num
75 | return nil
76 | }
77 |
78 | func (config *Config) SetMaxCoroutineNum(num int) error {
79 | if err := config.ValidatorNum(num); err != nil {
80 | return err
81 | }
82 | config.MaxCoroutineNum = num
83 | return nil
84 | }
85 |
86 | func (config *Config) GetAccessToken() (string, error) {
87 | if config.AccessToken == "" || config.ExpiredAt < (time.Now().Unix()-3600) {
88 | tmpUrl := fmt.Sprintf(
89 | "https://aip.baidubce.com/oauth/2.0/token?client_id=%s&client_secret=%s&grant_type=client_credentials",
90 | config.GetAK(), config.GetSK(),
91 | )
92 | resp, err := req.R().Post(tmpUrl)
93 | if err != nil {
94 | return "", err
95 | }
96 | respBytes, err := io.ReadAll(resp.Body)
97 | if err != nil {
98 | return "", fmt.Errorf("读取报文异常, 错误: %s", err.Error())
99 | }
100 | auth := &authResp{}
101 | if err = json.Unmarshal(respBytes, auth); err != nil {
102 | return "", fmt.Errorf("解析报文异常, 错误: %s", err.Error())
103 | }
104 | config.AccessToken = auth.AccessToken
105 | config.ExpiredAt = time.Now().Unix() + auth.ExpiresIn
106 | }
107 |
108 | return config.AccessToken, nil
109 | }
110 |
111 | type authResp struct {
112 | RefreshToken string `json:"refresh_token"`
113 | ExpiresIn int64 `json:"expires_in"`
114 | SessionKey string `json:"session_key"`
115 | AccessToken string `json:"access_token"`
116 | Scope string `json:"scope"`
117 | SessionSecret string `json:"session_secret"`
118 | }
119 |
--------------------------------------------------------------------------------
/domain/service/translator/ai_baidu/lang.go:
--------------------------------------------------------------------------------
1 | package ai_baidu
2 |
3 | import (
4 | "anto/domain/service/translator"
5 | )
6 |
7 | var langSupported = []translator.LangPair{
8 | {"zh", "中文"},
9 | {"en", "英语"},
10 | {"jp", "日语"},
11 | {"kor", "韩语"},
12 | {"fra", "法语"},
13 | {"spa", "西班牙语"},
14 | {"th", "泰语"},
15 | {"ara", "阿拉伯语"},
16 | {"ru", "俄语"},
17 | {"pt", "葡萄牙语"},
18 | {"de", "德语"},
19 | {"it", "意大利语"},
20 | {"el", "希腊语"},
21 | {"nl", "荷兰语"},
22 | {"pl", "波兰语"},
23 | {"bul", "保加利亚语"},
24 | {"est", "爱沙尼亚语"},
25 | {"dan", "丹麦语"},
26 | {"fin", "芬兰语"},
27 | {"cs", "捷克语"},
28 | {"rom", "罗马尼亚语"},
29 | {"slo", "斯洛文尼亚语"},
30 | {"swe", "瑞典语"},
31 | {"hu", "匈牙利语"},
32 | {"vie", "越南语"},
33 | }
34 |
--------------------------------------------------------------------------------
/domain/service/translator/ai_baidu/translator.go:
--------------------------------------------------------------------------------
1 | package ai_baidu
2 |
3 | import (
4 | "anto/domain/service/translator"
5 | "anto/lib/log"
6 | "context"
7 | "encoding/json"
8 | "fmt"
9 | "github.com/golang-module/carbon"
10 | "sync"
11 | )
12 |
13 | var api = "https://aip.baidubce.com/rpc/2.0/mt/texttrans/v1"
14 |
15 | var (
16 | apiTranslator *Translator
17 | onceTranslator sync.Once
18 | )
19 |
20 | func API() *Translator {
21 | onceTranslator.Do(func() {
22 | apiTranslator = New()
23 | })
24 | return apiTranslator
25 | }
26 |
27 | func New() *Translator {
28 | return &Translator{
29 | id: "ai_baidu",
30 | name: "百度智能云",
31 | sep: "\n",
32 | langSupported: langSupported,
33 | }
34 | }
35 |
36 | type Translator struct {
37 | id string
38 | name string
39 | cfg translator.ImplConfig
40 | qps int
41 | procMax int
42 | textMaxLen int
43 | langSupported []translator.LangPair
44 | sep string
45 | }
46 |
47 | func (customT *Translator) Init(cfg translator.ImplConfig) { customT.cfg = cfg }
48 |
49 | func (customT *Translator) GetId() string { return customT.id }
50 | func (customT *Translator) GetShortId() string { return "ai_bd" }
51 | func (customT *Translator) GetName() string { return customT.name }
52 | func (customT *Translator) GetCfg() translator.ImplConfig { return customT.cfg }
53 | func (customT *Translator) GetLangSupported() []translator.LangPair { return customT.langSupported }
54 | func (customT *Translator) GetSep() string { return customT.sep }
55 | func (customT *Translator) IsValid() bool {
56 | return customT.cfg != nil && customT.cfg.GetAK() != "" && customT.cfg.GetSK() != ""
57 | }
58 |
59 | func (customT *Translator) Translate(ctx context.Context, args *translator.TranslateArgs) (*translator.TranslateRes, error) {
60 | timeStart := carbon.Now()
61 | accessToken, err := customT.GetCfg().(*Config).GetAccessToken()
62 | if err != nil {
63 | return nil, err
64 | }
65 | urlQueried := fmt.Sprintf("%s?access_token=%s", api, accessToken)
66 | bodyContent := translateReq{Q: args.TextContent, From: args.FromLang, To: args.ToLang}
67 | respBytes, err := translator.RequestSimpleHttp(ctx, customT, urlQueried, true, bodyContent, nil)
68 | if err != nil {
69 | return nil, err
70 | }
71 | respObj := new(translateResp)
72 | if err = json.Unmarshal(respBytes, respObj); err != nil {
73 | log.Singleton().ErrorF("解析报文异常, 引擎: %s, 错误: %s", customT.GetName(), err)
74 | return nil, fmt.Errorf("解析报文出现异常, 错误: %s", err.Error())
75 | }
76 |
77 | ret := new(translator.TranslateRes)
78 | for _, transBlockArray := range respObj.Result.TransResult {
79 | ret.Results = append(ret.Results, &translator.TranslateResBlock{
80 | Id: transBlockArray.Src,
81 | TextTranslated: transBlockArray.Dst,
82 | })
83 | }
84 |
85 | ret.TimeUsed = int(carbon.Now().DiffInSeconds(timeStart))
86 | return ret, nil
87 | }
88 |
89 | type translateReq struct {
90 | Q string `json:"q"`
91 | From string `json:"from"`
92 | To string `json:"to"`
93 | }
94 |
95 | type translateResp struct {
96 | Result struct {
97 | From string `json:"from"`
98 | TransResult []struct {
99 | Dst string `json:"dst"`
100 | Src string `json:"src"`
101 | } `json:"trans_result"`
102 | To string `json:"to"`
103 | } `json:"result"`
104 | LogId int64 `json:"log_id"`
105 | }
106 |
--------------------------------------------------------------------------------
/domain/service/translator/ali_cloud_mt/config.go:
--------------------------------------------------------------------------------
1 | package ali_cloud_mt
2 |
3 | import (
4 | "anto/domain/service/translator"
5 | "github.com/spf13/viper"
6 | )
7 |
8 | type Config struct {
9 | *translator.DefaultConfig
10 | AKId string `mapstructure:"ak_id"`
11 | AKSecret string `mapstructure:"ak_secret"`
12 | Region string `mapstructure:"region"`
13 | QPS int `mapstructure:"qps"`
14 | MaxCharNum int `mapstructure:"max_single_text_length"`
15 | MaxCoroutineNum int `mapstructure:"max_coroutine_num"`
16 | }
17 |
18 | func (config *Config) Default() translator.ImplConfig {
19 | return &Config{
20 | AKId: "", AKSecret: "", Region: "cn-hangzhou",
21 | MaxCharNum: 3000, QPS: 50, MaxCoroutineNum: 25,
22 | }
23 | }
24 |
25 | func (config *Config) SyncDisk(currentViper *viper.Viper) error {
26 | tagAndVal := config.JoinAllTagAndValue(API(), config, "mapstructure")
27 |
28 | for tag, val := range tagAndVal {
29 | currentViper.Set(tag, val)
30 | }
31 | return nil
32 | }
33 |
34 | func (config *Config) GetAK() string { return config.AKId }
35 | func (config *Config) GetSK() string { return config.AKSecret }
36 | func (config *Config) GetRegion() string {
37 | if config.Region != "" {
38 | return config.Region
39 | }
40 | return "cn-hangzhou"
41 | }
42 |
43 | func (config *Config) GetQPS() int { return config.QPS }
44 | func (config *Config) GetMaxCharNum() int { return config.MaxCharNum }
45 | func (config *Config) GetMaxCoroutineNum() int { return config.MaxCoroutineNum }
46 |
47 | func (config *Config) SetAK(str string) error {
48 | if err := config.ValidatorStr(str); err != nil {
49 | return err
50 | }
51 | config.AKId = str
52 | return nil
53 | }
54 | func (config *Config) SetSK(str string) error {
55 | if err := config.ValidatorStr(str); err != nil {
56 | return err
57 | }
58 | config.AKSecret = str
59 | return nil
60 | }
61 | func (config *Config) SetRegion(str string) error {
62 | if err := config.ValidatorStr(str); err != nil {
63 | return err
64 | }
65 | config.Region = str
66 | return nil
67 | }
68 |
69 | func (config *Config) SetQPS(num int) error {
70 | if err := config.ValidatorNum(num); err != nil {
71 | return err
72 | }
73 | config.QPS = num
74 | return nil
75 | }
76 |
77 | func (config *Config) SetMaxCharNum(num int) error {
78 | if err := config.ValidatorNum(num); err != nil {
79 | return err
80 | }
81 | config.MaxCharNum = num
82 | return nil
83 | }
84 |
85 | func (config *Config) SetMaxCoroutineNum(num int) error {
86 | if err := config.ValidatorNum(num); err != nil {
87 | return err
88 | }
89 | config.MaxCoroutineNum = num
90 | return nil
91 | }
92 |
--------------------------------------------------------------------------------
/domain/service/translator/ali_cloud_mt/lang.go:
--------------------------------------------------------------------------------
1 | package ali_cloud_mt
2 |
3 | import (
4 | "anto/domain/service/translator"
5 | )
6 |
7 | var langSupported = []translator.LangPair{
8 | {"zh", "中文"},
9 | {"en", "英语"},
10 | {"ja", "日语"},
11 | {"ko", "韩语"},
12 | {"ru", "俄语"},
13 | {"fr", "法语"},
14 | {"de", "德语"},
15 | {"ar", "阿拉伯语"},
16 | }
17 |
--------------------------------------------------------------------------------
/domain/service/translator/ali_cloud_mt/translator.go:
--------------------------------------------------------------------------------
1 | package ali_cloud_mt
2 |
3 | import (
4 | "anto/domain/service/translator"
5 | "anto/lib/log"
6 | "anto/lib/restrictor"
7 | "context"
8 | "encoding/json"
9 | "fmt"
10 | "github.com/aliyun/alibaba-cloud-sdk-go/sdk"
11 | "github.com/aliyun/alibaba-cloud-sdk-go/sdk/auth/credentials"
12 | alimt "github.com/aliyun/alibaba-cloud-sdk-go/services/alimt"
13 | "github.com/golang-module/carbon"
14 | "github.com/spf13/cast"
15 | "strings"
16 | "sync"
17 | )
18 |
19 | var (
20 | apiTranslator *Translator
21 | onceTranslator sync.Once
22 | )
23 |
24 | func API() *Translator {
25 | onceTranslator.Do(func() {
26 | apiTranslator = New()
27 | })
28 | return apiTranslator
29 | }
30 |
31 | func New() *Translator {
32 | return &Translator{
33 | id: "ali_cloud_mt",
34 | name: "阿里云",
35 | sep: "\n",
36 | langSupported: langSupported,
37 | isClientOk: false,
38 | }
39 | }
40 |
41 | type Translator struct {
42 | id string
43 | name string
44 | cfg translator.ImplConfig
45 | langSupported []translator.LangPair
46 | sep string
47 | mtClient *alimt.Client
48 | isClientOk bool
49 | }
50 |
51 | func (customT *Translator) Init(cfg translator.ImplConfig) {
52 | customT.cfg = cfg
53 | customT.clientBuilder()
54 | }
55 |
56 | func (customT *Translator) GetId() string { return customT.id }
57 | func (customT *Translator) GetShortId() string { return "ac" }
58 | func (customT *Translator) GetName() string { return customT.name }
59 | func (customT *Translator) GetCfg() translator.ImplConfig { return customT.cfg }
60 | func (customT *Translator) GetLangSupported() []translator.LangPair { return customT.langSupported }
61 | func (customT *Translator) GetSep() string { return customT.sep }
62 | func (customT *Translator) IsValid() bool {
63 | return customT.cfg != nil && customT.cfg.GetAK() != "" && customT.cfg.GetSK() != "" && customT.isClientOk == true
64 | }
65 |
66 | func (customT *Translator) Translate(ctx context.Context, args *translator.TranslateArgs) (*translator.TranslateRes, error) {
67 | timeStart := carbon.Now()
68 | ret := new(translator.TranslateRes)
69 |
70 | texts := strings.Split(args.TextContent, customT.GetSep())
71 | var txtMap []map[int]string
72 | for idx, text := range texts {
73 | if idx%50 == 0 {
74 | txtMap = append(txtMap, map[int]string{})
75 | }
76 | txtMap[idx/50][idx] = text
77 | }
78 | for _, currentTxtBlock := range txtMap {
79 | bytes, _ := json.Marshal(currentTxtBlock)
80 | req := alimt.CreateGetBatchTranslateRequest()
81 | req.Scheme = "https"
82 | req.TargetLanguage = args.ToLang
83 | req.SourceLanguage = args.FromLang
84 | req.ApiType = "translate_standard"
85 | req.FormatType = "text"
86 | req.Scene = "general"
87 | req.SourceText = string(bytes)
88 | if err := restrictor.Singleton().Wait(customT.GetId(), ctx); err != nil {
89 | return nil, fmt.Errorf("限流异常, 错误: %s", err.Error())
90 | }
91 | resp, err := customT.mtClient.GetBatchTranslate(req)
92 | if err != nil {
93 | log.Singleton().ErrorF("引擎: %s, 错误: %s", customT.GetName(), err)
94 | return nil, fmt.Errorf("引擎: %s, 错误: %s", customT.GetName(), err)
95 | }
96 | for _, translated := range resp.TranslatedList {
97 | if translated["code"] != "200" {
98 | log.Singleton().ErrorF("引擎: %s, 错误: %s", customT.GetName(), translated["errorMsg"].(string))
99 | return nil, fmt.Errorf("引擎: %s, 错误: %s", customT.GetName(), translated["errorMsg"].(string))
100 | }
101 | idx := cast.ToInt(translated["index"].(string))
102 | ret.Results = append(ret.Results, &translator.TranslateResBlock{
103 | Id: texts[idx], TextTranslated: translated["translated"].(string),
104 | })
105 | }
106 | }
107 |
108 | ret.TimeUsed = int(carbon.Now().DiffInSeconds(timeStart))
109 | return ret, nil
110 | }
111 |
112 | func (customT *Translator) clientBuilder() {
113 | if customT.cfg == nil || customT.cfg.GetAK() == "" || customT.cfg.GetSK() == "" {
114 | return
115 | }
116 | config := sdk.NewConfig()
117 |
118 | credential := credentials.NewAccessKeyCredential(customT.cfg.GetAK(), customT.cfg.GetSK())
119 | client, err := alimt.NewClientWithOptions(customT.cfg.GetRegion(), config, credential)
120 | if err != nil {
121 | log.Singleton().ErrorF("引擎: %s, 错误: 生成客户端失败(%s)", customT.GetName(), err)
122 | return
123 | }
124 | customT.mtClient = client
125 | customT.isClientOk = true
126 | }
127 |
--------------------------------------------------------------------------------
/domain/service/translator/baidu/config.go:
--------------------------------------------------------------------------------
1 | package baidu
2 |
3 | import (
4 | "anto/domain/service/translator"
5 | "github.com/spf13/viper"
6 | )
7 |
8 | type Config struct {
9 | *translator.DefaultConfig
10 | AppId string `mapstructure:"app_id"` // 应用ID
11 | AppKey string `mapstructure:"app_key"` // 应用密钥
12 | QPS int `mapstructure:"qps"`
13 | MaxCharNum int `mapstructure:"max_single_text_length"`
14 | MaxCoroutineNum int `mapstructure:"max_coroutine_num"`
15 | }
16 |
17 | func (config *Config) Default() translator.ImplConfig {
18 | return &Config{
19 | AppId: "", AppKey: "",
20 | MaxCharNum: 1000, QPS: 1, MaxCoroutineNum: 1,
21 | }
22 | }
23 |
24 | func (config *Config) SyncDisk(currentViper *viper.Viper) error {
25 | tagAndVal := config.JoinAllTagAndValue(API(), config, "mapstructure")
26 |
27 | for tag, val := range tagAndVal {
28 | currentViper.Set(tag, val)
29 | }
30 | return nil
31 | }
32 |
33 | func (config *Config) GetAK() string { return config.AppId }
34 | func (config *Config) GetSK() string { return config.AppKey }
35 |
36 | func (config *Config) GetQPS() int { return config.QPS }
37 | func (config *Config) GetMaxCharNum() int { return config.MaxCharNum }
38 | func (config *Config) GetMaxCoroutineNum() int { return config.MaxCoroutineNum }
39 |
40 | func (config *Config) SetAK(str string) error {
41 | if err := config.ValidatorStr(str); err != nil {
42 | return err
43 | }
44 | config.AppId = str
45 | return nil
46 | }
47 | func (config *Config) SetSK(str string) error {
48 | if err := config.ValidatorStr(str); err != nil {
49 | return err
50 | }
51 | config.AppKey = str
52 | return nil
53 | }
54 |
55 | func (config *Config) SetQPS(num int) error {
56 | if err := config.ValidatorNum(num); err != nil {
57 | return err
58 | }
59 | config.QPS = num
60 | return nil
61 | }
62 |
63 | func (config *Config) SetMaxCharNum(num int) error {
64 | if err := config.ValidatorNum(num); err != nil {
65 | return err
66 | }
67 | config.MaxCharNum = num
68 | return nil
69 | }
70 |
71 | func (config *Config) SetMaxCoroutineNum(num int) error {
72 | if err := config.ValidatorNum(num); err != nil {
73 | return err
74 | }
75 | config.MaxCoroutineNum = num
76 | return nil
77 | }
78 |
--------------------------------------------------------------------------------
/domain/service/translator/baidu/lang.go:
--------------------------------------------------------------------------------
1 | package baidu
2 |
3 | import (
4 | "anto/domain/service/translator"
5 | )
6 |
7 | var langSupported = []translator.LangPair{
8 | {"zh", "中文"},
9 | {"en", "英语"},
10 | {"jp", "日语"},
11 | {"kor", "韩语"},
12 | {"fra", "法语"},
13 | {"spa", "西班牙语"},
14 | {"th", "泰语"},
15 | {"ara", "阿拉伯语"},
16 | {"ru", "俄语"},
17 | {"pt", "葡萄牙语"},
18 | {"de", "德语"},
19 | {"it", "意大利语"},
20 | {"el", "希腊语"},
21 | {"nl", "荷兰语"},
22 | {"pl", "波兰语"},
23 | {"bul", "保加利亚语"},
24 | {"est", "爱沙尼亚语"},
25 | {"dan", "丹麦语"},
26 | {"fin", "芬兰语"},
27 | {"cs", "捷克语"},
28 | {"rom", "罗马尼亚语"},
29 | {"slo", "斯洛文尼亚语"},
30 | {"swe", "瑞典语"},
31 | {"hu", "匈牙利语"},
32 | {"vie", "越南语"},
33 | }
34 |
--------------------------------------------------------------------------------
/domain/service/translator/baidu/translator.go:
--------------------------------------------------------------------------------
1 | package baidu
2 |
3 | import (
4 | "anto/domain/service/translator"
5 | "anto/lib/log"
6 | "anto/lib/util"
7 | "context"
8 | "crypto/md5"
9 | "encoding/json"
10 | "fmt"
11 | "github.com/golang-module/carbon"
12 | "net/url"
13 | "sync"
14 | )
15 |
16 | var api = "https://fanyi-api.baidu.com/api/trans/vip/translate"
17 |
18 | var (
19 | apiTranslator *Translator
20 | onceTranslator sync.Once
21 | )
22 |
23 | func API() *Translator {
24 | onceTranslator.Do(func() {
25 | apiTranslator = New()
26 | })
27 | return apiTranslator
28 | }
29 |
30 | func New() *Translator {
31 | return &Translator{
32 | id: "baidu",
33 | name: "百度翻译",
34 | sep: "\n",
35 | langSupported: langSupported,
36 | }
37 | }
38 |
39 | type Translator struct {
40 | id string
41 | name string
42 | cfg translator.ImplConfig
43 | qps int
44 | procMax int
45 | textMaxLen int
46 | langSupported []translator.LangPair
47 | sep string
48 | }
49 |
50 | func (customT *Translator) Init(cfg translator.ImplConfig) { customT.cfg = cfg }
51 |
52 | func (customT *Translator) GetId() string { return customT.id }
53 | func (customT *Translator) GetShortId() string { return "bd" }
54 | func (customT *Translator) GetName() string { return customT.name }
55 | func (customT *Translator) GetCfg() translator.ImplConfig { return customT.cfg }
56 | func (customT *Translator) GetLangSupported() []translator.LangPair { return customT.langSupported }
57 | func (customT *Translator) GetSep() string { return customT.sep }
58 | func (customT *Translator) IsValid() bool {
59 | return customT.cfg != nil && customT.cfg.GetAK() != "" && customT.cfg.GetSK() != ""
60 | }
61 |
62 | func (customT *Translator) Translate(ctx context.Context, args *translator.TranslateArgs) (*translator.TranslateRes, error) {
63 | timeStart := carbon.Now()
64 | salt := util.Uid()
65 | sign := customT.signBuilder(args.TextContent, salt)
66 | urlQueried := fmt.Sprintf(
67 | "%s?q=%s&from=%s&to=%s&appid=%s&salt=%s&sign=%s", api,
68 | url.QueryEscape(args.TextContent), args.FromLang, args.ToLang,
69 | customT.cfg.GetAK(), salt, sign,
70 | )
71 | respBytes, err := translator.RequestSimpleHttp(ctx, customT, urlQueried, false, nil, nil)
72 | if err != nil {
73 | return nil, err
74 | }
75 | respObj := new(remoteResp)
76 | if err = json.Unmarshal(respBytes, respObj); err != nil {
77 | log.Singleton().ErrorF("解析报文异常, 引擎: %s, 错误: %s", customT.GetName(), err)
78 | return nil, fmt.Errorf("解析报文出现异常, 错误: %s", err.Error())
79 | }
80 | if respObj.ErrorCode != "" && respObj.ErrorCode != "52000" {
81 | log.Singleton().ErrorF("接口响应异常, 引擎: %s, 错误: %s(%s)", customT.GetName(), respObj.ErrorCode, respObj.ErrorMsg)
82 | return nil, fmt.Errorf("翻译异常, 代码: %s, 错误: %s", respObj.ErrorCode, respObj.ErrorMsg)
83 | }
84 |
85 | ret := new(translator.TranslateRes)
86 | for _, transBlockArray := range respObj.Results {
87 | ret.Results = append(ret.Results, &translator.TranslateResBlock{
88 | Id: transBlockArray.Src,
89 | TextTranslated: transBlockArray.Dst,
90 | })
91 | }
92 |
93 | ret.TimeUsed = int(carbon.Now().DiffInSeconds(timeStart))
94 | return ret, nil
95 | }
96 |
97 | func (customT *Translator) signBuilder(strQuery string, salt string) string {
98 | tmpStr := fmt.Sprintf("%s%s%s%s", customT.cfg.GetAK(), strQuery, salt, customT.cfg.GetSK())
99 | tmpMD5 := md5.New()
100 | tmpMD5.Write([]byte(tmpStr))
101 | return fmt.Sprintf("%x", tmpMD5.Sum(nil))
102 | }
103 |
104 | type remoteResp struct {
105 | ErrorCode string `json:"error_code,omitempty"`
106 | ErrorMsg string `json:"error_msg,omitempty"`
107 | From string `json:"from"`
108 | To string `json:"to"`
109 | Results []struct {
110 | Src string `json:"src"`
111 | Dst string `json:"dst"`
112 | } `json:"trans_result"`
113 | }
114 |
--------------------------------------------------------------------------------
/domain/service/translator/caiyunai/config.go:
--------------------------------------------------------------------------------
1 | package caiyunai
2 |
3 | import (
4 | "anto/domain/service/translator"
5 | "github.com/spf13/viper"
6 | )
7 |
8 | type Config struct {
9 | *translator.DefaultConfig
10 | Token string `mapstructure:"token"`
11 | QPS int `mapstructure:"qps"`
12 | MaxCharNum int `mapstructure:"max_single_text_length"`
13 | MaxCoroutineNum int `mapstructure:"max_coroutine_num"`
14 | }
15 |
16 | func (config *Config) Default() translator.ImplConfig {
17 | // 这是官网给出的测试Token, 随便造
18 | return &Config{
19 | Token: "3975l6lr5pcbvidl6jl2",
20 | MaxCharNum: 5000, QPS: 10, MaxCoroutineNum: 5,
21 | }
22 | }
23 |
24 | func (config *Config) SyncDisk(currentViper *viper.Viper) error {
25 | tagAndVal := config.JoinAllTagAndValue(API(), config, "mapstructure")
26 |
27 | for tag, val := range tagAndVal {
28 | currentViper.Set(tag, val)
29 | }
30 | return nil
31 | }
32 |
33 | func (config *Config) GetAK() string { return config.Token }
34 |
35 | func (config *Config) GetQPS() int { return config.QPS }
36 | func (config *Config) GetMaxCharNum() int { return config.MaxCharNum }
37 | func (config *Config) GetMaxCoroutineNum() int { return config.MaxCoroutineNum }
38 |
39 | func (config *Config) SetAK(str string) error {
40 | if err := config.ValidatorStr(str); err != nil {
41 | return err
42 | }
43 | config.Token = str
44 | return nil
45 | }
46 |
47 | func (config *Config) SetQPS(num int) error {
48 | if err := config.ValidatorNum(num); err != nil {
49 | return err
50 | }
51 | config.QPS = num
52 | return nil
53 | }
54 |
55 | func (config *Config) SetMaxCharNum(num int) error {
56 | if err := config.ValidatorNum(num); err != nil {
57 | return err
58 | }
59 | config.MaxCharNum = num
60 | return nil
61 | }
62 |
63 | func (config *Config) SetMaxCoroutineNum(num int) error {
64 | if err := config.ValidatorNum(num); err != nil {
65 | return err
66 | }
67 | config.MaxCoroutineNum = num
68 | return nil
69 | }
70 |
--------------------------------------------------------------------------------
/domain/service/translator/caiyunai/lang.go:
--------------------------------------------------------------------------------
1 | package caiyunai
2 |
3 | import (
4 | "anto/domain/service/translator"
5 | )
6 |
7 | var langSupported = []translator.LangPair{
8 | {"zh", "中文"},
9 | {"en", "英语"},
10 | {"ja", "日语"},
11 | }
12 |
--------------------------------------------------------------------------------
/domain/service/translator/caiyunai/translator.go:
--------------------------------------------------------------------------------
1 | package caiyunai
2 |
3 | import (
4 | "anto/domain/service/translator"
5 | "anto/lib/log"
6 | "anto/lib/util"
7 | "context"
8 | "encoding/json"
9 | "fmt"
10 | "github.com/golang-module/carbon"
11 | "strings"
12 | "sync"
13 | )
14 |
15 | var (
16 | apiTranslator *Translator
17 | onceTranslator sync.Once
18 | api = "http://api.interpreter.caiyunai.com/v1/translator"
19 | )
20 |
21 | func API() *Translator {
22 | onceTranslator.Do(func() {
23 | apiTranslator = New()
24 | })
25 | return apiTranslator
26 | }
27 |
28 | func New() *Translator {
29 | return &Translator{
30 | id: "caiyun_ai",
31 | name: "彩云小译",
32 | sep: "\n",
33 | langSupported: langSupported,
34 | }
35 | }
36 |
37 | type Translator struct {
38 | id string
39 | name string
40 | cfg translator.ImplConfig
41 | langSupported []translator.LangPair
42 | sep string
43 | }
44 |
45 | func (customT *Translator) Init(cfg translator.ImplConfig) { customT.cfg = cfg }
46 |
47 | func (customT *Translator) GetId() string { return customT.id }
48 | func (customT *Translator) GetShortId() string { return "cy" }
49 | func (customT *Translator) GetName() string { return customT.name }
50 | func (customT *Translator) GetCfg() translator.ImplConfig { return customT.cfg }
51 | func (customT *Translator) GetLangSupported() []translator.LangPair { return customT.langSupported }
52 | func (customT *Translator) GetSep() string { return customT.sep }
53 | func (customT *Translator) IsValid() bool { return customT.cfg != nil && customT.cfg.GetAK() != "" }
54 |
55 | func (customT *Translator) Translate(ctx context.Context, args *translator.TranslateArgs) (*translator.TranslateRes, error) {
56 | timeStart := carbon.Now()
57 | texts := strings.Split(args.TextContent, customT.GetSep())
58 | req := new(translateReq)
59 | req.Source = texts
60 | req.RequestId = util.Uid()
61 | req.TransType = fmt.Sprintf("%s2%s", args.FromLang, args.ToLang)
62 |
63 | reqBytes, _ := json.Marshal(req)
64 | respBytes, err := translator.RequestSimpleHttp(ctx, customT, api, true, reqBytes, map[string]string{
65 | "x-authorization": fmt.Sprintf("token %s", customT.cfg.GetAK()),
66 | })
67 | if err != nil {
68 | return nil, err
69 | }
70 |
71 | resp := new(translateResp)
72 | if err = json.Unmarshal(respBytes, resp); err != nil {
73 | log.Singleton().ErrorF("解析报文异常, 引擎: %s, 错误: %s", customT.GetName(), err)
74 | return nil, fmt.Errorf("解析报文出现异常, 错误: %s", err.Error())
75 | }
76 |
77 | if resp.Msg != "" {
78 | log.Singleton().ErrorF("接口响应异常, 引擎: %s, 错误: %s", customT.GetName(), resp.Msg)
79 | return nil, fmt.Errorf("接口响应异常, 引擎: %s, 错误: %s", customT.GetName(), resp.Msg)
80 | }
81 |
82 | if len(texts) != len(resp.Target) {
83 | return nil, translator.ErrSrcAndTgtNotMatched
84 | }
85 |
86 | ret := new(translator.TranslateRes)
87 |
88 | for textIdx, textTarget := range resp.Target {
89 | ret.Results = append(ret.Results, &translator.TranslateResBlock{
90 | Id: texts[textIdx],
91 | TextTranslated: textTarget,
92 | })
93 | }
94 |
95 | ret.TimeUsed = int(carbon.Now().DiffAbsInSeconds(timeStart))
96 | return ret, nil
97 |
98 | }
99 |
100 | type translateReq struct {
101 | Source []string `json:"source"`
102 | TransType string `json:"trans_type"`
103 | RequestId string `json:"request_id"`
104 | }
105 |
106 | type translateResp struct {
107 | Msg string `json:"message,omitempty"`
108 | SrcTgt []string `json:"src_tgt,omitempty"`
109 | Target []string `json:"target,omitempty"`
110 | Confidence float64 `json:"confidence,omitempty"` // 可信度?
111 | Rc int `json:"rc,omitempty"`
112 | }
113 |
--------------------------------------------------------------------------------
/domain/service/translator/config.go:
--------------------------------------------------------------------------------
1 | package translator
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "github.com/spf13/viper"
7 | "reflect"
8 | )
9 |
10 | const (
11 | ConfigInvalidStr string = "nil"
12 | ConfigInvalidInt int = -1
13 | )
14 |
15 | // ImplConfig 引擎配置接口
16 | type ImplConfig interface {
17 | GetAK() string // access-key or app-key or data-id and so on
18 | GetSK() string
19 | GetProjectKey() string
20 | GetRegion() string
21 | GetQPS() int
22 | GetMaxCharNum() int
23 | GetMaxCoroutineNum() int
24 |
25 | SetAK(ak string) error
26 | SetSK(sk string) error
27 | SetProjectKey(projectKey string) error
28 | SetRegion(region string) error
29 | SetQPS(num int) error
30 | SetMaxCharNum(num int) error
31 | SetMaxCoroutineNum(num int) error
32 |
33 | Default() ImplConfig // 输出默认配置
34 | SyncDisk(viper *viper.Viper) error // 同步到磁盘
35 | }
36 |
37 | // DefaultConfig 默认配置结构体, 供具体引擎配置嵌入使用
38 | type DefaultConfig struct{}
39 |
40 | func (defaultConfig *DefaultConfig) GetAK() string { return ConfigInvalidStr }
41 |
42 | func (defaultConfig *DefaultConfig) GetSK() string { return ConfigInvalidStr }
43 |
44 | func (defaultConfig *DefaultConfig) GetProjectKey() string { return ConfigInvalidStr }
45 |
46 | func (defaultConfig *DefaultConfig) GetRegion() string { return ConfigInvalidStr }
47 |
48 | func (defaultConfig *DefaultConfig) GetQPS() int { return ConfigInvalidInt }
49 |
50 | func (defaultConfig *DefaultConfig) GetMaxCharNum() int { return ConfigInvalidInt }
51 |
52 | func (defaultConfig *DefaultConfig) GetMaxCoroutineNum() int { return ConfigInvalidInt }
53 |
54 | func (defaultConfig *DefaultConfig) SetAK(_ string) error { return nil }
55 |
56 | func (defaultConfig *DefaultConfig) SetSK(_ string) error { return nil }
57 |
58 | func (defaultConfig *DefaultConfig) SetProjectKey(_ string) error { return nil }
59 |
60 | func (defaultConfig *DefaultConfig) SetRegion(_ string) error { return nil }
61 |
62 | func (defaultConfig *DefaultConfig) SetQPS(_ int) error { return nil }
63 |
64 | func (defaultConfig *DefaultConfig) SetMaxCharNum(_ int) error { return nil }
65 |
66 | func (defaultConfig *DefaultConfig) SetMaxCoroutineNum(_ int) error { return nil }
67 |
68 | func (defaultConfig *DefaultConfig) Default() ImplConfig { return nil }
69 |
70 | func (defaultConfig *DefaultConfig) SyncDisk(_ *viper.Viper) error {
71 | return errors.New("当前配置暂未实现磁盘同步方法")
72 | }
73 |
74 | func (defaultConfig *DefaultConfig) JoinAllTagAndValue(engine ImplTranslator, config ImplConfig, tagName string) map[string]interface{} {
75 | engineId := engine.GetId()
76 | configType := reflect.TypeOf(config)
77 | configVal := reflect.ValueOf(config)
78 | if configType.Kind() == reflect.Ptr { // 指针不支持
79 | configType = configType.Elem()
80 | }
81 | if configVal.Kind() == reflect.Ptr { // 指针不支持
82 | configVal = configVal.Elem()
83 | }
84 |
85 | result := make(map[string]interface{})
86 |
87 | for i := 0; i < configType.NumField(); i++ {
88 | currentField := configType.Field(i)
89 | // 当前仅支持整型和字符串
90 | if currentField.Type.Kind() != reflect.Int &&
91 | currentField.Type.Kind() != reflect.String {
92 | continue
93 | }
94 | tagVal := currentField.Tag.Get(tagName)
95 | if tagVal == "" || tagVal == "-" {
96 | continue
97 | }
98 | // @todo 可以直接在这里IO的, 但是想了一下还是交给具体配置处理, 毕竟功能还是要分开, 该方法只负责联合标签和具体值
99 | result[fmt.Sprintf("%s.%s", engineId, tagVal)] = configVal.Field(i).Interface()
100 | }
101 | return result
102 | }
103 |
104 | func (defaultConfig *DefaultConfig) ValidatorNum(num int) error {
105 | if num <= 0 {
106 | return errors.New("当前数值无效, 必须大于0")
107 | }
108 | return nil
109 | }
110 |
111 | func (defaultConfig *DefaultConfig) ValidatorStr(str string) error {
112 | if str == ConfigInvalidStr {
113 | return errors.New("当前字符串无效")
114 | }
115 | return nil
116 | }
117 |
--------------------------------------------------------------------------------
/domain/service/translator/deepl/config.go:
--------------------------------------------------------------------------------
1 | package deepl
2 |
3 | import (
4 | "anto/domain/service/translator"
5 | "github.com/spf13/viper"
6 | )
7 |
8 | type Config struct {
9 | *translator.DefaultConfig
10 | ApiKey string `mapstructure:"api_key"`
11 | QPS int `mapstructure:"qps"`
12 | MaxCharNum int `mapstructure:"max_single_text_length"`
13 | MaxCoroutineNum int `mapstructure:"max_coroutine_num"`
14 | }
15 |
16 | func (config *Config) Default() translator.ImplConfig {
17 | return &Config{
18 | ApiKey: "",
19 | MaxCharNum: 1000, QPS: 10, MaxCoroutineNum: 5,
20 | }
21 | }
22 |
23 | func (config *Config) SyncDisk(currentViper *viper.Viper) error {
24 | tagAndVal := config.JoinAllTagAndValue(API(), config, "mapstructure")
25 |
26 | for tag, val := range tagAndVal {
27 | currentViper.Set(tag, val)
28 | }
29 | return nil
30 | }
31 |
32 | func (config *Config) GetAK() string { return config.ApiKey }
33 | func (config *Config) GetQPS() int { return config.QPS }
34 | func (config *Config) GetMaxCharNum() int { return config.MaxCharNum }
35 | func (config *Config) GetMaxCoroutineNum() int { return config.MaxCoroutineNum }
36 |
37 | func (config *Config) SetAK(str string) error {
38 | if err := config.ValidatorStr(str); err != nil {
39 | return err
40 | }
41 | config.ApiKey = str
42 | return nil
43 | }
44 |
45 | func (config *Config) SetQPS(num int) error {
46 | if err := config.ValidatorNum(num); err != nil {
47 | return err
48 | }
49 | config.QPS = num
50 | return nil
51 | }
52 |
53 | func (config *Config) SetMaxCharNum(num int) error {
54 | if err := config.ValidatorNum(num); err != nil {
55 | return err
56 | }
57 | config.MaxCharNum = num
58 | return nil
59 | }
60 |
61 | func (config *Config) SetMaxCoroutineNum(num int) error {
62 | if err := config.ValidatorNum(num); err != nil {
63 | return err
64 | }
65 | config.MaxCoroutineNum = num
66 | return nil
67 | }
68 |
--------------------------------------------------------------------------------
/domain/service/translator/deepl/lang.go:
--------------------------------------------------------------------------------
1 | package deepl
2 |
3 | import (
4 | "anto/domain/service/translator"
5 | )
6 |
7 | var langSupported = []translator.LangPair{
8 | {"ZH", "中文"},
9 | {"EN", "英语"},
10 | {"DE", "德语"},
11 | {"ES", "西班牙语"},
12 | {"FR", "法语"},
13 | {"IT", "意大利语"},
14 | {"PL", "波兰语"},
15 | {"PT", "葡萄牙语"},
16 | {"RU", "俄语"},
17 | {"BG", "保加利亚语"},
18 | {"CS", "捷克语"},
19 | {"DA", "丹麦语"},
20 | {"EL", "希腊语"},
21 | {"EL", "丹麦语"},
22 | {"ET", "爱沙尼亚语"},
23 | {"FI", "芬兰语"},
24 | {"HU", "匈牙利语"},
25 | {"LT", "立陶宛语"},
26 | {"LV", "拉脱维亚语"},
27 | {"RO", "罗马尼亚语"},
28 | {"SK", "斯洛伐克语"},
29 | {"SL", "斯洛文尼亚语"},
30 | {"SV", "瑞典语"},
31 | }
32 |
--------------------------------------------------------------------------------
/domain/service/translator/deepl/translator.go:
--------------------------------------------------------------------------------
1 | package deepl
2 |
3 | import (
4 | "anto/domain/service/translator"
5 | "anto/lib/log"
6 | "context"
7 | "encoding/json"
8 | "fmt"
9 | "github.com/golang-module/carbon"
10 | "strings"
11 | "sync"
12 | )
13 |
14 | var (
15 | apiTranslator *Translator
16 | onceTranslator sync.Once
17 | )
18 |
19 | const (
20 | TRANSLATE_API_FREE string = "https://api-free.deepl.com/v2/translate"
21 | TRANSLATE_API_PRO string = "https://api.deepl.com/v2/translate"
22 | )
23 |
24 | func API() *Translator {
25 | onceTranslator.Do(func() {
26 | apiTranslator = New()
27 | })
28 | return apiTranslator
29 | }
30 |
31 | func New() *Translator {
32 | return &Translator{
33 | id: "deepl",
34 | name: "DeepL",
35 | sep: "\n",
36 | langSupported: langSupported,
37 | }
38 | }
39 |
40 | type Translator struct {
41 | id string
42 | name string
43 | cfg translator.ImplConfig
44 | langSupported []translator.LangPair
45 | sep string
46 | }
47 |
48 | func (customT *Translator) Init(cfg translator.ImplConfig) { customT.cfg = cfg }
49 |
50 | func (customT *Translator) GetId() string { return customT.id }
51 | func (customT *Translator) GetShortId() string { return "dl" }
52 | func (customT *Translator) GetName() string { return customT.name }
53 | func (customT *Translator) GetCfg() translator.ImplConfig { return customT.cfg }
54 | func (customT *Translator) GetLangSupported() []translator.LangPair { return customT.langSupported }
55 | func (customT *Translator) GetSep() string { return customT.sep }
56 | func (customT *Translator) IsValid() bool { return true }
57 |
58 | func (customT *Translator) Translate(ctx context.Context, args *translator.TranslateArgs) (*translator.TranslateRes, error) {
59 | timeStart := carbon.Now()
60 | texts := strings.Split(args.TextContent, customT.GetSep())
61 | req := &translateReq{
62 | Text: texts, SourceLang: args.FromLang, TargetLang: args.ToLang, SplitSentences: "0",
63 | }
64 |
65 | reqBytes, _ := json.Marshal(req)
66 | respBytes, err := translator.RequestSimpleHttp(ctx, customT, TRANSLATE_API_FREE, true, reqBytes, map[string]string{
67 | "Authorization": fmt.Sprintf("DeepL-Auth-Key %s", customT.cfg.GetAK()),
68 | })
69 | if err != nil {
70 | return nil, err
71 | }
72 |
73 | resp := new(translateResp)
74 | if err = json.Unmarshal(respBytes, resp); err != nil {
75 | log.Singleton().ErrorF("解析报文异常, 引擎: %s, 错误: %s", customT.GetName(), err)
76 | return nil, fmt.Errorf("解析报文出现异常, 错误: %s", err.Error())
77 | }
78 |
79 | ret := new(translator.TranslateRes)
80 |
81 | for textIdx, textTarget := range resp.Translations {
82 | ret.Results = append(ret.Results, &translator.TranslateResBlock{
83 | Id: texts[textIdx],
84 | TextTranslated: textTarget.Text,
85 | })
86 | }
87 |
88 | ret.TimeUsed = int(carbon.Now().DiffAbsInSeconds(timeStart))
89 | return ret, nil
90 | }
91 |
92 | type translateReq struct {
93 | Text []string `json:"text"`
94 | SourceLang string `json:"source_lang"`
95 | TargetLang string `json:"target_lang"`
96 | SplitSentences string `json:"split_sentences"`
97 | }
98 |
99 | type translateResp struct {
100 | Translations []struct {
101 | DetectedSourceLanguage string `json:"detected_source_language"`
102 | Text string `json:"text"`
103 | } `json:"translations"`
104 | }
105 |
--------------------------------------------------------------------------------
/domain/service/translator/deepl_pro/config.go:
--------------------------------------------------------------------------------
1 | package deepl_pro
2 |
3 | import (
4 | "anto/domain/service/translator"
5 | "github.com/spf13/viper"
6 | )
7 |
8 | type Config struct {
9 | *translator.DefaultConfig
10 | ApiKey string `mapstructure:"api_key"`
11 | QPS int `mapstructure:"qps"`
12 | MaxCharNum int `mapstructure:"max_single_text_length"`
13 | MaxCoroutineNum int `mapstructure:"max_coroutine_num"`
14 | }
15 |
16 | func (config *Config) Default() translator.ImplConfig {
17 | return &Config{
18 | ApiKey: "",
19 | MaxCharNum: 1000, QPS: 10, MaxCoroutineNum: 5,
20 | }
21 | }
22 |
23 | func (config *Config) SyncDisk(currentViper *viper.Viper) error {
24 | tagAndVal := config.JoinAllTagAndValue(API(), config, "mapstructure")
25 |
26 | for tag, val := range tagAndVal {
27 | currentViper.Set(tag, val)
28 | }
29 | return nil
30 | }
31 |
32 | func (config *Config) GetAK() string { return config.ApiKey }
33 | func (config *Config) GetQPS() int { return config.QPS }
34 | func (config *Config) GetMaxCharNum() int { return config.MaxCharNum }
35 | func (config *Config) GetMaxCoroutineNum() int { return config.MaxCoroutineNum }
36 |
37 | func (config *Config) SetAK(str string) error {
38 | if err := config.ValidatorStr(str); err != nil {
39 | return err
40 | }
41 | config.ApiKey = str
42 | return nil
43 | }
44 |
45 | func (config *Config) SetQPS(num int) error {
46 | if err := config.ValidatorNum(num); err != nil {
47 | return err
48 | }
49 | config.QPS = num
50 | return nil
51 | }
52 |
53 | func (config *Config) SetMaxCharNum(num int) error {
54 | if err := config.ValidatorNum(num); err != nil {
55 | return err
56 | }
57 | config.MaxCharNum = num
58 | return nil
59 | }
60 |
61 | func (config *Config) SetMaxCoroutineNum(num int) error {
62 | if err := config.ValidatorNum(num); err != nil {
63 | return err
64 | }
65 | config.MaxCoroutineNum = num
66 | return nil
67 | }
68 |
--------------------------------------------------------------------------------
/domain/service/translator/deepl_pro/lang.go:
--------------------------------------------------------------------------------
1 | package deepl_pro
2 |
3 | import (
4 | "anto/domain/service/translator"
5 | )
6 |
7 | var langSupported = []translator.LangPair{
8 | {"ZH", "中文"},
9 | {"EN", "英语"},
10 | {"DE", "德语"},
11 | {"ES", "西班牙语"},
12 | {"FR", "法语"},
13 | {"IT", "意大利语"},
14 | {"PL", "波兰语"},
15 | {"PT", "葡萄牙语"},
16 | {"RU", "俄语"},
17 | {"BG", "保加利亚语"},
18 | {"CS", "捷克语"},
19 | {"DA", "丹麦语"},
20 | {"EL", "希腊语"},
21 | {"EL", "丹麦语"},
22 | {"ET", "爱沙尼亚语"},
23 | {"FI", "芬兰语"},
24 | {"HU", "匈牙利语"},
25 | {"LT", "立陶宛语"},
26 | {"LV", "拉脱维亚语"},
27 | {"RO", "罗马尼亚语"},
28 | {"SK", "斯洛伐克语"},
29 | {"SL", "斯洛文尼亚语"},
30 | {"SV", "瑞典语"},
31 | }
32 |
--------------------------------------------------------------------------------
/domain/service/translator/deepl_pro/translator.go:
--------------------------------------------------------------------------------
1 | package deepl_pro
2 |
3 | import (
4 | "anto/domain/service/translator"
5 | "anto/lib/log"
6 | "context"
7 | "encoding/json"
8 | "fmt"
9 | "github.com/golang-module/carbon"
10 | "strings"
11 | "sync"
12 | )
13 |
14 | var (
15 | apiTranslator *Translator
16 | onceTranslator sync.Once
17 | )
18 |
19 | const (
20 | TRANSLATE_API_FREE string = "https://api-free.deepl.com/v2/translate"
21 | TRANSLATE_API_PRO string = "https://api.deepl.com/v2/translate"
22 | )
23 |
24 | func API() *Translator {
25 | onceTranslator.Do(func() {
26 | apiTranslator = New()
27 | })
28 | return apiTranslator
29 | }
30 |
31 | func New() *Translator {
32 | return &Translator{
33 | id: "deepl_pro",
34 | name: "DeepL_PRO",
35 | sep: "\n",
36 | langSupported: langSupported,
37 | }
38 | }
39 |
40 | type Translator struct {
41 | id string
42 | name string
43 | cfg translator.ImplConfig
44 | langSupported []translator.LangPair
45 | sep string
46 | }
47 |
48 | func (customT *Translator) Init(cfg translator.ImplConfig) { customT.cfg = cfg }
49 |
50 | func (customT *Translator) GetId() string { return customT.id }
51 | func (customT *Translator) GetShortId() string { return "dlp" }
52 | func (customT *Translator) GetName() string { return customT.name }
53 | func (customT *Translator) GetCfg() translator.ImplConfig { return customT.cfg }
54 | func (customT *Translator) GetLangSupported() []translator.LangPair { return customT.langSupported }
55 | func (customT *Translator) GetSep() string { return customT.sep }
56 | func (customT *Translator) IsValid() bool { return true }
57 |
58 | func (customT *Translator) Translate(ctx context.Context, args *translator.TranslateArgs) (*translator.TranslateRes, error) {
59 | timeStart := carbon.Now()
60 | texts := strings.Split(args.TextContent, customT.GetSep())
61 | req := &translateReq{
62 | Text: texts, SourceLang: args.FromLang, TargetLang: args.ToLang, SplitSentences: "0",
63 | }
64 |
65 | reqBytes, _ := json.Marshal(req)
66 | respBytes, err := translator.RequestSimpleHttp(ctx, customT, TRANSLATE_API_PRO, true, reqBytes, map[string]string{
67 | "Authorization": fmt.Sprintf("DeepL-Auth-Key %s", customT.cfg.GetAK()),
68 | })
69 | if err != nil {
70 | return nil, err
71 | }
72 |
73 | resp := new(translateResp)
74 | if err = json.Unmarshal(respBytes, resp); err != nil {
75 | log.Singleton().ErrorF("解析报文异常, 引擎: %s, 错误: %s", customT.GetName(), err)
76 | return nil, fmt.Errorf("解析报文出现异常, 错误: %s", err.Error())
77 | }
78 |
79 | ret := new(translator.TranslateRes)
80 |
81 | for textIdx, textTarget := range resp.Translations {
82 | ret.Results = append(ret.Results, &translator.TranslateResBlock{
83 | Id: texts[textIdx],
84 | TextTranslated: textTarget.Text,
85 | })
86 | }
87 |
88 | ret.TimeUsed = int(carbon.Now().DiffAbsInSeconds(timeStart))
89 | return ret, nil
90 | }
91 |
92 | type translateReq struct {
93 | Text []string `json:"text"`
94 | SourceLang string `json:"source_lang"`
95 | TargetLang string `json:"target_lang"`
96 | SplitSentences string `json:"split_sentences"`
97 | }
98 |
99 | type translateResp struct {
100 | Translations []struct {
101 | DetectedSourceLanguage string `json:"detected_source_language"`
102 | Text string `json:"text"`
103 | } `json:"translations"`
104 | }
105 |
--------------------------------------------------------------------------------
/domain/service/translator/errors.go:
--------------------------------------------------------------------------------
1 | package translator
2 |
3 | import "errors"
4 |
5 | var (
6 | ErrSrcAndTgtNotMatched = errors.New("翻译异常, 错误: 原文和译文数量不对等")
7 | )
8 |
--------------------------------------------------------------------------------
/domain/service/translator/g_deepl_x/config.go:
--------------------------------------------------------------------------------
1 | package g_deepl_x
2 |
3 | import (
4 | "anto/domain/service/translator"
5 | "github.com/spf13/viper"
6 | )
7 |
8 | type Config struct {
9 | *translator.DefaultConfig
10 | QPS int `mapstructure:"qps"`
11 | MaxCharNum int `mapstructure:"max_single_text_length"`
12 | MaxCoroutineNum int `mapstructure:"max_coroutine_num"`
13 | }
14 |
15 | func (config *Config) Default() translator.ImplConfig {
16 | return &Config{
17 | MaxCharNum: 1000, QPS: 10, MaxCoroutineNum: 5,
18 | }
19 | }
20 |
21 | func (config *Config) SyncDisk(currentViper *viper.Viper) error {
22 | tagAndVal := config.JoinAllTagAndValue(API(), config, "mapstructure")
23 |
24 | for tag, val := range tagAndVal {
25 | currentViper.Set(tag, val)
26 | }
27 | return nil
28 | }
29 |
30 | func (config *Config) GetQPS() int { return config.QPS }
31 | func (config *Config) GetMaxCharNum() int { return config.MaxCharNum }
32 | func (config *Config) GetMaxCoroutineNum() int { return config.MaxCoroutineNum }
33 |
34 | func (config *Config) SetQPS(num int) error {
35 | if err := config.ValidatorNum(num); err != nil {
36 | return err
37 | }
38 | config.QPS = num
39 | return nil
40 | }
41 |
42 | func (config *Config) SetMaxCharNum(num int) error {
43 | if err := config.ValidatorNum(num); err != nil {
44 | return err
45 | }
46 | config.MaxCharNum = num
47 | return nil
48 | }
49 |
50 | func (config *Config) SetMaxCoroutineNum(num int) error {
51 | if err := config.ValidatorNum(num); err != nil {
52 | return err
53 | }
54 | config.MaxCoroutineNum = num
55 | return nil
56 | }
57 |
--------------------------------------------------------------------------------
/domain/service/translator/g_deepl_x/lang.go:
--------------------------------------------------------------------------------
1 | package g_deepl_x
2 |
3 | import (
4 | "anto/domain/service/translator"
5 | )
6 |
7 | var langSupported = []translator.LangPair{
8 | {"ZH", "中文"},
9 | {"EN", "英语"},
10 | {"DE", "德语"},
11 | {"ES", "西班牙语"},
12 | {"FR", "法语"},
13 | {"IT", "意大利语"},
14 | {"PL", "波兰语"},
15 | {"PT", "葡萄牙语"},
16 | {"RU", "俄语"},
17 | {"BG", "保加利亚语"},
18 | {"CS", "捷克语"},
19 | {"DA", "丹麦语"},
20 | {"EL", "希腊语"},
21 | {"EL", "丹麦语"},
22 | {"ET", "爱沙尼亚语"},
23 | {"FI", "芬兰语"},
24 | {"HU", "匈牙利语"},
25 | {"LT", "立陶宛语"},
26 | {"LV", "拉脱维亚语"},
27 | {"RO", "罗马尼亚语"},
28 | {"SK", "斯洛伐克语"},
29 | {"SL", "斯洛文尼亚语"},
30 | {"SV", "瑞典语"},
31 | }
32 |
--------------------------------------------------------------------------------
/domain/service/translator/g_deepl_x/translator.go:
--------------------------------------------------------------------------------
1 | package g_deepl_x
2 |
3 | import (
4 | "anto/domain/service/translator"
5 | "context"
6 | "github.com/OwO-Network/gdeeplx"
7 | "github.com/golang-module/carbon"
8 | "strings"
9 | "sync"
10 | )
11 |
12 | var (
13 | apiTranslator *Translator
14 | onceTranslator sync.Once
15 | )
16 |
17 | func API() *Translator {
18 | onceTranslator.Do(func() {
19 | apiTranslator = New()
20 | })
21 | return apiTranslator
22 | }
23 |
24 | func New() *Translator {
25 | return &Translator{
26 | id: "g_deepl_x",
27 | name: "GDeeplX",
28 | sep: "\n",
29 | langSupported: langSupported,
30 | }
31 | }
32 |
33 | type Translator struct {
34 | id string
35 | name string
36 | cfg translator.ImplConfig
37 | langSupported []translator.LangPair
38 | sep string
39 | }
40 |
41 | func (customT *Translator) Init(cfg translator.ImplConfig) { customT.cfg = cfg }
42 |
43 | func (customT *Translator) GetId() string { return customT.id }
44 | func (customT *Translator) GetShortId() string { return "gdx" }
45 | func (customT *Translator) GetName() string { return customT.name }
46 | func (customT *Translator) GetCfg() translator.ImplConfig { return customT.cfg }
47 | func (customT *Translator) GetLangSupported() []translator.LangPair { return customT.langSupported }
48 | func (customT *Translator) GetSep() string { return customT.sep }
49 | func (customT *Translator) IsValid() bool { return true }
50 |
51 | func (customT *Translator) Translate(ctx context.Context, args *translator.TranslateArgs) (*translator.TranslateRes, error) {
52 | timeStart := carbon.Now()
53 | result, err := gdeeplx.Translate(args.TextContent, args.FromLang, args.ToLang, 0)
54 | if err != nil {
55 | return nil, err
56 | }
57 | resultParsed := result.(map[string]interface{})
58 | textTranslatedList := strings.Split(resultParsed["data"].(string), customT.sep)
59 | textSourceList := strings.Split(args.TextContent, customT.sep)
60 | if len(textSourceList) != len(textTranslatedList) {
61 | return nil, translator.ErrSrcAndTgtNotMatched
62 | }
63 |
64 | ret := new(translator.TranslateRes)
65 | for textIdx, textSource := range textSourceList {
66 | ret.Results = append(ret.Results, &translator.TranslateResBlock{
67 | Id: textSource,
68 | TextTranslated: textTranslatedList[textIdx],
69 | })
70 | }
71 |
72 | ret.TimeUsed = int(carbon.Now().DiffAbsInSeconds(timeStart))
73 | return ret, nil
74 |
75 | }
76 |
--------------------------------------------------------------------------------
/domain/service/translator/google_cloud/config.go:
--------------------------------------------------------------------------------
1 | package google_cloud
2 |
3 | import (
4 | "anto/domain/service/translator"
5 | "github.com/spf13/viper"
6 | )
7 |
8 | type Config struct {
9 | *translator.DefaultConfig
10 | ApiKey string `mapstructure:"api_key"`
11 | QPS int `mapstructure:"qps"`
12 | MaxCharNum int `mapstructure:"max_single_text_length"`
13 | MaxCoroutineNum int `mapstructure:"max_coroutine_num"`
14 | }
15 |
16 | func (config *Config) Default() translator.ImplConfig {
17 | return &Config{
18 | ApiKey: "",
19 | MaxCharNum: 1000, QPS: 10, MaxCoroutineNum: 5,
20 | }
21 | }
22 |
23 | func (config *Config) SyncDisk(currentViper *viper.Viper) error {
24 | tagAndVal := config.JoinAllTagAndValue(API(), config, "mapstructure")
25 |
26 | for tag, val := range tagAndVal {
27 | currentViper.Set(tag, val)
28 | }
29 | return nil
30 | }
31 |
32 | func (config *Config) GetAK() string { return config.ApiKey }
33 |
34 | func (config *Config) GetQPS() int { return config.QPS }
35 | func (config *Config) GetMaxCharNum() int { return config.MaxCharNum }
36 | func (config *Config) GetMaxCoroutineNum() int { return config.MaxCoroutineNum }
37 |
38 | func (config *Config) SetAK(str string) error {
39 | if err := config.ValidatorStr(str); err != nil {
40 | return err
41 | }
42 | config.ApiKey = str
43 | return nil
44 | }
45 |
46 | func (config *Config) SetQPS(num int) error {
47 | if err := config.ValidatorNum(num); err != nil {
48 | return err
49 | }
50 | config.QPS = num
51 | return nil
52 | }
53 |
54 | func (config *Config) SetMaxCharNum(num int) error {
55 | if err := config.ValidatorNum(num); err != nil {
56 | return err
57 | }
58 | config.MaxCharNum = num
59 | return nil
60 | }
61 |
62 | func (config *Config) SetMaxCoroutineNum(num int) error {
63 | if err := config.ValidatorNum(num); err != nil {
64 | return err
65 | }
66 | config.MaxCoroutineNum = num
67 | return nil
68 | }
69 |
--------------------------------------------------------------------------------
/domain/service/translator/google_cloud/translator.go:
--------------------------------------------------------------------------------
1 | package google_cloud
2 |
3 | import (
4 | "anto/domain/service/translator"
5 | "cloud.google.com/go/translate"
6 | "context"
7 | "errors"
8 | "fmt"
9 | "github.com/golang-module/carbon"
10 | "google.golang.org/api/option"
11 | "strings"
12 | "sync"
13 | )
14 |
15 | var (
16 | apiTranslator *Translator
17 | onceTranslator sync.Once
18 | )
19 |
20 | func API() *Translator {
21 | onceTranslator.Do(func() {
22 | apiTranslator = New()
23 | })
24 | return apiTranslator
25 | }
26 |
27 | func New() *Translator {
28 | return &Translator{
29 | id: "google_cloud",
30 | name: "谷歌云",
31 | sep: "\n",
32 | langSupported: langSupported,
33 | }
34 | }
35 |
36 | type Translator struct {
37 | id string
38 | name string
39 | cfg translator.ImplConfig
40 | langSupported []translator.LangPair
41 | sep string
42 | }
43 |
44 | func (customT *Translator) Init(cfg translator.ImplConfig) { customT.cfg = cfg }
45 |
46 | func (customT *Translator) GetId() string { return customT.id }
47 | func (customT *Translator) GetShortId() string { return "gc" }
48 | func (customT *Translator) GetName() string { return customT.name }
49 | func (customT *Translator) GetCfg() translator.ImplConfig { return customT.cfg }
50 | func (customT *Translator) GetLangSupported() []translator.LangPair { return customT.langSupported }
51 | func (customT *Translator) GetSep() string { return customT.sep }
52 | func (customT *Translator) IsValid() bool { return customT.cfg.GetAK() != "" }
53 |
54 | func (customT *Translator) Translate(ctx context.Context, args *translator.TranslateArgs) (*translator.TranslateRes, error) {
55 | timeStart := carbon.Now()
56 | client, err := translate.NewClient(ctx, option.WithAPIKey(customT.cfg.GetAK()))
57 | if err != nil {
58 | return nil, err
59 | }
60 | fromLangTag := convertLangToTag(args.FromLang)
61 | toLangTag := convertLangToTag(args.ToLang)
62 | if fromLangTag == nil || toLangTag == nil {
63 | return nil, errors.New(fmt.Sprintf("来源语种或目标语种暂不支持"))
64 | }
65 | textRaw := strings.Split(args.TextContent, customT.GetSep())
66 | results, err := client.Translate(ctx, textRaw, *fromLangTag, &translate.Options{
67 | Source: *toLangTag, Format: translate.Text,
68 | })
69 | if err != nil {
70 | return nil, err
71 | }
72 | if len(results) != len(textRaw) {
73 | return nil, translator.ErrSrcAndTgtNotMatched
74 | }
75 |
76 | ret := new(translator.TranslateRes)
77 | for textIdx, textSource := range textRaw {
78 | ret.Results = append(ret.Results, &translator.TranslateResBlock{Id: textSource, TextTranslated: results[textIdx].Text})
79 | }
80 |
81 | ret.TimeUsed = int(carbon.Now().DiffAbsInSeconds(timeStart))
82 | return ret, nil
83 |
84 | }
85 |
86 | type lingVaMTResp struct {
87 | State bool `json:"__N_SSG"`
88 | Props struct {
89 | Type int `json:"type"`
90 | TextTranslated string `json:"translation"`
91 | Params struct {
92 | FromLanguage string `json:"source"`
93 | ToLanguage string `json:"target"`
94 | TextSource string `json:"query"`
95 | } `json:"initial"`
96 | } `json:"pageProps"`
97 | }
98 |
--------------------------------------------------------------------------------
/domain/service/translator/huawei_cloud_nlp/config.go:
--------------------------------------------------------------------------------
1 | package huawei_cloud_nlp
2 |
3 | import (
4 | "anto/domain/service/translator"
5 | "github.com/spf13/viper"
6 | )
7 |
8 | type Config struct {
9 | *translator.DefaultConfig
10 | AKId string `mapstructure:"ak_id"` // Access Key
11 | SkKey string `mapstructure:"sk_key"` // Secret Access Key
12 | Region string `mapstructure:"region"` // 当前接口开发的区域, 目前仅支持华北-北京四终端节点
13 | ProjectId string `mapstructure:"project_id"` // 项目ID
14 | QPS int `mapstructure:"qps"`
15 | MaxCharNum int `mapstructure:"max_single_text_length"`
16 | MaxCoroutineNum int `mapstructure:"max_coroutine_num"`
17 | }
18 |
19 | func (config *Config) Default() translator.ImplConfig {
20 | return &Config{
21 | AKId: "", SkKey: "", Region: "cn-north-4", ProjectId: "",
22 | MaxCharNum: 2000, QPS: 20, MaxCoroutineNum: 10,
23 | }
24 | }
25 |
26 | func (config *Config) SyncDisk(currentViper *viper.Viper) error {
27 | tagAndVal := config.JoinAllTagAndValue(API(), config, "mapstructure")
28 |
29 | for tag, val := range tagAndVal {
30 | currentViper.Set(tag, val)
31 | }
32 | return nil
33 | }
34 |
35 | func (config *Config) GetAK() string { return config.AKId }
36 | func (config *Config) GetSK() string { return config.SkKey }
37 | func (config *Config) GetRegion() string {
38 | if config.Region != "" {
39 | return config.Region
40 | }
41 | return "cn-north-4"
42 | }
43 | func (config *Config) GetProjectKey() string { return config.ProjectId }
44 |
45 | func (config *Config) GetQPS() int { return config.QPS }
46 | func (config *Config) GetMaxCharNum() int { return config.MaxCharNum }
47 | func (config *Config) GetMaxCoroutineNum() int { return config.MaxCoroutineNum }
48 |
49 | func (config *Config) SetAK(str string) error {
50 | if err := config.ValidatorStr(str); err != nil {
51 | return err
52 | }
53 | config.AKId = str
54 | return nil
55 | }
56 |
57 | func (config *Config) SetSK(str string) error {
58 | if err := config.ValidatorStr(str); err != nil {
59 | return err
60 | }
61 | config.SkKey = str
62 | return nil
63 | }
64 |
65 | func (config *Config) SetRegion(str string) error {
66 | if err := config.ValidatorStr(str); err != nil {
67 | return err
68 | }
69 | config.Region = str
70 | return nil
71 | }
72 |
73 | func (config *Config) SetProjectKey(str string) error {
74 | if str != "" {
75 | if err := config.ValidatorStr(str); err != nil {
76 | return err
77 | }
78 | }
79 |
80 | config.ProjectId = str
81 | return nil
82 | }
83 |
84 | func (config *Config) SetQPS(num int) error {
85 | if err := config.ValidatorNum(num); err != nil {
86 | return err
87 | }
88 | config.QPS = num
89 | return nil
90 | }
91 |
92 | func (config *Config) SetMaxCharNum(num int) error {
93 | if err := config.ValidatorNum(num); err != nil {
94 | return err
95 | }
96 | config.MaxCharNum = num
97 | return nil
98 | }
99 |
100 | func (config *Config) SetMaxCoroutineNum(num int) error {
101 | if err := config.ValidatorNum(num); err != nil {
102 | return err
103 | }
104 | config.MaxCoroutineNum = num
105 | return nil
106 | }
107 |
--------------------------------------------------------------------------------
/domain/service/translator/huawei_cloud_nlp/lang.go:
--------------------------------------------------------------------------------
1 | package huawei_cloud_nlp
2 |
3 | import (
4 | "anto/domain/service/translator"
5 | "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/nlp/v2/model"
6 | )
7 |
8 | var langSupported = []translator.LangPair{
9 | {"zh", "中文(简体)"},
10 | {"en", "英语"},
11 | {"ru", "俄语"},
12 | {"fr", "法语"},
13 | {"de", "德语"},
14 | {"ko", "韩语"},
15 | {"ja", "日语"},
16 | {"th", "泰语"},
17 | {"ar", "阿拉伯语"},
18 | {"pt", "葡萄牙语"},
19 | {"tr", "土耳其语"},
20 | {"es", "西班牙语"},
21 | {"vi", "越南语"},
22 | }
23 |
24 | var langTo = map[string]model.TextTranslationReqTo{
25 | "zh": model.GetTextTranslationReqToEnum().ZH,
26 | "en": model.GetTextTranslationReqToEnum().EN,
27 | "ru": model.GetTextTranslationReqToEnum().RU,
28 | "fr": model.GetTextTranslationReqToEnum().FR,
29 | "de": model.GetTextTranslationReqToEnum().DE,
30 | "ko": model.GetTextTranslationReqToEnum().KO,
31 | "ja": model.GetTextTranslationReqToEnum().JA,
32 | "th": model.GetTextTranslationReqToEnum().TH,
33 | "ar": model.GetTextTranslationReqToEnum().AR,
34 | "pt": model.GetTextTranslationReqToEnum().PT,
35 | "tr": model.GetTextTranslationReqToEnum().TR,
36 | "es": model.GetTextTranslationReqToEnum().ES,
37 | "vi": model.GetTextTranslationReqToEnum().VI,
38 | }
39 |
40 | var langFrom = map[string]model.TextTranslationReqFrom{
41 | "zh": model.GetTextTranslationReqFromEnum().ZH,
42 | "en": model.GetTextTranslationReqFromEnum().EN,
43 | "ru": model.GetTextTranslationReqFromEnum().RU,
44 | "fr": model.GetTextTranslationReqFromEnum().FR,
45 | "de": model.GetTextTranslationReqFromEnum().DE,
46 | "ko": model.GetTextTranslationReqFromEnum().KO,
47 | "ja": model.GetTextTranslationReqFromEnum().JA,
48 | "th": model.GetTextTranslationReqFromEnum().TH,
49 | "ar": model.GetTextTranslationReqFromEnum().AR,
50 | "pt": model.GetTextTranslationReqFromEnum().PT,
51 | "tr": model.GetTextTranslationReqFromEnum().TR,
52 | "es": model.GetTextTranslationReqFromEnum().ES,
53 | "vi": model.GetTextTranslationReqFromEnum().VI,
54 | }
55 |
--------------------------------------------------------------------------------
/domain/service/translator/huawei_cloud_nlp/translator.go:
--------------------------------------------------------------------------------
1 | package huawei_cloud_nlp
2 |
3 | import (
4 | "anto/domain/service/translator"
5 | "anto/lib/log"
6 | "anto/lib/restrictor"
7 | "context"
8 | "fmt"
9 | "github.com/golang-module/carbon"
10 | "github.com/huaweicloud/huaweicloud-sdk-go-v3/core/auth/basic"
11 | nlp "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/nlp/v2"
12 | "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/nlp/v2/model"
13 | region "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/nlp/v2/region"
14 | "strings"
15 | "sync"
16 | )
17 |
18 | var (
19 | apiTranslator *Translator
20 | onceTranslator sync.Once
21 | )
22 |
23 | func API() *Translator {
24 | onceTranslator.Do(func() {
25 | apiTranslator = New()
26 | })
27 | return apiTranslator
28 | }
29 |
30 | func New() *Translator {
31 | return &Translator{
32 | id: "huawei_cloud_nlp",
33 | name: "华为云",
34 | sep: "\n",
35 | langSupported: langSupported,
36 | }
37 | }
38 |
39 | type Translator struct {
40 | id string
41 | name string
42 | cfg translator.ImplConfig
43 | langSupported []translator.LangPair
44 | sep string
45 | }
46 |
47 | func (customT *Translator) Init(cfg translator.ImplConfig) {
48 | customT.cfg = cfg
49 | }
50 |
51 | func (customT *Translator) GetId() string { return customT.id }
52 | func (customT *Translator) GetShortId() string { return "hw" }
53 | func (customT *Translator) GetName() string { return customT.name }
54 | func (customT *Translator) GetCfg() translator.ImplConfig { return customT.cfg }
55 | func (customT *Translator) GetLangSupported() []translator.LangPair { return customT.langSupported }
56 | func (customT *Translator) GetSep() string { return customT.sep }
57 |
58 | func (customT *Translator) IsValid() bool {
59 | return customT.cfg != nil &&
60 | customT.cfg.GetAK() != "" && customT.cfg.GetSK() != "" &&
61 | customT.cfg.GetRegion() != ""
62 | }
63 |
64 | func (customT *Translator) Translate(ctx context.Context, args *translator.TranslateArgs) (*translator.TranslateRes, error) {
65 | timeStart := carbon.Now()
66 | ret := new(translator.TranslateRes)
67 |
68 | request := &model.RunTextTranslationRequest{}
69 | sceneTextTranslationReq := model.GetTextTranslationReqSceneEnum().COMMON
70 | request.Body = &model.TextTranslationReq{
71 | Scene: &sceneTextTranslationReq,
72 | To: langTo[args.ToLang],
73 | From: langFrom[args.FromLang],
74 | Text: args.TextContent,
75 | }
76 | if err := restrictor.Singleton().Wait(customT.GetId(), ctx); err != nil {
77 | return nil, fmt.Errorf("限流异常, 错误: %s", err.Error())
78 | }
79 | resp, err := customT.getClient().RunTextTranslation(request)
80 |
81 | if err != nil {
82 | log.Singleton().ErrorF("调用接口失败, 引擎: %s, 错误: %s", customT.GetName(), err)
83 | return nil, fmt.Errorf("调用接口失败(%s)", err)
84 | }
85 | if resp.ErrorCode != nil && *resp.ErrorCode != "" {
86 | log.Singleton().ErrorF("接口响应错误, 引擎: %s, 错误: %s(%s)", customT.GetName(), *resp.ErrorCode, *resp.ErrorMsg)
87 | return nil, fmt.Errorf("响应错误(代码: %s, 错误: %s)", *resp.ErrorCode, *resp.ErrorMsg)
88 | }
89 |
90 | srcTexts := strings.Split(*resp.SrcText, customT.GetSep())
91 | translatedTexts := strings.Split(*resp.TranslatedText, customT.GetSep())
92 | if len(srcTexts) != len(translatedTexts) {
93 | return nil, translator.ErrSrcAndTgtNotMatched
94 | }
95 | for idx, text := range srcTexts {
96 | ret.Results = append(ret.Results, &translator.TranslateResBlock{
97 | Id: text,
98 | TextTranslated: translatedTexts[idx],
99 | })
100 | }
101 |
102 | ret.TimeUsed = int(carbon.Now().DiffAbsInSeconds(timeStart))
103 | return ret, nil
104 |
105 | }
106 |
107 | func (customT *Translator) getAuth() *basic.Credentials {
108 | return basic.NewCredentialsBuilder().WithAk(customT.cfg.GetAK()).WithSk(customT.cfg.GetSK()).Build()
109 | }
110 |
111 | func (customT *Translator) getClient() *nlp.NlpClient {
112 | return nlp.NewNlpClient(
113 | nlp.NlpClientBuilder().
114 | WithRegion(region.ValueOf(customT.cfg.GetRegion())).
115 | WithCredential(customT.getAuth()).
116 | Build(),
117 | )
118 | }
119 |
--------------------------------------------------------------------------------
/domain/service/translator/ling_va/config.go:
--------------------------------------------------------------------------------
1 | package ling_va
2 |
3 | import (
4 | "anto/domain/service/translator"
5 | "github.com/spf13/viper"
6 | )
7 |
8 | type Config struct {
9 | *translator.DefaultConfig
10 | DataId string `mapstructure:"data_id"`
11 | QPS int `mapstructure:"qps"`
12 | MaxCharNum int `mapstructure:"max_single_text_length"`
13 | MaxCoroutineNum int `mapstructure:"max_coroutine_num"`
14 | }
15 |
16 | func (config *Config) Default() translator.ImplConfig {
17 | return &Config{
18 | DataId: "3qnDcUVykFKnSC3cdRX2t",
19 | MaxCharNum: 1000, QPS: 10, MaxCoroutineNum: 5,
20 | }
21 | }
22 |
23 | func (config *Config) SyncDisk(currentViper *viper.Viper) error {
24 | tagAndVal := config.JoinAllTagAndValue(API(), config, "mapstructure")
25 |
26 | for tag, val := range tagAndVal {
27 | currentViper.Set(tag, val)
28 | }
29 | return nil
30 | }
31 |
32 | func (config *Config) GetAK() string { return config.DataId }
33 |
34 | func (config *Config) GetQPS() int { return config.QPS }
35 | func (config *Config) GetMaxCharNum() int { return config.MaxCharNum }
36 | func (config *Config) GetMaxCoroutineNum() int { return config.MaxCoroutineNum }
37 |
38 | func (config *Config) SetAK(str string) error {
39 | if err := config.ValidatorStr(str); err != nil {
40 | return err
41 | }
42 | config.DataId = str
43 | return nil
44 | }
45 |
46 | func (config *Config) SetQPS(num int) error {
47 | if err := config.ValidatorNum(num); err != nil {
48 | return err
49 | }
50 | config.QPS = num
51 | return nil
52 | }
53 |
54 | func (config *Config) SetMaxCharNum(num int) error {
55 | if err := config.ValidatorNum(num); err != nil {
56 | return err
57 | }
58 | config.MaxCharNum = num
59 | return nil
60 | }
61 |
62 | func (config *Config) SetMaxCoroutineNum(num int) error {
63 | if err := config.ValidatorNum(num); err != nil {
64 | return err
65 | }
66 | config.MaxCoroutineNum = num
67 | return nil
68 | }
69 |
--------------------------------------------------------------------------------
/domain/service/translator/ling_va/lang.go:
--------------------------------------------------------------------------------
1 | package ling_va
2 |
3 | import (
4 | "anto/domain/service/translator"
5 | )
6 |
7 | var langSupported = []translator.LangPair{
8 | {"zh", "中文"},
9 | {"en", "英语"},
10 | {"ja", "日语"},
11 | {"ru", "俄语"},
12 | {"fr", "法语"},
13 | {"ko", "韩语"},
14 | {"de", "德语"},
15 | {"da", "丹麦语"},
16 | {"nl", "荷兰语"},
17 | {"it", "意大利语"},
18 | {"ar", "阿拉伯语"},
19 | {"be", "白俄罗斯语"},
20 | }
21 |
--------------------------------------------------------------------------------
/domain/service/translator/ling_va/translator.go:
--------------------------------------------------------------------------------
1 | package ling_va
2 |
3 | import (
4 | "anto/domain/service/translator"
5 | "anto/lib/log"
6 | "context"
7 | "encoding/json"
8 | "fmt"
9 | "github.com/golang-module/carbon"
10 | "go.uber.org/zap"
11 | "net/url"
12 | "strings"
13 | "sync"
14 | )
15 |
16 | var (
17 | apiTranslator *Translator
18 | onceTranslator sync.Once
19 | )
20 |
21 | func API() *Translator {
22 | onceTranslator.Do(func() {
23 | apiTranslator = New()
24 | })
25 | return apiTranslator
26 | }
27 |
28 | func New() *Translator {
29 | return &Translator{
30 | id: "ling_va",
31 | name: "Lingva",
32 | sep: "\n",
33 | langSupported: langSupported,
34 | }
35 | }
36 |
37 | // Translator LingVA翻译已崩溃, 当前处于不可用状态, 所以直接禁用
38 | type Translator struct {
39 | id string
40 | name string
41 | cfg translator.ImplConfig
42 | langSupported []translator.LangPair
43 | sep string
44 | }
45 |
46 | func (customT *Translator) Init(cfg translator.ImplConfig) { customT.cfg = cfg }
47 |
48 | func (customT *Translator) GetId() string { return customT.id }
49 | func (customT *Translator) GetShortId() string { return "lv" }
50 | func (customT *Translator) GetName() string { return customT.name }
51 | func (customT *Translator) GetCfg() translator.ImplConfig { return customT.cfg }
52 | func (customT *Translator) GetLangSupported() []translator.LangPair { return customT.langSupported }
53 | func (customT *Translator) GetSep() string { return customT.sep }
54 | func (customT *Translator) IsValid() bool { return false }
55 |
56 | func (customT *Translator) Translate(ctx context.Context, args *translator.TranslateArgs) (*translator.TranslateRes, error) {
57 | timeStart := carbon.Now()
58 |
59 | var api = fmt.Sprintf("https://lingva.ml/_next/data/%s/", customT.cfg.GetAK())
60 | queryUrl := fmt.Sprintf(
61 | "%s/%s/%s/%s.json", api,
62 | args.FromLang, args.ToLang, url.PathEscape(args.TextContent),
63 | )
64 | respBytes, err := translator.RequestSimpleHttp(ctx, customT, queryUrl, false, nil, nil)
65 | if err != nil {
66 | return nil, err
67 | }
68 | lingVaResp := new(lingVaMTResp)
69 | if err := json.Unmarshal(respBytes, lingVaResp); err != nil {
70 | fmt.Println(string(respBytes))
71 | log.Singleton().ErrorF("解析报文异常, 引擎: %s, 错误: %s", customT.GetName(), err)
72 | return nil, fmt.Errorf("解析报文出现异常, 错误: %s", err.Error())
73 | }
74 | if lingVaResp.State == false {
75 | log.Singleton().ErrorF("接口响应异常, 引擎: %s, 错误: %s", customT.GetName(), err, zap.String("result", string(respBytes)))
76 | return nil, fmt.Errorf("翻译异常")
77 | }
78 | textTranslatedList := strings.Split(lingVaResp.Props.TextTranslated, customT.sep)
79 | textSourceList := strings.Split(lingVaResp.Props.Params.TextSource, customT.sep)
80 | if len(textSourceList) != len(textTranslatedList) {
81 | return nil, translator.ErrSrcAndTgtNotMatched
82 | }
83 |
84 | ret := new(translator.TranslateRes)
85 | for textIdx, textSource := range textSourceList {
86 | ret.Results = append(ret.Results, &translator.TranslateResBlock{
87 | Id: textSource,
88 | TextTranslated: textTranslatedList[textIdx],
89 | })
90 | }
91 |
92 | ret.TimeUsed = int(carbon.Now().DiffAbsInSeconds(timeStart))
93 | return ret, nil
94 |
95 | }
96 |
97 | type lingVaMTResp struct {
98 | State bool `json:"__N_SSG"`
99 | Props struct {
100 | Type int `json:"type"`
101 | TextTranslated string `json:"translation"`
102 | Params struct {
103 | FromLanguage string `json:"source"`
104 | ToLanguage string `json:"target"`
105 | TextSource string `json:"query"`
106 | } `json:"initial"`
107 | } `json:"pageProps"`
108 | }
109 |
--------------------------------------------------------------------------------
/domain/service/translator/microsoft_edge/config.go:
--------------------------------------------------------------------------------
1 | package microsoft_edge
2 |
3 | import (
4 | "anto/domain/service/translator"
5 | "github.com/spf13/viper"
6 | )
7 |
8 | type Config struct {
9 | *translator.DefaultConfig
10 | QPS int `mapstructure:"qps"`
11 | MaxCharNum int `mapstructure:"max_single_text_length"`
12 | MaxCoroutineNum int `mapstructure:"max_coroutine_num"`
13 | }
14 |
15 | func (config *Config) Default() translator.ImplConfig {
16 | return &Config{
17 | MaxCharNum: 1000, QPS: 10, MaxCoroutineNum: 5,
18 | }
19 | }
20 |
21 | func (config *Config) SyncDisk(currentViper *viper.Viper) error {
22 | tagAndVal := config.JoinAllTagAndValue(API(), config, "mapstructure")
23 |
24 | for tag, val := range tagAndVal {
25 | currentViper.Set(tag, val)
26 | }
27 | return nil
28 | }
29 |
30 | func (config *Config) GetQPS() int { return config.QPS }
31 | func (config *Config) GetMaxCharNum() int { return config.MaxCharNum }
32 | func (config *Config) GetMaxCoroutineNum() int { return config.MaxCoroutineNum }
33 |
34 | func (config *Config) SetQPS(num int) error {
35 | if err := config.ValidatorNum(num); err != nil {
36 | return err
37 | }
38 | config.QPS = num
39 | return nil
40 | }
41 |
42 | func (config *Config) SetMaxCharNum(num int) error {
43 | if err := config.ValidatorNum(num); err != nil {
44 | return err
45 | }
46 | config.MaxCharNum = num
47 | return nil
48 | }
49 |
50 | func (config *Config) SetMaxCoroutineNum(num int) error {
51 | if err := config.ValidatorNum(num); err != nil {
52 | return err
53 | }
54 | config.MaxCoroutineNum = num
55 | return nil
56 | }
57 |
--------------------------------------------------------------------------------
/domain/service/translator/microsoft_edge/lang.go:
--------------------------------------------------------------------------------
1 | package microsoft_edge
2 |
3 | import (
4 | "anto/domain/service/translator"
5 | )
6 |
7 | var langSupported = []translator.LangPair{
8 | {"zh-cn", "中文"},
9 | {"en", "英语"},
10 | {"ja", "日语"},
11 | {"ru", "俄语"},
12 | {"fr", "法语"},
13 | {"ko", "韩语"},
14 | {"de", "德语"},
15 | {"da", "丹麦语"},
16 | {"nl", "荷兰语"},
17 | {"it", "意大利语"},
18 | {"ar", "阿拉伯语"},
19 | {"be", "白俄罗斯语"},
20 | }
21 |
--------------------------------------------------------------------------------
/domain/service/translator/microsoft_edge/translator.go:
--------------------------------------------------------------------------------
1 | package microsoft_edge
2 |
3 | import (
4 | "anto/domain/service/translator"
5 | "anto/lib/log"
6 | "context"
7 | "encoding/json"
8 | "fmt"
9 | "github.com/golang-module/carbon"
10 | "strings"
11 | "sync"
12 | )
13 |
14 | var (
15 | apiTranslator *Translator
16 | onceTranslator sync.Once
17 | )
18 |
19 | func API() *Translator {
20 | onceTranslator.Do(func() {
21 | apiTranslator = New()
22 | })
23 | return apiTranslator
24 | }
25 |
26 | func New() *Translator {
27 | return &Translator{
28 | id: "edge",
29 | name: "微软翻译",
30 | sep: "\n",
31 | langSupported: langSupported,
32 | }
33 | }
34 |
35 | // Translator LingVA翻译已崩溃, 当前处于不可用状态, 所以直接禁用
36 | type Translator struct {
37 | id string
38 | name string
39 | cfg translator.ImplConfig
40 | langSupported []translator.LangPair
41 | sep string
42 | token string
43 | }
44 |
45 | func (customT *Translator) Init(cfg translator.ImplConfig) { customT.cfg = cfg }
46 |
47 | func (customT *Translator) GetId() string { return customT.id }
48 | func (customT *Translator) GetShortId() string { return "me" }
49 | func (customT *Translator) GetName() string { return customT.name }
50 | func (customT *Translator) GetCfg() translator.ImplConfig { return customT.cfg }
51 | func (customT *Translator) GetLangSupported() []translator.LangPair { return customT.langSupported }
52 | func (customT *Translator) GetSep() string { return customT.sep }
53 | func (customT *Translator) IsValid() bool { return true }
54 |
55 | func (customT *Translator) Translate(ctx context.Context, args *translator.TranslateArgs) (*translator.TranslateRes, error) {
56 | timeStart := carbon.Now()
57 | token, err := customT.getToken(ctx)
58 | if err != nil {
59 | return nil, err
60 | }
61 |
62 | var queryUrl = fmt.Sprintf(
63 | "https://api-edge.cognitive.microsofttranslator.com/translate?from=%s&to=%s&api-version=3.0&includeSentenceLength=true",
64 | args.FromLang, args.ToLang,
65 | )
66 | req := []microsoftEdgeReq{}
67 | textReq := strings.Split(args.TextContent, customT.sep)
68 | for _, text := range textReq {
69 | req = append(req, microsoftEdgeReq{Text: text})
70 | }
71 | respBytes, err := translator.RequestSimpleHttp(ctx, customT, queryUrl, true, req, map[string]string{
72 | "Authorization": fmt.Sprintf("Bearer Bearer %s", token),
73 | })
74 | if err != nil {
75 | return nil, err
76 | }
77 | resp := []microsoftEdgeResp{}
78 | if err := json.Unmarshal(respBytes, &resp); err != nil {
79 | log.Singleton().ErrorF("解析报文异常, 引擎: %s, 错误: %s", customT.GetName(), err)
80 | return nil, fmt.Errorf("解析报文出现异常, 错误: %s", err.Error())
81 | }
82 | textResp := []string{}
83 | for _, edgeResp := range resp {
84 | if edgeResp.Error.Code != 0 {
85 | // The request is not authorized because credentials are missing or invalid.
86 | if edgeResp.Error.Code == 401001 {
87 | customT.token = ""
88 | }
89 | return nil, fmt.Errorf("翻译异常: %s", edgeResp.Error.Message)
90 | }
91 | for _, translation := range edgeResp.Translations {
92 | textResp = append(textResp, translation.Text)
93 | }
94 | }
95 | if len(textReq) != len(textResp) {
96 | return nil, translator.ErrSrcAndTgtNotMatched
97 | }
98 |
99 | ret := new(translator.TranslateRes)
100 | for textIdx, textSource := range textReq {
101 | ret.Results = append(ret.Results, &translator.TranslateResBlock{
102 | Id: textSource,
103 | TextTranslated: textResp[textIdx],
104 | })
105 | }
106 |
107 | ret.TimeUsed = int(carbon.Now().DiffAbsInSeconds(timeStart))
108 | return ret, nil
109 |
110 | }
111 |
112 | func (customT *Translator) getToken(ctx context.Context) (token string, err error) {
113 | if customT.token == "" {
114 | tokenBytes := []byte{}
115 | tokenBytes, err = translator.RequestSimpleHttp(ctx, customT, "https://edge.microsoft.com/translate/auth", false, nil, nil)
116 | if err != nil {
117 | return
118 | }
119 | customT.token = string(tokenBytes)
120 | }
121 | token = customT.token
122 | return
123 | }
124 |
125 | type microsoftEdgeReq struct {
126 | Text string `json:"text"`
127 | }
128 |
129 | type microsoftEdgeResp struct {
130 | Error struct {
131 | Code int `json:"code"`
132 | Message string `json:"message"`
133 | } `json:"error"`
134 | DetectedLanguage struct {
135 | Language string `json:"language"`
136 | Score float64 `json:"score"`
137 | } `json:"detectedLanguage"`
138 | Translations []struct {
139 | Text string `json:"text"`
140 | To string `json:"to"`
141 | SentLen struct {
142 | SrcSentLen []int `json:"srcSentLen"`
143 | TransSentLen []int `json:"transSentLen"`
144 | } `json:"sentLen"`
145 | } `json:"translations"`
146 | }
147 |
--------------------------------------------------------------------------------
/domain/service/translator/niutrans/config.go:
--------------------------------------------------------------------------------
1 | package niutrans
2 |
3 | import (
4 | "anto/domain/service/translator"
5 | "github.com/spf13/viper"
6 | )
7 |
8 | type Config struct {
9 | *translator.DefaultConfig
10 | AppKey string `mapstructure:"app_key"`
11 | QPS int `mapstructure:"qps"`
12 | MaxCharNum int `mapstructure:"max_single_text_length"`
13 | MaxCoroutineNum int `mapstructure:"max_coroutine_num"`
14 | }
15 |
16 | func (config *Config) Default() translator.ImplConfig {
17 | return &Config{
18 | AppKey: "",
19 | MaxCharNum: 5000, QPS: 50, MaxCoroutineNum: 20,
20 | }
21 | }
22 |
23 | func (config *Config) SyncDisk(currentViper *viper.Viper) error {
24 | tagAndVal := config.JoinAllTagAndValue(API(), config, "mapstructure")
25 |
26 | for tag, val := range tagAndVal {
27 | currentViper.Set(tag, val)
28 | }
29 | return nil
30 | }
31 |
32 | func (config *Config) GetAK() string { return config.AppKey }
33 |
34 | func (config *Config) GetQPS() int { return config.QPS }
35 | func (config *Config) GetMaxCharNum() int { return config.MaxCharNum }
36 | func (config *Config) GetMaxCoroutineNum() int { return config.MaxCoroutineNum }
37 |
38 | func (config *Config) SetAK(str string) error {
39 | if err := config.ValidatorStr(str); err != nil {
40 | return err
41 | }
42 | config.AppKey = str
43 | return nil
44 | }
45 |
46 | func (config *Config) SetQPS(num int) error {
47 | if err := config.ValidatorNum(num); err != nil {
48 | return err
49 | }
50 | config.QPS = num
51 | return nil
52 | }
53 |
54 | func (config *Config) SetMaxCharNum(num int) error {
55 | if err := config.ValidatorNum(num); err != nil {
56 | return err
57 | }
58 | config.MaxCharNum = num
59 | return nil
60 | }
61 |
62 | func (config *Config) SetMaxCoroutineNum(num int) error {
63 | if err := config.ValidatorNum(num); err != nil {
64 | return err
65 | }
66 | config.MaxCoroutineNum = num
67 | return nil
68 | }
69 |
--------------------------------------------------------------------------------
/domain/service/translator/niutrans/lang.go:
--------------------------------------------------------------------------------
1 | package niutrans
2 |
3 | import (
4 | "anto/domain/service/translator"
5 | )
6 |
7 | // https://niutrans.com/documents/contents/trans_text#languageList
8 | var langSupported = []translator.LangPair{
9 | {"zh", "中文"},
10 | {"en", "英语"},
11 | {"ko", "韩语"},
12 | {"ja", "日语"},
13 | {"fr", "法语"},
14 | {"de", "德语"},
15 | {"it", "意大利语"},
16 | }
17 |
--------------------------------------------------------------------------------
/domain/service/translator/niutrans/translator.go:
--------------------------------------------------------------------------------
1 | package niutrans
2 |
3 | import (
4 | "anto/domain/service/translator"
5 | "anto/lib/log"
6 | "context"
7 | "encoding/json"
8 | "fmt"
9 | "github.com/golang-module/carbon"
10 | "strings"
11 | "sync"
12 | )
13 |
14 | const apiTranslate = "https://api.niutrans.com/NiuTransServer/translation"
15 |
16 | var (
17 | apiSingleton *Translator
18 | onceSingleton sync.Once
19 | )
20 |
21 | func API() *Translator {
22 | onceSingleton.Do(func() {
23 | apiSingleton = New()
24 | })
25 | return apiSingleton
26 | }
27 |
28 | func New() *Translator {
29 | return &Translator{
30 | id: "niutrans",
31 | name: "小牛翻译",
32 | sep: "\n",
33 | langSupported: langSupported,
34 | }
35 | }
36 |
37 | type Translator struct {
38 | id string
39 | name string
40 | cfg translator.ImplConfig
41 | langSupported []translator.LangPair
42 | sep string
43 | }
44 |
45 | func (customT *Translator) Init(cfg translator.ImplConfig) { customT.cfg = cfg }
46 |
47 | func (customT *Translator) GetId() string { return customT.id }
48 | func (customT *Translator) GetShortId() string { return "nt" }
49 | func (customT *Translator) GetName() string { return customT.name }
50 | func (customT *Translator) GetCfg() translator.ImplConfig { return customT.cfg }
51 | func (customT *Translator) GetLangSupported() []translator.LangPair { return customT.langSupported }
52 | func (customT *Translator) GetSep() string { return customT.sep }
53 | func (customT *Translator) IsValid() bool { return customT.cfg != nil && customT.cfg.GetAK() != "" }
54 |
55 | func (customT *Translator) Translate(ctx context.Context, args *translator.TranslateArgs) (*translator.TranslateRes, error) {
56 | timeStart := carbon.Now()
57 | tr := &translateRequest{
58 | Apikey: customT.cfg.GetAK(),
59 | SrcText: args.TextContent,
60 | From: args.FromLang,
61 | To: args.ToLang,
62 | }
63 | reqBytes, _ := json.Marshal(tr)
64 | respBytes, err := translator.RequestSimpleHttp(ctx, customT, apiTranslate, true, reqBytes, nil)
65 | if err != nil {
66 | return nil, err
67 | }
68 |
69 | resp := new(translateResponse)
70 | if err = json.Unmarshal(respBytes, resp); err != nil {
71 | log.Singleton().ErrorF("解析报文异常, 引擎: %s, 错误: %s", customT.GetName(), err)
72 | return nil, fmt.Errorf("解析报文出现异常, 错误: %s", err.Error())
73 | }
74 |
75 | if resp.ErrorMsg != "" {
76 | log.Singleton().ErrorF("接口响应异常, 引擎: %s, 错误: %s(%s)", customT.GetName(), resp.ErrorMsg, resp.ErrorCode)
77 | return nil, fmt.Errorf("接口响应异常, 引擎: %s, 错误: %s", customT.GetName(), resp.ErrorMsg)
78 | }
79 |
80 | srcTexts := strings.Split(args.TextContent, customT.GetSep())
81 | tgtTexts := strings.Split(resp.TgtText, customT.GetSep())
82 | if len(srcTexts) != len(tgtTexts) {
83 | return nil, translator.ErrSrcAndTgtNotMatched
84 | }
85 |
86 | ret := new(translator.TranslateRes)
87 |
88 | for textIdx, textTarget := range tgtTexts {
89 | ret.Results = append(ret.Results, &translator.TranslateResBlock{
90 | Id: srcTexts[textIdx], TextTranslated: textTarget,
91 | })
92 | }
93 |
94 | ret.TimeUsed = int(carbon.Now().DiffAbsInSeconds(timeStart))
95 | return ret, nil
96 |
97 | }
98 |
99 | type translateRequest struct {
100 | Apikey string `json:"apikey"`
101 | SrcText string `json:"src_text"`
102 | From string `json:"from"`
103 | To string `json:"to"`
104 | }
105 |
106 | type translateResponse struct {
107 | TgtText string `json:"tgt_text,omitempty"`
108 | To string `json:"to"`
109 | From string `json:"from"`
110 | ErrorCode string `json:"error_code,omitempty"`
111 | ErrorMsg string `json:"error_msg,omitempty"`
112 | }
113 |
--------------------------------------------------------------------------------
/domain/service/translator/openai/config.go:
--------------------------------------------------------------------------------
1 | package openai
2 |
3 | import (
4 | "anto/domain/service/translator"
5 | "github.com/spf13/viper"
6 | )
7 |
8 | type Config struct {
9 | *translator.DefaultConfig
10 | AppKey string `mapstructure:"app_key"`
11 | ProjectKey string `mapstructure:"project_key"`
12 | QPS int `mapstructure:"qps"`
13 | MaxCharNum int `mapstructure:"max_single_text_length"`
14 | MaxCoroutineNum int `mapstructure:"max_coroutine_num"`
15 | }
16 |
17 | func (config *Config) Default() translator.ImplConfig {
18 | return &Config{
19 | AppKey: "", ProjectKey: "gpt-3.5-turbo",
20 | MaxCharNum: 2000, QPS: 1, MaxCoroutineNum: 1,
21 | }
22 | }
23 |
24 | func (config *Config) SyncDisk(currentViper *viper.Viper) error {
25 | tagAndVal := config.JoinAllTagAndValue(API(), config, "mapstructure")
26 |
27 | for tag, val := range tagAndVal {
28 | currentViper.Set(tag, val)
29 | }
30 | return nil
31 | }
32 |
33 | func (config *Config) GetAK() string { return config.AppKey }
34 | func (config *Config) GetProjectKey() string { return config.ProjectKey }
35 | func (config *Config) GetQPS() int { return config.QPS }
36 | func (config *Config) GetMaxCharNum() int { return config.MaxCharNum }
37 | func (config *Config) GetMaxCoroutineNum() int { return config.MaxCoroutineNum }
38 |
39 | func (config *Config) SetAK(str string) error {
40 | if err := config.ValidatorStr(str); err != nil {
41 | return err
42 | }
43 | config.AppKey = str
44 | return nil
45 | }
46 |
47 | func (config *Config) SetProjectKey(projectKey string) error {
48 | if projectKey == "" {
49 | projectKey = "gpt-3.5-turbo"
50 | }
51 | config.ProjectKey = projectKey
52 | return nil
53 | }
54 |
55 | func (config *Config) SetQPS(num int) error {
56 | config.QPS = 1
57 | return nil
58 | }
59 |
60 | func (config *Config) SetMaxCharNum(num int) error {
61 | if err := config.ValidatorNum(num); err != nil {
62 | return err
63 | }
64 | if num > 2000 {
65 | num = 2000
66 | }
67 | config.MaxCharNum = num
68 | return nil
69 | }
70 |
71 | func (config *Config) SetMaxCoroutineNum(num int) error {
72 | config.MaxCoroutineNum = 1
73 | return nil
74 | }
75 |
--------------------------------------------------------------------------------
/domain/service/translator/openai/lang.go:
--------------------------------------------------------------------------------
1 | package openai
2 |
3 | import (
4 | "anto/domain/service/translator"
5 | )
6 |
7 | var langSupported = []translator.LangPair{
8 | {"Chinese", "中文"},
9 | {"English", "英语"},
10 | }
11 |
--------------------------------------------------------------------------------
/domain/service/translator/openai_sweet/config.go:
--------------------------------------------------------------------------------
1 | package openai_sweet
2 |
3 | import (
4 | "anto/domain/service/translator"
5 | "github.com/spf13/viper"
6 | )
7 |
8 | type Config struct {
9 | *translator.DefaultConfig
10 | AppKey string `mapstructure:"app_key"`
11 | ProjectKey string `mapstructure:"project_key"`
12 | QPS int `mapstructure:"qps"`
13 | MaxCharNum int `mapstructure:"max_single_text_length"`
14 | MaxCoroutineNum int `mapstructure:"max_coroutine_num"`
15 | }
16 |
17 | func (config *Config) Default() translator.ImplConfig {
18 | return &Config{
19 | AppKey: "", ProjectKey: "gpt-3.5-turbo",
20 | MaxCharNum: 2000, QPS: 1, MaxCoroutineNum: 1,
21 | }
22 | }
23 |
24 | func (config *Config) SyncDisk(currentViper *viper.Viper) error {
25 | tagAndVal := config.JoinAllTagAndValue(API(), config, "mapstructure")
26 |
27 | for tag, val := range tagAndVal {
28 | currentViper.Set(tag, val)
29 | }
30 | return nil
31 | }
32 |
33 | func (config *Config) GetAK() string { return config.AppKey }
34 | func (config *Config) GetProjectKey() string { return config.ProjectKey }
35 | func (config *Config) GetQPS() int { return config.QPS }
36 | func (config *Config) GetMaxCharNum() int { return config.MaxCharNum }
37 | func (config *Config) GetMaxCoroutineNum() int { return config.MaxCoroutineNum }
38 |
39 | func (config *Config) SetAK(str string) error {
40 | if err := config.ValidatorStr(str); err != nil {
41 | return err
42 | }
43 | config.AppKey = str
44 | return nil
45 | }
46 |
47 | func (config *Config) SetProjectKey(projectKey string) error {
48 | if projectKey == "" {
49 | projectKey = "gpt-3.5-turbo"
50 | }
51 | config.ProjectKey = projectKey
52 | return nil
53 | }
54 |
55 | func (config *Config) SetQPS(num int) error {
56 | config.QPS = 1
57 | return nil
58 | }
59 |
60 | func (config *Config) SetMaxCharNum(num int) error {
61 | if err := config.ValidatorNum(num); err != nil {
62 | return err
63 | }
64 | if num > 2000 {
65 | num = 2000
66 | }
67 | config.MaxCharNum = num
68 | return nil
69 | }
70 |
71 | func (config *Config) SetMaxCoroutineNum(num int) error {
72 | config.MaxCoroutineNum = 1
73 | return nil
74 | }
75 |
--------------------------------------------------------------------------------
/domain/service/translator/openai_sweet/lang.go:
--------------------------------------------------------------------------------
1 | package openai_sweet
2 |
3 | import (
4 | "anto/domain/service/translator"
5 | )
6 |
7 | var langSupported = []translator.LangPair{
8 | {"Chinese", "中文"},
9 | {"English", "英语"},
10 | }
11 |
--------------------------------------------------------------------------------
/domain/service/translator/openapi_youdao/config.go:
--------------------------------------------------------------------------------
1 | package openapi_youdao
2 |
3 | import (
4 | "anto/domain/service/translator"
5 | "github.com/spf13/viper"
6 | )
7 |
8 | type Config struct {
9 | *translator.DefaultConfig
10 | AppKey string `mapstructure:"app_key"` // 应用ID
11 | AppSecret string `mapstructure:"app_secret"` // 应用密钥
12 | QPS int `mapstructure:"qps"`
13 | MaxCharNum int `mapstructure:"max_single_text_length"`
14 | MaxCoroutineNum int `mapstructure:"max_coroutine_num"`
15 | }
16 |
17 | func (config *Config) Default() translator.ImplConfig {
18 | return &Config{
19 | AppKey: "", AppSecret: "",
20 | MaxCharNum: 5000, QPS: 1, MaxCoroutineNum: 1,
21 | }
22 | }
23 |
24 | func (config *Config) SyncDisk(currentViper *viper.Viper) error {
25 | tagAndVal := config.JoinAllTagAndValue(API(), config, "mapstructure")
26 |
27 | for tag, val := range tagAndVal {
28 | currentViper.Set(tag, val)
29 | }
30 | return nil
31 | }
32 |
33 | func (config *Config) GetAK() string { return config.AppKey }
34 | func (config *Config) GetSK() string { return config.AppSecret }
35 |
36 | func (config *Config) GetQPS() int { return config.QPS }
37 | func (config *Config) GetMaxCharNum() int { return config.MaxCharNum }
38 | func (config *Config) GetMaxCoroutineNum() int { return config.MaxCoroutineNum }
39 |
40 | func (config *Config) SetAK(str string) error {
41 | if err := config.ValidatorStr(str); err != nil {
42 | return err
43 | }
44 | config.AppKey = str
45 | return nil
46 | }
47 |
48 | func (config *Config) SetSK(str string) error {
49 | if err := config.ValidatorStr(str); err != nil {
50 | return err
51 | }
52 | config.AppSecret = str
53 | return nil
54 | }
55 |
56 | func (config *Config) SetQPS(num int) error {
57 | if err := config.ValidatorNum(num); err != nil {
58 | return err
59 | }
60 | config.QPS = num
61 | return nil
62 | }
63 |
64 | func (config *Config) SetMaxCharNum(num int) error {
65 | if err := config.ValidatorNum(num); err != nil {
66 | return err
67 | }
68 | config.MaxCharNum = num
69 | return nil
70 | }
71 |
72 | func (config *Config) SetMaxCoroutineNum(num int) error {
73 | if err := config.ValidatorNum(num); err != nil {
74 | return err
75 | }
76 | config.MaxCoroutineNum = num
77 | return nil
78 | }
79 |
--------------------------------------------------------------------------------
/domain/service/translator/openapi_youdao/error.go:
--------------------------------------------------------------------------------
1 | package openapi_youdao
2 |
3 | var errorMap = map[string]string{
4 | "101": "缺少必填的参数,首先确保必填参数齐全, 然后确认参数书写是否正确",
5 | "102": "不支持的语言类型",
6 | "103": "翻译文本过长",
7 | "104": "不支持的API类型",
8 | "105": "不支持的签名类型",
9 | "106": "不支持的响应类型",
10 | "107": "不支持的传输加密类型",
11 | "108": "应用ID无效, 注册账号, 登录后台创建应用和实例并完成绑定, 可获得应用ID和应用密钥等信息",
12 | "109": "batchLog格式不正确",
13 | "110": "无相关服务的有效实例,应用没有绑定服务, 可以新建服务, 绑定服务",
14 | "111": "开发者账号无效",
15 | "112": "请求服务无效",
16 | "113": "翻译字符串(q)不能为空",
17 | "118": "detectLevel取值错误",
18 | "201": "解密失败, 可能为DES,BASE64,URLDecode的错误",
19 | "202": "签名检验失败",
20 | "203": "访问IP地址不在可访问IP列表",
21 | "205": "请求的接口与应用的平台类型不一致",
22 | "206": "因为时间戳无效导致签名校验失败",
23 | "207": "重放请求",
24 | "301": "辞典查询失败",
25 | "302": "翻译查询失败",
26 | "303": "服务端的其它异常",
27 | "304": "会话闲置太久超时",
28 | "401": "账户已经欠费, 请进行账户充值",
29 | "402": "offlinesdk不可用",
30 | "411": "访问频率受限,请稍后访问",
31 | "412": "长请求过于频繁, 请稍后访问",
32 | }
33 |
--------------------------------------------------------------------------------
/domain/service/translator/openapi_youdao/lang.go:
--------------------------------------------------------------------------------
1 | package openapi_youdao
2 |
3 | import (
4 | "anto/domain/service/translator"
5 | )
6 |
7 | var langSupported = []translator.LangPair{
8 | {"zh-CHS", "中文"},
9 | {"en", "英语"},
10 | {"ja", "日语"},
11 | {"ko", "韩语"},
12 | {"fr", "法语"},
13 | {"es", "西班牙语"},
14 | {"pt", "葡萄牙语"},
15 | {"it", "意大利语"},
16 | {"ru", "俄语"},
17 | {"vi", "越南语"},
18 | {"de", "德语"},
19 | {"ar", "阿拉伯语"},
20 | }
21 |
--------------------------------------------------------------------------------
/domain/service/translator/openapi_youdao/translator.go:
--------------------------------------------------------------------------------
1 | package openapi_youdao
2 |
3 | import (
4 | "anto/domain/service/translator"
5 | "anto/lib/log"
6 | "anto/lib/util"
7 | "context"
8 | "crypto/sha256"
9 | "encoding/hex"
10 | "encoding/json"
11 | "fmt"
12 | "github.com/golang-module/carbon"
13 | "github.com/google/go-querystring/query"
14 | "strings"
15 | "sync"
16 | )
17 |
18 | var (
19 | apiTranslator *Translator
20 | onceTranslator sync.Once
21 | )
22 |
23 | func API() *Translator {
24 | onceTranslator.Do(func() {
25 | apiTranslator = New()
26 | })
27 | return apiTranslator
28 | }
29 |
30 | func New() *Translator {
31 | return &Translator{
32 | id: "openapi_youdao",
33 | name: "有道智云",
34 | api: "https://openapi.youdao.com/v2/api",
35 | sep: "\n",
36 | langSupported: langSupported,
37 | }
38 | }
39 |
40 | type Translator struct {
41 | id string
42 | name string
43 | cfg translator.ImplConfig
44 | api string
45 | langSupported []translator.LangPair
46 | sep string
47 | }
48 |
49 | func (customT *Translator) Init(cfg translator.ImplConfig) { customT.cfg = cfg }
50 |
51 | func (customT *Translator) GetId() string { return customT.id }
52 | func (customT *Translator) GetShortId() string { return "oy" }
53 | func (customT *Translator) GetName() string { return customT.name }
54 | func (customT *Translator) GetCfg() translator.ImplConfig { return customT.cfg }
55 | func (customT *Translator) GetLangSupported() []translator.LangPair { return customT.langSupported }
56 | func (customT *Translator) GetSep() string { return customT.sep }
57 | func (customT *Translator) IsValid() bool {
58 | return customT.cfg != nil && customT.cfg.GetAK() != "" && customT.cfg.GetSK() != ""
59 | }
60 |
61 | func (customT *Translator) Translate(ctx context.Context, args *translator.TranslateArgs) (*translator.TranslateRes, error) {
62 | timeStart := carbon.Now()
63 | texts := strings.Split(args.TextContent, customT.GetSep())
64 | newReq := &remoteReq{
65 | TextQuery: texts,
66 | From: args.FromLang, To: args.ToLang,
67 | AppKey: customT.cfg.GetAK(), Salt: util.Uid(), SignType: "v3",
68 | CurrentTime: fmt.Sprintf("%d", carbon.Now().Timestamp()),
69 | }
70 | newReq.Sign = customT.signBuilder(strings.Join(texts, ""), newReq.Salt, newReq.CurrentTime)
71 | params, _ := query.Values(newReq)
72 | urlQueried := fmt.Sprintf("%s?%s", customT.api, params.Encode())
73 | respBytes, err := translator.RequestSimpleHttp(ctx, customT, urlQueried, false, nil, nil)
74 | if err != nil {
75 | return nil, err
76 | }
77 | newResp := new(remoteResp)
78 | if err = json.Unmarshal(respBytes, newResp); err != nil {
79 | log.Singleton().ErrorF("引擎: %s, 错误: 解析报文异常(%s)", customT.GetName(), err)
80 | return nil, fmt.Errorf("错误: 解析报文出现异常(%s)", err.Error())
81 | }
82 |
83 | if newResp.ErrorCode != "0" {
84 | errMsg := errorMap[newResp.ErrorCode]
85 | log.Singleton().ErrorF("引擎: %s, 错误: 接口响应异常(%s:%s)", customT.GetName(), newResp.ErrorCode, errMsg)
86 | return nil, fmt.Errorf("错误: 翻译异常(%s:%s)", newResp.ErrorCode, errMsg)
87 | }
88 |
89 | ret := new(translator.TranslateRes)
90 | for _, transBlock := range newResp.TranslateResults {
91 | ret.Results = append(ret.Results, &translator.TranslateResBlock{
92 | Id: transBlock.Query,
93 | TextTranslated: transBlock.Translation,
94 | })
95 | }
96 |
97 | ret.TimeUsed = int(carbon.Now().DiffInSeconds(timeStart))
98 | return ret, nil
99 | }
100 |
101 | func (customT *Translator) signBuilder(textQuery, salt, currentTime string) string {
102 | tmpQuery := textQuery
103 |
104 | if tmpLen := len(textQuery); tmpLen > 20 {
105 | tmpQuery = fmt.Sprintf("%s%d%s", textQuery[0:10], tmpLen, textQuery[tmpLen-10:])
106 | }
107 | tmpQuery = fmt.Sprintf("%s%s%s%s%s", customT.cfg.GetAK(), tmpQuery, salt, currentTime, customT.cfg.GetSK())
108 | newSha := sha256.New()
109 | newSha.Write([]byte(tmpQuery))
110 |
111 | return hex.EncodeToString(newSha.Sum(nil))
112 | }
113 |
114 | type remoteReq struct {
115 | TextQuery []string `url:"q"` // 要翻译的文本.可指定多个
116 | From string `url:"from"` // 源语言
117 | To string `url:"to"` // 目标语言
118 | AppKey string `url:"appKey"` // 应用标识(应用ID)
119 | Salt string `url:"salt"` // 随机字符串,可使用UUID进行生产
120 | CurrentTime string `url:"curtime"` // 随机字符串,可使用UUID进行生产
121 | Sign string `url:"sign"` // 签名信息sha256(appKey+q+salt+密钥)
122 | SignType string `url:"signType"` // 签名类型(v3)
123 | }
124 |
125 | type remoteResp struct {
126 | ErrorCode string `json:"errorCode"`
127 | ErrorIndex []int `json:"errorIndex"`
128 | TranslateResults []struct {
129 | Query string `json:"query"`
130 | Translation string `json:"translation"`
131 | Type string `json:"type"`
132 | VerifyResult string `json:"verifyResult"`
133 | } `json:"translateResults"`
134 | }
135 |
--------------------------------------------------------------------------------
/domain/service/translator/request.go:
--------------------------------------------------------------------------------
1 | package translator
2 |
3 | import (
4 | "anto/lib/log"
5 | "anto/lib/restrictor"
6 | "context"
7 | "fmt"
8 | "github.com/imroc/req/v3"
9 | "io"
10 | "strings"
11 | )
12 |
13 | func RequestSimpleHttp(ctx context.Context, engine ImplTranslator, url string, isPost bool, body interface{}, headers map[string]string) ([]byte, error) {
14 | if err := restrictor.Singleton().Wait(engine.GetId(), ctx); err != nil {
15 | return nil, fmt.Errorf("限流异常, 错误: %s", err.Error())
16 | }
17 | if headers == nil {
18 | headers = make(map[string]string)
19 | }
20 | headers["content-type"] = "application/json"
21 | headers["accept"] = "application/json"
22 |
23 | client := req.C().SetCommonHeaders(headers).SetCommonRetryCount(3)
24 | if strings.Contains(url, "api.openai.com") {
25 | client.SetProxyURL("http://127.0.0.1:7890")
26 | }
27 | request := client.R()
28 | if isPost && body != nil {
29 | request.SetBody(body)
30 | }
31 | var httpResp *req.Response
32 | var err error
33 | if isPost {
34 | httpResp, err = request.Post(url)
35 | } else {
36 | httpResp, err = request.Get(url)
37 | }
38 | defer func() {
39 | if httpResp != nil && httpResp.Body != nil {
40 | _ = httpResp.Body.Close()
41 | }
42 | }()
43 |
44 | if err != nil {
45 | log.Singleton().ErrorF("调用接口失败, 引擎: %s, 错误: %s", engine.GetName(), err)
46 | return nil, fmt.Errorf("网络请求异常, 错误: %s", err.Error())
47 | }
48 |
49 | if httpResp.StatusCode != 200 {
50 | log.Singleton().ErrorF("调用接口失败, 引擎: %s, 错误: %d(%s)", engine.GetName(), httpResp.StatusCode, httpResp.Status)
51 | return nil, fmt.Errorf("网络响应异常, 错误: %d(%s)", httpResp.StatusCode, httpResp.Status)
52 | }
53 |
54 | respBytes, err := io.ReadAll(httpResp.Body)
55 | if err != nil {
56 | log.Singleton().ErrorF("读取报文异常, 引擎: %s, 错误: %s", engine.GetName(), err)
57 | return nil, fmt.Errorf("读取报文异常, 错误: %s", err.Error())
58 | }
59 |
60 | return respBytes, nil
61 | }
62 |
--------------------------------------------------------------------------------
/domain/service/translator/tencent_cloud_mt/config.go:
--------------------------------------------------------------------------------
1 | package tencent_cloud_mt
2 |
3 | import (
4 | "anto/domain/service/translator"
5 | "github.com/spf13/viper"
6 | "strconv"
7 | )
8 |
9 | type Config struct {
10 | *translator.DefaultConfig
11 | SecretId string `mapstructure:"secret_id"` // 用于标识接口调用者身份
12 | SecretKey string `mapstructure:"secret_key"` // 用于验证接口调用者的身份
13 | Region string `mapstructure:"region"` // 地域参数
14 | ProjectId int64 `mapstructure:"project_id"` // 项目ID, 默认值为0
15 | QPS int `mapstructure:"qps"`
16 | MaxCharNum int `mapstructure:"max_single_text_length"`
17 | MaxCoroutineNum int `mapstructure:"max_coroutine_num"`
18 | }
19 |
20 | func (config *Config) Default() translator.ImplConfig {
21 | return &Config{
22 | SecretId: "", SecretKey: "", Region: "ap-chengdu", ProjectId: 0,
23 | MaxCharNum: 2000, QPS: 5, MaxCoroutineNum: 4,
24 | }
25 | }
26 |
27 | func (config *Config) SyncDisk(currentViper *viper.Viper) error {
28 | tagAndVal := config.JoinAllTagAndValue(API(), config, "mapstructure")
29 |
30 | for tag, val := range tagAndVal {
31 | currentViper.Set(tag, val)
32 | }
33 | return nil
34 | }
35 |
36 | func (config *Config) GetAK() string { return config.SecretId }
37 | func (config *Config) GetSK() string { return config.SecretKey }
38 | func (config *Config) GetRegion() string { return config.Region }
39 | func (config *Config) GetProjectKey() string { return strconv.Itoa(int(config.ProjectId)) }
40 | func (config *Config) GetProjectKeyPtr() *int64 { return &config.ProjectId }
41 |
42 | func (config *Config) GetQPS() int { return config.QPS }
43 | func (config *Config) GetMaxCharNum() int { return config.MaxCharNum }
44 | func (config *Config) GetMaxCoroutineNum() int { return config.MaxCoroutineNum }
45 |
46 | func (config *Config) SetAK(str string) error {
47 | if err := config.ValidatorStr(str); err != nil {
48 | return err
49 | }
50 | config.SecretId = str
51 | return nil
52 | }
53 |
54 | func (config *Config) SetSK(str string) error {
55 | if err := config.ValidatorStr(str); err != nil {
56 | return err
57 | }
58 | config.SecretKey = str
59 | return nil
60 | }
61 |
62 | func (config *Config) SetProjectKey(projectKey string) error {
63 | if err := config.ValidatorStr(projectKey); err != nil {
64 | return err
65 | }
66 | tmpPK, err := strconv.Atoi(projectKey)
67 | if err != nil {
68 | return err
69 | }
70 | config.ProjectId = int64(tmpPK)
71 | return nil
72 | }
73 |
74 | func (config *Config) SetQPS(num int) error {
75 | if err := config.ValidatorNum(num); err != nil {
76 | return err
77 | }
78 | config.QPS = num
79 | return nil
80 | }
81 |
82 | func (config *Config) SetMaxCharNum(num int) error {
83 | if err := config.ValidatorNum(num); err != nil {
84 | return err
85 | }
86 | config.MaxCharNum = num
87 | return nil
88 | }
89 |
90 | func (config *Config) SetMaxCoroutineNum(num int) error {
91 | if err := config.ValidatorNum(num); err != nil {
92 | return err
93 | }
94 | config.MaxCoroutineNum = num
95 | return nil
96 | }
97 |
--------------------------------------------------------------------------------
/domain/service/translator/tencent_cloud_mt/lang.go:
--------------------------------------------------------------------------------
1 | package tencent_cloud_mt
2 |
3 | import (
4 | "anto/domain/service/translator"
5 | )
6 |
7 | var langSupported = []translator.LangPair{
8 | {"zh", "中文"},
9 | {"en", "英语"},
10 | {"ja", "日语"},
11 | {"ko", "韩语"},
12 | {"fr", "法语"},
13 | {"es", "西班牙语"},
14 | {"it", "意大利语"},
15 | {"de", "德语"},
16 | {"tr", "土耳其语"},
17 | {"ru", "俄语"},
18 | {"pt", "葡萄牙语"},
19 | {"vi", "越南语"},
20 | {"id", "印尼语"},
21 | {"th", "泰语"},
22 | {"ms", "马来西亚语"},
23 | {"ar", "阿拉伯语"},
24 | {"hi", "印地语"},
25 | }
26 |
--------------------------------------------------------------------------------
/domain/service/translator/translator.go:
--------------------------------------------------------------------------------
1 | package translator
2 |
3 | import "context"
4 |
5 | type ImplTranslator interface {
6 | Init(cfg ImplConfig)
7 | GetId() string
8 | GetShortId() string
9 | GetName() string
10 | GetCfg() ImplConfig
11 | GetLangSupported() []LangPair
12 | GetSep() string
13 | IsValid() bool
14 | Translate(context.Context, *TranslateArgs) (*TranslateRes, error)
15 | }
16 |
17 | type TranslateArgs struct {
18 | FromLang string
19 | ToLang string
20 | TextContent string
21 | }
22 |
23 | type TranslateRes struct {
24 | TimeUsed int
25 | Msg []string
26 | Results []*TranslateResBlock
27 | }
28 |
29 | type TranslateResBlock struct {
30 | Id string // 采用原文做ID
31 | TextTranslated string
32 | }
33 |
34 | type LangPair struct {
35 | Key string // 语种编码
36 | Name string // 语种名称
37 | }
38 |
--------------------------------------------------------------------------------
/domain/service/translator/volcengine/config.go:
--------------------------------------------------------------------------------
1 | package volcengine
2 |
3 | import (
4 | "anto/domain/service/translator"
5 | "github.com/spf13/viper"
6 | )
7 |
8 | type Config struct {
9 | *translator.DefaultConfig
10 | AccessKey string `mapstructure:"access_key"`
11 | SecretKey string `mapstructure:"secret_key"`
12 | QPS int `mapstructure:"qps"`
13 | MaxCharNum int `mapstructure:"max_single_text_length"`
14 | MaxCoroutineNum int `mapstructure:"max_coroutine_num"`
15 | }
16 |
17 | func (config *Config) Default() translator.ImplConfig {
18 | return &Config{
19 | AccessKey: "", SecretKey: "",
20 | MaxCharNum: 5000, QPS: 10, MaxCoroutineNum: 20,
21 | }
22 | }
23 |
24 | func (config *Config) SyncDisk(currentViper *viper.Viper) error {
25 | tagAndVal := config.JoinAllTagAndValue(API(), config, "mapstructure")
26 |
27 | for tag, val := range tagAndVal {
28 | currentViper.Set(tag, val)
29 | }
30 | return nil
31 | }
32 |
33 | func (config *Config) GetAK() string { return config.AccessKey }
34 | func (config *Config) GetSK() string { return config.SecretKey }
35 | func (config *Config) GetQPS() int { return config.QPS }
36 | func (config *Config) GetMaxCharNum() int { return config.MaxCharNum }
37 | func (config *Config) GetMaxCoroutineNum() int { return config.MaxCoroutineNum }
38 |
39 | func (config *Config) SetAK(str string) error {
40 | if err := config.ValidatorStr(str); err != nil {
41 | return err
42 | }
43 | config.AccessKey = str
44 | return nil
45 | }
46 |
47 | func (config *Config) SetSK(str string) error {
48 | if err := config.ValidatorStr(str); err != nil {
49 | return err
50 | }
51 | config.SecretKey = str
52 | return nil
53 | }
54 |
55 | func (config *Config) SetQPS(num int) error {
56 | if err := config.ValidatorNum(num); err != nil {
57 | return err
58 | }
59 | config.QPS = num
60 | return nil
61 | }
62 |
63 | func (config *Config) SetMaxCharNum(num int) error {
64 | if err := config.ValidatorNum(num); err != nil {
65 | return err
66 | }
67 | config.MaxCharNum = num
68 | return nil
69 | }
70 |
71 | func (config *Config) SetMaxCoroutineNum(num int) error {
72 | if err := config.ValidatorNum(num); err != nil {
73 | return err
74 | }
75 | config.MaxCoroutineNum = num
76 | return nil
77 | }
78 |
--------------------------------------------------------------------------------
/domain/service/translator/volcengine/lang.go:
--------------------------------------------------------------------------------
1 | package volcengine
2 |
3 | import (
4 | "anto/domain/service/translator"
5 | )
6 |
7 | var langSupported = []translator.LangPair{
8 | {"zh", "中文"},
9 | {"en", "英语"},
10 | {"ja", "日语"},
11 | {"ru", "俄语"},
12 | {"fr", "法语"},
13 | {"ko", "韩语"},
14 | {"de", "德语"},
15 | }
16 |
--------------------------------------------------------------------------------
/domain/service/translator/volcengine/translator.go:
--------------------------------------------------------------------------------
1 | package volcengine
2 |
3 | import (
4 | "anto/domain/service/translator"
5 | "anto/lib/log"
6 | "anto/lib/restrictor"
7 | "context"
8 | "encoding/json"
9 | "fmt"
10 | "github.com/golang-module/carbon"
11 | "github.com/volcengine/volc-sdk-golang/base"
12 | "net/http"
13 | "net/url"
14 | "strings"
15 | "sync"
16 | "time"
17 | )
18 |
19 | const (
20 | host = "open.volcengineapi.com"
21 | service = "translate"
22 | version = "2020-06-01"
23 | )
24 |
25 | var (
26 | apiSingleton *Translator
27 | onceSingleton sync.Once
28 | )
29 |
30 | func API() *Translator {
31 | onceSingleton.Do(func() {
32 | apiSingleton = New()
33 | })
34 | return apiSingleton
35 | }
36 |
37 | func New() *Translator {
38 | return &Translator{
39 | id: "volc_engine",
40 | name: "火山引擎",
41 | sep: "\n",
42 | langSupported: langSupported,
43 | }
44 | }
45 |
46 | type Translator struct {
47 | id string
48 | name string
49 | cfg translator.ImplConfig
50 | langSupported []translator.LangPair
51 | sep string
52 | mtClient *base.Client
53 | }
54 |
55 | func (customT *Translator) Init(cfg translator.ImplConfig) { customT.cfg = cfg }
56 |
57 | func (customT *Translator) GetId() string { return customT.id }
58 | func (customT *Translator) GetShortId() string { return "ve" }
59 | func (customT *Translator) GetName() string { return customT.name }
60 | func (customT *Translator) GetCfg() translator.ImplConfig { return customT.cfg }
61 | func (customT *Translator) GetLangSupported() []translator.LangPair { return customT.langSupported }
62 | func (customT *Translator) GetSep() string { return customT.sep }
63 | func (customT *Translator) IsValid() bool {
64 | return customT.cfg != nil && customT.cfg.GetAK() != "" && customT.cfg.GetSK() != ""
65 | }
66 |
67 | func (customT *Translator) Translate(ctx context.Context, args *translator.TranslateArgs) (*translator.TranslateRes, error) {
68 | timeStart := carbon.Now()
69 |
70 | params := &translateRequestParams{
71 | SourceLanguage: args.FromLang,
72 | TargetLanguage: args.ToLang,
73 | }
74 | params.TextList = append(params.TextList, args.TextContent)
75 | jsonBytes, _ := json.Marshal(params)
76 | if err := restrictor.Singleton().Wait(customT.GetId(), ctx); err != nil {
77 | return nil, fmt.Errorf("限流异常, 错误: %s", err.Error())
78 | }
79 | respBytes, _, err := customT.client().Json("TranslateText", nil, string(jsonBytes))
80 | if err != nil {
81 | return nil, err
82 | }
83 |
84 | resp := new(translateResponse)
85 | if err = json.Unmarshal(respBytes, resp); err != nil {
86 | log.Singleton().ErrorF("解析报文异常, 引擎: %s, 错误: %s", customT.GetName(), err)
87 | return nil, fmt.Errorf("解析报文出现异常, 错误: %s", err.Error())
88 | }
89 |
90 | if resp.ResponseMetaData.Error.Code != "" {
91 | log.Singleton().ErrorF("接口响应异常, 引擎: %s, 错误: %s(%s)", customT.GetName(), resp.ResponseMetaData.Error.Message, resp.ResponseMetaData.Error.Code)
92 | return nil, fmt.Errorf("接口响应异常, 引擎: %s, 错误: %s", customT.GetName(), resp.ResponseMetaData.Error.Message)
93 | }
94 | srcTexts := strings.Split(args.TextContent, customT.GetSep())
95 | tgtTexts := strings.Split(resp.TranslationList[0].Translation, customT.GetSep())
96 | if len(srcTexts) != len(tgtTexts) {
97 | return nil, translator.ErrSrcAndTgtNotMatched
98 | }
99 |
100 | ret := new(translator.TranslateRes)
101 |
102 | for textIdx, textTarget := range tgtTexts {
103 | ret.Results = append(ret.Results, &translator.TranslateResBlock{
104 | Id: srcTexts[textIdx], TextTranslated: textTarget,
105 | })
106 | }
107 |
108 | ret.TimeUsed = int(carbon.Now().DiffAbsInSeconds(timeStart))
109 | return ret, nil
110 |
111 | }
112 |
113 | func (customT *Translator) client() *base.Client {
114 | if customT.mtClient == nil {
115 | serviceInfo := &base.ServiceInfo{
116 | Timeout: 5 * time.Second, Host: host,
117 | Header: http.Header{"Accept": []string{"application/json"}},
118 | Credentials: base.Credentials{Region: base.RegionCnNorth1, Service: service},
119 | }
120 |
121 | apiInfoList := map[string]*base.ApiInfo{
122 | "TranslateText": {
123 | Method: http.MethodPost, Path: "/",
124 | Query: url.Values{"Action": []string{"TranslateText"}, "Version": []string{version}},
125 | },
126 | }
127 | client := base.NewClient(serviceInfo, apiInfoList)
128 | client.SetAccessKey(customT.cfg.GetAK())
129 | client.SetSecretKey(customT.cfg.GetSK())
130 | customT.mtClient = client
131 | }
132 |
133 | return customT.mtClient
134 | }
135 |
136 | type translateRequestParams struct {
137 | SourceLanguage string
138 | TargetLanguage string
139 | TextList []string
140 | }
141 |
142 | type translateResponse struct {
143 | ResponseMetaData struct {
144 | RequestId string `json:"RequestId"`
145 | Action string `json:"Action"`
146 | Version string `json:"Version"`
147 | Service string `json:"Service"`
148 | Region string `json:"Region"`
149 | Error struct {
150 | Code string `json:"Code"`
151 | Message string `json:"Message"`
152 | } `json:"Error"`
153 | } `json:"ResponseMetaData"`
154 | TranslationList []struct {
155 | Translation string `json:"Translation"`
156 | //DetectedSourceLanguage string `json:"DetectedSourceLanguage"`
157 | //Extra interface{} `json:"Extra"`
158 | } `json:"TranslationList"`
159 | }
160 |
--------------------------------------------------------------------------------
/domain/service/translator/xfyun/config.go:
--------------------------------------------------------------------------------
1 | package xfyun
2 |
3 | import (
4 | "anto/domain/service/translator"
5 | "github.com/spf13/viper"
6 | )
7 |
8 | type Config struct {
9 | *translator.DefaultConfig
10 | AppId string `mapstructure:"app_id"`
11 | ApiKey string `mapstructure:"api_key"`
12 | ApiSecret string `mapstructure:"api_secret"`
13 | QPS int `mapstructure:"qps"`
14 | MaxCharNum int `mapstructure:"max_single_text_length"`
15 | MaxCoroutineNum int `mapstructure:"max_coroutine_num"`
16 | }
17 |
18 | func (config *Config) Default() translator.ImplConfig {
19 | return &Config{
20 | AppId: "", ApiKey: "", ApiSecret: "",
21 | MaxCharNum: 5000, QPS: 50, MaxCoroutineNum: 20,
22 | }
23 | }
24 |
25 | func (config *Config) SyncDisk(currentViper *viper.Viper) error {
26 | tagAndVal := config.JoinAllTagAndValue(API(), config, "mapstructure")
27 |
28 | for tag, val := range tagAndVal {
29 | currentViper.Set(tag, val)
30 | }
31 | return nil
32 | }
33 |
34 | func (config *Config) GetProjectKey() string { return config.AppId }
35 | func (config *Config) GetAK() string { return config.ApiKey }
36 | func (config *Config) GetSK() string { return config.ApiSecret }
37 |
38 | func (config *Config) GetQPS() int { return config.QPS }
39 | func (config *Config) GetMaxCharNum() int { return config.MaxCharNum }
40 | func (config *Config) GetMaxCoroutineNum() int { return config.MaxCoroutineNum }
41 |
42 | func (config *Config) SetProjectKey(str string) error {
43 | if err := config.ValidatorStr(str); err != nil {
44 | return err
45 | }
46 | config.AppId = str
47 | return nil
48 | }
49 |
50 | func (config *Config) SetAK(str string) error {
51 | if err := config.ValidatorStr(str); err != nil {
52 | return err
53 | }
54 | config.ApiKey = str
55 | return nil
56 | }
57 |
58 | func (config *Config) SetSK(str string) error {
59 | if err := config.ValidatorStr(str); err != nil {
60 | return err
61 | }
62 | config.ApiSecret = str
63 | return nil
64 | }
65 |
66 | func (config *Config) SetQPS(num int) error {
67 | if err := config.ValidatorNum(num); err != nil {
68 | return err
69 | }
70 | config.QPS = num
71 | return nil
72 | }
73 |
74 | func (config *Config) SetMaxCharNum(num int) error {
75 | if err := config.ValidatorNum(num); err != nil {
76 | return err
77 | }
78 | config.MaxCharNum = num
79 | return nil
80 | }
81 |
82 | func (config *Config) SetMaxCoroutineNum(num int) error {
83 | if err := config.ValidatorNum(num); err != nil {
84 | return err
85 | }
86 | config.MaxCoroutineNum = num
87 | return nil
88 | }
89 |
--------------------------------------------------------------------------------
/domain/service/translator/xfyun/lang.go:
--------------------------------------------------------------------------------
1 | package xfyun
2 |
3 | import (
4 | "anto/domain/service/translator"
5 | )
6 |
7 | // @link https://www.xfyun.cn/doc/nlp/xftrans_new/API.html#%E8%AF%AD%E7%A7%8D%E5%88%97%E8%A1%A8
8 | var langSupported = []translator.LangPair{
9 | {"cn", "中文"}, {"en", "英语"}, {"cs", "捷克语"}, {"ha", "豪萨语"},
10 | {"ja", "日语"}, {"ro", "罗马尼亚语"}, {"hu", "匈牙利语"}, {"ko", "韩语"},
11 | {"sv", "瑞典语"}, {"sw", "斯瓦希里语"}, {"th", "泰语"}, {"nl", "荷兰语"},
12 | {"uz", "乌兹别克语"}, {"ru", "俄语"}, {"pl", "波兰语"}, {"zu", "祖鲁语"},
13 | {"bg", "保加利亚语"}, {"ar", "阿拉伯语"}, {"el", "希腊语"}, {"uk", "乌克兰语"},
14 | {"fa", "波斯语"}, {"he", "希伯来语"}, {"vi", "越南语"}, {"ps", "普什图语"},
15 | {"hy", "亚美尼亚语"}, {"ms", "马来语"}, {"ur", "乌尔都语"}, {"hy", "亚美尼亚语"},
16 | {"ms", "马来语"}, {"ur", "乌尔都语"}, {"ka", "格鲁吉亚语"}, {"id", "印尼语"},
17 | {"yue", "广东话"}, {"tl", "菲律宾语"}, {"bn", "孟加拉语"}, {"ii", "彝语"},
18 | {"de", "德语"}, {"nm", "外蒙语"}, {"zua", "壮语"}, {"es", "西班牙语"},
19 | {"kk", "外哈语"}, {"mn", "内蒙语"}, {"fr", "法语"}, {"tr", "土耳其语"},
20 | {"kka", "内哈萨克语"},
21 | }
22 |
--------------------------------------------------------------------------------
/domain/service/translator/youdao/config.go:
--------------------------------------------------------------------------------
1 | package youdao
2 |
3 | import (
4 | "anto/domain/service/translator"
5 | "github.com/spf13/viper"
6 | )
7 |
8 | type Config struct {
9 | *translator.DefaultConfig `mapstructure:"-"`
10 | QPS int `mapstructure:"qps"`
11 | MaxCharNum int `mapstructure:"max_single_text_length"`
12 | MaxCoroutineNum int `mapstructure:"max_coroutine_num"`
13 | }
14 |
15 | func (config *Config) Default() translator.ImplConfig {
16 | return &Config{
17 | MaxCharNum: 2000, QPS: 50, MaxCoroutineNum: 20,
18 | }
19 | }
20 |
21 | func (config *Config) SyncDisk(currentViper *viper.Viper) error {
22 | tagAndVal := config.JoinAllTagAndValue(API(), config, "mapstructure")
23 |
24 | for tag, val := range tagAndVal {
25 | currentViper.Set(tag, val)
26 | }
27 | return nil
28 | }
29 |
30 | func (config *Config) GetQPS() int { return config.QPS }
31 | func (config *Config) GetMaxCharNum() int { return config.MaxCharNum }
32 | func (config *Config) GetMaxCoroutineNum() int { return config.MaxCoroutineNum }
33 |
34 | func (config *Config) SetQPS(num int) error {
35 | if err := config.ValidatorNum(num); err != nil {
36 | return err
37 | }
38 | config.QPS = num
39 | return nil
40 | }
41 |
42 | func (config *Config) SetMaxCharNum(num int) error {
43 | if err := config.ValidatorNum(num); err != nil {
44 | return err
45 | }
46 | config.MaxCharNum = num
47 | return nil
48 | }
49 |
50 | func (config *Config) SetMaxCoroutineNum(num int) error {
51 | if err := config.ValidatorNum(num); err != nil {
52 | return err
53 | }
54 | config.MaxCoroutineNum = num
55 | return nil
56 | }
57 |
--------------------------------------------------------------------------------
/domain/service/translator/youdao/lang.go:
--------------------------------------------------------------------------------
1 | package youdao
2 |
3 | import (
4 | "anto/domain/service/translator"
5 | )
6 |
7 | var langSupported = []translator.LangPair{
8 | {"zh_cn", "中文"},
9 | {"en", "英语"},
10 | }
11 |
--------------------------------------------------------------------------------
/domain/service/translator/youdao/translator.go:
--------------------------------------------------------------------------------
1 | package youdao
2 |
3 | import (
4 | "anto/domain/service/translator"
5 | "anto/lib/log"
6 | "context"
7 | "encoding/json"
8 | "fmt"
9 | "github.com/golang-module/carbon"
10 | "go.uber.org/zap"
11 | "net/url"
12 | "strings"
13 | "sync"
14 | )
15 |
16 | var api = "https://fanyi.youdao.com/translate?&doctype=json"
17 |
18 | var (
19 | apiTranslator *Translator
20 | onceTranslator sync.Once
21 | )
22 |
23 | func API() *Translator {
24 | onceTranslator.Do(func() {
25 | apiTranslator = New()
26 | })
27 | return apiTranslator
28 | }
29 |
30 | func New() *Translator {
31 | return &Translator{
32 | id: "youdao",
33 | name: "有道翻译",
34 | qps: 50,
35 | procMax: 20,
36 | textMaxLen: 2000,
37 | sep: "\n",
38 | langSupported: langSupported,
39 | }
40 | }
41 |
42 | type Translator struct {
43 | id string
44 | name string
45 | cfg translator.ImplConfig
46 | qps int
47 | procMax int
48 | textMaxLen int
49 | langSupported []translator.LangPair
50 | sep string
51 | }
52 |
53 | func (customT *Translator) Init(cfg translator.ImplConfig) { customT.cfg = cfg }
54 |
55 | func (customT *Translator) GetId() string { return customT.id }
56 | func (customT *Translator) GetShortId() string { return "yd" }
57 | func (customT *Translator) GetName() string { return customT.name }
58 | func (customT *Translator) GetCfg() translator.ImplConfig { return customT.cfg }
59 | func (customT *Translator) GetLangSupported() []translator.LangPair { return customT.langSupported }
60 | func (customT *Translator) GetSep() string { return customT.sep }
61 | func (customT *Translator) IsValid() bool { return false }
62 |
63 | func (customT *Translator) Translate(ctx context.Context, args *translator.TranslateArgs) (*translator.TranslateRes, error) {
64 | timeStart := carbon.Now()
65 | urlQueried := fmt.Sprintf(
66 | "%s&type=%s2%s&i=%s", api,
67 | strings.ToUpper(args.FromLang), strings.ToUpper(args.ToLang),
68 | url.QueryEscape(args.TextContent),
69 | )
70 |
71 | respBytes, err := translator.RequestSimpleHttp(ctx, customT, urlQueried, false, nil, nil)
72 | if err != nil {
73 | return nil, err
74 | }
75 | youDaoResp := new(youDaoMTResp)
76 | if err = json.Unmarshal(respBytes, youDaoResp); err != nil {
77 | log.Singleton().ErrorF("解析报文异常, 引擎: %s, 错误: %s", customT.GetName(), err)
78 | return nil, fmt.Errorf("解析报文出现异常, 错误: %s", err.Error())
79 | }
80 | if youDaoResp.ErrorCode != 0 {
81 | log.Singleton().ErrorF("接口响应异常, 引擎: %s, 错误: %s", customT.GetName(), err, zap.String("result", string(respBytes)))
82 | return nil, fmt.Errorf("翻译异常, 代码: %d", youDaoResp.ErrorCode)
83 | }
84 | srcArrSplit := strings.Split(args.TextContent, customT.sep)
85 | if len(srcArrSplit) != len(youDaoResp.TransResult) {
86 | return nil, translator.ErrSrcAndTgtNotMatched
87 | }
88 | ret := new(translator.TranslateRes)
89 | for idx, transBlockArray := range youDaoResp.TransResult {
90 | var tgtArrEvaluated []string
91 | // ?+空格 会导致意外分行, 搞不清楚这个服务的换行标识是什么, 多标准的么
92 | for _, block := range transBlockArray {
93 | tgtArrEvaluated = append(tgtArrEvaluated, block.Tgt)
94 | }
95 | ret.Results = append(ret.Results, &translator.TranslateResBlock{
96 | Id: srcArrSplit[idx],
97 | TextTranslated: strings.Join(tgtArrEvaluated, " "),
98 | })
99 |
100 | }
101 |
102 | ret.TimeUsed = int(carbon.Now().DiffInSeconds(timeStart))
103 | return ret, nil
104 | }
105 |
106 | type youDaoMTResp struct {
107 | Type string `json:"type"`
108 | ErrorCode int `json:"errorCode"`
109 | ElapsedTime int `json:"elapsedTime"`
110 | TransResult [][]struct {
111 | Src string `json:"src,omitempty"` // 原文
112 | Tgt string `json:"tgt,omitempty"` // 译文
113 | } `json:"translateResult"`
114 | }
115 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module anto
2 |
3 | go 1.21
4 |
5 | require (
6 | cloud.google.com/go/translate v1.9.0
7 | github.com/OwO-Network/gdeeplx v0.0.1
8 | github.com/aliyun/alibaba-cloud-sdk-go v1.62.267
9 | github.com/golang-module/carbon v1.7.3
10 | github.com/google/go-querystring v1.1.0
11 | github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.34
12 | github.com/imroc/req/v3 v3.42.0
13 | github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible
14 | github.com/lxn/walk v0.0.0-20210112085537-c389da54e794
15 | github.com/lxn/win v0.0.0-20210218163916-a377121e959e
16 | github.com/spf13/viper v1.15.0
17 | github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.628
18 | github.com/twharmon/gouid v0.5.2
19 | github.com/volcengine/volc-sdk-golang v1.0.95
20 | golang.org/x/time v0.1.0
21 | )
22 |
23 | require (
24 | cloud.google.com/go v0.110.2 // indirect
25 | cloud.google.com/go/compute v1.19.3 // indirect
26 | cloud.google.com/go/compute/metadata v0.2.3 // indirect
27 | github.com/abadojack/whatlanggo v1.0.1 // indirect
28 | github.com/andybalholm/brotli v1.0.5 // indirect
29 | github.com/cenkalti/backoff/v4 v4.1.2 // indirect
30 | github.com/cloudflare/circl v1.3.3 // indirect
31 | github.com/gaukas/godicttls v0.0.4 // indirect
32 | github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
33 | github.com/gobuffalo/envy v1.7.0 // indirect
34 | github.com/gobuffalo/packd v0.3.0 // indirect
35 | github.com/gobuffalo/packr v1.30.1 // indirect
36 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
37 | github.com/golang/mock v1.6.0 // indirect
38 | github.com/golang/protobuf v1.5.3 // indirect
39 | github.com/google/pprof v0.0.0-20230912144702-c363fe2c2ed8 // indirect
40 | github.com/google/s2a-go v0.1.4 // indirect
41 | github.com/google/uuid v1.3.0 // indirect
42 | github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect
43 | github.com/googleapis/gax-go/v2 v2.11.0 // indirect
44 | github.com/hashicorp/errwrap v1.1.0 // indirect
45 | github.com/hashicorp/go-multierror v1.1.1 // indirect
46 | github.com/jmespath/go-jmespath v0.4.0 // indirect
47 | github.com/joho/godotenv v1.3.0 // indirect
48 | github.com/jonboulle/clockwork v0.3.0 // indirect
49 | github.com/json-iterator/go v1.1.12 // indirect
50 | github.com/klauspost/compress v1.17.0 // indirect
51 | github.com/lestrrat-go/strftime v1.0.6 // indirect
52 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
53 | github.com/modern-go/reflect2 v1.0.2 // indirect
54 | github.com/onsi/ginkgo/v2 v2.12.0 // indirect
55 | github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b // indirect
56 | github.com/pkg/errors v0.9.1 // indirect
57 | github.com/quic-go/qpack v0.4.0 // indirect
58 | github.com/quic-go/qtls-go1-20 v0.3.4 // indirect
59 | github.com/quic-go/quic-go v0.38.1 // indirect
60 | github.com/refraction-networking/utls v1.5.3 // indirect
61 | github.com/rogpeppe/go-internal v1.6.1 // indirect
62 | github.com/tidwall/gjson v1.14.4 // indirect
63 | github.com/tidwall/match v1.1.1 // indirect
64 | github.com/tidwall/pretty v1.2.0 // indirect
65 | go.mongodb.org/mongo-driver v1.11.2 // indirect
66 | go.opencensus.io v0.24.0 // indirect
67 | go.uber.org/atomic v1.9.0 // indirect
68 | go.uber.org/multierr v1.8.0 // indirect
69 | golang.org/x/crypto v0.13.0 // indirect
70 | golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
71 | golang.org/x/mod v0.12.0 // indirect
72 | golang.org/x/net v0.15.0 // indirect
73 | golang.org/x/oauth2 v0.8.0 // indirect
74 | golang.org/x/tools v0.13.0 // indirect
75 | google.golang.org/api v0.126.0 // indirect
76 | google.golang.org/appengine v1.6.7 // indirect
77 | google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc // indirect
78 | google.golang.org/grpc v1.55.0 // indirect
79 | google.golang.org/protobuf v1.30.0 // indirect
80 | gopkg.in/Knetic/govaluate.v3 v3.0.0 // indirect
81 | )
82 |
83 | require (
84 | github.com/fsnotify/fsnotify v1.6.0 // indirect
85 | github.com/hashicorp/hcl v1.0.0 // indirect
86 | github.com/magiconair/properties v1.8.7 // indirect
87 | github.com/mitchellh/mapstructure v1.5.0 // indirect
88 | github.com/pelletier/go-toml/v2 v2.0.6 // indirect
89 | github.com/spf13/afero v1.9.3 // indirect
90 | github.com/spf13/cast v1.5.0
91 | github.com/spf13/jwalterweatherman v1.1.0 // indirect
92 | github.com/spf13/pflag v1.0.5 // indirect
93 | github.com/subosito/gotenv v1.4.2 // indirect
94 | go.uber.org/zap v1.24.0
95 | golang.org/x/sys v0.12.0 // indirect
96 | golang.org/x/text v0.13.0 // indirect
97 | gopkg.in/ini.v1 v1.67.0 // indirect
98 | gopkg.in/yaml.v3 v3.0.1 // indirect
99 | )
100 |
--------------------------------------------------------------------------------
/lib/log/log.go:
--------------------------------------------------------------------------------
1 | package log
2 |
3 | import (
4 | "github.com/golang-module/carbon"
5 | rotatelogs "github.com/lestrrat-go/file-rotatelogs"
6 | "go.uber.org/zap"
7 | "go.uber.org/zap/zapcore"
8 | "io"
9 | "strings"
10 | "sync"
11 | "time"
12 | )
13 |
14 | var (
15 | apiLog *Log
16 | onceLog sync.Once
17 | )
18 |
19 | func Singleton() *Log {
20 | onceLog.Do(func() {
21 | apiLog = new(Log)
22 | apiLog.initEncoder()
23 | apiLog.initLogger()
24 | })
25 | return apiLog
26 | }
27 |
28 | type Log struct {
29 | logger *zap.SugaredLogger
30 | encoder zapcore.Encoder
31 | }
32 |
33 | func (customLog *Log) Debug(args ...interface{}) {
34 | customLog.logger.Debug(args...)
35 | }
36 | func (customLog *Log) Info(args ...interface{}) {
37 | customLog.logger.Info(args...)
38 | }
39 | func (customLog *Log) InfoF(tpl string, args ...interface{}) {
40 | customLog.logger.Infof(tpl, args...)
41 | }
42 | func (customLog *Log) Warn(args ...interface{}) {
43 | customLog.logger.Warn(args...)
44 | }
45 | func (customLog *Log) WarnF(tpl string, args ...interface{}) {
46 | customLog.logger.Warnf(tpl, args...)
47 | }
48 | func (customLog *Log) Error(args ...interface{}) {
49 | customLog.logger.Error(args...)
50 | }
51 | func (customLog *Log) ErrorF(tpl string, args ...interface{}) {
52 | customLog.logger.Errorf(tpl, args...)
53 | }
54 | func (customLog *Log) Panic(args ...interface{}) {
55 | customLog.logger.Panic(args...)
56 | }
57 | func (customLog *Log) Fatal(args ...interface{}) {
58 | customLog.logger.Fatal(args...)
59 | }
60 |
61 | func (customLog *Log) initLogger() {
62 | infoLevel := zap.LevelEnablerFunc(func(lv zapcore.Level) bool {
63 | return lv >= zapcore.InfoLevel
64 | })
65 | errorLevel := zap.LevelEnablerFunc(func(lv zapcore.Level) bool {
66 | return lv >= zapcore.ErrorLevel
67 | })
68 |
69 | ioInfo := customLog.initWriter("./logs/info.log")
70 | ioErr := customLog.initWriter("./logs/error.log")
71 |
72 | core := zapcore.NewTee(
73 | zapcore.NewCore(customLog.encoder, zapcore.AddSync(ioInfo), infoLevel),
74 | zapcore.NewCore(customLog.encoder, zapcore.AddSync(ioErr), errorLevel),
75 | )
76 | customLog.logger = zap.New(core, zap.AddCaller()).Sugar()
77 | }
78 |
79 | func (customLog *Log) initWriter(filename string) io.Writer {
80 | fd, err := rotatelogs.New(strings.Replace(filename, ".log", "", -1) + ".%Y%m%d.log")
81 | if err != nil {
82 | panic(err)
83 | }
84 | return fd
85 | }
86 |
87 | func (customLog *Log) initEncoder() {
88 | customLog.encoder = zapcore.NewJSONEncoder(zapcore.EncoderConfig{
89 | MessageKey: "msg", LevelKey: "level", TimeKey: "ts", StacktraceKey: "trace",
90 | EncodeLevel: zapcore.LowercaseLevelEncoder,
91 | EncodeTime: func(time time.Time, encoder zapcore.PrimitiveArrayEncoder) {
92 | encoder.AppendString(carbon.FromStdTime(time).Layout(carbon.ShortDateTimeLayout))
93 | },
94 | EncodeDuration: func(d time.Duration, enc zapcore.PrimitiveArrayEncoder) {
95 | enc.AppendInt64(int64(d) / 1000000)
96 | },
97 | })
98 | }
99 |
--------------------------------------------------------------------------------
/lib/nohup/resident.go:
--------------------------------------------------------------------------------
1 | package nohup
2 |
3 | import (
4 | "context"
5 | )
6 |
7 | type IResident interface {
8 | Run(ctx context.Context, stopFn context.CancelFunc)
9 | Close()
10 | }
11 |
12 | func NewResident(parentCtx context.Context, programs ...IResident) {
13 | ctx, stop := context.WithCancel(parentCtx)
14 | defer stop()
15 | for _, program := range programs {
16 | program.Run(ctx, stop)
17 | }
18 | <-ctx.Done()
19 | stop()
20 |
21 | for _, program := range programs {
22 | program.Close()
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/lib/restrictor/restrictor.go:
--------------------------------------------------------------------------------
1 | package restrictor
2 |
3 | import (
4 | "anto/lib/log"
5 | "context"
6 | "golang.org/x/time/rate"
7 | "sync"
8 | )
9 |
10 | var (
11 | apiRestrictor *Restrictor
12 | onceRestrictor sync.Once
13 | )
14 |
15 | func Singleton() *Restrictor {
16 | onceRestrictor.Do(func() {
17 | apiRestrictor = new(Restrictor)
18 | })
19 | return apiRestrictor
20 | }
21 |
22 | type Restrictor struct {
23 | instances sync.Map
24 | }
25 |
26 | func (o *Restrictor) Get(key string) *rate.Limiter {
27 | currentLimiter, isOk := o.instances.Load(key)
28 | if !isOk || currentLimiter == nil {
29 | currentLimiter = rate.NewLimiter(1, 1)
30 | }
31 | return currentLimiter.(*rate.Limiter)
32 | }
33 |
34 | func (o *Restrictor) Set(key string, limiter *rate.Limiter) {
35 | o.instances.Store(key, limiter)
36 | }
37 |
38 | func (o *Restrictor) Allow(key string) bool {
39 | return o.Get(key).Allow()
40 | }
41 |
42 | func (o *Restrictor) Wait(key string, ctx context.Context) error {
43 | err := o.Get(key).Wait(ctx)
44 | if err != nil {
45 | log.Singleton().ErrorF("等待令牌异常(关键字: %s), 错误: %s", key, err.Error())
46 | }
47 |
48 | return err
49 | }
50 |
--------------------------------------------------------------------------------
/lib/srt/block.go:
--------------------------------------------------------------------------------
1 | package srt
2 |
3 | import (
4 | "fmt"
5 | "github.com/golang-module/carbon"
6 | "strconv"
7 | "strings"
8 | )
9 |
10 | func (customB *Block) IsValid() bool {
11 | return customB.SeqNo > 0 &&
12 | customB.TimeStart != "" && customB.TimeEnd != "" && customB.TimeSep != "" &&
13 | customB.MainTrack != ""
14 | }
15 |
16 | func (customB *Block) decodeSeqNo(lineStr string) (isSeqNo bool, err error) {
17 | if lineStr == "" || customB.SeqNo > 0 {
18 | return
19 | }
20 | var tmpNum int
21 | tmpNum, err = strconv.Atoi(lineStr)
22 | if err != nil {
23 | return
24 | }
25 | if tmpNum <= 0 {
26 | err = fmt.Errorf("无效序列号[%d]", tmpNum)
27 | return
28 | }
29 | customB.SeqNo = tmpNum
30 | isSeqNo = true
31 | return
32 | }
33 |
34 | func (customB *Block) decodeTimeLine(lineStr string) (isTimeLine bool, err error) {
35 | if lineStr == "" || !strings.Contains(lineStr, timeSep) {
36 | return
37 | }
38 | if customB.SeqNo == 0 {
39 | return
40 | }
41 | timeSplit := strings.Split(lineStr, timeSep)
42 | if len(timeSplit) != 2 {
43 | return
44 | }
45 | firstBlock := strings.TrimSpace(timeSplit[0])
46 | secondBlock := strings.TrimSpace(timeSplit[1])
47 | layout := "15:04:05.999"
48 | if carbon.ParseByLayout(firstBlock, layout).IsInvalid() ||
49 | carbon.ParseByLayout(secondBlock, layout).IsInvalid() {
50 | return
51 | }
52 | customB.TimeStart = firstBlock
53 | customB.TimeEnd = secondBlock
54 | customB.TimeSep = timeSep
55 | isTimeLine = true
56 | return
57 | }
58 |
59 | func (customB *Block) decodeMainTrack(lineStr string) (isMain bool, err error) {
60 | if lineStr == "" || customB.MainTrack != "" {
61 | return
62 | }
63 | if customB.SeqNo == 0 || customB.TimeStart == "" {
64 | return
65 | }
66 | customB.MainTrack = lineStr
67 | isMain = true
68 | return
69 | }
70 |
71 | func (customB *Block) decodeSubTrack(lineStr string) (isSub bool, err error) {
72 | if lineStr == "" || customB.SubTrack != "" {
73 | return
74 | }
75 | if customB.SeqNo == 0 || customB.TimeStart == "" || customB.MainTrack == "" {
76 | return
77 | }
78 | customB.SubTrack = lineStr
79 | isSub = true
80 | return
81 | }
82 |
83 | func (customB *Block) encode(flagInverse bool, flagTrackExportedMode int) []byte {
84 | blockStr := fmt.Sprintf("%d\n%s %s %s\n", customB.SeqNo,
85 | customB.TimeStart, customB.TimeSep, customB.TimeEnd)
86 | if customB.SubTrack == "" {
87 | blockStr = fmt.Sprintf("%s%s\n", blockStr, customB.MainTrack)
88 | } else {
89 | if flagTrackExportedMode != 0 {
90 | if flagTrackExportedMode == 1 {
91 | if flagInverse == true {
92 | blockStr = fmt.Sprintf("%s%s\n", blockStr, customB.SubTrack)
93 | } else {
94 | blockStr = fmt.Sprintf("%s%s\n", blockStr, customB.MainTrack)
95 | }
96 | } else if flagTrackExportedMode == 2 {
97 | if flagInverse == true {
98 | blockStr = fmt.Sprintf("%s%s\n", blockStr, customB.MainTrack)
99 | } else {
100 | blockStr = fmt.Sprintf("%s%s\n", blockStr, customB.SubTrack)
101 | }
102 | }
103 | } else {
104 | if flagInverse == false {
105 | blockStr = fmt.Sprintf("%s%s\n%s\n", blockStr, customB.MainTrack, customB.SubTrack)
106 | } else {
107 | blockStr = fmt.Sprintf("%s%s\n%s\n", blockStr, customB.SubTrack, customB.MainTrack)
108 | }
109 | }
110 | }
111 |
112 | return []byte(blockStr)
113 | }
114 |
--------------------------------------------------------------------------------
/lib/srt/decode.go:
--------------------------------------------------------------------------------
1 | package srt
2 |
3 | import (
4 | "anto/lib/util"
5 | "bufio"
6 | "fmt"
7 | "io"
8 | "strings"
9 | )
10 |
11 | func (customS *Srt) Decode(fileStream io.Reader) (err error) {
12 | var lineBytes []byte
13 | var currentLine string
14 | var currentBlock *Block
15 |
16 | fileReader := bufio.NewReader(fileStream)
17 | isHeader := true
18 | for {
19 | lineBytes = []byte{}
20 | currentLine = ""
21 |
22 | lineBytes, err = fileReader.ReadBytes('\n')
23 | if isHeader {
24 | if util.HasUTF8Dom(lineBytes) {
25 | lineBytes = lineBytes[3:]
26 | }
27 |
28 | isHeader = false
29 | }
30 | currentLine = strings.TrimSpace(string(lineBytes))
31 |
32 | if customS.decodeBroken(err) {
33 | return
34 | }
35 |
36 | if customS.decodeEOFDone(err, currentLine) {
37 | err = nil
38 | break
39 | }
40 |
41 | if currentLine == "" { // 检测到空行 字幕块间隔行
42 | if currentBlock != nil {
43 | if !currentBlock.IsValid() {
44 | err = fmt.Errorf("字幕块[序列号: %d]无效", currentBlock.SeqNo)
45 | return
46 | }
47 | customS.Blocks = append(customS.Blocks, currentBlock)
48 | currentBlock = nil
49 | }
50 | continue // 可能存在连续空行?
51 | }
52 |
53 | if currentBlock == nil { // isSeqNo
54 | currentBlock = new(Block)
55 | if _, err = currentBlock.decodeSeqNo(currentLine); err != nil {
56 | err = fmt.Errorf("解析序列号异常: %s", err)
57 | return
58 | }
59 | continue
60 | }
61 | if isTimeLine, _ := currentBlock.decodeTimeLine(currentLine); isTimeLine {
62 | continue
63 | }
64 | if isMain, _ := currentBlock.decodeMainTrack(currentLine); isMain {
65 | continue
66 | }
67 | if isSub, _ := currentBlock.decodeSubTrack(currentLine); isSub {
68 | continue
69 | }
70 | err = fmt.Errorf("解析字幕块异常, 出现多余行[%s][最近序列号: %d]", currentLine, currentBlock.SeqNo)
71 | return
72 | }
73 |
74 | if currentBlock != nil {
75 | customS.Blocks = append(customS.Blocks, currentBlock)
76 | currentBlock = nil
77 | }
78 | return
79 | }
80 |
81 | func (customS *Srt) decodeBroken(err error) bool {
82 | return err != nil && err != io.EOF
83 | }
84 |
85 | func (customS *Srt) decodeEOFDone(err error, currentLine string) bool {
86 | return err == io.EOF && currentLine == ""
87 | }
88 |
--------------------------------------------------------------------------------
/lib/srt/encode.go:
--------------------------------------------------------------------------------
1 | package srt
2 |
3 | type EncodeOpt struct {
4 | FlagTrackExport int // 导出轨道模式 0-全轨 1-主轨 2-副轨
5 | FlagIsInverse bool
6 | }
7 |
8 | func (customS *Srt) Encode(opts *EncodeOpt) ([]byte, error) {
9 | var fsBytes []byte
10 | for _, block := range customS.Blocks {
11 | fsBytes = append(fsBytes, block.encode(opts.FlagIsInverse, opts.FlagTrackExport)...)
12 | fsBytes = append(fsBytes, '\n')
13 | }
14 | return fsBytes, nil
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/lib/srt/srt.go:
--------------------------------------------------------------------------------
1 | package srt
2 |
3 | import "strings"
4 |
5 | const timeSep = "-->"
6 |
7 | type Srt struct {
8 | FileName string
9 | FilePath string
10 | FileSign string
11 | FileSize int
12 | CntBlock int
13 | Blocks []*Block
14 | FlagTranslated bool
15 | }
16 |
17 | type Block struct {
18 | SeqNo int
19 | TimeStart string
20 | TimeEnd string
21 | TimeSep string
22 | MainTrack string
23 | SubTrack string
24 | }
25 |
26 | // FileNameSync 从FilePath中解析文件名称-强行覆盖
27 | func (customS *Srt) FileNameSync() {
28 | if customS.FilePath == "" {
29 | return
30 | }
31 | filepathArray := strings.Split(customS.FilePath, "/")
32 | filename := filepathArray[len(filepathArray)-1] // 可能要做其他处理, 先保留这个中间转换
33 | customS.FileName = filename
34 | }
35 |
--------------------------------------------------------------------------------
/lib/util/rand.go:
--------------------------------------------------------------------------------
1 | package util
2 |
3 | import (
4 | _const "anto/common"
5 | "github.com/twharmon/gouid"
6 | )
7 |
8 | func Uid() string {
9 | return gouid.String(_const.GoUidLen, gouid.LowerCaseAlphaNum)
10 | }
11 |
--------------------------------------------------------------------------------
/lib/util/time.go:
--------------------------------------------------------------------------------
1 | package util
2 |
3 | import "github.com/golang-module/carbon"
4 |
5 | func GetShortDateTime() string {
6 | return carbon.Now().Layout(carbon.ShortDateTimeLayout)
7 | }
8 |
9 | func GetDateTime() string {
10 | return carbon.Now().Layout(carbon.DateTimeLayout)
11 | }
12 |
13 | func GetSecondsFromTime(timeStart carbon.Carbon) int {
14 | return int(carbon.Now().DiffAbsInSeconds(timeStart))
15 | }
16 |
--------------------------------------------------------------------------------
/lib/util/util.go:
--------------------------------------------------------------------------------
1 | package util
2 |
3 | import (
4 | "os"
5 | "os/exec"
6 | )
7 |
8 | func HasUTF8Dom(bytes []byte) bool {
9 | return len(bytes) >= 3 && bytes[0] == 0xEF && bytes[1] == 0xBB && bytes[2] == 0xBF
10 | }
11 |
12 | // IsSrtFile 是否为srt文件
13 | func IsSrtFile(strFilepath string) bool {
14 | if len(strFilepath) <= 4 {
15 | return false
16 | }
17 | return strFilepath[len(strFilepath)-3:] == "srt"
18 | }
19 |
20 | // IsFileOrDirExisted 检测文件或路径是否存在
21 | func IsFileOrDirExisted(strFilepath string) error {
22 | fd, err := os.Open(strFilepath)
23 | defer func() {
24 | if fd != nil {
25 | _ = fd.Close()
26 | }
27 | }()
28 | if err != nil {
29 | return err
30 | }
31 | return nil
32 | }
33 |
34 | // Redirect2DefaultBrowser 跳转到默认浏览器打开指定网址
35 | func Redirect2DefaultBrowser(strUrl string) error {
36 | cmd := exec.Command("cmd", "/C", "start "+strUrl)
37 | return cmd.Run()
38 | }
39 |
--------------------------------------------------------------------------------
/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/speauty/anto/d537a1ad2b935ec8c6973912e1a5ed919d932ea7/logo.png
--------------------------------------------------------------------------------
/platform/win/menu.go:
--------------------------------------------------------------------------------
1 | package win
2 |
3 | import (
4 | "anto/common"
5 | "anto/lib/log"
6 | page2 "anto/platform/win/page"
7 | "anto/platform/win/ui"
8 | "anto/platform/win/ui/msg"
9 | "errors"
10 | "fmt"
11 | "github.com/lxn/walk"
12 | . "github.com/lxn/walk/declarative"
13 | "go.uber.org/zap"
14 | "io"
15 | "io/fs"
16 | "net/http"
17 | "os"
18 | "path/filepath"
19 | "sync"
20 | "time"
21 | )
22 |
23 | var (
24 | apiTTMenu *TTMenu
25 | onceTTMenu sync.Once
26 | )
27 |
28 | func GetInstance() *TTMenu {
29 | onceTTMenu.Do(func() {
30 | apiTTMenu = new(TTMenu)
31 | })
32 | return apiTTMenu
33 | }
34 |
35 | type TTMenu struct {
36 | mainWindow *walk.MainWindow
37 | actionDownloadHandle *walk.Action
38 | }
39 |
40 | func (customM *TTMenu) GetMenus() []MenuItem {
41 | return []MenuItem{
42 | Menu{
43 | Text: "文件",
44 | Items: []MenuItem{
45 | Action{Text: "设置", OnTriggered: customM.eventSettings},
46 | Separator{},
47 | Action{AssignTo: &customM.actionDownloadHandle, Text: "下载新版", OnTriggered: customM.eventActionDownloadLatestVersion},
48 | Action{Text: "清除日志", OnTriggered: customM.eventActionDelLog},
49 | Separator{},
50 | Action{Text: "退出", OnTriggered: customM.eventActionQuit},
51 | },
52 | },
53 | Action{Text: "字幕翻译", OnTriggered: customM.eventSubtitleTranslate},
54 | }
55 | }
56 |
57 | func (customM *TTMenu) eventSettings() {
58 | currentPage := page2.GetSettings()
59 | customM.eventGoPage(currentPage.GetId(), currentPage.GetName())
60 | }
61 |
62 | func (customM *TTMenu) eventActionDownloadLatestVersion() {
63 | if customM.actionDownloadHandle.Enabled() {
64 | _ = customM.actionDownloadHandle.SetEnabled(false)
65 | } else {
66 | return
67 | }
68 | mainWindow := ui.Singleton().GetWindow()
69 | isOk, _ := msg.Confirm(mainWindow, fmt.Sprintf("下载应用的最新版本,是否继续?"))
70 | if isOk {
71 | go func() {
72 | defer func() {
73 | _ = customM.actionDownloadHandle.SetEnabled(true)
74 | }()
75 | resp, err := http.Get(common.DownloadLatestVersionUrl)
76 | if err != nil {
77 | tmpErr := fmt.Errorf("下载最新版本异常, 错误: %s", err)
78 | if ui.Singleton().Notification() != nil {
79 | _ = ui.Singleton().Notification().ShowError("", tmpErr.Error())
80 | } else {
81 | msg.Err(mainWindow, tmpErr)
82 | }
83 | return
84 | }
85 | defer func() {
86 | _ = resp.Body.Close()
87 | }()
88 | appBytes, _ := io.ReadAll(resp.Body)
89 | if resp.StatusCode == http.StatusNotFound {
90 | tmpErr := errors.New("下载最新版本异常, 错误: 暂未找到")
91 | if ui.Singleton().Notification() != nil {
92 | _ = ui.Singleton().Notification().ShowError("", tmpErr.Error())
93 | } else {
94 | msg.Err(mainWindow, tmpErr)
95 | }
96 | return
97 | }
98 | fileName := filepath.Base(common.DownloadLatestVersionUrl)
99 |
100 | if err := os.WriteFile(fileName, appBytes, os.ModePerm); err != nil {
101 | tmpErr := fmt.Errorf("下载最新版本异常, 错误: %s", err)
102 | if ui.Singleton().Notification() != nil {
103 | _ = ui.Singleton().Notification().ShowError("", tmpErr.Error())
104 | } else {
105 | msg.Err(mainWindow, tmpErr)
106 | }
107 | return
108 | }
109 | strMsg := fmt.Sprintf("下载最新版本成功[%s], 关闭当前应用, 双击打开对应可执行文件即可", fileName)
110 | if ui.Singleton().Notification() != nil {
111 | _ = ui.Singleton().Notification().ShowInfo("", strMsg)
112 | } else {
113 | msg.Info(mainWindow, strMsg)
114 | }
115 | }()
116 | } else {
117 | _ = customM.actionDownloadHandle.SetEnabled(true)
118 | }
119 | }
120 |
121 | func (customM *TTMenu) eventActionDelLog() {
122 | mainWindow := ui.Singleton().GetWindow()
123 | isOk, _ := msg.Confirm(mainWindow, fmt.Sprintf("清除日志会删除今日之前的所有日志文件,是否继续?"))
124 | if isOk {
125 | now := time.Now()
126 | currentDayZero := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.Local)
127 | cntDel := 0
128 | _ = filepath.Walk("logs", func(path string, info fs.FileInfo, err error) error {
129 | if info.ModTime().Before(currentDayZero) {
130 | if err := os.Remove(path); err == nil {
131 | cntDel++
132 | }
133 | }
134 | return nil
135 | })
136 | if cntDel > 0 {
137 | msg.Info(ui.Singleton().GetWindow(), fmt.Sprintf("删除历史日志文件(数量: %d)", cntDel))
138 | } else {
139 | msg.Info(ui.Singleton().GetWindow(), "暂无历史日志文件")
140 | }
141 | }
142 | }
143 |
144 | func (customM *TTMenu) eventActionQuit() {
145 | mainWindow := ui.Singleton().GetWindow()
146 | isOk, _ := msg.Confirm(mainWindow, fmt.Sprintf("即将退出当前应用,是否确认?"))
147 | if isOk {
148 | _ = mainWindow.Close()
149 | }
150 | }
151 |
152 | func (customM *TTMenu) eventSubtitleTranslate() {
153 | currentPage := page2.GetSubripTranslate()
154 | customM.eventGoPage(currentPage.GetId(), currentPage.GetName())
155 | }
156 |
157 | func (customM *TTMenu) eventGoPage(pageId string, name string) {
158 | if pageId == "" {
159 | return
160 | }
161 | if err := ui.Singleton().GoPage(pageId); err != nil {
162 | log.Singleton().Error("跳转页面异常", zap.String("page", name), zap.String("id", pageId), zap.Error(err))
163 | msg.Err(ui.Singleton().GetWindow(), fmt.Errorf("跳转页面[%s]异常", name))
164 | }
165 | }
166 |
--------------------------------------------------------------------------------
/platform/win/page/page.go:
--------------------------------------------------------------------------------
1 | package page
2 |
3 | import (
4 | "anto/platform/win/ui/pack"
5 | "github.com/lxn/walk"
6 | . "github.com/lxn/walk/declarative"
7 | )
8 |
9 | func StdRootWidget(rootWidget **walk.Composite, widgets ...Widget) Widget {
10 | return pack.UIComposite(
11 | pack.NewUICompositeArgs(rootWidget).
12 | SetVisible(false).
13 | SetLayoutVBox(false).
14 | SetWidgets(widgets),
15 | )
16 | }
17 |
18 | func StdBrowserSelectorWidget(title string, btOnClickFn walk.EventHandler, echoTarget **walk.Label) Widget {
19 | return pack.UIComposite(
20 | pack.NewUICompositeArgs(nil).SetLayoutHBox(true).SetWidgets(
21 | pack.NewWidgetGroup().Append(
22 | pack.UILabel(pack.NewUILabelArgs(nil).SetText(title)),
23 | pack.UIPushBtn(pack.NewUIPushBtnArgs(nil).SetText("选择").SetOnClicked(btOnClickFn)),
24 | pack.UILabel(pack.NewUILabelArgs(echoTarget).SetEnabled(false)),
25 | ).AppendZeroHSpacer().GetWidgets()))
26 | }
27 |
--------------------------------------------------------------------------------
/platform/win/ui/cfg.go:
--------------------------------------------------------------------------------
1 | package ui
2 |
3 | import (
4 | _const "anto/common"
5 | )
6 |
7 | type Cfg struct {
8 | Title string `mapstructure:"title"`
9 | Icon string `mapstructure:"icon"`
10 | ResourceDir string `mapstructure:"resource_dir"`
11 | }
12 |
13 | func (customC Cfg) Default() *Cfg {
14 | return &Cfg{Title: _const.UITitle, Icon: _const.UIIcon, ResourceDir: _const.UIResourceDir}
15 | }
16 |
--------------------------------------------------------------------------------
/platform/win/ui/event.go:
--------------------------------------------------------------------------------
1 | package ui
2 |
3 | import "github.com/lxn/walk"
4 |
5 | func (customG *Gui) eventCustomClose(canceled *bool, reason walk.CloseReason) {
6 | reason = walk.CloseReasonUser
7 | *canceled = false
8 | customG.ctxCancelFn()
9 | }
10 |
--------------------------------------------------------------------------------
/platform/win/ui/handle/file_dialog.go:
--------------------------------------------------------------------------------
1 | package handle
2 |
3 | import (
4 | "anto/platform/win/ui/msg"
5 | "fmt"
6 | "github.com/lxn/walk"
7 | )
8 |
9 | func FileDialogHandle(args *FileDialogHandleArgs) {
10 | dlg := new(walk.FileDialog)
11 | dlg.Title = args.title
12 | dlg.Filter = args.filter
13 | topic := ""
14 | var isAccepted bool
15 | var err error
16 | if args.isFileSelector {
17 | topic = "文件"
18 | isAccepted, err = dlg.ShowOpen(args.owner)
19 | } else {
20 | topic = "目录"
21 | isAccepted, err = dlg.ShowBrowseFolder(args.owner)
22 | }
23 | if err != nil {
24 | msg.Err(args.owner, fmt.Errorf("选择%s异常, 错误: %s", topic, err))
25 | return
26 | } else if !isAccepted {
27 | return
28 | }
29 | _ = (*args.pathEchoHandle).SetText(dlg.FilePath)
30 | }
31 |
32 | type FileDialogHandleArgs struct {
33 | owner walk.Form
34 | pathEchoHandle **walk.Label
35 | title string
36 | filter string
37 | isFileSelector bool
38 | }
39 |
40 | func NewFileDialogHandleArgs(owner walk.Form, pathEchoHandle **walk.Label) *FileDialogHandleArgs {
41 | return &FileDialogHandleArgs{owner: owner, pathEchoHandle: pathEchoHandle, isFileSelector: true}
42 | }
43 |
44 | func (customT *FileDialogHandleArgs) File() *FileDialogHandleArgs {
45 | customT.isFileSelector = true
46 | return customT
47 | }
48 |
49 | func (customT *FileDialogHandleArgs) Folder() *FileDialogHandleArgs {
50 | customT.isFileSelector = false
51 | return customT
52 | }
53 |
54 | func (customT *FileDialogHandleArgs) SetOwner(owner walk.Form) *FileDialogHandleArgs {
55 | customT.owner = owner
56 | return customT
57 | }
58 |
59 | func (customT *FileDialogHandleArgs) SetTitle(title string) *FileDialogHandleArgs {
60 | customT.title = title
61 | return customT
62 | }
63 |
64 | func (customT *FileDialogHandleArgs) SetFilter(filter string) *FileDialogHandleArgs {
65 | customT.filter = filter
66 | return customT
67 | }
68 |
69 | func (customT *FileDialogHandleArgs) SetPathEchoHandle(pathEchoHandle **walk.Label) *FileDialogHandleArgs {
70 | customT.pathEchoHandle = pathEchoHandle
71 | return customT
72 | }
73 |
--------------------------------------------------------------------------------
/platform/win/ui/msg/msg.go:
--------------------------------------------------------------------------------
1 | package msg
2 |
3 | import "github.com/lxn/walk"
4 |
5 | const msgTitle = "提示"
6 |
7 | func Info(owner walk.Form, msg string) int {
8 | return walk.MsgBox(owner, msgTitle, msg, walk.MsgBoxOK|walk.MsgBoxIconInformation)
9 | }
10 |
11 | func Warn(owner walk.Form, msg string) int {
12 | return walk.MsgBox(owner, msgTitle, msg, walk.MsgBoxOK|walk.MsgBoxIconWarning)
13 | }
14 |
15 | func Err(owner walk.Form, err error) int {
16 | return walk.MsgBox(owner, msgTitle, err.Error(), walk.MsgBoxOK|walk.MsgBoxIconError)
17 | }
18 |
19 | /* 这个。。。似乎意思不大
20 | func Help(owner walk.Form, msg string) int {
21 | return walk.MsgBox(owner, msgTitle, msg, walk.MsgBoxHelp|walk.MsgBoxIconExclamation)
22 | }
23 | */
24 |
25 | func Confirm(owner walk.Form, msg string) (bool, bool) {
26 | ret := walk.MsgBox(owner, msgTitle, msg, walk.MsgBoxYesNo|walk.MsgBoxIconExclamation)
27 | return ret == 6, ret == 7
28 | }
29 |
--------------------------------------------------------------------------------
/platform/win/ui/notification.go:
--------------------------------------------------------------------------------
1 | package ui
2 |
3 | import (
4 | "anto/platform/win/ui/msg"
5 | "errors"
6 | "github.com/lxn/walk"
7 | )
8 |
9 | func (customG *Gui) initNotification() {
10 | if customG.notification == nil {
11 | tmpNotification, err := walk.NewNotifyIcon(customG.GetWindow())
12 | if err != nil {
13 | msg.Err(customG.GetWindow(), errors.New("初始化系统通知失败"))
14 | return
15 | }
16 | customG.notification = tmpNotification
17 | //defer tmpNotification.Dispose()
18 | }
19 |
20 | _ = customG.notification.SetVisible(true)
21 | }
22 |
23 | func (customG *Gui) Notification() *walk.NotifyIcon {
24 | return customG.notification
25 | }
26 |
--------------------------------------------------------------------------------
/platform/win/ui/pack/ui_checkbox.go:
--------------------------------------------------------------------------------
1 | package pack
2 |
3 | import (
4 | "github.com/lxn/walk"
5 | . "github.com/lxn/walk/declarative"
6 | )
7 |
8 | func UICheckBox(args *UICheckBoxArgs) CheckBox {
9 | return CheckBox{
10 | AssignTo: args.assignTo,
11 | Checked: args.checked,
12 | MinSize: args.customSize,
13 | MaxSize: args.customSize,
14 | Text: args.text,
15 | OnClicked: args.onClickedFn,
16 | }
17 | }
18 |
19 | func NewUICheckBoxArgs(assignTo **walk.CheckBox) *UICheckBoxArgs {
20 | return &UICheckBoxArgs{assignTo: assignTo}
21 | }
22 |
23 | type UICheckBoxArgs struct {
24 | assignTo **walk.CheckBox
25 | checked bool
26 | customSize Size
27 | text string
28 | onClickedFn walk.EventHandler
29 | }
30 |
31 | func (customT *UICheckBoxArgs) SetAssignTo(assignTo **walk.CheckBox) *UICheckBoxArgs {
32 | customT.assignTo = assignTo
33 | return customT
34 | }
35 |
36 | func (customT *UICheckBoxArgs) SetChecked(checked bool) *UICheckBoxArgs {
37 | customT.checked = checked
38 | return customT
39 | }
40 |
41 | func (customT *UICheckBoxArgs) SetCustomSize(customSize Size) *UICheckBoxArgs {
42 | customT.customSize = customSize
43 | return customT
44 | }
45 |
46 | func (customT *UICheckBoxArgs) SetText(text string) *UICheckBoxArgs {
47 | customT.text = text
48 | return customT
49 | }
50 |
51 | func (customT *UICheckBoxArgs) SetOnClickedFn(onClickedFn walk.EventHandler) *UICheckBoxArgs {
52 | customT.onClickedFn = onClickedFn
53 | return customT
54 | }
55 |
--------------------------------------------------------------------------------
/platform/win/ui/pack/ui_combo_box.go:
--------------------------------------------------------------------------------
1 | package pack
2 |
3 | import (
4 | "github.com/lxn/walk"
5 | . "github.com/lxn/walk/declarative"
6 | )
7 |
8 | func UIComboBox(args *UIComboBoxArgs) Widget {
9 | return ComboBox{
10 | AssignTo: args.assignTo,
11 | Model: args.model,
12 | CurrentIndex: args.currentIdx,
13 | OnCurrentIndexChanged: args.onCurrentIdxChangedFn,
14 | DisplayMember: args.displayMember,
15 | BindingMember: args.bindingMember,
16 | MinSize: args.customSize,
17 | MaxSize: args.customSize,
18 | }
19 | }
20 |
21 | func NewUIComboBoxArgs(assignTo **walk.ComboBox) *UIComboBoxArgs {
22 | return &UIComboBoxArgs{assignTo: assignTo, customSize: Size{Width: 80}}
23 | }
24 |
25 | type UIComboBoxArgs struct {
26 | assignTo **walk.ComboBox
27 | model interface{}
28 | currentIdx interface{}
29 | onCurrentIdxChangedFn walk.EventHandler
30 | displayMember string
31 | bindingMember string
32 | customSize Size
33 | }
34 |
35 | func (customT *UIComboBoxArgs) SetCustomSize(customSize Size) *UIComboBoxArgs {
36 | customT.customSize = customSize
37 | return customT
38 | }
39 |
40 | func (customT *UIComboBoxArgs) SetDisplayMember(displayMember string) *UIComboBoxArgs {
41 | customT.displayMember = displayMember
42 | return customT
43 | }
44 |
45 | func (customT *UIComboBoxArgs) SetBindingMember(bindingMember string) *UIComboBoxArgs {
46 | customT.bindingMember = bindingMember
47 | return customT
48 | }
49 |
50 | func (customT *UIComboBoxArgs) SetAssignTo(assignTo **walk.ComboBox) *UIComboBoxArgs {
51 | customT.assignTo = assignTo
52 | return customT
53 | }
54 |
55 | func (customT *UIComboBoxArgs) SetModel(model interface{}) *UIComboBoxArgs {
56 | customT.model = model
57 | return customT
58 | }
59 |
60 | func (customT *UIComboBoxArgs) SetCurrentIdx(currentIdx interface{}) *UIComboBoxArgs {
61 | customT.currentIdx = currentIdx
62 | return customT
63 | }
64 |
65 | func (customT *UIComboBoxArgs) SetOnCurrentIdxChangedFn(onCurrentIdxChangedFn walk.EventHandler) *UIComboBoxArgs {
66 | customT.onCurrentIdxChangedFn = onCurrentIdxChangedFn
67 | return customT
68 | }
69 |
--------------------------------------------------------------------------------
/platform/win/ui/pack/ui_composite.go:
--------------------------------------------------------------------------------
1 | package pack
2 |
3 | import (
4 | "github.com/lxn/walk"
5 | . "github.com/lxn/walk/declarative"
6 | )
7 |
8 | func UIComposite(args *UICompositeArgs) Widget {
9 | return Composite{
10 | MinSize: args.customSize,
11 | MaxSize: args.customSize,
12 | Layout: args.layout,
13 | Alignment: args.alignment,
14 | Children: args.widgets,
15 | AssignTo: args.assignTo,
16 | Visible: args.visible,
17 | }
18 | }
19 |
20 | func NewUICompositeArgs(assignTo **walk.Composite) *UICompositeArgs {
21 | return &UICompositeArgs{
22 | assignTo: assignTo,
23 | alignment: AlignHCenterVCenter,
24 | layout: VBox{},
25 | visible: true,
26 | }
27 | }
28 |
29 | type UICompositeArgs struct {
30 | alignment Alignment2D
31 | assignTo **walk.Composite
32 | customSize Size
33 | layout Layout
34 | widgets []Widget
35 | visible Property
36 | }
37 |
38 | func (customT *UICompositeArgs) SetVisible(visible Property) *UICompositeArgs {
39 | customT.visible = visible
40 | return customT
41 | }
42 | func (customT *UICompositeArgs) SetAssignTo(assignTo **walk.Composite) *UICompositeArgs {
43 | customT.assignTo = assignTo
44 | return customT
45 | }
46 | func (customT *UICompositeArgs) SetAlignment(alignment Alignment2D) *UICompositeArgs {
47 | customT.alignment = alignment
48 | return customT
49 | }
50 | func (customT *UICompositeArgs) SetCustomSize(customSize Size) *UICompositeArgs {
51 | customT.customSize = customSize
52 | return customT
53 | }
54 | func (customT *UICompositeArgs) SetLayout(layout Layout) *UICompositeArgs {
55 | customT.layout = layout
56 | return customT
57 | }
58 | func (customT *UICompositeArgs) SetLayoutHBox(flagMarginsZero bool) *UICompositeArgs {
59 | customT.layout = HBox{MarginsZero: flagMarginsZero}
60 | return customT
61 | }
62 | func (customT *UICompositeArgs) SetLayoutVBox(flagMarginsZero bool) *UICompositeArgs {
63 | customT.layout = VBox{MarginsZero: flagMarginsZero}
64 | return customT
65 | }
66 |
67 | func (customT *UICompositeArgs) SetWidgets(widgets []Widget) *UICompositeArgs {
68 | customT.widgets = widgets
69 | return customT
70 | }
71 |
--------------------------------------------------------------------------------
/platform/win/ui/pack/ui_group_box.go:
--------------------------------------------------------------------------------
1 | package pack
2 |
3 | import (
4 | "github.com/lxn/walk"
5 | . "github.com/lxn/walk/declarative"
6 | )
7 |
8 | func UIGroupBox(args *UIGroupBoxArgs) Widget {
9 | return GroupBox{
10 | Title: args.title,
11 | MinSize: args.customSize,
12 | MaxSize: args.customSize,
13 | Layout: args.layout,
14 | Alignment: args.alignment,
15 | Children: args.widgets,
16 | AssignTo: args.assignTo,
17 | Visible: args.visible,
18 | }
19 | }
20 |
21 | func NewUIGroupBoxArgs(assignTo **walk.GroupBox) *UIGroupBoxArgs {
22 | return &UIGroupBoxArgs{
23 | assignTo: assignTo,
24 | alignment: AlignHCenterVCenter,
25 | layout: VBox{},
26 | visible: true,
27 | }
28 | }
29 |
30 | type UIGroupBoxArgs struct {
31 | title string
32 | alignment Alignment2D
33 | assignTo **walk.GroupBox
34 | customSize Size
35 | layout Layout
36 | widgets []Widget
37 | visible bool
38 | }
39 |
40 | func (customT *UIGroupBoxArgs) SetVisible(visible bool) *UIGroupBoxArgs {
41 | customT.visible = visible
42 | return customT
43 | }
44 |
45 | func (customT *UIGroupBoxArgs) SetTitle(title string) *UIGroupBoxArgs {
46 | customT.title = title
47 | return customT
48 | }
49 |
50 | func (customT *UIGroupBoxArgs) SetAssignTo(assignTo **walk.GroupBox) *UIGroupBoxArgs {
51 | customT.assignTo = assignTo
52 | return customT
53 | }
54 | func (customT *UIGroupBoxArgs) SetAlignment(alignment Alignment2D) *UIGroupBoxArgs {
55 | customT.alignment = alignment
56 | return customT
57 | }
58 | func (customT *UIGroupBoxArgs) SetCustomSize(customSize Size) *UIGroupBoxArgs {
59 | customT.customSize = customSize
60 | return customT
61 | }
62 | func (customT *UIGroupBoxArgs) SetLayout(layout Layout) *UIGroupBoxArgs {
63 | customT.layout = layout
64 | return customT
65 | }
66 | func (customT *UIGroupBoxArgs) SetLayoutHBox(flagMarginsZero bool) *UIGroupBoxArgs {
67 | return customT.SetLayout(HBox{MarginsZero: flagMarginsZero})
68 | }
69 | func (customT *UIGroupBoxArgs) SetLayoutVBox(flagMarginsZero bool) *UIGroupBoxArgs {
70 | return customT.SetLayout(VBox{MarginsZero: flagMarginsZero})
71 | }
72 | func (customT *UIGroupBoxArgs) SetWidgets(widgets []Widget) *UIGroupBoxArgs {
73 | customT.widgets = widgets
74 | return customT
75 | }
76 |
--------------------------------------------------------------------------------
/platform/win/ui/pack/ui_image_view.go:
--------------------------------------------------------------------------------
1 | package pack
2 |
3 | import (
4 | "github.com/lxn/walk"
5 | . "github.com/lxn/walk/declarative"
6 | )
7 |
8 | func UIImageView(args *UIImageViewArgs) Widget {
9 | return ImageView{
10 | AssignTo: args.assignTo,
11 | Image: args.image,
12 | MinSize: args.customSize,
13 | MaxSize: args.customSize,
14 | Mode: args.mode,
15 | }
16 | }
17 |
18 | func NewUIImageViewArgs(assignTo **walk.ImageView) *UIImageViewArgs {
19 | return &UIImageViewArgs{assignTo: assignTo, mode: ImageViewModeShrink}
20 | }
21 |
22 | type UIImageViewArgs struct {
23 | assignTo **walk.ImageView
24 | image Property
25 | customSize Size
26 | mode ImageViewMode
27 | }
28 |
29 | func (customT *UIImageViewArgs) SetAssignTo(assignTo **walk.ImageView) *UIImageViewArgs {
30 | customT.assignTo = assignTo
31 | return customT
32 | }
33 |
34 | func (customT *UIImageViewArgs) SetImage(image Property) *UIImageViewArgs {
35 | customT.image = image
36 | return customT
37 | }
38 |
39 | func (customT *UIImageViewArgs) SetCustomSize(customSize Size) *UIImageViewArgs {
40 | customT.customSize = customSize
41 | return customT
42 | }
43 |
44 | func (customT *UIImageViewArgs) SetMode(mode ImageViewMode) *UIImageViewArgs {
45 | customT.mode = mode
46 | return customT
47 | }
48 |
--------------------------------------------------------------------------------
/platform/win/ui/pack/ui_label.go:
--------------------------------------------------------------------------------
1 | package pack
2 |
3 | import (
4 | "github.com/lxn/walk"
5 | . "github.com/lxn/walk/declarative"
6 | )
7 |
8 | func UILabel(args *UILabelArgs) Widget {
9 | return Label{
10 | Enabled: args.enabled,
11 | MaxSize: args.customSize,
12 | MinSize: args.customSize,
13 | Visible: args.visible,
14 | AssignTo: args.assignTo,
15 | Text: args.text,
16 | }
17 | }
18 |
19 | func NewUILabelArgs(assignTo **walk.Label) *UILabelArgs {
20 | return &UILabelArgs{
21 | assignTo: assignTo,
22 | visible: true,
23 | enabled: true,
24 | }
25 | }
26 |
27 | type UILabelArgs struct {
28 | assignTo **walk.Label
29 | visible bool
30 | enabled bool
31 | text string
32 | customSize Size
33 | }
34 |
35 | func (customT *UILabelArgs) SetAssignTo(assignTo **walk.Label) *UILabelArgs {
36 | customT.assignTo = assignTo
37 | return customT
38 | }
39 |
40 | func (customT *UILabelArgs) SetVisible(visible bool) *UILabelArgs {
41 | customT.visible = visible
42 | return customT
43 | }
44 |
45 | func (customT *UILabelArgs) SetEnabled(enabled bool) *UILabelArgs {
46 | customT.enabled = enabled
47 | return customT
48 | }
49 |
50 | func (customT *UILabelArgs) SetText(text string) *UILabelArgs {
51 | customT.text = text
52 | return customT
53 | }
54 |
55 | func (customT *UILabelArgs) SetCustomSize(customSize Size) *UILabelArgs {
56 | customT.customSize = customSize
57 | return customT
58 | }
59 |
--------------------------------------------------------------------------------
/platform/win/ui/pack/ui_line_edit.go:
--------------------------------------------------------------------------------
1 | package pack
2 |
3 | import (
4 | "github.com/lxn/walk"
5 | . "github.com/lxn/walk/declarative"
6 | )
7 |
8 | func UILineEdit(args *UILineEditArgs) Widget {
9 | return LineEdit{
10 | MinSize: args.customSize,
11 | MaxSize: args.customSize,
12 | AssignTo: args.assignTo,
13 | ReadOnly: args.readOnly,
14 | OnTextChanged: args.onTextChanged,
15 | Text: args.text,
16 | }
17 | }
18 |
19 | func NewUILineEditArgs(assignTo **walk.LineEdit) *UILineEditArgs {
20 | return &UILineEditArgs{assignTo: assignTo}
21 | }
22 |
23 | type UILineEditArgs struct {
24 | assignTo **walk.LineEdit
25 | customSize Size
26 | readOnly bool
27 | onTextChanged walk.EventHandler
28 | text string
29 | }
30 |
31 | func (customT *UILineEditArgs) SetText(text string) *UILineEditArgs {
32 | customT.text = text
33 | return customT
34 | }
35 |
36 | func (customT *UILineEditArgs) SetReadOnly(readOnly bool) *UILineEditArgs {
37 | customT.readOnly = readOnly
38 | return customT
39 | }
40 |
41 | func (customT *UILineEditArgs) SetOnTextChanged(onTextChanged walk.EventHandler) *UILineEditArgs {
42 | customT.onTextChanged = onTextChanged
43 | return customT
44 | }
45 |
46 | func (customT *UILineEditArgs) SetAssignTo(assignTo **walk.LineEdit) *UILineEditArgs {
47 | customT.assignTo = assignTo
48 | return customT
49 | }
50 |
51 | func (customT *UILineEditArgs) SetCustomSize(customSize Size) *UILineEditArgs {
52 | customT.customSize = customSize
53 | return customT
54 | }
55 |
--------------------------------------------------------------------------------
/platform/win/ui/pack/ui_push_btn.go:
--------------------------------------------------------------------------------
1 | package pack
2 |
3 | import (
4 | "github.com/lxn/walk"
5 | . "github.com/lxn/walk/declarative"
6 | )
7 |
8 | func UIPushBtn(args *UIPushBtnArgs) Widget {
9 | return PushButton{
10 | AssignTo: args.assignTo,
11 | MinSize: args.customSize,
12 | MaxSize: args.customSize,
13 | Text: args.text,
14 | OnClicked: args.onClicked,
15 | }
16 | }
17 |
18 | func NewUIPushBtnArgs(assignTo **walk.PushButton) *UIPushBtnArgs {
19 | return &UIPushBtnArgs{assignTo: assignTo, text: "btn", customSize: Size{Width: 80}}
20 | }
21 |
22 | type UIPushBtnArgs struct {
23 | assignTo **walk.PushButton
24 | customSize Size
25 | text string
26 | onClicked walk.EventHandler
27 | }
28 |
29 | func (customT *UIPushBtnArgs) SetAssignTo(assignTo **walk.PushButton) *UIPushBtnArgs {
30 | customT.assignTo = assignTo
31 | return customT
32 | }
33 |
34 | func (customT *UIPushBtnArgs) SetCustomSize(customSize Size) *UIPushBtnArgs {
35 | customT.customSize = customSize
36 | return customT
37 | }
38 |
39 | func (customT *UIPushBtnArgs) SetText(text string) *UIPushBtnArgs {
40 | customT.text = text
41 | return customT
42 | }
43 |
44 | func (customT *UIPushBtnArgs) SetOnClicked(onClicked walk.EventHandler) *UIPushBtnArgs {
45 | customT.onClicked = onClicked
46 | return customT
47 | }
48 |
--------------------------------------------------------------------------------
/platform/win/ui/pack/ui_scroll_view.go:
--------------------------------------------------------------------------------
1 | package pack
2 |
3 | import (
4 | "github.com/lxn/walk"
5 | . "github.com/lxn/walk/declarative"
6 | )
7 |
8 | func UIScrollView(args *UIScrollViewArgs) Widget {
9 | return ScrollView{
10 | MaxSize: args.customSize,
11 | MinSize: args.customSize,
12 | Visible: args.visible,
13 | AssignTo: args.assignTo,
14 | Children: args.children,
15 | Layout: args.layout,
16 | HorizontalFixed: args.horizontalFixed,
17 | VerticalFixed: args.verticalFixed,
18 | }
19 | }
20 |
21 | func NewUIScrollViewArgs(assignTo **walk.ScrollView) *UIScrollViewArgs {
22 | return &UIScrollViewArgs{
23 | assignTo: assignTo,
24 | visible: true,
25 | layout: VBox{MarginsZero: true},
26 | horizontalFixed: false,
27 | verticalFixed: false,
28 | }
29 | }
30 |
31 | type UIScrollViewArgs struct {
32 | assignTo **walk.ScrollView
33 | visible bool
34 | customSize Size
35 | children []Widget
36 | layout Layout
37 | horizontalFixed bool
38 | verticalFixed bool
39 | }
40 |
41 | func (customT *UIScrollViewArgs) SetLayout(layout Layout) *UIScrollViewArgs {
42 | customT.layout = layout
43 | return customT
44 | }
45 |
46 | func (customT *UIScrollViewArgs) SetHorizontalFixed(horizontalFixed bool) *UIScrollViewArgs {
47 | customT.horizontalFixed = horizontalFixed
48 | return customT
49 | }
50 |
51 | func (customT *UIScrollViewArgs) HorizontalFixed() *UIScrollViewArgs {
52 | customT.horizontalFixed = true
53 | return customT
54 | }
55 |
56 | func (customT *UIScrollViewArgs) SetVerticalFixed(verticalFixed bool) *UIScrollViewArgs {
57 | customT.verticalFixed = verticalFixed
58 | return customT
59 | }
60 |
61 | func (customT *UIScrollViewArgs) VerticalFixed() *UIScrollViewArgs {
62 | customT.verticalFixed = true
63 | return customT
64 | }
65 |
66 | func (customT *UIScrollViewArgs) SetChildren(children []Widget) *UIScrollViewArgs {
67 | customT.children = children
68 | return customT
69 | }
70 |
71 | func (customT *UIScrollViewArgs) SetAssignTo(assignTo **walk.ScrollView) *UIScrollViewArgs {
72 | customT.assignTo = assignTo
73 | return customT
74 | }
75 |
76 | func (customT *UIScrollViewArgs) SetVisible(visible bool) *UIScrollViewArgs {
77 | customT.visible = visible
78 | return customT
79 | }
80 |
81 | func (customT *UIScrollViewArgs) SetCustomSize(customSize Size) *UIScrollViewArgs {
82 | customT.customSize = customSize
83 | return customT
84 | }
85 |
--------------------------------------------------------------------------------
/platform/win/ui/pack/ui_text_edit.go:
--------------------------------------------------------------------------------
1 | package pack
2 |
3 | import (
4 | "github.com/lxn/walk"
5 | . "github.com/lxn/walk/declarative"
6 | )
7 |
8 | func UITextEdit(args *UITextEditArgs) Widget {
9 | return TextEdit{
10 | MinSize: args.customSize,
11 | MaxSize: args.customSize,
12 | AssignTo: args.assignTo,
13 | VScroll: args.vScroll,
14 | HScroll: args.hScroll,
15 | OnTextChanged: args.onTextChanged,
16 | ReadOnly: args.readOnly,
17 | }
18 | }
19 |
20 | func NewUITextEditArgs(assignTo **walk.TextEdit) *UITextEditArgs {
21 | return &UITextEditArgs{assignTo: assignTo}
22 | }
23 |
24 | type UITextEditArgs struct {
25 | assignTo **walk.TextEdit
26 | customSize Size
27 | vScroll bool
28 | hScroll bool
29 | readOnly bool
30 | onTextChanged walk.EventHandler
31 | }
32 |
33 | func (customT *UITextEditArgs) SetReadOnly(readOnly bool) *UITextEditArgs {
34 | customT.readOnly = readOnly
35 | return customT
36 | }
37 |
38 | func (customT *UITextEditArgs) SetOnTextChanged(onTextChanged walk.EventHandler) *UITextEditArgs {
39 | customT.onTextChanged = onTextChanged
40 | return customT
41 | }
42 |
43 | func (customT *UITextEditArgs) SetAssignTo(assignTo **walk.TextEdit) *UITextEditArgs {
44 | customT.assignTo = assignTo
45 | return customT
46 | }
47 |
48 | func (customT *UITextEditArgs) SetCustomSize(customSize Size) *UITextEditArgs {
49 | customT.customSize = customSize
50 | return customT
51 | }
52 |
53 | func (customT *UITextEditArgs) SetVScroll(vScroll bool) *UITextEditArgs {
54 | customT.vScroll = vScroll
55 | return customT
56 | }
57 |
58 | func (customT *UITextEditArgs) SetHScroll(hScroll bool) *UITextEditArgs {
59 | customT.hScroll = hScroll
60 | return customT
61 | }
62 |
--------------------------------------------------------------------------------
/platform/win/ui/pack/ui_text_label.go:
--------------------------------------------------------------------------------
1 | package pack
2 |
3 | import (
4 | "github.com/lxn/walk"
5 | . "github.com/lxn/walk/declarative"
6 | )
7 |
8 | func UITextLabel(args *UITextLabelArgs) Widget {
9 | return TextLabel{
10 | Enabled: args.enabled,
11 | MaxSize: args.customSize,
12 | MinSize: args.customSize,
13 | Visible: args.visible,
14 | AssignTo: args.assignTo,
15 | Text: args.text,
16 | }
17 | }
18 |
19 | func NewUITextLabelArgs(assignTo **walk.TextLabel) *UITextLabelArgs {
20 | return &UITextLabelArgs{
21 | assignTo: assignTo,
22 | visible: true,
23 | enabled: true,
24 | }
25 | }
26 |
27 | type UITextLabelArgs struct {
28 | assignTo **walk.TextLabel
29 | visible bool
30 | enabled bool
31 | text string
32 | customSize Size
33 | }
34 |
35 | func (customT *UITextLabelArgs) SetAssignTo(assignTo **walk.TextLabel) *UITextLabelArgs {
36 | customT.assignTo = assignTo
37 | return customT
38 | }
39 |
40 | func (customT *UITextLabelArgs) SetVisible(visible bool) *UITextLabelArgs {
41 | customT.visible = visible
42 | return customT
43 | }
44 |
45 | func (customT *UITextLabelArgs) SetEnabled(enabled bool) *UITextLabelArgs {
46 | customT.enabled = enabled
47 | return customT
48 | }
49 |
50 | func (customT *UITextLabelArgs) SetText(text string) *UITextLabelArgs {
51 | customT.text = text
52 | return customT
53 | }
54 |
55 | func (customT *UITextLabelArgs) SetCustomSize(customSize Size) *UITextLabelArgs {
56 | customT.customSize = customSize
57 | return customT
58 | }
59 |
--------------------------------------------------------------------------------
/platform/win/ui/pack/widget_group.go:
--------------------------------------------------------------------------------
1 | package pack
2 |
3 | import . "github.com/lxn/walk/declarative"
4 |
5 | func NewWidgetGroup() *WidgetGroup {
6 | return new(WidgetGroup)
7 | }
8 |
9 | type WidgetGroup struct {
10 | widgets []Widget
11 | }
12 |
13 | func (customT *WidgetGroup) Append(widgets ...Widget) *WidgetGroup {
14 | if len(widgets) > 0 {
15 | customT.widgets = append(customT.widgets, widgets...)
16 | }
17 | return customT
18 | }
19 |
20 | func (customT *WidgetGroup) AppendZeroHSpacer() *WidgetGroup {
21 | customT.widgets = append(customT.widgets, HSpacer{})
22 | return customT
23 | }
24 |
25 | func (customT *WidgetGroup) AppendZeroVSpacer() *WidgetGroup {
26 | customT.widgets = append(customT.widgets, VSpacer{})
27 | return customT
28 | }
29 |
30 | func (customT *WidgetGroup) GetWidgets() []Widget {
31 | return customT.widgets
32 | }
33 |
--------------------------------------------------------------------------------
/platform/win/ui/page.go:
--------------------------------------------------------------------------------
1 | package ui
2 |
3 | import (
4 | "fmt"
5 | "github.com/lxn/walk"
6 | . "github.com/lxn/walk/declarative"
7 | "sync"
8 | )
9 |
10 | type IPage interface {
11 | GetId() string
12 | GetName() string
13 | BindWindow(win *walk.MainWindow)
14 | SetVisible(isVisible bool)
15 | GetWidget() Widget
16 | Reset()
17 | }
18 |
19 | type PageCtl struct {
20 | current IPage
21 | pages sync.Map
22 | menus []MenuItem
23 | }
24 |
25 | func (customPC *PageCtl) PushPages(pages ...IPage) {
26 | for _, page := range pages {
27 | if _, isExist := customPC.pages.Load(page.GetId()); isExist {
28 | continue
29 | }
30 | customPC.pages.Store(page.GetId(), page)
31 | }
32 | }
33 |
34 | func (customPC *PageCtl) SetCurrent(pageId string) error {
35 | currentPage, err := customPC.GetPageById(pageId)
36 | if err != nil {
37 | return err
38 | }
39 | customPC.current = currentPage
40 | defer customPC.Render()
41 | return nil
42 | }
43 |
44 | func (customPC *PageCtl) Render() {
45 | customPC.pages.Range(func(pageId, currentPage any) bool {
46 | currentPage.(IPage).SetVisible(currentPage.(IPage).GetId() == customPC.current.GetId())
47 | currentPage.(IPage).Reset()
48 | return true
49 | })
50 | }
51 |
52 | func (customPC *PageCtl) GetPageById(pageId string) (IPage, error) {
53 | currentPage, isOk := customPC.pages.Load(pageId)
54 | if !isOk {
55 | return nil, fmt.Errorf("当前页面[%s]不存在", pageId)
56 | }
57 | return currentPage.(IPage), nil
58 | }
59 |
60 | func (customPC *PageCtl) Bind(win *walk.MainWindow) {
61 | customPC.pages.Range(func(pageId, currentPage any) bool {
62 | currentPage.(IPage).BindWindow(win)
63 | return true
64 | })
65 | return
66 | }
67 |
68 | func (customPC *PageCtl) GetWidgets() []Widget {
69 | var widgets []Widget
70 | customPC.pages.Range(func(pageId, currentPage any) bool {
71 | widgets = append(widgets, currentPage.(IPage).GetWidget())
72 | return true
73 | })
74 | return widgets
75 | }
76 |
--------------------------------------------------------------------------------
/platform/win/ui/sets.go:
--------------------------------------------------------------------------------
1 | package ui
2 |
3 | import (
4 | "github.com/lxn/walk"
5 | "github.com/lxn/win"
6 | )
7 |
8 | func (customG *Gui) setWindowFlag() {
9 | win.SetWindowLong(customG.GetWindow().Handle(), win.GWL_STYLE,
10 | win.GetWindowLong(customG.GetWindow().Handle(), win.GWL_STYLE) & ^win.WS_MAXIMIZEBOX & ^win.WS_THICKFRAME)
11 | }
12 |
13 | func (customG *Gui) setWindowCenter() {
14 | scrWidth := win.GetSystemMetrics(win.SM_CXSCREEN)
15 | scrHeight := win.GetSystemMetrics(win.SM_CYSCREEN)
16 | _ = customG.GetWindow().SetBounds(walk.Rectangle{
17 | X: int((scrWidth - width) / 2), Y: int((scrHeight - height) / 2),
18 | Width: width, Height: height,
19 | })
20 | }
21 |
22 | func (customG *Gui) setWindowMinAndMaxSize() {
23 | minMaxSize := walk.Size{Width: width, Height: height}
24 | _ = customG.GetWindow().SetMinMaxSize(minMaxSize, minMaxSize)
25 | }
26 |
--------------------------------------------------------------------------------
/platform/win/ui/ui.go:
--------------------------------------------------------------------------------
1 | package ui
2 |
3 | import (
4 | "anto/lib/log"
5 | "context"
6 | "sync"
7 |
8 | "github.com/lxn/walk"
9 | . "github.com/lxn/walk/declarative"
10 | )
11 |
12 | const width = 800
13 | const height = 600
14 |
15 | var (
16 | apiSingleton *Gui
17 | onceSingleton sync.Once
18 | )
19 |
20 | func Singleton() *Gui {
21 | onceSingleton.Do(func() {
22 | apiSingleton = new(Gui)
23 | apiSingleton.pageCtl = new(PageCtl)
24 | })
25 | return apiSingleton
26 | }
27 |
28 | type Gui struct {
29 | ctx context.Context
30 | ctxCancelFn context.CancelFunc
31 | win *walk.MainWindow
32 | notification *walk.NotifyIcon
33 | cfg *Cfg
34 | menus []MenuItem
35 | pageCtl *PageCtl
36 | }
37 |
38 | func (customG *Gui) Init(cfg *Cfg) error {
39 | customG.cfg = cfg
40 |
41 | if err := customG.initMainWindow(); err != nil {
42 | return err
43 | }
44 |
45 | customG.initNotification()
46 |
47 | customG.setWindowFlag()
48 | customG.setWindowCenter()
49 | customG.setWindowMinAndMaxSize()
50 |
51 | customG.pageCtl.Bind(customG.GetWindow())
52 |
53 | customG.GetWindow().SetVisible(true)
54 |
55 | _ = customG.GetWindow().SetFocus()
56 |
57 | return nil
58 | }
59 |
60 | func (customG *Gui) Run(ctx context.Context, fnCancel context.CancelFunc) {
61 | customG.ctx = ctx
62 | customG.ctxCancelFn = fnCancel
63 |
64 | customG.win.Closing().Attach(customG.eventCustomClose)
65 |
66 | customG.GetWindow().Run()
67 | }
68 |
69 | func (customG *Gui) Close() {
70 | _ = customG.win.Close()
71 | }
72 |
73 | func (customG *Gui) GetWindow() *walk.MainWindow {
74 | return customG.win
75 | }
76 |
77 | func (customG *Gui) RegisterPages(pages ...IPage) {
78 | customG.pageCtl.PushPages(pages...)
79 | return
80 | }
81 |
82 | func (customG *Gui) GoPage(pageId string) error {
83 | return customG.pageCtl.SetCurrent(pageId)
84 | }
85 |
86 | func (customG *Gui) RegisterMenus(menus []MenuItem) {
87 | customG.menus = menus
88 | return
89 | }
90 |
91 | func (customG *Gui) initMainWindow() error {
92 | return MainWindow{
93 | AssignTo: &customG.win,
94 | Title: customG.cfg.Title, Icon: "./favicon.ico",
95 | Visible: false, Persistent: true,
96 | Layout: VBox{MarginsZero: true},
97 | MenuItems: customG.menus,
98 | Children: customG.pageCtl.GetWidgets(),
99 | }.Create()
100 | }
101 |
102 | func (customG *Gui) log() *log.Log {
103 | return log.Singleton()
104 | }
105 |
--------------------------------------------------------------------------------
/platform/win/win.go:
--------------------------------------------------------------------------------
1 | package win
2 |
3 | import (
4 | "anto/cfg"
5 | "anto/cron/detector"
6 | "anto/cron/reader"
7 | "anto/cron/translate"
8 | "anto/cron/writer"
9 | "anto/lib/log"
10 | "anto/lib/nohup"
11 | "anto/platform/win/page"
12 | "anto/platform/win/ui"
13 | "context"
14 | )
15 |
16 | func Run(ctx context.Context) {
17 | ui.Singleton().RegisterMenus(GetInstance().GetMenus())
18 |
19 | ui.Singleton().RegisterPages(page.GetSettings(), page.GetSubripTranslate())
20 |
21 | if err := ui.Singleton().Init(cfg.Singleton().UI); err != nil {
22 | log.Singleton().ErrorF("UI启动崩溃, 错误: %s", err)
23 | panic(err)
24 | }
25 |
26 | _ = ui.Singleton().GoPage(page.GetSubripTranslate().GetId())
27 |
28 | nohup.NewResident(
29 | ctx,
30 | detector.Singleton(), reader.Singleton(), translate.Singleton(), writer.Singleton(),
31 | ui.Singleton(),
32 | )
33 | }
34 |
--------------------------------------------------------------------------------
/resource/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/speauty/anto/d537a1ad2b935ec8c6973912e1a5ed919d932ea7/resource/favicon.ico
--------------------------------------------------------------------------------
/resource/resource.go:
--------------------------------------------------------------------------------
1 | package resource
2 |
3 | import _ "embed"
4 |
5 | //go:embed favicon.ico
6 | var Favicon []byte
7 |
--------------------------------------------------------------------------------