124 lines
3.1 KiB
Go
124 lines
3.1 KiB
Go
package translate
|
||
|
||
import (
|
||
"context"
|
||
"encoding/json"
|
||
"errors"
|
||
"fmt"
|
||
"io"
|
||
"net/http"
|
||
"net/url"
|
||
"strings"
|
||
"time"
|
||
)
|
||
|
||
var defaultClient = &http.Client{
|
||
Timeout: 15 * time.Second,
|
||
}
|
||
|
||
var apiBaseURL = "https://translate.googleapis.com/translate_a/single"
|
||
|
||
// Result 包含翻译后内容及源语言
|
||
type Result struct {
|
||
OriginalText string `json:"original_text"`
|
||
TranslatedText string `json:"translated_text"`
|
||
SourceLang string `json:"source_lang"`
|
||
TargetLang string `json:"target_lang"`
|
||
}
|
||
|
||
// SetApiBaseURL 设置翻译引擎基础地址(主要用于单元测试与代理)
|
||
func SetApiBaseURL(u string) {
|
||
if u != "" {
|
||
apiBaseURL = u
|
||
}
|
||
}
|
||
|
||
// SetDefaultClient 设置 HTTP 客户端(主要用于单元测试与超时配置)
|
||
func SetDefaultClient(c *http.Client) {
|
||
if c != nil {
|
||
defaultClient = c
|
||
}
|
||
}
|
||
|
||
// TranslateText 调用免配置翻译引擎将文本翻译为目标语言(默认 zh-CN)
|
||
func TranslateText(ctx context.Context, text string, targetLang string) (*Result, error) {
|
||
text = strings.TrimSpace(text)
|
||
if text == "" {
|
||
return &Result{
|
||
OriginalText: "",
|
||
TranslatedText: "",
|
||
SourceLang: "auto",
|
||
TargetLang: targetLang,
|
||
}, nil
|
||
}
|
||
|
||
if targetLang == "" {
|
||
targetLang = "zh-CN"
|
||
}
|
||
|
||
apiURL := fmt.Sprintf("%s?client=gtx&sl=auto&tl=%s&dt=t", apiBaseURL, url.QueryEscape(targetLang))
|
||
formData := url.Values{}
|
||
formData.Set("q", text)
|
||
|
||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, apiURL, strings.NewReader(formData.Encode()))
|
||
if err != nil {
|
||
return nil, fmt.Errorf("创建翻译请求失败: %w", err)
|
||
}
|
||
|
||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded;charset=utf-8")
|
||
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36")
|
||
|
||
resp, err := defaultClient.Do(req)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("请求翻译服务失败: %w", err)
|
||
}
|
||
defer resp.Body.Close()
|
||
|
||
if resp.StatusCode != http.StatusOK {
|
||
bodyBytes, _ := io.ReadAll(io.LimitReader(resp.Body, 512))
|
||
return nil, fmt.Errorf("翻译服务返回异常状态码 %d: %s", resp.StatusCode, string(bodyBytes))
|
||
}
|
||
|
||
bodyBytes, err := io.ReadAll(io.LimitReader(resp.Body, 4<<20))
|
||
if err != nil {
|
||
return nil, fmt.Errorf("读取翻译响应失败: %w", err)
|
||
}
|
||
|
||
var rawResponse []any
|
||
if err := json.Unmarshal(bodyBytes, &rawResponse); err != nil {
|
||
return nil, fmt.Errorf("解析翻译响应结构失败: %w", err)
|
||
}
|
||
|
||
if len(rawResponse) == 0 {
|
||
return nil, errors.New("翻译响应格式为空")
|
||
}
|
||
|
||
sentences, ok := rawResponse[0].([]any)
|
||
if !ok {
|
||
return nil, errors.New("未找到译文段落列表")
|
||
}
|
||
|
||
var translatedSb strings.Builder
|
||
for _, item := range sentences {
|
||
if sentenceArr, ok := item.([]any); ok && len(sentenceArr) > 0 {
|
||
if seg, ok := sentenceArr[0].(string); ok {
|
||
translatedSb.WriteString(seg)
|
||
}
|
||
}
|
||
}
|
||
|
||
sourceLang := "auto"
|
||
if len(rawResponse) > 2 {
|
||
if sl, ok := rawResponse[2].(string); ok && sl != "" {
|
||
sourceLang = sl
|
||
}
|
||
}
|
||
|
||
return &Result{
|
||
OriginalText: text,
|
||
TranslatedText: translatedSb.String(),
|
||
SourceLang: sourceLang,
|
||
TargetLang: targetLang,
|
||
}, nil
|
||
}
|