🚧 Optimize

This commit is contained in:
2025-10-01 18:15:22 +08:00
parent cf8bf688bf
commit 1216b0b67c
22 changed files with 2015 additions and 2556 deletions

View File

@@ -2,15 +2,14 @@
package translator
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"math/rand"
"net/http"
"net/http/cookiejar"
"net/url"
"regexp"
"strings"
"time"
@@ -21,68 +20,61 @@ import (
type BingTranslator struct {
httpClient *http.Client // HTTP客户端
Timeout time.Duration // 请求超时时间
session *BingSession // Bing会话
token *tokenInfo // Token信息
languages map[string]LanguageInfo // 支持的语言列表
}
// BingSession 保持Bing翻译会话状态
type BingSession struct {
Cookie map[string]string // 会话Cookie
Headers map[string]string // 会话请求头
Token string // 翻译Token
Key string // 翻译Key
IG string // IG参数
// tokenInfo 存储token信息
type tokenInfo struct {
Value string
ExpiresAt time.Time
}
// TranslateRequest 翻译请求结构
type TranslateRequest struct {
Text string `json:"Text"`
}
// TranslateResponse 翻译响应结构
type TranslateResponse struct {
Translations []struct {
Text string `json:"text"`
} `json:"translations"`
}
// ErrorResponse 错误响应结构
type ErrorResponse struct {
Error struct {
Code int `json:"code"`
Message string `json:"message"`
} `json:"error"`
}
// 常量定义
const (
bingDefaultTimeout = 30 * time.Second
bingTranslatorURL = "https://cn.bing.com/translator"
bingTranslateAPIURL = "https://cn.bing.com/ttranslatev3"
bingTokenURL = "https://edge.microsoft.com/translate/auth"
bingTranslateAPIURL = "https://api-edge.cognitive.microsofttranslator.com/translate"
tokenValidDuration = 25 * time.Minute // Token有效期比实际30分钟稍短以确保安全
)
// 错误定义
var (
ErrBingNetworkError = errors.New("bing translator network error")
ErrBingParseError = errors.New("bing translator parse error")
ErrBingTokenError = errors.New("failed to get bing translator token")
ErrBingEmptyResponse = errors.New("empty response from bing translator")
ErrBingRateLimit = errors.New("bing translator rate limit reached")
)
// 用户代理列表
var userAgents = []string{
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.0",
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36",
}
// NewBingTranslator 创建一个新的Bing翻译器实例
func NewBingTranslator() *BingTranslator {
// 初始化随机数种子
rand.New(rand.NewSource(time.Now().UnixNano()))
// 创建带Cookie存储的HTTP客户端
jar, _ := cookiejar.New(nil)
translator := &BingTranslator{
httpClient: &http.Client{
Timeout: bingDefaultTimeout,
// 启用Cookie存储
Jar: jar,
},
Timeout: bingDefaultTimeout,
session: &BingSession{
Headers: make(map[string]string),
Cookie: make(map[string]string),
},
Timeout: bingDefaultTimeout,
languages: initBingLanguages(),
}
// 初始化会话
translator.refreshSession()
return translator
}
@@ -91,129 +83,13 @@ func initBingLanguages() map[string]LanguageInfo {
// 创建语言映射表
languages := make(map[string]LanguageInfo)
// 添加所有支持的语言
// 基于 Microsoft Translator 支持的语言列表
// 参考: https://learn.microsoft.com/en-us/azure/ai-services/translator/language-support
// 添加支持的语言
// 基于 Microsoft Translator API 支持的语言列表
// 参考: https://docs.microsoft.com/en-us/azure/cognitive-services/translator/language-support
// 常用语言
languages["en"] = LanguageInfo{Code: "en", Name: "English"}
languages["zh-Hans"] = LanguageInfo{Code: "zh-Hans", Name: "Chinese Simplified"}
languages["zh-Hant"] = LanguageInfo{Code: "zh-Hant", Name: "Chinese Traditional"}
languages["ja"] = LanguageInfo{Code: "ja", Name: "Japanese"}
languages["ko"] = LanguageInfo{Code: "ko", Name: "Korean"}
languages["fr"] = LanguageInfo{Code: "fr", Name: "French"}
languages["fr-ca"] = LanguageInfo{Code: "fr-ca", Name: "French (Canada)"}
languages["de"] = LanguageInfo{Code: "de", Name: "German"}
languages["es"] = LanguageInfo{Code: "es", Name: "Spanish"}
languages["ru"] = LanguageInfo{Code: "ru", Name: "Russian"}
languages["pt"] = LanguageInfo{Code: "pt", Name: "Portuguese (Brazil)"}
languages["pt-br"] = LanguageInfo{Code: "pt-br", Name: "Portuguese (Brazil)"}
languages["pt-pt"] = LanguageInfo{Code: "pt-pt", Name: "Portuguese (Portugal)"}
languages["it"] = LanguageInfo{Code: "it", Name: "Italian"}
languages["ar"] = LanguageInfo{Code: "ar", Name: "Arabic"}
// 特殊语言
languages["yue"] = LanguageInfo{Code: "yue", Name: "Cantonese (Traditional)"}
languages["lzh"] = LanguageInfo{Code: "lzh", Name: "Chinese (Literary)"}
// 其他语言
languages["af"] = LanguageInfo{Code: "af", Name: "Afrikaans"}
languages["am"] = LanguageInfo{Code: "am", Name: "Amharic"}
languages["as"] = LanguageInfo{Code: "as", Name: "Assamese"}
languages["az"] = LanguageInfo{Code: "az", Name: "Azerbaijani (Latin)"}
languages["ba"] = LanguageInfo{Code: "ba", Name: "Bashkir"}
languages["bg"] = LanguageInfo{Code: "bg", Name: "Bulgarian"}
languages["bn"] = LanguageInfo{Code: "bn", Name: "Bangla"}
languages["bo"] = LanguageInfo{Code: "bo", Name: "Tibetan"}
languages["bs"] = LanguageInfo{Code: "bs", Name: "Bosnian (Latin)"}
languages["ca"] = LanguageInfo{Code: "ca", Name: "Catalan"}
languages["cs"] = LanguageInfo{Code: "cs", Name: "Czech"}
languages["cy"] = LanguageInfo{Code: "cy", Name: "Welsh"}
languages["da"] = LanguageInfo{Code: "da", Name: "Danish"}
languages["dv"] = LanguageInfo{Code: "dv", Name: "Divehi"}
languages["el"] = LanguageInfo{Code: "el", Name: "Greek"}
languages["et"] = LanguageInfo{Code: "et", Name: "Estonian"}
languages["eu"] = LanguageInfo{Code: "eu", Name: "Basque"}
languages["fa"] = LanguageInfo{Code: "fa", Name: "Persian"}
languages["fi"] = LanguageInfo{Code: "fi", Name: "Finnish"}
languages["fil"] = LanguageInfo{Code: "fil", Name: "Filipino"}
languages["fj"] = LanguageInfo{Code: "fj", Name: "Fijian"}
languages["fo"] = LanguageInfo{Code: "fo", Name: "Faroese"}
languages["ga"] = LanguageInfo{Code: "ga", Name: "Irish"}
languages["gl"] = LanguageInfo{Code: "gl", Name: "Galician"}
languages["gu"] = LanguageInfo{Code: "gu", Name: "Gujarati"}
languages["ha"] = LanguageInfo{Code: "ha", Name: "Hausa"}
languages["he"] = LanguageInfo{Code: "he", Name: "Hebrew"}
languages["hi"] = LanguageInfo{Code: "hi", Name: "Hindi"}
languages["hr"] = LanguageInfo{Code: "hr", Name: "Croatian"}
languages["ht"] = LanguageInfo{Code: "ht", Name: "Haitian Creole"}
languages["hu"] = LanguageInfo{Code: "hu", Name: "Hungarian"}
languages["hy"] = LanguageInfo{Code: "hy", Name: "Armenian"}
languages["id"] = LanguageInfo{Code: "id", Name: "Indonesian"}
languages["ig"] = LanguageInfo{Code: "ig", Name: "Igbo"}
languages["is"] = LanguageInfo{Code: "is", Name: "Icelandic"}
languages["ka"] = LanguageInfo{Code: "ka", Name: "Georgian"}
languages["kk"] = LanguageInfo{Code: "kk", Name: "Kazakh"}
languages["km"] = LanguageInfo{Code: "km", Name: "Khmer"}
languages["kn"] = LanguageInfo{Code: "kn", Name: "Kannada"}
languages["ku"] = LanguageInfo{Code: "ku", Name: "Kurdish (Arabic) (Central)"}
languages["ky"] = LanguageInfo{Code: "ky", Name: "Kyrgyz (Cyrillic)"}
languages["lo"] = LanguageInfo{Code: "lo", Name: "Lao"}
languages["lt"] = LanguageInfo{Code: "lt", Name: "Lithuanian"}
languages["lv"] = LanguageInfo{Code: "lv", Name: "Latvian"}
languages["mg"] = LanguageInfo{Code: "mg", Name: "Malagasy"}
languages["mi"] = LanguageInfo{Code: "mi", Name: "Maori"}
languages["mk"] = LanguageInfo{Code: "mk", Name: "Macedonian"}
languages["ml"] = LanguageInfo{Code: "ml", Name: "Malayalam"}
languages["mn-Cyrl"] = LanguageInfo{Code: "mn-Cyrl", Name: "Mongolian (Cyrillic)"}
languages["mr"] = LanguageInfo{Code: "mr", Name: "Marathi"}
languages["ms"] = LanguageInfo{Code: "ms", Name: "Malay (Latin)"}
languages["mt"] = LanguageInfo{Code: "mt", Name: "Maltese"}
languages["mww"] = LanguageInfo{Code: "mww", Name: "Hmong Daw (Latin)"}
languages["my"] = LanguageInfo{Code: "my", Name: "Myanmar (Burmese)"}
languages["nb"] = LanguageInfo{Code: "nb", Name: "Norwegian Bokmål"}
languages["ne"] = LanguageInfo{Code: "ne", Name: "Nepali"}
languages["nl"] = LanguageInfo{Code: "nl", Name: "Dutch"}
languages["or"] = LanguageInfo{Code: "or", Name: "Odia"}
languages["otq"] = LanguageInfo{Code: "otq", Name: "Queretaro Otomi"}
languages["pa"] = LanguageInfo{Code: "pa", Name: "Punjabi"}
languages["pl"] = LanguageInfo{Code: "pl", Name: "Polish"}
languages["prs"] = LanguageInfo{Code: "prs", Name: "Dari"}
languages["ps"] = LanguageInfo{Code: "ps", Name: "Pashto"}
languages["ro"] = LanguageInfo{Code: "ro", Name: "Romanian"}
languages["rw"] = LanguageInfo{Code: "rw", Name: "Kinyarwanda"}
languages["sk"] = LanguageInfo{Code: "sk", Name: "Slovak"}
languages["sl"] = LanguageInfo{Code: "sl", Name: "Slovenian"}
languages["sm"] = LanguageInfo{Code: "sm", Name: "Samoan (Latin)"}
languages["sn"] = LanguageInfo{Code: "sn", Name: "chiShona"}
languages["so"] = LanguageInfo{Code: "so", Name: "Somali"}
languages["sq"] = LanguageInfo{Code: "sq", Name: "Albanian"}
languages["sr-Cyrl"] = LanguageInfo{Code: "sr-Cyrl", Name: "Serbian (Cyrillic)"}
languages["sr"] = LanguageInfo{Code: "sr", Name: "Serbian (Latin)"}
languages["sr-latn"] = LanguageInfo{Code: "sr-latn", Name: "Serbian (Latin)"}
languages["sv"] = LanguageInfo{Code: "sv", Name: "Swedish"}
languages["sw"] = LanguageInfo{Code: "sw", Name: "Swahili (Latin)"}
languages["ta"] = LanguageInfo{Code: "ta", Name: "Tamil"}
languages["te"] = LanguageInfo{Code: "te", Name: "Telugu"}
languages["th"] = LanguageInfo{Code: "th", Name: "Thai"}
languages["ti"] = LanguageInfo{Code: "ti", Name: "Tigrinya"}
languages["tk"] = LanguageInfo{Code: "tk", Name: "Turkmen (Latin)"}
languages["tlh-Latn"] = LanguageInfo{Code: "tlh-Latn", Name: "Klingon"}
languages["tlh-Piqd"] = LanguageInfo{Code: "tlh-Piqd", Name: "Klingon (plqaD)"}
languages["to"] = LanguageInfo{Code: "to", Name: "Tongan"}
languages["tr"] = LanguageInfo{Code: "tr", Name: "Turkish"}
languages["tt"] = LanguageInfo{Code: "tt", Name: "Tatar (Latin)"}
languages["ty"] = LanguageInfo{Code: "ty", Name: "Tahitian"}
languages["ug"] = LanguageInfo{Code: "ug", Name: "Uyghur (Arabic)"}
languages["uk"] = LanguageInfo{Code: "uk", Name: "Ukrainian"}
languages["ur"] = LanguageInfo{Code: "ur", Name: "Urdu"}
languages["uz"] = LanguageInfo{Code: "uz", Name: "Uzbek (Latin)"}
languages["vi"] = LanguageInfo{Code: "vi", Name: "Vietnamese"}
languages["yua"] = LanguageInfo{Code: "yua", Name: "Yucatec Maya"}
languages["zu"] = LanguageInfo{Code: "zu", Name: "Zulu"}
// 添加一些特殊情况的映射
languages["zh"] = LanguageInfo{Code: "zh-Hans", Name: "Chinese Simplified"} // 将zh映射到zh-Hans
languages["zh-Hans"] = LanguageInfo{Code: "zh-Hans", Name: "Chinese (Simplified)"}
languages["zh-Hant"] = LanguageInfo{Code: "zh-Hant", Name: "Chinese (Traditional)"}
return languages
}
@@ -224,91 +100,59 @@ func (t *BingTranslator) SetTimeout(timeout time.Duration) {
t.httpClient.Timeout = timeout
}
// getRandomUserAgent 获取随机用户代理
func getRandomUserAgent() string {
return userAgents[rand.Intn(len(userAgents))]
}
// refreshSession 刷新翻译会话
func (t *BingTranslator) refreshSession() error {
// 设置随机用户代理
userAgent := getRandomUserAgent()
t.session.Headers["User-Agent"] = userAgent
t.session.Headers["Referer"] = bingTranslatorURL
t.session.Headers["Accept"] = "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8"
t.session.Headers["Accept-Language"] = "en-US,en;q=0.5"
t.session.Headers["Connection"] = "keep-alive"
t.session.Headers["Upgrade-Insecure-Requests"] = "1"
t.session.Headers["Cache-Control"] = "max-age=0"
// getToken 获取访问token
func (t *BingTranslator) getToken(ctx context.Context) (string, error) {
// 检查token是否有效
if t.token != nil && time.Now().Before(t.token.ExpiresAt) {
return t.token.Value, nil
}
// 创建请求
req, err := http.NewRequest("GET", bingTranslatorURL, nil)
req, err := http.NewRequestWithContext(ctx, "GET", bingTokenURL, nil)
if err != nil {
return fmt.Errorf("the creation request failed: %w", err)
return "", fmt.Errorf("failed to create token request: %w", err)
}
// 设置请求头
for k, v := range t.session.Headers {
req.Header.Set(k, v)
}
req.Header.Set("Accept", "*/*")
req.Header.Set("Accept-Language", "zh-TW,zh;q=0.9,ja;q=0.8,zh-CN;q=0.7,en-US;q=0.6,en;q=0.5")
req.Header.Set("Cache-Control", "no-cache")
req.Header.Set("Pragma", "no-cache")
req.Header.Set("Referrer-Policy", "strict-origin-when-cross-origin")
req.Header.Set("Sec-Fetch-Dest", "empty")
req.Header.Set("Sec-Fetch-Mode", "cors")
req.Header.Set("Sec-Fetch-Site", "none")
// 发送请求
resp, err := t.httpClient.Do(req)
if err != nil {
return fmt.Errorf("%w: %v", ErrBingNetworkError, err)
return "", fmt.Errorf("failed to get token: %w", err)
}
defer resp.Body.Close()
// 保存Cookie
for _, cookie := range resp.Cookies() {
t.session.Cookie[cookie.Name] = cookie.Value
// 检查状态码
if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("token request failed with status %d", resp.StatusCode)
}
// 读取响应内容
// 读取响应
body, err := io.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("read response failed: %w", err)
return "", fmt.Errorf("failed to read token response: %w", err)
}
content := string(body)
// 提取参数
// 1. 提取key和token
paramsPattern := regexp.MustCompile(`params_AbusePreventionHelper\s*=\s*(\[.*?\]);`)
paramsMatch := paramsPattern.FindStringSubmatch(content)
if paramsMatch == nil || len(paramsMatch) < 2 {
return fmt.Errorf("%w: params_AbusePreventionHelper could not be extracted", ErrBingTokenError)
token := strings.TrimSpace(string(body))
if token == "" {
return "", fmt.Errorf("empty token received")
}
// 解析参数数组
paramsStr := paramsMatch[1]
paramsStr = strings.ReplaceAll(paramsStr, "[", "")
paramsStr = strings.ReplaceAll(paramsStr, "]", "")
paramsParts := strings.Split(paramsStr, ",")
if len(paramsParts) < 2 {
return fmt.Errorf("%w: params_AbusePreventionHelper format is incorrect", ErrBingTokenError)
// 缓存token
t.token = &tokenInfo{
Value: token,
ExpiresAt: time.Now().Add(tokenValidDuration),
}
// 提取key和token
t.session.Key = strings.Trim(paramsParts[0], `"' `)
t.session.Token = strings.Trim(paramsParts[1], `"' `)
// 2. 提取IG值
igPattern := regexp.MustCompile(`IG:"(\w+)"`)
igMatch := igPattern.FindStringSubmatch(content)
if igMatch == nil || len(igMatch) < 2 {
return fmt.Errorf("%w: Unable to extract IG values", ErrBingTokenError)
}
t.session.IG = igMatch[1]
// 更新会话头部
t.session.Headers["IG"] = t.session.IG
t.session.Headers["key"] = t.session.Key
t.session.Headers["token"] = t.session.Token
return nil
return token, nil
}
// Translate 使用标准语言标签进行文本翻译
@@ -328,139 +172,94 @@ func (t *BingTranslator) TranslateWithParams(text string, params TranslationPara
// translate 执行实际翻译操作
func (t *BingTranslator) translate(text, from, to string) (string, error) {
// 如果没有会话或关键参数缺失,刷新会话
if t.session == nil || t.session.Token == "" || t.session.Key == "" || t.session.IG == "" {
if err := t.refreshSession(); err != nil {
return "", fmt.Errorf("the refresh session failed: %w", err)
}
if text == "" {
return "", fmt.Errorf("text cannot be empty")
}
// 生成随机IID
randNum := rand.Intn(10) // 0-9的随机数
iid := fmt.Sprintf("translator.5019.%d", 1+randNum%3) // 生成随机IID
// 创建带超时的context
ctx, cancel := context.WithTimeout(context.Background(), t.Timeout)
defer cancel()
// 构建URL - 确保使用双&符号
reqURL := fmt.Sprintf("%s?isVertical=1&&IG=%s&IID=%s",
bingTranslateAPIURL, t.session.IG, iid)
// 标准化语言代码
fromLang := t.GetStandardLanguageCode(from)
toLang := t.GetStandardLanguageCode(to)
// 构建表单数据
formData := url.Values{}
formData.Set("fromLang", fromLang)
formData.Set("text", text)
formData.Set("to", toLang)
formData.Set("token", t.session.Token)
formData.Set("key", t.session.Key)
formDataStr := formData.Encode()
// 创建请求
req, err := http.NewRequest("POST", reqURL, strings.NewReader(formDataStr))
// 获取token
token, err := t.getToken(ctx)
if err != nil {
return "", fmt.Errorf("The creation request failed: %w", err)
return "", fmt.Errorf("failed to get token: %w", err)
}
// 构建请求URL
params := url.Values{}
if from != "auto" {
params.Set("from", from)
}
params.Set("to", to)
params.Set("api-version", "3.0")
params.Set("includeSentenceLength", "true")
fullURL := fmt.Sprintf("%s?%s", bingTranslateAPIURL, params.Encode())
// 构建请求体
requestBody := []TranslateRequest{{Text: text}}
jsonBody, err := json.Marshal(requestBody)
if err != nil {
return "", fmt.Errorf("failed to marshal request: %w", err)
}
// 创建HTTP请求
req, err := http.NewRequestWithContext(ctx, "POST", fullURL, bytes.NewBuffer(jsonBody))
if err != nil {
return "", fmt.Errorf("failed to create request: %w", err)
}
// 设置请求头
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.Header.Set("User-Agent", t.session.Headers["User-Agent"])
req.Header.Set("Referer", bingTranslatorURL)
req.Header.Set("Accept", "*/*")
req.Header.Set("Accept-Language", "en-US,en;q=0.5")
req.Header.Set("Origin", "https://cn.bing.com")
req.Header.Set("Connection", "keep-alive")
req.Header.Set("X-Requested-With", "XMLHttpRequest")
// 添加Cookie
for name, value := range t.session.Cookie {
req.AddCookie(&http.Cookie{
Name: name,
Value: value,
})
}
req.Header.Set("Accept", "application/json")
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36")
// 发送请求
resp, err := t.httpClient.Do(req)
if err != nil {
return "", fmt.Errorf("%w: %v", ErrBingNetworkError, err)
}
defer resp.Body.Close()
// 读取响应
body, err := io.ReadAll(resp.Body)
resp.Body.Close()
if err != nil {
return "", fmt.Errorf("read response failed: %w", err)
return "", fmt.Errorf("failed to read response: %w", err)
}
if len(body) == 0 {
return "", ErrBingEmptyResponse
// 检查HTTP状态码
if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("HTTP error %d: %s", resp.StatusCode, string(body))
}
// 尝试解析响应
var result interface{}
if err := json.Unmarshal(body, &result); err != nil {
// 首先尝试解析为错误响应
var errorResp ErrorResponse
if err := json.Unmarshal(body, &errorResp); err == nil && errorResp.Error.Code != 0 {
return "", fmt.Errorf("translation error %d: %s", errorResp.Error.Code, errorResp.Error.Message)
}
// 解析翻译响应
var translateResp []TranslateResponse
if err := json.Unmarshal(body, &translateResp); err != nil {
return "", fmt.Errorf("%w: %v", ErrBingParseError, err)
}
// 检查是否是字典类型
if resultDict, ok := result.(map[string]interface{}); ok {
// 检查是否需要验证码
if _, hasCaptcha := resultDict["ShowCaptcha"]; hasCaptcha {
return "", ErrBingRateLimit
}
// 检查状态码
if statusCode, hasStatus := resultDict["statusCode"]; hasStatus {
if statusCode.(float64) == 400 {
// 检查是否有错误消息
if errorMsg, hasError := resultDict["errorMessage"]; hasError && errorMsg.(string) != "" {
return "", fmt.Errorf("translation failed: %s", errorMsg)
}
// 如果没有明确的错误消息可能是API变更或其他问题
return "", fmt.Errorf("translation request failed (status code: 400)")
} else if statusCode.(float64) == 429 {
return "", ErrBingRateLimit
}
}
// 尝试从错误响应中提取详细信息
if message, hasMessage := resultDict["message"]; hasMessage {
return "", fmt.Errorf("translation failed: %v", message)
}
// 尝试从响应中获取翻译结果
if translations, hasTranslations := resultDict["translations"]; hasTranslations {
if translationsArray, ok := translations.([]interface{}); ok && len(translationsArray) > 0 {
if translation, ok := translationsArray[0].(map[string]interface{}); ok {
if text, ok := translation["text"].(string); ok {
return text, nil
}
}
}
}
// 其他错误
return "", fmt.Errorf("translation failed: %v", resultDict)
if len(translateResp) == 0 || len(translateResp[0].Translations) == 0 {
return "", ErrBingEmptyResponse
}
// 应该是数组类型
if resultArray, ok := result.([]interface{}); ok && len(resultArray) > 0 {
firstItem := resultArray[0]
if itemDict, ok := firstItem.(map[string]interface{}); ok {
if translations, ok := itemDict["translations"].([]interface{}); ok && len(translations) > 0 {
if translation, ok := translations[0].(map[string]interface{}); ok {
if text, ok := translation["text"].(string); ok {
return text, nil
}
}
}
// 合并所有翻译片段
var result strings.Builder
for i, translation := range translateResp[0].Translations {
if i > 0 {
result.WriteString(" ")
}
result.WriteString(translation.Text)
}
return "", fmt.Errorf("%w: The response format is not as expected", ErrBingParseError)
return result.String(), nil
}
// GetSupportedLanguages 获取翻译器支持的语言列表
@@ -473,11 +272,3 @@ func (t *BingTranslator) IsLanguageSupported(languageCode string) bool {
_, exists := t.languages[languageCode]
return exists
}
// GetStandardLanguageCode 获取标准化的语言代码
func (t *BingTranslator) GetStandardLanguageCode(languageCode string) string {
if info, exists := t.languages[languageCode]; exists {
return info.Code
}
return languageCode // 如果没有找到映射,返回原始代码
}

View File

@@ -112,40 +112,8 @@ func initDeeplLanguages() map[string]LanguageInfo {
// 基于 DeepL API 支持的语言列表
// 参考: https://developers.deepl.com/docs/resources/supported-languages
// 源语言和目标语言
languages["ar"] = LanguageInfo{Code: "AR", Name: "Arabic"}
languages["bg"] = LanguageInfo{Code: "BG", Name: "Bulgarian"}
languages["cs"] = LanguageInfo{Code: "CS", Name: "Czech"}
languages["da"] = LanguageInfo{Code: "DA", Name: "Danish"}
languages["de"] = LanguageInfo{Code: "DE", Name: "German"}
languages["el"] = LanguageInfo{Code: "EL", Name: "Greek"}
// 源语言和目标语言 - 精简为中英互译
languages["en"] = LanguageInfo{Code: "EN", Name: "English"}
languages["en-gb"] = LanguageInfo{Code: "EN-GB", Name: "English (British)"}
languages["en-us"] = LanguageInfo{Code: "EN-US", Name: "English (American)"}
languages["es"] = LanguageInfo{Code: "ES", Name: "Spanish"}
languages["et"] = LanguageInfo{Code: "ET", Name: "Estonian"}
languages["fi"] = LanguageInfo{Code: "FI", Name: "Finnish"}
languages["fr"] = LanguageInfo{Code: "FR", Name: "French"}
languages["hu"] = LanguageInfo{Code: "HU", Name: "Hungarian"}
languages["id"] = LanguageInfo{Code: "ID", Name: "Indonesian"}
languages["it"] = LanguageInfo{Code: "IT", Name: "Italian"}
languages["ja"] = LanguageInfo{Code: "JA", Name: "Japanese"}
languages["ko"] = LanguageInfo{Code: "KO", Name: "Korean"}
languages["lt"] = LanguageInfo{Code: "LT", Name: "Lithuanian"}
languages["lv"] = LanguageInfo{Code: "LV", Name: "Latvian"}
languages["nb"] = LanguageInfo{Code: "NB", Name: "Norwegian Bokmål"}
languages["nl"] = LanguageInfo{Code: "NL", Name: "Dutch"}
languages["pl"] = LanguageInfo{Code: "PL", Name: "Polish"}
languages["pt"] = LanguageInfo{Code: "PT", Name: "Portuguese"}
languages["pt-br"] = LanguageInfo{Code: "PT-BR", Name: "Portuguese (Brazilian)"}
languages["pt-pt"] = LanguageInfo{Code: "PT-PT", Name: "Portuguese (Portugal)"}
languages["ro"] = LanguageInfo{Code: "RO", Name: "Romanian"}
languages["ru"] = LanguageInfo{Code: "RU", Name: "Russian"}
languages["sk"] = LanguageInfo{Code: "SK", Name: "Slovak"}
languages["sl"] = LanguageInfo{Code: "SL", Name: "Slovenian"}
languages["sv"] = LanguageInfo{Code: "SV", Name: "Swedish"}
languages["tr"] = LanguageInfo{Code: "TR", Name: "Turkish"}
languages["uk"] = LanguageInfo{Code: "UK", Name: "Ukrainian"}
languages["zh"] = LanguageInfo{Code: "ZH", Name: "Chinese"}
return languages
@@ -157,11 +125,6 @@ func (t *DeeplTranslator) SetTimeout(timeout time.Duration) {
t.httpClient.Timeout = timeout
}
// SetDeeplHost 设置DeepL主机
func (t *DeeplTranslator) SetDeeplHost(host string) {
t.DeeplHost = host
}
// Translate 使用标准语言标签进行文本翻译
func (t *DeeplTranslator) Translate(text string, from language.Tag, to language.Tag) (string, error) {
return t.translate(text, from.String(), to.String())
@@ -319,9 +282,3 @@ func (t *DeeplTranslator) IsLanguageSupported(languageCode string) bool {
_, ok := t.languages[strings.ToLower(languageCode)]
return ok
}
// GetStandardLanguageCode 获取标准化的语言代码
func (t *DeeplTranslator) GetStandardLanguageCode(languageCode string) string {
// 简单返回小写版本作为标准代码
return strings.ToLower(languageCode)
}

View File

@@ -2,20 +2,17 @@
package translator
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"log"
"math"
"net/http"
"net/url"
"regexp"
"strconv"
"strings"
"time"
"github.com/robertkrimen/otto"
"golang.org/x/text/language"
)
@@ -24,405 +21,228 @@ var (
ErrBadNetwork = errors.New("bad network, please check your internet connection")
)
// GoogleTranslator Google翻译器结构体统一管理翻译功能
// 常量定义
const (
googleTranslateTKK = "448487.932609646" // 固定TKK值
)
// GoogleTranslator 带token的Google翻译器使用translate.google.com
type GoogleTranslator struct {
GoogleHost string // Google服务主机
vm *otto.Otto // JavaScript虚拟机
ttk otto.Value // 翻译token缓存
httpClient *http.Client // HTTP客户端
Timeout time.Duration // 请求超时时间
languages map[string]LanguageInfo // 支持的语言列表
}
// NewGoogleTranslator 创建一个新的Google翻译器实例
// 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 {
translator := &GoogleTranslator{
GoogleHost: "google.com",
vm: otto.New(),
Timeout: defaultTimeout,
return &GoogleTranslator{
httpClient: &http.Client{Timeout: defaultTimeout},
Timeout: defaultTimeout,
languages: initGoogleLanguages(),
}
// 初始化ttk
translator.ttk, _ = otto.ToValue("0")
return translator
}
// initGoogleLanguages 初始化Google翻译器支持的语言列表
func initGoogleLanguages() map[string]LanguageInfo {
// 创建语言映射表
languages := make(map[string]LanguageInfo)
// 添加所有支持的语言
// 参考: https://cloud.google.com/translate/docs/languages
// 添加自动检测
languages["auto"] = LanguageInfo{Code: "auto", Name: "Auto Detect"}
// 主要语言
// 只支持三种语言
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)"}
languages["ja"] = LanguageInfo{Code: "ja", Name: "Japanese"}
languages["ko"] = LanguageInfo{Code: "ko", Name: "Korean"}
languages["fr"] = LanguageInfo{Code: "fr", Name: "French"}
languages["de"] = LanguageInfo{Code: "de", Name: "German"}
languages["es"] = LanguageInfo{Code: "es", Name: "Spanish"}
languages["ru"] = LanguageInfo{Code: "ru", Name: "Russian"}
languages["it"] = LanguageInfo{Code: "it", Name: "Italian"}
languages["pt"] = LanguageInfo{Code: "pt", Name: "Portuguese"}
// 其他语言
languages["af"] = LanguageInfo{Code: "af", Name: "Afrikaans"}
languages["sq"] = LanguageInfo{Code: "sq", Name: "Albanian"}
languages["am"] = LanguageInfo{Code: "am", Name: "Amharic"}
languages["ar"] = LanguageInfo{Code: "ar", Name: "Arabic"}
languages["hy"] = LanguageInfo{Code: "hy", Name: "Armenian"}
languages["az"] = LanguageInfo{Code: "az", Name: "Azerbaijani"}
languages["eu"] = LanguageInfo{Code: "eu", Name: "Basque"}
languages["be"] = LanguageInfo{Code: "be", Name: "Belarusian"}
languages["bn"] = LanguageInfo{Code: "bn", Name: "Bengali"}
languages["bs"] = LanguageInfo{Code: "bs", Name: "Bosnian"}
languages["bg"] = LanguageInfo{Code: "bg", Name: "Bulgarian"}
languages["ca"] = LanguageInfo{Code: "ca", Name: "Catalan"}
languages["ceb"] = LanguageInfo{Code: "ceb", Name: "Cebuano"}
languages["zh"] = LanguageInfo{Code: "zh", Name: "Chinese"}
languages["co"] = LanguageInfo{Code: "co", Name: "Corsican"}
languages["hr"] = LanguageInfo{Code: "hr", Name: "Croatian"}
languages["cs"] = LanguageInfo{Code: "cs", Name: "Czech"}
languages["da"] = LanguageInfo{Code: "da", Name: "Danish"}
languages["nl"] = LanguageInfo{Code: "nl", Name: "Dutch"}
languages["eo"] = LanguageInfo{Code: "eo", Name: "Esperanto"}
languages["et"] = LanguageInfo{Code: "et", Name: "Estonian"}
languages["fi"] = LanguageInfo{Code: "fi", Name: "Finnish"}
languages["fy"] = LanguageInfo{Code: "fy", Name: "Frisian"}
languages["gl"] = LanguageInfo{Code: "gl", Name: "Galician"}
languages["ka"] = LanguageInfo{Code: "ka", Name: "Georgian"}
languages["el"] = LanguageInfo{Code: "el", Name: "Greek"}
languages["gu"] = LanguageInfo{Code: "gu", Name: "Gujarati"}
languages["ht"] = LanguageInfo{Code: "ht", Name: "Haitian Creole"}
languages["ha"] = LanguageInfo{Code: "ha", Name: "Hausa"}
languages["haw"] = LanguageInfo{Code: "haw", Name: "Hawaiian"}
languages["he"] = LanguageInfo{Code: "he", Name: "Hebrew"}
languages["hi"] = LanguageInfo{Code: "hi", Name: "Hindi"}
languages["hmn"] = LanguageInfo{Code: "hmn", Name: "Hmong"}
languages["hu"] = LanguageInfo{Code: "hu", Name: "Hungarian"}
languages["is"] = LanguageInfo{Code: "is", Name: "Icelandic"}
languages["ig"] = LanguageInfo{Code: "ig", Name: "Igbo"}
languages["id"] = LanguageInfo{Code: "id", Name: "Indonesian"}
languages["ga"] = LanguageInfo{Code: "ga", Name: "Irish"}
languages["jw"] = LanguageInfo{Code: "jw", Name: "Javanese"}
languages["kn"] = LanguageInfo{Code: "kn", Name: "Kannada"}
languages["kk"] = LanguageInfo{Code: "kk", Name: "Kazakh"}
languages["km"] = LanguageInfo{Code: "km", Name: "Khmer"}
languages["ku"] = LanguageInfo{Code: "ku", Name: "Kurdish"}
languages["ky"] = LanguageInfo{Code: "ky", Name: "Kyrgyz"}
languages["lo"] = LanguageInfo{Code: "lo", Name: "Lao"}
languages["la"] = LanguageInfo{Code: "la", Name: "Latin"}
languages["lv"] = LanguageInfo{Code: "lv", Name: "Latvian"}
languages["lt"] = LanguageInfo{Code: "lt", Name: "Lithuanian"}
languages["lb"] = LanguageInfo{Code: "lb", Name: "Luxembourgish"}
languages["mk"] = LanguageInfo{Code: "mk", Name: "Macedonian"}
languages["mg"] = LanguageInfo{Code: "mg", Name: "Malagasy"}
languages["ms"] = LanguageInfo{Code: "ms", Name: "Malay"}
languages["ml"] = LanguageInfo{Code: "ml", Name: "Malayalam"}
languages["mt"] = LanguageInfo{Code: "mt", Name: "Maltese"}
languages["mi"] = LanguageInfo{Code: "mi", Name: "Maori"}
languages["mr"] = LanguageInfo{Code: "mr", Name: "Marathi"}
languages["mn"] = LanguageInfo{Code: "mn", Name: "Mongolian"}
languages["my"] = LanguageInfo{Code: "my", Name: "Myanmar (Burmese)"}
languages["ne"] = LanguageInfo{Code: "ne", Name: "Nepali"}
languages["no"] = LanguageInfo{Code: "no", Name: "Norwegian"}
languages["ny"] = LanguageInfo{Code: "ny", Name: "Nyanja (Chichewa)"}
languages["ps"] = LanguageInfo{Code: "ps", Name: "Pashto"}
languages["fa"] = LanguageInfo{Code: "fa", Name: "Persian"}
languages["pl"] = LanguageInfo{Code: "pl", Name: "Polish"}
languages["pt-br"] = LanguageInfo{Code: "pt-BR", Name: "Portuguese (Brazil)"}
languages["pt-pt"] = LanguageInfo{Code: "pt-PT", Name: "Portuguese (Portugal)"}
languages["pa"] = LanguageInfo{Code: "pa", Name: "Punjabi"}
languages["ro"] = LanguageInfo{Code: "ro", Name: "Romanian"}
languages["sm"] = LanguageInfo{Code: "sm", Name: "Samoan"}
languages["gd"] = LanguageInfo{Code: "gd", Name: "Scots Gaelic"}
languages["sr"] = LanguageInfo{Code: "sr", Name: "Serbian"}
languages["st"] = LanguageInfo{Code: "st", Name: "Sesotho"}
languages["sn"] = LanguageInfo{Code: "sn", Name: "Shona"}
languages["sd"] = LanguageInfo{Code: "sd", Name: "Sindhi"}
languages["si"] = LanguageInfo{Code: "si", Name: "Sinhala (Sinhalese)"}
languages["sk"] = LanguageInfo{Code: "sk", Name: "Slovak"}
languages["sl"] = LanguageInfo{Code: "sl", Name: "Slovenian"}
languages["so"] = LanguageInfo{Code: "so", Name: "Somali"}
languages["su"] = LanguageInfo{Code: "su", Name: "Sundanese"}
languages["sw"] = LanguageInfo{Code: "sw", Name: "Swahili"}
languages["sv"] = LanguageInfo{Code: "sv", Name: "Swedish"}
languages["tl"] = LanguageInfo{Code: "tl", Name: "Tagalog (Filipino)"}
languages["tg"] = LanguageInfo{Code: "tg", Name: "Tajik"}
languages["ta"] = LanguageInfo{Code: "ta", Name: "Tamil"}
languages["te"] = LanguageInfo{Code: "te", Name: "Telugu"}
languages["th"] = LanguageInfo{Code: "th", Name: "Thai"}
languages["tr"] = LanguageInfo{Code: "tr", Name: "Turkish"}
languages["uk"] = LanguageInfo{Code: "uk", Name: "Ukrainian"}
languages["ur"] = LanguageInfo{Code: "ur", Name: "Urdu"}
languages["uz"] = LanguageInfo{Code: "uz", Name: "Uzbek"}
languages["vi"] = LanguageInfo{Code: "vi", Name: "Vietnamese"}
languages["cy"] = LanguageInfo{Code: "cy", Name: "Welsh"}
languages["xh"] = LanguageInfo{Code: "xh", Name: "Xhosa"}
languages["yi"] = LanguageInfo{Code: "yi", Name: "Yiddish"}
languages["yo"] = LanguageInfo{Code: "yo", Name: "Yoruba"}
languages["zu"] = LanguageInfo{Code: "zu", Name: "Zulu"}
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
}
// SetGoogleHost 设置Google主机
func (t *GoogleTranslator) SetGoogleHost(host string) {
t.GoogleHost = host
}
// Translate 使用Go语言提供的标准语言标签进行文本翻译
func (t *GoogleTranslator) Translate(text string, from language.Tag, to language.Tag) (string, error) {
return t.translate(text, from.String(), to.String(), false)
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, true)
return t.translate(text, params.From, params.To)
}
// translate 执行实际翻译操作
func (t *GoogleTranslator) translate(text, from, to string, withVerification bool) (string, error) {
if withVerification {
if _, err := language.Parse(from); err != nil && from != "auto" {
log.Println("[WARNING], '" + from + "' is a invalid language, switching to 'auto'")
from = "auto"
}
if _, err := language.Parse(to); err != nil {
log.Println("[WARNING], '" + to + "' is a invalid language, switching to 'en'")
to = "en"
}
// 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)
}
textValue, _ := otto.ToValue(text)
urlStr := fmt.Sprintf("https://translate.%s/translate_a/single", t.GoogleHost)
token := t.getToken(textValue)
fullURL := apiURL + "?" + params.Encode()
data := map[string]string{
"client": "gtx",
"sl": from,
"tl": to,
"hl": to,
"ie": "UTF-8",
"oe": "UTF-8",
"otf": "1",
"ssel": "0",
"tsel": "0",
"kc": "7",
"q": text,
}
u, err := url.Parse(urlStr)
// 创建请求
req, err := http.NewRequestWithContext(ctx, "GET", fullURL, nil)
if err != nil {
return "", err
}
parameters := url.Values{}
for k, v := range data {
parameters.Add(k, v)
}
for _, v := range []string{"at", "bd", "ex", "ld", "md", "qca", "rw", "rm", "ss", "t"} {
parameters.Add("dt", v)
}
parameters.Add("tk", token)
u.RawQuery = parameters.Encode()
req, err := http.NewRequest("GET", u.String(), nil)
if err != nil {
return "", err
}
r, err := t.httpClient.Do(req)
if err != nil {
if errors.Is(err, http.ErrHandlerTimeout) {
return "", ErrBadNetwork
}
return "", err
}
if r.StatusCode != http.StatusOK {
return "", fmt.Errorf("API error: status code %d", r.StatusCode)
}
raw, err := io.ReadAll(r.Body)
if err != nil {
return "", err
}
defer r.Body.Close()
var resp []interface{}
err = json.Unmarshal(raw, &resp)
if err != nil {
return "", err
}
responseText := ""
for _, obj := range resp[0].([]interface{}) {
if len(obj.([]interface{})) == 0 {
break
}
t, ok := obj.([]interface{})[0].(string)
if ok {
responseText += t
}
}
return responseText, nil
}
// getToken 获取翻译API所需的token
func (t *GoogleTranslator) getToken(text otto.Value) string {
ttk, err := t.updateTTK()
if err != nil {
return ""
}
tk, err := t.generateToken(text, ttk)
if err != nil {
return ""
}
return strings.Replace(tk.String(), "&tk=", "", -1)
}
// updateTTK 更新TTK值
func (t *GoogleTranslator) updateTTK() (otto.Value, error) {
timestamp := time.Now().UnixNano() / 3600000
now := math.Floor(float64(timestamp))
ttk, err := strconv.ParseFloat(t.ttk.String(), 64)
if err != nil {
return otto.UndefinedValue(), err
}
if ttk == now {
return t.ttk, nil
}
req, err := http.NewRequest("GET", fmt.Sprintf("https://translate.%s", t.GoogleHost), nil)
if err != nil {
return otto.UndefinedValue(), err
}
// 发送请求
resp, err := t.httpClient.Do(req)
if err != nil {
return otto.UndefinedValue(), err
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 otto.UndefinedValue(), err
return "", err
}
matches := regexp.MustCompile(`tkk:\s?'(.+?)'`).FindStringSubmatch(string(body))
if len(matches) > 0 {
v, err := otto.ToValue(matches[0])
if err != nil {
return otto.UndefinedValue(), err
}
t.ttk = v
return v, nil
// 解析JSON响应
var result []interface{}
if err := json.Unmarshal(body, &result); err != nil {
return "", err
}
return t.ttk, nil
}
// generateToken 生成翻译API所需的token
func (t *GoogleTranslator) generateToken(a otto.Value, TTK otto.Value) (otto.Value, error) {
err := t.vm.Set("x", a)
if err != nil {
return otto.UndefinedValue(), err
if len(result) == 0 {
return "", errors.New("unexpected response format")
}
_ = t.vm.Set("internalTTK", TTK)
// 提取翻译文本
translations, ok := result[0].([]interface{})
if !ok {
return "", errors.New("unexpected response format")
}
result, err := t.vm.Run(`
function sM(a) {
var b;
if (null !== yr)
b = yr;
else {
b = wr(String.fromCharCode(84));
var c = wr(String.fromCharCode(75));
b = [b(), b()];
b[1] = c();
b = (yr = window[b.join(c())] || "") || ""
}
var d = wr(String.fromCharCode(116))
, c = wr(String.fromCharCode(107))
, d = [d(), d()];
d[1] = c();
c = "&" + d.join("") + "=";
d = b.split(".");
b = Number(d[0]) || 0;
for (var e = [], f = 0, g = 0; g < a.length; g++) {
var l = a.charCodeAt(g);
128 > l ? e[f++] = l : (2048 > l ? e[f++] = l >> 6 | 192 : (55296 == (l & 64512) && g + 1 < a.length && 56320 == (a.charCodeAt(g + 1) & 64512) ? (l = 65536 + ((l & 1023) << 10) + (a.charCodeAt(++g) & 1023),
e[f++] = l >> 18 | 240,
e[f++] = l >> 12 & 63 | 128) : e[f++] = l >> 12 | 224,
e[f++] = l >> 6 & 63 | 128),
e[f++] = l & 63 | 128)
}
a = b;
for (f = 0; f < e.length; f++)
a += e[f],
a = xr(a, "+-a^+6");
a = xr(a, "+-3^+b+-f");
a ^= Number(d[1]) || 0;
0 > a && (a = (a & 2147483647) + 2147483648);
a %= 1E6;
return c + (a.toString() + "." + (a ^ b))
}
var yr = null;
var wr = function(a) {
return function() {
return a
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)
}
}
, xr = function(a, b) {
for (var c = 0; c < b.length - 2; c += 3) {
var d = b.charAt(c + 2)
, d = "a" <= d ? d.charCodeAt(0) - 87 : Number(d)
, d = "+" == b.charAt(c + 1) ? a >>> d : a << d;
a = "+" == b.charAt(c) ? a + d & 4294967295 : a ^ d
}
return a
};
var window = {
TKK: internalTTK
};
sM(x)
`)
if err != nil {
return otto.UndefinedValue(), err
}
return result, nil
return translatedText.String(), nil
}
// GetSupportedLanguages 获取翻译器支持的语言列表
@@ -436,8 +256,24 @@ func (t *GoogleTranslator) IsLanguageSupported(languageCode string) bool {
return ok
}
// GetStandardLanguageCode 获取标准化的语言代码
func (t *GoogleTranslator) GetStandardLanguageCode(languageCode string) string {
// 简单返回小写版本作为标准代码
return strings.ToLower(languageCode)
// 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
}

View File

@@ -0,0 +1,103 @@
package translator
import (
"context"
"encoding/json"
"errors"
"fmt"
"golang.org/x/text/language"
"io"
"net/http"
"net/url"
"time"
)
// NewGoogleTranslatorTokenFree 创建一个新的无token的Google翻译器实例
func NewGoogleTranslatorTokenFree() *GoogleTranslatorTokenFree {
return &GoogleTranslatorTokenFree{
httpClient: &http.Client{Timeout: defaultTimeout},
Timeout: defaultTimeout,
languages: initGoogleLanguages(),
}
}
// SetTimeout 设置请求超时时间
func (t *GoogleTranslatorTokenFree) SetTimeout(timeout time.Duration) {
t.Timeout = timeout
t.httpClient.Timeout = timeout
}
// Translate 使用Go语言提供的标准语言标签进行文本翻译
func (t *GoogleTranslatorTokenFree) Translate(text string, from language.Tag, to language.Tag) (string, error) {
return t.translate(text, from.String(), to.String())
}
// TranslateWithParams 使用简单字符串参数进行文本翻译
func (t *GoogleTranslatorTokenFree) 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 *GoogleTranslatorTokenFree) translate(text, from, to string) (string, error) {
ctx, cancel := context.WithTimeout(context.Background(), t.Timeout)
defer cancel()
// 构建请求URL无token版本
apiURL := "https://translate.googleapis.com/translate_a/t"
params := url.Values{}
params.Set("client", "dict-chrome-ex")
params.Set("sl", from)
params.Set("tl", to)
params.Set("q", text)
fullURL := apiURL + "?" + params.Encode()
// 创建请求
req, err := http.NewRequestWithContext(ctx, "GET", fullURL, nil)
if err != nil {
return "", err
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
// 发送请求
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
}
// 提取翻译文本
var translatedTexts []string
visitArrayItems(result, func(obj interface{}) {
if text, ok := obj.(string); ok {
translatedTexts = append(translatedTexts, text)
}
})
if len(translatedTexts) == 0 {
return "", errors.New("no translation found")
}
// 返回第一个翻译结果
return translatedTexts[0], nil
}

View File

@@ -0,0 +1,210 @@
// Package translator 提供文本翻译功能
package translator
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"strings"
"time"
"golang.org/x/text/language"
)
// TartuNLPTranslator TartuNLP翻译器结构体
type TartuNLPTranslator struct {
httpClient *http.Client // HTTP客户端
Timeout time.Duration // 请求超时时间
languages map[string]LanguageInfo // 支持的语言列表
}
// 常量定义
const (
tartuNLPDefaultTimeout = 300 * time.Second // 默认超时时间300秒
tartuNLPAPIURL = "https://api.tartunlp.ai/translation/v2" // TartuNLP API地址
tartuNLPLengthLimit = 5000 // 长度限制5000字符
)
// 错误定义
var (
ErrTartuNLPNetworkError = errors.New("tartunlp translator network error")
ErrTartuNLPUnsupportedLang = errors.New("tartunlp translator unsupported language")
ErrTartuNLPResponseError = errors.New("tartunlp translator response error")
ErrTartuNLPLengthExceeded = errors.New("tartunlp translator text length exceeded")
)
// TartuNLPRequest TartuNLP请求结构体
type TartuNLPRequest struct {
Text []string `json:"text"` // 要翻译的文本数组
Src string `json:"src"` // 源语言
Tgt string `json:"tgt"` // 目标语言
}
// TartuNLPResponse TartuNLP响应结构体
type TartuNLPResponse struct {
Result []string `json:"result"` // 翻译结果数组
}
// NewTartuNLPTranslator 创建一个新的TartuNLP翻译器实例
func NewTartuNLPTranslator() *TartuNLPTranslator {
translator := &TartuNLPTranslator{
httpClient: &http.Client{
Timeout: tartuNLPDefaultTimeout,
},
Timeout: tartuNLPDefaultTimeout,
languages: initTartuNLPLanguages(),
}
return translator
}
// initTartuNLPLanguages 初始化TartuNLP翻译器支持的语言列表
func initTartuNLPLanguages() map[string]LanguageInfo {
// 创建语言映射表
languages := make(map[string]LanguageInfo)
// 添加支持的语言
// 基于 TartuNLP API 支持的语言列表
languages["en"] = LanguageInfo{Code: "en", Name: "English"}
languages["et"] = LanguageInfo{Code: "et", Name: "Estonian"}
languages["de"] = LanguageInfo{Code: "de", Name: "German"}
languages["lt"] = LanguageInfo{Code: "lt", Name: "Lithuanian"}
languages["lv"] = LanguageInfo{Code: "lv", Name: "Latvian"}
languages["fi"] = LanguageInfo{Code: "fi", Name: "Finnish"}
languages["ru"] = LanguageInfo{Code: "ru", Name: "Russian"}
languages["no"] = LanguageInfo{Code: "no", Name: "Norwegian"}
languages["hu"] = LanguageInfo{Code: "hu", Name: "Hungarian"}
languages["se"] = LanguageInfo{Code: "se", Name: "Swedish"}
return languages
}
// SetTimeout 设置请求超时时间
func (t *TartuNLPTranslator) SetTimeout(timeout time.Duration) {
t.Timeout = timeout
t.httpClient.Timeout = timeout
}
// Translate 使用标准语言标签进行文本翻译
func (t *TartuNLPTranslator) Translate(text string, from language.Tag, to language.Tag) (string, error) {
return t.translate(text, from.String(), to.String())
}
// TranslateWithParams 使用简单字符串参数进行文本翻译
func (t *TartuNLPTranslator) TranslateWithParams(text string, params TranslationParams) (string, error) {
// 设置超时时间(如果有指定)
if params.Timeout > 0 {
t.SetTimeout(params.Timeout)
}
return t.translate(text, params.From, params.To)
}
// checkLengthLimit 检查文本长度是否超出限制
func (t *TartuNLPTranslator) checkLengthLimit(text string) error {
if len(text) > tartuNLPLengthLimit {
return fmt.Errorf("%w: text length %d exceeds limit %d", ErrTartuNLPLengthExceeded, len(text), tartuNLPLengthLimit)
}
return nil
}
// translate 执行实际翻译操作
func (t *TartuNLPTranslator) translate(text, from, to string) (string, error) {
if text == "" {
return "", fmt.Errorf("text cannot be empty")
}
// 检查文本长度限制
if err := t.checkLengthLimit(text); err != nil {
return "", err
}
// 转换语言代码为TartuNLP格式
fromLower := strings.ToLower(from)
toLower := strings.ToLower(to)
// 验证源语言支持
if _, ok := t.languages[fromLower]; !ok {
return "", fmt.Errorf("%w: source language '%s' not supported by TartuNLP", ErrTartuNLPUnsupportedLang, from)
}
// 验证目标语言支持
if _, ok := t.languages[toLower]; !ok {
return "", fmt.Errorf("%w: target language '%s' not supported by TartuNLP", ErrTartuNLPUnsupportedLang, to)
}
// 创建带超时的context
ctx, cancel := context.WithTimeout(context.Background(), t.Timeout)
defer cancel()
// 构建请求体
request := TartuNLPRequest{
Text: []string{text},
Src: fromLower,
Tgt: toLower,
}
// 序列化请求
jsonData, err := json.Marshal(request)
if err != nil {
return "", fmt.Errorf("failed to marshal request: %w", err)
}
// 创建HTTP请求
req, err := http.NewRequestWithContext(ctx, "POST", tartuNLPAPIURL, bytes.NewBuffer(jsonData))
if err != nil {
return "", fmt.Errorf("failed to create request: %w", err)
}
// 设置请求头
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Accept", "application/json")
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36")
// 发送请求
resp, err := t.httpClient.Do(req)
if err != nil {
return "", fmt.Errorf("%w: %v", ErrTartuNLPNetworkError, err)
}
defer resp.Body.Close()
// 检查HTTP状态码
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return "", fmt.Errorf("%w: HTTP %d - %s", ErrTartuNLPResponseError, resp.StatusCode, string(body))
}
// 读取响应体
body, err := io.ReadAll(resp.Body)
if err != nil {
return "", fmt.Errorf("failed to read response body: %w", err)
}
// 解析响应
var response TartuNLPResponse
if err := json.Unmarshal(body, &response); err != nil {
return "", fmt.Errorf("%w: failed to parse response: %v", ErrTartuNLPResponseError, err)
}
// 检查响应结果
if len(response.Result) == 0 {
return "", fmt.Errorf("%w: empty translation result", ErrTartuNLPResponseError)
}
return response.Result[0], nil
}
// GetSupportedLanguages 获取翻译器支持的语言列表
func (t *TartuNLPTranslator) GetSupportedLanguages() map[string]LanguageInfo {
return t.languages
}
// IsLanguageSupported 检查指定的语言代码是否受支持
func (t *TartuNLPTranslator) IsLanguageSupported(languageCode string) bool {
_, exists := t.languages[strings.ToLower(languageCode)]
return exists
}

View File

@@ -32,6 +32,8 @@ const (
YoudaoTranslatorType TranslatorType = "youdao"
// DeeplTranslatorType DeepL翻译器
DeeplTranslatorType TranslatorType = "deepl"
// TartuNLPTranslatorType TartuNLP翻译器
TartuNLPTranslatorType TranslatorType = "tartunlp"
)
// LanguageInfo 语言信息结构体
@@ -56,9 +58,6 @@ type Translator interface {
// IsLanguageSupported 检查指定的语言代码是否受支持
IsLanguageSupported(languageCode string) bool
// GetStandardLanguageCode 获取标准化的语言代码
GetStandardLanguageCode(languageCode string) string
}
// TranslatorFactory 翻译器工厂,用于创建不同类型的翻译器
@@ -80,6 +79,8 @@ func (f *TranslatorFactory) Create(translatorType TranslatorType) (Translator, e
return NewYoudaoTranslator(), nil
case DeeplTranslatorType:
return NewDeeplTranslator(), nil
case TartuNLPTranslatorType:
return NewTartuNLPTranslator(), nil
default:
return nil, fmt.Errorf("unsupported translator type: %s", translatorType)
}

View File

@@ -50,8 +50,7 @@ func initYoudaoLanguages() map[string]LanguageInfo {
// 创建语言映射表
languages := make(map[string]LanguageInfo)
// 自动检测
languages["auto"] = LanguageInfo{Code: "auto", Name: "Auto"}
languages["auto"] = LanguageInfo{Code: "AUTO", Name: "Auto"}
return languages
}
@@ -64,8 +63,7 @@ func (t *YoudaoTranslator) SetTimeout(timeout time.Duration) {
// Translate 使用标准语言标签进行文本翻译
func (t *YoudaoTranslator) Translate(text string, from language.Tag, to language.Tag) (string, error) {
// 有道翻译不需要指定源语言和目标语言,它会自动检测
return t.translate(text)
return t.translate(text, to.String())
}
// TranslateWithParams 使用简单字符串参数进行文本翻译
@@ -75,16 +73,15 @@ func (t *YoudaoTranslator) TranslateWithParams(text string, params TranslationPa
t.SetTimeout(params.Timeout)
}
// 有道翻译不需要指定源语言和目标语言,它会自动检测
return t.translate(text)
return t.translate(text, params.To)
}
// translate 执行实际翻译操作
func (t *YoudaoTranslator) translate(text string) (string, error) {
func (t *YoudaoTranslator) translate(text string, typeName string) (string, error) {
// 构建表单数据
form := url.Values{}
form.Add("inputtext", text)
form.Add("type", "AUTO")
form.Add("type", typeName)
// 创建请求
req, err := http.NewRequest("POST", youdaoTranslateURL, strings.NewReader(form.Encode()))