-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit a45225d
Showing
7 changed files
with
225 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
.idea |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
# Translate |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
package translate | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"golang.org/x/time/rate" | ||
"io" | ||
"net/http" | ||
"net/url" | ||
"strings" | ||
"time" | ||
) | ||
|
||
const translateURL = "https://translate.googleapis.com/translate_a/single" | ||
|
||
const ( | ||
LangZhCn = "zh-CN" | ||
) | ||
|
||
type Client struct { | ||
limiter *rate.Limiter | ||
targetLang string | ||
} | ||
|
||
func NewClient(opts ...Option) *Client { | ||
c := Client{ | ||
targetLang: LangZhCn, | ||
limiter: rate.NewLimiter(rate.Every(time.Second*10), 5), | ||
} | ||
for _, opt := range opts { | ||
opt(&c) | ||
} | ||
return &c | ||
} | ||
|
||
func (r Client) Translates(texts []string) ([]string, error) { | ||
return r.TranslatesWithTargetLang(texts, r.targetLang) | ||
} | ||
|
||
func (r Client) Translate(text string) (string, error) { | ||
return r.TranslateWithTargetLang(text, r.targetLang) | ||
} | ||
|
||
type kv struct { | ||
key string | ||
val string | ||
} | ||
|
||
func (r Client) TranslatesWithTargetLang(texts []string, targetLang string) ([]string, error) { | ||
if len(texts) == 0 { | ||
return []string{}, nil | ||
} | ||
_ = r.limiter.Wait(context.Background()) | ||
var contents []string | ||
for _, item := range texts { | ||
if item != "" { | ||
contents = append(contents, item) | ||
} | ||
} | ||
result, err := r.request(strings.Join(contents, "\n"), targetLang) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
var trans []kv | ||
if len(result) > 0 { | ||
if items, ok := result[0].([]any); ok { | ||
for _, item := range items { | ||
if values, ok := item.([]any); ok && len(values) >= 2 { | ||
trans = append(trans, kv{ | ||
strings.Trim(values[1].(string), " \n"), | ||
strings.Trim(values[0].(string), " \n"), | ||
}) | ||
} | ||
} | ||
} | ||
} | ||
|
||
for i, text := range texts { | ||
var temp []kv | ||
for _, item := range trans { | ||
if strings.Contains(text, item.key) { | ||
text = strings.Replace(text, item.key, item.val, 1) | ||
} else { | ||
temp = append(temp, item) | ||
} | ||
} | ||
trans = temp | ||
texts[i] = text | ||
} | ||
return texts, nil | ||
} | ||
|
||
func (r Client) TranslateWithTargetLang(text, targetLang string) (string, error) { | ||
_ = r.limiter.Wait(context.Background()) | ||
result, err := r.request(text, targetLang) | ||
if err != nil { | ||
return "", err | ||
} | ||
var content string | ||
if len(result) > 0 { | ||
if items, ok := result[0].([]any); ok { | ||
for _, item := range items { | ||
if values, ok := item.([]any); ok && len(values) >= 2 { | ||
content += values[0].(string) | ||
} | ||
} | ||
} | ||
} | ||
return content, nil | ||
} | ||
|
||
func (r Client) request(text string, targetLang string) ([]any, error) { | ||
data := url.Values{} | ||
data.Add("client", "gtx") | ||
data.Add("sl", "auto") | ||
data.Add("tl", targetLang) | ||
data.Add("dt", "t") | ||
data.Add("q", text) | ||
|
||
resp, err := http.Post(translateURL, "application/x-www-form-urlencoded", strings.NewReader(data.Encode())) | ||
if err != nil { | ||
return nil, err | ||
} | ||
defer func() { | ||
_ = resp.Body.Close() | ||
}() | ||
|
||
body, err := io.ReadAll(resp.Body) | ||
if err != nil { | ||
return nil, err | ||
} | ||
var result []any | ||
err = json.Unmarshal(body, &result) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return result, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
package translate | ||
|
||
import ( | ||
"reflect" | ||
"testing" | ||
) | ||
|
||
func TestClient_Translates(t *testing.T) { | ||
type args struct { | ||
texts []string | ||
} | ||
tests := []struct { | ||
name string | ||
args args | ||
want []string | ||
wantErr bool | ||
}{ | ||
{"", args{[]string{"hello", "world"}}, []string{"你好", "世界"}, false}, | ||
} | ||
r := NewClient() | ||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
got, err := r.Translates(tt.args.texts) | ||
if (err != nil) != tt.wantErr { | ||
t.Errorf("Translates() error = %v, wantErr %v", err, tt.wantErr) | ||
return | ||
} | ||
if !reflect.DeepEqual(got, tt.want) { | ||
t.Errorf("Translates() got = %v, want %v", got, tt.want) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
func TestClient_Translate(t *testing.T) { | ||
type args struct { | ||
text string | ||
} | ||
tests := []struct { | ||
name string | ||
args args | ||
want string | ||
wantErr bool | ||
}{ | ||
{"", args{"hello world"}, "你好世界", false}, | ||
} | ||
r := NewClient() | ||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
got, err := r.Translate(tt.args.text) | ||
if (err != nil) != tt.wantErr { | ||
t.Errorf("Translate() error = %v, wantErr %v", err, tt.wantErr) | ||
return | ||
} | ||
if got != tt.want { | ||
t.Errorf("Translate() got = %v, want %v", got, tt.want) | ||
} | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
module github.com/eiixy/go-translate | ||
|
||
go 1.20 | ||
|
||
require golang.org/x/time v0.5.0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= | ||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package translate | ||
|
||
import "golang.org/x/time/rate" | ||
|
||
type Option func(client *Client) | ||
|
||
func WithTargetLang(lang string) Option { | ||
return func(client *Client) { | ||
client.targetLang = lang | ||
} | ||
} | ||
|
||
func WithLimiter(limiter *rate.Limiter) Option { | ||
return func(client *Client) { | ||
client.limiter = limiter | ||
} | ||
} |