280 lines
7.4 KiB
Go
280 lines
7.4 KiB
Go
// Package translator 提供文本翻译功能
|
||
package translator
|
||
|
||
import (
|
||
"context"
|
||
"encoding/json"
|
||
"errors"
|
||
"fmt"
|
||
"io"
|
||
"net/http"
|
||
"net/url"
|
||
"strconv"
|
||
"strings"
|
||
"time"
|
||
|
||
"golang.org/x/text/language"
|
||
)
|
||
|
||
// 错误定义
|
||
var (
|
||
ErrBadNetwork = errors.New("bad network, please check your internet connection")
|
||
)
|
||
|
||
// 常量定义
|
||
const (
|
||
googleTranslateTKK = "448487.932609646" // 固定TKK值
|
||
)
|
||
|
||
// GoogleTranslator 带token的Google翻译器(使用translate.google.com)
|
||
type GoogleTranslator struct {
|
||
httpClient *http.Client // HTTP客户端
|
||
Timeout time.Duration // 请求超时时间
|
||
languages map[string]LanguageInfo // 支持的语言列表
|
||
}
|
||
|
||
// GoogleTranslatorTokenFree 无token的Google翻译器(使用translate.googleapis.com)
|
||
type GoogleTranslatorTokenFree struct {
|
||
httpClient *http.Client // HTTP客户端
|
||
Timeout time.Duration // 请求超时时间
|
||
languages map[string]LanguageInfo // 支持的语言列表
|
||
}
|
||
|
||
// NewGoogleTranslator 创建一个新的带token的Google翻译器实例
|
||
func NewGoogleTranslator() *GoogleTranslator {
|
||
return &GoogleTranslator{
|
||
httpClient: &http.Client{Timeout: defaultTimeout},
|
||
Timeout: defaultTimeout,
|
||
languages: initGoogleLanguages(),
|
||
}
|
||
}
|
||
|
||
// initGoogleLanguages 初始化Google翻译器支持的语言列表
|
||
func initGoogleLanguages() map[string]LanguageInfo {
|
||
languages := make(map[string]LanguageInfo)
|
||
|
||
// 只支持三种语言
|
||
languages["en"] = LanguageInfo{Code: "en", Name: "English"}
|
||
languages["zh-cn"] = LanguageInfo{Code: "zh-CN", Name: "Chinese (Simplified)"}
|
||
languages["zh-tw"] = LanguageInfo{Code: "zh-TW", Name: "Chinese (Traditional)"}
|
||
|
||
return languages
|
||
}
|
||
|
||
// generateToken 生成翻译token
|
||
func generateToken(query string) string {
|
||
// 实现TypeScript中的token生成逻辑
|
||
tkkSplited := strings.Split(googleTranslateTKK, ".")
|
||
tkkIndex, _ := strconv.Atoi(tkkSplited[0])
|
||
tkkKey, _ := strconv.Atoi(tkkSplited[1])
|
||
|
||
// 转换查询字符串为字节数组
|
||
bytesArray := transformQuery(query)
|
||
|
||
// 计算hash
|
||
encodingRound := tkkIndex
|
||
for _, b := range bytesArray {
|
||
encodingRound += int(b)
|
||
encodingRound = shiftLeftOrRightThenSumOrXor(encodingRound, "+-a^+6")
|
||
}
|
||
encodingRound = shiftLeftOrRightThenSumOrXor(encodingRound, "+-3^+b+-f")
|
||
|
||
encodingRound ^= tkkKey
|
||
if encodingRound <= 0 {
|
||
encodingRound = (encodingRound & 2147483647) + 2147483648
|
||
}
|
||
|
||
normalizedResult := encodingRound % 1000000
|
||
return fmt.Sprintf("%d.%d", normalizedResult, normalizedResult^tkkIndex)
|
||
}
|
||
|
||
// transformQuery 转换查询字符串
|
||
func transformQuery(query string) []byte {
|
||
var bytesArray []byte
|
||
runes := []rune(query)
|
||
|
||
for i := 0; i < len(runes); i++ {
|
||
charCode := int(runes[i])
|
||
|
||
if charCode < 128 {
|
||
bytesArray = append(bytesArray, byte(charCode))
|
||
} else if charCode < 2048 {
|
||
bytesArray = append(bytesArray, byte((charCode>>6)|192))
|
||
bytesArray = append(bytesArray, byte((charCode&63)|128))
|
||
} else {
|
||
if (charCode&64512) == 55296 && i+1 < len(runes) && (int(runes[i+1])&64512) == 56320 {
|
||
charCode = 65536 + ((charCode & 1023) << 10) + (int(runes[i+1]) & 1023)
|
||
i++
|
||
bytesArray = append(bytesArray, byte((charCode>>18)|240))
|
||
bytesArray = append(bytesArray, byte(((charCode>>12)&63)|128))
|
||
} else {
|
||
bytesArray = append(bytesArray, byte((charCode>>12)|224))
|
||
}
|
||
bytesArray = append(bytesArray, byte(((charCode>>6)&63)|128))
|
||
bytesArray = append(bytesArray, byte((charCode&63)|128))
|
||
}
|
||
}
|
||
|
||
return bytesArray
|
||
}
|
||
|
||
// shiftLeftOrRightThenSumOrXor 位运算操作
|
||
func shiftLeftOrRightThenSumOrXor(num int, optString string) int {
|
||
for i := 0; i < len(optString)-2; i += 3 {
|
||
acc := int(optString[i+2])
|
||
if acc >= 'a' {
|
||
acc = acc - 87
|
||
} else {
|
||
acc = acc - '0'
|
||
}
|
||
|
||
if optString[i+1] == '+' {
|
||
acc = num >> uint(acc)
|
||
} else {
|
||
acc = num << uint(acc)
|
||
}
|
||
|
||
if optString[i] == '+' {
|
||
num = (num + acc) & 4294967295
|
||
} else {
|
||
num ^= acc
|
||
}
|
||
}
|
||
return num
|
||
}
|
||
|
||
// SetTimeout 设置请求超时时间
|
||
func (t *GoogleTranslator) SetTimeout(timeout time.Duration) {
|
||
t.Timeout = timeout
|
||
t.httpClient.Timeout = timeout
|
||
}
|
||
|
||
// Translate 使用Go语言提供的标准语言标签进行文本翻译
|
||
func (t *GoogleTranslator) Translate(text string, from language.Tag, to language.Tag) (string, error) {
|
||
return t.translate(text, from.String(), to.String())
|
||
}
|
||
|
||
// TranslateWithParams 使用简单字符串参数进行文本翻译
|
||
func (t *GoogleTranslator) TranslateWithParams(text string, params TranslationParams) (string, error) {
|
||
if params.Timeout > 0 {
|
||
t.SetTimeout(params.Timeout)
|
||
}
|
||
return t.translate(text, params.From, params.To)
|
||
}
|
||
|
||
// translate 执行实际翻译操作(带token版本)
|
||
func (t *GoogleTranslator) translate(text, from, to string) (string, error) {
|
||
ctx, cancel := context.WithTimeout(context.Background(), t.Timeout)
|
||
defer cancel()
|
||
|
||
// 生成token
|
||
token := generateToken(text)
|
||
|
||
// 构建请求URL
|
||
apiURL := "https://translate.google.com/translate_a/single"
|
||
params := url.Values{}
|
||
params.Set("client", "t")
|
||
params.Set("sl", from)
|
||
params.Set("tl", to)
|
||
params.Set("hl", to)
|
||
params.Set("ie", "UTF-8")
|
||
params.Set("oe", "UTF-8")
|
||
params.Set("otf", "1")
|
||
params.Set("ssel", "0")
|
||
params.Set("tsel", "0")
|
||
params.Set("kc", "7")
|
||
params.Set("q", text)
|
||
params.Set("tk", token)
|
||
|
||
// 添加dt参数
|
||
dtParams := []string{"at", "bd", "ex", "ld", "md", "qca", "rw", "rm", "ss", "t"}
|
||
for _, dt := range dtParams {
|
||
params.Add("dt", dt)
|
||
}
|
||
|
||
fullURL := apiURL + "?" + params.Encode()
|
||
|
||
// 创建请求
|
||
req, err := http.NewRequestWithContext(ctx, "GET", fullURL, nil)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
|
||
// 发送请求
|
||
resp, err := t.httpClient.Do(req)
|
||
if err != nil {
|
||
return "", ErrBadNetwork
|
||
}
|
||
defer resp.Body.Close()
|
||
|
||
if resp.StatusCode != http.StatusOK {
|
||
return "", fmt.Errorf("API error: status code %d", resp.StatusCode)
|
||
}
|
||
|
||
// 读取响应
|
||
body, err := io.ReadAll(resp.Body)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
|
||
// 解析JSON响应
|
||
var result []interface{}
|
||
if err := json.Unmarshal(body, &result); err != nil {
|
||
return "", err
|
||
}
|
||
|
||
if len(result) == 0 {
|
||
return "", errors.New("unexpected response format")
|
||
}
|
||
|
||
// 提取翻译文本
|
||
translations, ok := result[0].([]interface{})
|
||
if !ok {
|
||
return "", errors.New("unexpected response format")
|
||
}
|
||
|
||
var translatedText strings.Builder
|
||
for _, translation := range translations {
|
||
if chunk, ok := translation.([]interface{}); ok && len(chunk) > 0 {
|
||
if text, ok := chunk[0].(string); ok {
|
||
translatedText.WriteString(text)
|
||
}
|
||
}
|
||
}
|
||
|
||
return translatedText.String(), nil
|
||
}
|
||
|
||
// GetSupportedLanguages 获取翻译器支持的语言列表
|
||
func (t *GoogleTranslator) GetSupportedLanguages() map[string]LanguageInfo {
|
||
return t.languages
|
||
}
|
||
|
||
// IsLanguageSupported 检查指定的语言代码是否受支持
|
||
func (t *GoogleTranslator) IsLanguageSupported(languageCode string) bool {
|
||
_, ok := t.languages[strings.ToLower(languageCode)]
|
||
return ok
|
||
}
|
||
|
||
// visitArrayItems 递归访问数组项
|
||
func visitArrayItems(arr []interface{}, visitor func(interface{})) {
|
||
for _, obj := range arr {
|
||
if subArr, ok := obj.([]interface{}); ok {
|
||
visitArrayItems(subArr, visitor)
|
||
} else {
|
||
visitor(obj)
|
||
}
|
||
}
|
||
}
|
||
|
||
// GetSupportedLanguages 获取翻译器支持的语言列表
|
||
func (t *GoogleTranslatorTokenFree) GetSupportedLanguages() map[string]LanguageInfo {
|
||
return t.languages
|
||
}
|
||
|
||
// IsLanguageSupported 检查指定的语言代码是否受支持
|
||
func (t *GoogleTranslatorTokenFree) IsLanguageSupported(languageCode string) bool {
|
||
_, ok := t.languages[strings.ToLower(languageCode)]
|
||
return ok
|
||
}
|