✨ Add translation features
This commit is contained in:
@@ -6,7 +6,9 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"net/http/cookiejar"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
@@ -17,51 +19,296 @@ import (
|
||||
|
||||
// BingTranslator Bing翻译器结构体
|
||||
type BingTranslator struct {
|
||||
BingHost string // Bing服务主机
|
||||
httpClient *http.Client // HTTP客户端
|
||||
Timeout time.Duration // 请求超时时间
|
||||
httpClient *http.Client // HTTP客户端
|
||||
Timeout time.Duration // 请求超时时间
|
||||
session *BingSession // Bing会话
|
||||
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参数
|
||||
}
|
||||
|
||||
// 常量定义
|
||||
const (
|
||||
bingDefaultTimeout = 30 * time.Second
|
||||
defaultBingHost = "cn.bing.com" // 使用cn.bing.com作为默认域名
|
||||
bingDefaultTimeout = 30 * time.Second
|
||||
bingTranslatorURL = "https://cn.bing.com/translator"
|
||||
bingTranslateAPIURL = "https://cn.bing.com/ttranslatev3"
|
||||
)
|
||||
|
||||
// 错误定义
|
||||
var (
|
||||
ErrBingNetworkError = errors.New("bing translator network error")
|
||||
ErrBingParseError = errors.New("bing translator parse error")
|
||||
ErrBingTokenError = errors.New("failed to get bing translator token")
|
||||
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")
|
||||
)
|
||||
|
||||
// BingTranslationParams Bing翻译所需的参数
|
||||
type BingTranslationParams struct {
|
||||
Token string // token参数
|
||||
Key string // key参数
|
||||
IG string // IG参数
|
||||
// 用户代理列表
|
||||
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{
|
||||
BingHost: defaultBingHost,
|
||||
Timeout: bingDefaultTimeout,
|
||||
httpClient: &http.Client{Timeout: bingDefaultTimeout},
|
||||
httpClient: &http.Client{
|
||||
Timeout: bingDefaultTimeout,
|
||||
// 启用Cookie存储
|
||||
Jar: jar,
|
||||
},
|
||||
Timeout: bingDefaultTimeout,
|
||||
session: &BingSession{
|
||||
Headers: make(map[string]string),
|
||||
Cookie: make(map[string]string),
|
||||
},
|
||||
languages: initBingLanguages(),
|
||||
}
|
||||
|
||||
// 初始化会话
|
||||
translator.refreshSession()
|
||||
|
||||
return translator
|
||||
}
|
||||
|
||||
// initBingLanguages 初始化Bing翻译器支持的语言列表
|
||||
func initBingLanguages() map[string]LanguageInfo {
|
||||
// 创建语言映射表
|
||||
languages := make(map[string]LanguageInfo)
|
||||
|
||||
// 添加所有支持的语言
|
||||
// 基于 Microsoft Translator 支持的语言列表
|
||||
// 参考: https://learn.microsoft.com/en-us/azure/ai-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
|
||||
|
||||
return languages
|
||||
}
|
||||
|
||||
// SetTimeout 设置请求超时时间
|
||||
func (t *BingTranslator) SetTimeout(timeout time.Duration) {
|
||||
t.Timeout = timeout
|
||||
t.httpClient.Timeout = timeout
|
||||
}
|
||||
|
||||
// SetBingHost 设置Bing主机
|
||||
func (t *BingTranslator) SetBingHost(host string) {
|
||||
t.BingHost = host
|
||||
// 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"
|
||||
|
||||
// 创建请求
|
||||
req, err := http.NewRequest("GET", bingTranslatorURL, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("the creation request failed: %w", err)
|
||||
}
|
||||
|
||||
// 设置请求头
|
||||
for k, v := range t.session.Headers {
|
||||
req.Header.Set(k, v)
|
||||
}
|
||||
|
||||
// 发送请求
|
||||
resp, err := t.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %v", ErrBingNetworkError, err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// 保存Cookie
|
||||
for _, cookie := range resp.Cookies() {
|
||||
t.session.Cookie[cookie.Name] = cookie.Value
|
||||
}
|
||||
|
||||
// 读取响应内容
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read response failed: %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)
|
||||
}
|
||||
|
||||
// 解析参数数组
|
||||
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)
|
||||
}
|
||||
|
||||
// 提取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
|
||||
}
|
||||
|
||||
// Translate 使用标准语言标签进行文本翻译
|
||||
@@ -71,207 +318,166 @@ func (t *BingTranslator) Translate(text string, from language.Tag, to language.T
|
||||
|
||||
// TranslateWithParams 使用简单字符串参数进行文本翻译
|
||||
func (t *BingTranslator) TranslateWithParams(text string, params TranslationParams) (string, error) {
|
||||
// 设置超时时间(如果有指定)
|
||||
if params.Timeout > 0 {
|
||||
t.SetTimeout(params.Timeout)
|
||||
}
|
||||
|
||||
return t.translate(text, params.From, params.To)
|
||||
}
|
||||
|
||||
// translate 执行实际翻译操作
|
||||
func (t *BingTranslator) translate(text, from, to string) (string, error) {
|
||||
// 获取翻译所需的参数
|
||||
params, err := t.ExtractBingTranslationParams()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to extract bing translation params: %w", err)
|
||||
}
|
||||
|
||||
// 执行翻译
|
||||
return t.GetBingTranslation(params.Token, params.Key, params.IG, text, from, to)
|
||||
}
|
||||
|
||||
// ExtractBingTranslationParams 提取Bing翻译所需的参数
|
||||
func (t *BingTranslator) ExtractBingTranslationParams() (*BingTranslationParams, error) {
|
||||
// 发送GET请求获取网页内容
|
||||
url := fmt.Sprintf("https://%s/translator?mkt=zh-CN", t.BingHost)
|
||||
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create request: %w", err)
|
||||
}
|
||||
|
||||
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36")
|
||||
req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7")
|
||||
req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8")
|
||||
|
||||
resp, err := t.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %v", ErrBingNetworkError, err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("failed to access Bing translator page: status code %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
// 读取响应内容
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read response: %w", err)
|
||||
}
|
||||
|
||||
pageContent := string(body)
|
||||
|
||||
// 模式1: 标准的params_AbusePreventionHelper数组
|
||||
keyPattern := regexp.MustCompile(`params_AbusePreventionHelper\s*=\s*\[([^\]]+)\]`)
|
||||
keyMatch := keyPattern.FindStringSubmatch(pageContent)
|
||||
|
||||
var key, token string
|
||||
|
||||
if len(keyMatch) >= 2 {
|
||||
// 提取并解析数组
|
||||
paramsStr := keyMatch[1]
|
||||
paramsList := strings.Split(paramsStr, ",")
|
||||
|
||||
if len(paramsList) >= 2 {
|
||||
// 清理引号
|
||||
key = strings.Trim(paramsList[0], `"' `)
|
||||
token = strings.Trim(paramsList[1], `"' `)
|
||||
// 如果没有会话或关键参数缺失,刷新会话
|
||||
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 key == "" || token == "" {
|
||||
// 模式2: 查找_G.Token和_G.Key
|
||||
tokenPattern := regexp.MustCompile(`_G\.Token\s*=\s*["']([^"']+)["']`)
|
||||
tokenMatch := tokenPattern.FindStringSubmatch(pageContent)
|
||||
// 生成随机IID
|
||||
randNum := rand.Intn(10) // 0-9的随机数
|
||||
iid := fmt.Sprintf("translator.5019.%d", 1+randNum%3) // 生成随机IID
|
||||
|
||||
keyPattern := regexp.MustCompile(`_G\.Key\s*=\s*["']?([^"',]+)["']?`)
|
||||
keyMatch := keyPattern.FindStringSubmatch(pageContent)
|
||||
// 构建URL - 确保使用双&符号
|
||||
reqURL := fmt.Sprintf("%s?isVertical=1&&IG=%s&IID=%s",
|
||||
bingTranslateAPIURL, t.session.IG, iid)
|
||||
|
||||
if len(tokenMatch) >= 2 && len(keyMatch) >= 2 {
|
||||
token = tokenMatch[1]
|
||||
key = keyMatch[1]
|
||||
}
|
||||
}
|
||||
// 标准化语言代码
|
||||
fromLang := t.GetStandardLanguageCode(from)
|
||||
toLang := t.GetStandardLanguageCode(to)
|
||||
|
||||
// 如果仍然失败,尝试JSON格式
|
||||
if key == "" || token == "" {
|
||||
jsonPattern := regexp.MustCompile(`"token"\s*:\s*"([^"]+)"\s*,\s*"key"\s*:\s*"?([^",]+)"?`)
|
||||
jsonMatch := jsonPattern.FindStringSubmatch(pageContent)
|
||||
// 构建表单数据
|
||||
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)
|
||||
|
||||
if len(jsonMatch) >= 3 {
|
||||
token = jsonMatch[1]
|
||||
key = jsonMatch[2]
|
||||
}
|
||||
}
|
||||
|
||||
// 如果所有模式都失败
|
||||
if key == "" || token == "" {
|
||||
return nil, fmt.Errorf("%w: unable to extract token and key", ErrBingTokenError)
|
||||
}
|
||||
|
||||
// 查找并提取 IG 参数,尝试多种格式
|
||||
var ig string
|
||||
|
||||
// 模式1: 标准IG格式
|
||||
igPattern := regexp.MustCompile(`IG["']?\s*:\s*["']([^"']+)["']`)
|
||||
igMatch := igPattern.FindStringSubmatch(pageContent)
|
||||
|
||||
if len(igMatch) >= 2 {
|
||||
ig = igMatch[1]
|
||||
} else {
|
||||
// 模式2: 备用IG格式
|
||||
igPattern = regexp.MustCompile(`"IG"\s*:\s*"([^"]+)"`)
|
||||
igMatch = igPattern.FindStringSubmatch(pageContent)
|
||||
|
||||
if len(igMatch) >= 2 {
|
||||
ig = igMatch[1]
|
||||
} else {
|
||||
// 模式3: _G.IG格式
|
||||
igPattern = regexp.MustCompile(`_G\.IG\s*=\s*["']([^"']+)["']`)
|
||||
igMatch = igPattern.FindStringSubmatch(pageContent)
|
||||
|
||||
if len(igMatch) >= 2 {
|
||||
ig = igMatch[1]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果所有IG提取模式都失败
|
||||
if ig == "" {
|
||||
return nil, fmt.Errorf("%w: unable to extract IG parameter", ErrBingTokenError)
|
||||
}
|
||||
|
||||
return &BingTranslationParams{
|
||||
Token: token,
|
||||
Key: key,
|
||||
IG: ig,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetBingTranslation 获取Bing翻译结果
|
||||
func (t *BingTranslator) GetBingTranslation(token, key, ig, text, fromLang, toLang string) (string, error) {
|
||||
// URL编码文本
|
||||
encodedText := url.QueryEscape(text)
|
||||
|
||||
// 构建POST请求的payload
|
||||
payload := fmt.Sprintf("fromLang=%s&to=%s&text=%s&token=%s&key=%s",
|
||||
fromLang, toLang, encodedText, token, key)
|
||||
|
||||
// 构建URL
|
||||
urlStr := fmt.Sprintf("https://%s/ttranslatev3?isVertical=1&IG=%s&IID=translator.5028", t.BingHost, ig)
|
||||
formDataStr := formData.Encode()
|
||||
|
||||
// 创建请求
|
||||
req, err := http.NewRequest("POST", urlStr, strings.NewReader(payload))
|
||||
req, err := http.NewRequest("POST", reqURL, strings.NewReader(formDataStr))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to create request: %w", err)
|
||||
return "", fmt.Errorf("The creation request failed: %w", err)
|
||||
}
|
||||
|
||||
// 设置请求头
|
||||
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36")
|
||||
req.Header.Set("Accept", "*/*")
|
||||
req.Header.Set("Host", t.BingHost)
|
||||
req.Header.Set("Connection", "keep-alive")
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
req.Header.Set("Origin", fmt.Sprintf("https://%s", t.BingHost))
|
||||
req.Header.Set("Referer", fmt.Sprintf("https://%s/translator", t.BingHost))
|
||||
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,
|
||||
})
|
||||
}
|
||||
|
||||
// 发送请求
|
||||
resp, err := t.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("%w: %v", ErrBingNetworkError, err)
|
||||
}
|
||||
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)
|
||||
resp.Body.Close()
|
||||
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to read response: %w", err)
|
||||
return "", fmt.Errorf("read response failed: %w", err)
|
||||
}
|
||||
|
||||
// 检查响应内容
|
||||
if len(body) == 0 {
|
||||
return "", fmt.Errorf("translation API returned empty response")
|
||||
return "", ErrBingEmptyResponse
|
||||
}
|
||||
|
||||
// 使用最简单的结构体解析JSON
|
||||
var response []struct {
|
||||
Translations []struct {
|
||||
Text string `json:"text"`
|
||||
} `json:"translations"`
|
||||
// 尝试解析响应
|
||||
var result interface{}
|
||||
if err := json.Unmarshal(body, &result); err != nil {
|
||||
return "", fmt.Errorf("%w: %v", ErrBingParseError, err)
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(body, &response); err != nil {
|
||||
return "", fmt.Errorf("%w: JSON parsing error: %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(response) == 0 || len(response[0].Translations) == 0 {
|
||||
return "", fmt.Errorf("%w: invalid response format", ErrBingParseError)
|
||||
// 应该是数组类型
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 返回翻译结果
|
||||
return response[0].Translations[0].Text, nil
|
||||
return "", fmt.Errorf("%w: The response format is not as expected", ErrBingParseError)
|
||||
}
|
||||
|
||||
// GetSupportedLanguages 获取翻译器支持的语言列表
|
||||
func (t *BingTranslator) GetSupportedLanguages() map[string]LanguageInfo {
|
||||
return t.languages
|
||||
}
|
||||
|
||||
// IsLanguageSupported 检查指定的语言代码是否受支持
|
||||
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 // 如果没有找到映射,返回原始代码
|
||||
}
|
||||
|
@@ -17,9 +17,10 @@ import (
|
||||
|
||||
// DeeplTranslator DeepL翻译器结构体
|
||||
type DeeplTranslator struct {
|
||||
DeeplHost string // DeepL服务主机
|
||||
httpClient *http.Client // HTTP客户端
|
||||
Timeout time.Duration // 请求超时时间
|
||||
DeeplHost string // DeepL服务主机
|
||||
httpClient *http.Client // HTTP客户端
|
||||
Timeout time.Duration // 请求超时时间
|
||||
languages map[string]LanguageInfo // 支持的语言列表
|
||||
}
|
||||
|
||||
// 常量定义
|
||||
@@ -36,66 +37,6 @@ var (
|
||||
ErrDeeplResponseError = errors.New("deepl translator response error")
|
||||
)
|
||||
|
||||
// 语言映射
|
||||
var deeplLangMap = map[string]string{
|
||||
"auto": "auto",
|
||||
"de": "DE",
|
||||
"en": "EN",
|
||||
"es": "ES",
|
||||
"fr": "FR",
|
||||
"it": "IT",
|
||||
"ja": "JA",
|
||||
"ko": "KO",
|
||||
"nl": "NL",
|
||||
"pl": "PL",
|
||||
"pt": "PT",
|
||||
"ru": "RU",
|
||||
"zh": "ZH",
|
||||
"bg": "BG",
|
||||
"cs": "CS",
|
||||
"da": "DA",
|
||||
"el": "EL",
|
||||
"et": "ET",
|
||||
"fi": "FI",
|
||||
"hu": "HU",
|
||||
"lt": "LT",
|
||||
"lv": "LV",
|
||||
"ro": "RO",
|
||||
"sk": "SK",
|
||||
"sl": "SL",
|
||||
"sv": "SV",
|
||||
}
|
||||
|
||||
// 反向语言映射
|
||||
var deeplLangMapReverse = map[string]string{
|
||||
"auto": "auto",
|
||||
"DE": "de",
|
||||
"EN": "en",
|
||||
"ES": "es",
|
||||
"FR": "fr",
|
||||
"IT": "it",
|
||||
"JA": "ja",
|
||||
"KO": "ko",
|
||||
"NL": "nl",
|
||||
"PL": "pl",
|
||||
"PT": "pt",
|
||||
"RU": "ru",
|
||||
"ZH": "zh",
|
||||
"BG": "bg",
|
||||
"CS": "cs",
|
||||
"DA": "da",
|
||||
"EL": "el",
|
||||
"ET": "et",
|
||||
"FI": "fi",
|
||||
"HU": "hu",
|
||||
"LT": "lt",
|
||||
"LV": "lv",
|
||||
"RO": "ro",
|
||||
"SK": "sk",
|
||||
"SL": "sl",
|
||||
"SV": "sv",
|
||||
}
|
||||
|
||||
// DeeplRequest DeepL请求结构体
|
||||
type DeeplRequest struct {
|
||||
Jsonrpc string `json:"jsonrpc"`
|
||||
@@ -156,11 +97,60 @@ func NewDeeplTranslator() *DeeplTranslator {
|
||||
DeeplHost: defaultDeeplHost,
|
||||
Timeout: deeplDefaultTimeout,
|
||||
httpClient: &http.Client{Timeout: deeplDefaultTimeout},
|
||||
languages: initDeeplLanguages(),
|
||||
}
|
||||
|
||||
return translator
|
||||
}
|
||||
|
||||
// initDeeplLanguages 初始化DeepL翻译器支持的语言列表
|
||||
func initDeeplLanguages() map[string]LanguageInfo {
|
||||
// 创建语言映射表
|
||||
languages := make(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
|
||||
}
|
||||
|
||||
// SetTimeout 设置请求超时时间
|
||||
func (t *DeeplTranslator) SetTimeout(timeout time.Duration) {
|
||||
t.Timeout = timeout
|
||||
@@ -179,38 +169,34 @@ func (t *DeeplTranslator) Translate(text string, from language.Tag, to language.
|
||||
|
||||
// TranslateWithParams 使用简单字符串参数进行文本翻译
|
||||
func (t *DeeplTranslator) TranslateWithParams(text string, params TranslationParams) (string, error) {
|
||||
tries := params.Tries
|
||||
if tries == 0 {
|
||||
tries = defaultNumberOfRetries
|
||||
// 设置超时时间(如果有指定)
|
||||
if params.Timeout > 0 {
|
||||
t.SetTimeout(params.Timeout)
|
||||
}
|
||||
|
||||
var result string
|
||||
var lastError error
|
||||
|
||||
for i := 0; i < tries; i++ {
|
||||
if i > 0 && params.Delay > 0 {
|
||||
time.Sleep(params.Delay)
|
||||
}
|
||||
|
||||
result, lastError = t.translate(text, params.From, params.To)
|
||||
if lastError == nil {
|
||||
return result, nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", lastError
|
||||
// 直接执行一次翻译
|
||||
return t.translate(text, params.From, params.To)
|
||||
}
|
||||
|
||||
// translate 执行实际翻译操作
|
||||
func (t *DeeplTranslator) translate(text, from, to string) (string, error) {
|
||||
// 转换语言代码为DeepL格式
|
||||
sourceLang, ok := deeplLangMap[strings.ToLower(from)]
|
||||
if !ok && from != "auto" {
|
||||
fromLower := strings.ToLower(from)
|
||||
toLower := strings.ToLower(to)
|
||||
|
||||
var sourceLang string
|
||||
if fromLower == "auto" {
|
||||
sourceLang = "auto"
|
||||
} else if fromLangInfo, ok := t.languages[fromLower]; ok {
|
||||
sourceLang = fromLangInfo.Code
|
||||
} else {
|
||||
sourceLang = "auto"
|
||||
}
|
||||
|
||||
targetLang, ok := deeplLangMap[strings.ToLower(to)]
|
||||
if !ok {
|
||||
var targetLang string
|
||||
if toLangInfo, ok := t.languages[toLower]; ok {
|
||||
targetLang = toLangInfo.Code
|
||||
} else {
|
||||
return "", fmt.Errorf("%w: language '%s' not supported by DeepL", ErrDeeplUnsupportedLang, to)
|
||||
}
|
||||
|
||||
@@ -262,39 +248,45 @@ func (t *DeeplTranslator) translate(text, from, to string) (string, error) {
|
||||
return "", fmt.Errorf("failed to create request: %w", err)
|
||||
}
|
||||
|
||||
// 设置请求头
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36")
|
||||
req.Header.Set("Accept", "*/*")
|
||||
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36")
|
||||
req.Header.Set("Connection", "keep-alive")
|
||||
req.Header.Set("Origin", "https://www.deepl.com")
|
||||
req.Header.Set("Referer", "https://www.deepl.com/translator")
|
||||
|
||||
// 执行请求
|
||||
resp, err := t.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("%w: %v", ErrDeeplNetworkError, err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// 检查响应状态
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return "", fmt.Errorf("%w: status code %d", ErrDeeplNetworkError, resp.StatusCode)
|
||||
return "", fmt.Errorf("API error: status code %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
// 读取响应
|
||||
// 读取响应内容
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to read response: %w", err)
|
||||
}
|
||||
|
||||
// 解析响应
|
||||
var deeplResp DeeplResponse
|
||||
err = json.Unmarshal(body, &deeplResp)
|
||||
if err != nil {
|
||||
var response DeeplResponse
|
||||
if err := json.Unmarshal(body, &response); err != nil {
|
||||
return "", fmt.Errorf("%w: %v", ErrDeeplResponseError, err)
|
||||
}
|
||||
|
||||
// 检查是否有有效的结果
|
||||
if len(deeplResp.Result.Texts) == 0 {
|
||||
return "", fmt.Errorf("%w: no translation result", ErrDeeplResponseError)
|
||||
// 检查结果
|
||||
if len(response.Result.Texts) == 0 {
|
||||
return "", fmt.Errorf("%w: empty translation result", ErrDeeplResponseError)
|
||||
}
|
||||
|
||||
// 返回翻译结果
|
||||
return deeplResp.Result.Texts[0].Text, nil
|
||||
return response.Result.Texts[0].Text, nil
|
||||
}
|
||||
|
||||
// getICount 获取文本中'i'字符的数量
|
||||
@@ -316,3 +308,20 @@ func getTimeStamp(iCount int) int64 {
|
||||
}
|
||||
return ts
|
||||
}
|
||||
|
||||
// GetSupportedLanguages 获取翻译器支持的语言列表
|
||||
func (t *DeeplTranslator) GetSupportedLanguages() map[string]LanguageInfo {
|
||||
return t.languages
|
||||
}
|
||||
|
||||
// IsLanguageSupported 检查指定的语言代码是否受支持
|
||||
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)
|
||||
}
|
||||
|
@@ -26,11 +26,12 @@ var (
|
||||
|
||||
// GoogleTranslator Google翻译器结构体,统一管理翻译功能
|
||||
type GoogleTranslator struct {
|
||||
GoogleHost string // Google服务主机
|
||||
vm *otto.Otto // JavaScript虚拟机
|
||||
ttk otto.Value // 翻译token缓存
|
||||
httpClient *http.Client // HTTP客户端
|
||||
Timeout time.Duration // 请求超时时间
|
||||
GoogleHost string // Google服务主机
|
||||
vm *otto.Otto // JavaScript虚拟机
|
||||
ttk otto.Value // 翻译token缓存
|
||||
httpClient *http.Client // HTTP客户端
|
||||
Timeout time.Duration // 请求超时时间
|
||||
languages map[string]LanguageInfo // 支持的语言列表
|
||||
}
|
||||
|
||||
// NewGoogleTranslator 创建一个新的Google翻译器实例
|
||||
@@ -40,6 +41,7 @@ func NewGoogleTranslator() *GoogleTranslator {
|
||||
vm: otto.New(),
|
||||
Timeout: defaultTimeout,
|
||||
httpClient: &http.Client{Timeout: defaultTimeout},
|
||||
languages: initGoogleLanguages(),
|
||||
}
|
||||
|
||||
// 初始化ttk
|
||||
@@ -48,6 +50,131 @@ func NewGoogleTranslator() *GoogleTranslator {
|
||||
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
|
||||
}
|
||||
|
||||
// SetTimeout 设置请求超时时间
|
||||
func (t *GoogleTranslator) SetTimeout(timeout time.Duration) {
|
||||
t.Timeout = timeout
|
||||
@@ -61,25 +188,21 @@ func (t *GoogleTranslator) SetGoogleHost(host string) {
|
||||
|
||||
// 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, defaultNumberOfRetries, 0)
|
||||
return t.translate(text, from.String(), to.String(), false)
|
||||
}
|
||||
|
||||
// TranslateWithParams 使用简单字符串参数进行文本翻译
|
||||
func (t *GoogleTranslator) TranslateWithParams(text string, params TranslationParams) (string, error) {
|
||||
tries := params.Tries
|
||||
if tries == 0 {
|
||||
tries = defaultNumberOfRetries
|
||||
// 设置超时时间(如果有指定)
|
||||
if params.Timeout > 0 {
|
||||
t.SetTimeout(params.Timeout)
|
||||
}
|
||||
|
||||
return t.translate(text, params.From, params.To, true, tries, params.Delay)
|
||||
return t.translate(text, params.From, params.To, true)
|
||||
}
|
||||
|
||||
// translate 执行实际翻译操作
|
||||
func (t *GoogleTranslator) translate(text, from, to string, withVerification bool, tries int, delay time.Duration) (string, error) {
|
||||
if tries == 0 {
|
||||
tries = defaultNumberOfRetries
|
||||
}
|
||||
|
||||
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'")
|
||||
@@ -125,29 +248,21 @@ func (t *GoogleTranslator) translate(text, from, to string, withVerification boo
|
||||
parameters.Add("tk", token)
|
||||
u.RawQuery = parameters.Encode()
|
||||
|
||||
var r *http.Response
|
||||
for tries > 0 {
|
||||
req, err := http.NewRequest("GET", u.String(), nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
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
|
||||
r, err := t.httpClient.Do(req)
|
||||
if err != nil {
|
||||
if errors.Is(err, http.ErrHandlerTimeout) {
|
||||
return "", ErrBadNetwork
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
|
||||
if r.StatusCode == http.StatusOK {
|
||||
break
|
||||
}
|
||||
|
||||
if r.StatusCode == http.StatusForbidden {
|
||||
tries--
|
||||
time.Sleep(delay)
|
||||
}
|
||||
if r.StatusCode != http.StatusOK {
|
||||
return "", fmt.Errorf("API error: status code %d", r.StatusCode)
|
||||
}
|
||||
|
||||
raw, err := io.ReadAll(r.Body)
|
||||
@@ -309,3 +424,20 @@ func (t *GoogleTranslator) generateToken(a otto.Value, TTK otto.Value) (otto.Val
|
||||
|
||||
return result, 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
|
||||
}
|
||||
|
||||
// GetStandardLanguageCode 获取标准化的语言代码
|
||||
func (t *GoogleTranslator) GetStandardLanguageCode(languageCode string) string {
|
||||
// 简单返回小写版本作为标准代码
|
||||
return strings.ToLower(languageCode)
|
||||
}
|
||||
|
@@ -10,16 +10,14 @@ import (
|
||||
|
||||
// TranslationParams 用于指定翻译参数
|
||||
type TranslationParams struct {
|
||||
From string // 源语言
|
||||
To string // 目标语言
|
||||
Tries int // 重试次数
|
||||
Delay time.Duration // 重试延迟
|
||||
From string // 源语言
|
||||
To string // 目标语言
|
||||
Timeout time.Duration // 超时时间
|
||||
}
|
||||
|
||||
// 常量定义
|
||||
const (
|
||||
defaultNumberOfRetries = 2
|
||||
defaultTimeout = 30 * time.Second
|
||||
defaultTimeout = 30 * time.Second
|
||||
)
|
||||
|
||||
// TranslatorType 翻译器类型
|
||||
@@ -36,6 +34,12 @@ const (
|
||||
DeeplTranslatorType TranslatorType = "deepl"
|
||||
)
|
||||
|
||||
// LanguageInfo 语言信息结构体
|
||||
type LanguageInfo struct {
|
||||
Code string // 语言代码
|
||||
Name string // 语言名称
|
||||
}
|
||||
|
||||
// Translator 翻译器接口,定义所有翻译器必须实现的方法
|
||||
type Translator interface {
|
||||
// Translate 使用Go语言提供的标准语言标签进行文本翻译
|
||||
@@ -46,6 +50,15 @@ type Translator interface {
|
||||
|
||||
// SetTimeout 设置请求超时时间
|
||||
SetTimeout(timeout time.Duration)
|
||||
|
||||
// GetSupportedLanguages 获取翻译器支持的语言列表
|
||||
GetSupportedLanguages() map[string]LanguageInfo
|
||||
|
||||
// IsLanguageSupported 检查指定的语言代码是否受支持
|
||||
IsLanguageSupported(languageCode string) bool
|
||||
|
||||
// GetStandardLanguageCode 获取标准化的语言代码
|
||||
GetStandardLanguageCode(languageCode string) string
|
||||
}
|
||||
|
||||
// TranslatorFactory 翻译器工厂,用于创建不同类型的翻译器
|
||||
|
@@ -17,8 +17,9 @@ import (
|
||||
|
||||
// YoudaoTranslator 有道翻译器结构体
|
||||
type YoudaoTranslator struct {
|
||||
httpClient *http.Client // HTTP客户端
|
||||
Timeout time.Duration // 请求超时时间
|
||||
httpClient *http.Client // HTTP客户端
|
||||
Timeout time.Duration // 请求超时时间
|
||||
languages map[string]LanguageInfo // 支持的语言列表
|
||||
}
|
||||
|
||||
// 常量定义
|
||||
@@ -38,11 +39,23 @@ func NewYoudaoTranslator() *YoudaoTranslator {
|
||||
translator := &YoudaoTranslator{
|
||||
Timeout: youdaoDefaultTimeout,
|
||||
httpClient: &http.Client{Timeout: youdaoDefaultTimeout},
|
||||
languages: initYoudaoLanguages(),
|
||||
}
|
||||
|
||||
return translator
|
||||
}
|
||||
|
||||
// initYoudaoLanguages 初始化有道翻译器支持的语言列表
|
||||
func initYoudaoLanguages() map[string]LanguageInfo {
|
||||
// 创建语言映射表
|
||||
languages := make(map[string]LanguageInfo)
|
||||
|
||||
// 自动检测
|
||||
languages["auto"] = LanguageInfo{Code: "auto", Name: "Auto"}
|
||||
|
||||
return languages
|
||||
}
|
||||
|
||||
// SetTimeout 设置请求超时时间
|
||||
func (t *YoudaoTranslator) SetTimeout(timeout time.Duration) {
|
||||
t.Timeout = timeout
|
||||
@@ -57,6 +70,11 @@ func (t *YoudaoTranslator) Translate(text string, from language.Tag, to language
|
||||
|
||||
// TranslateWithParams 使用简单字符串参数进行文本翻译
|
||||
func (t *YoudaoTranslator) TranslateWithParams(text string, params TranslationParams) (string, error) {
|
||||
// 设置超时时间(如果有指定)
|
||||
if params.Timeout > 0 {
|
||||
t.SetTimeout(params.Timeout)
|
||||
}
|
||||
|
||||
// 有道翻译不需要指定源语言和目标语言,它会自动检测
|
||||
return t.translate(text)
|
||||
}
|
||||
@@ -184,3 +202,20 @@ func (t *YoudaoTranslator) extractText(n *html.Node) string {
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// GetSupportedLanguages 获取翻译器支持的语言列表
|
||||
func (t *YoudaoTranslator) GetSupportedLanguages() map[string]LanguageInfo {
|
||||
return t.languages
|
||||
}
|
||||
|
||||
// IsLanguageSupported 检查指定的语言代码是否受支持
|
||||
func (t *YoudaoTranslator) IsLanguageSupported(languageCode string) bool {
|
||||
_, ok := t.languages[strings.ToLower(languageCode)]
|
||||
return ok
|
||||
}
|
||||
|
||||
// GetStandardLanguageCode 获取标准化的语言代码
|
||||
func (t *YoudaoTranslator) GetStandardLanguageCode(languageCode string) string {
|
||||
// 简单返回小写版本作为标准代码
|
||||
return strings.ToLower(languageCode)
|
||||
}
|
||||
|
Reference in New Issue
Block a user