2026-03-28 23:27:02 +08:00
|
|
|
|
package translator
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"context"
|
|
|
|
|
|
"fmt"
|
|
|
|
|
|
"time"
|
|
|
|
|
|
|
2026-03-29 21:10:28 +08:00
|
|
|
|
"github.com/titor/fanyi/internal/cache"
|
2026-03-28 23:27:02 +08:00
|
|
|
|
"github.com/titor/fanyi/internal/config"
|
2026-03-29 18:41:25 +08:00
|
|
|
|
"github.com/titor/fanyi/internal/content"
|
2026-03-28 23:27:02 +08:00
|
|
|
|
"github.com/titor/fanyi/internal/provider"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// Translator 核心翻译类
|
|
|
|
|
|
type Translator struct {
|
2026-03-29 18:41:25 +08:00
|
|
|
|
config *config.Config
|
|
|
|
|
|
provider provider.Provider
|
|
|
|
|
|
prompt *PromptManager
|
|
|
|
|
|
contentParser *content.Parser
|
2026-03-29 21:10:28 +08:00
|
|
|
|
cache cache.Cache
|
2026-03-28 23:27:02 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// NewTranslator 创建翻译器实例
|
|
|
|
|
|
func NewTranslator(config *config.Config, provider provider.Provider) *Translator {
|
2026-03-29 21:10:28 +08:00
|
|
|
|
translator := &Translator{
|
2026-03-29 18:41:25 +08:00
|
|
|
|
config: config,
|
|
|
|
|
|
provider: provider,
|
|
|
|
|
|
prompt: NewPromptManager(config.Prompts),
|
|
|
|
|
|
contentParser: content.NewParser(config.SkipKeywords),
|
2026-03-28 23:27:02 +08:00
|
|
|
|
}
|
2026-03-29 21:10:28 +08:00
|
|
|
|
|
|
|
|
|
|
// 初始化缓存(如果启用)
|
|
|
|
|
|
if config.Cache.Enabled {
|
|
|
|
|
|
cacheConfig := &cache.CacheConfig{
|
|
|
|
|
|
Enabled: config.Cache.Enabled,
|
|
|
|
|
|
MaxRecords: config.Cache.MaxRecords,
|
|
|
|
|
|
ExpireDays: config.Cache.ExpireDays,
|
|
|
|
|
|
DBPath: config.Cache.DBPath,
|
|
|
|
|
|
}
|
|
|
|
|
|
if cacheInstance, err := cache.NewSQLiteCache(cacheConfig); err == nil {
|
|
|
|
|
|
translator.cache = cacheInstance
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return translator
|
2026-03-28 23:27:02 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Translate 执行翻译
|
|
|
|
|
|
func (t *Translator) Translate(ctx context.Context, text string, options *TranslateOptions) (*TranslateResult, error) {
|
|
|
|
|
|
// 设置超时
|
|
|
|
|
|
timeoutCtx, cancel := context.WithTimeout(ctx, time.Duration(t.config.Timeout)*time.Second)
|
|
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
2026-03-29 18:41:25 +08:00
|
|
|
|
// 基础字符过滤
|
|
|
|
|
|
filteredText := content.FilterBasic(text, nil)
|
|
|
|
|
|
|
|
|
|
|
|
// 内容解析(包含代码检测)
|
|
|
|
|
|
parseResult, parseErr := t.contentParser.Parse(filteredText)
|
|
|
|
|
|
|
2026-03-28 23:27:02 +08:00
|
|
|
|
// 选择Prompt
|
|
|
|
|
|
prompt := ""
|
|
|
|
|
|
if options.PromptName != "" {
|
|
|
|
|
|
prompt = t.prompt.GetPrompt(options.PromptName)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-29 18:41:25 +08:00
|
|
|
|
// 如果包含代码且解析成功,使用增强的Prompt
|
|
|
|
|
|
if parseErr == nil && parseResult.HasCode {
|
|
|
|
|
|
enhancedPrompt := t.contentParser.BuildPrompt(parseResult)
|
|
|
|
|
|
if enhancedPrompt != "" {
|
|
|
|
|
|
if prompt != "" {
|
|
|
|
|
|
prompt = prompt + "\n\n" + enhancedPrompt
|
|
|
|
|
|
} else {
|
|
|
|
|
|
prompt = enhancedPrompt
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-28 23:27:02 +08:00
|
|
|
|
// 构建请求
|
|
|
|
|
|
req := &provider.TranslateRequest{
|
2026-03-29 18:41:25 +08:00
|
|
|
|
Text: filteredText,
|
2026-03-28 23:27:02 +08:00
|
|
|
|
FromLang: options.FromLang,
|
|
|
|
|
|
ToLang: options.ToLang,
|
|
|
|
|
|
Prompt: prompt,
|
|
|
|
|
|
Model: t.selectModel(options.Model),
|
|
|
|
|
|
Options: options.ExtraOptions,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-29 21:10:28 +08:00
|
|
|
|
// 检查缓存
|
|
|
|
|
|
if t.cache != nil {
|
|
|
|
|
|
cacheKey := cache.GenerateCacheKey(filteredText, options.FromLang, options.ToLang)
|
|
|
|
|
|
if cachedEntry, err := t.cache.Get(ctx, cacheKey); err == nil && cachedEntry != nil {
|
|
|
|
|
|
// 缓存命中
|
|
|
|
|
|
return &TranslateResult{
|
|
|
|
|
|
Original: text,
|
|
|
|
|
|
Translated: cachedEntry.TranslatedText,
|
|
|
|
|
|
FromLang: cachedEntry.FromLang,
|
|
|
|
|
|
ToLang: cachedEntry.ToLang,
|
|
|
|
|
|
Model: cachedEntry.Model,
|
|
|
|
|
|
Usage: &provider.Usage{
|
|
|
|
|
|
PromptTokens: cachedEntry.PromptTokens,
|
|
|
|
|
|
CompletionTokens: cachedEntry.CompletionTokens,
|
|
|
|
|
|
TotalTokens: cachedEntry.TotalTokens,
|
|
|
|
|
|
},
|
|
|
|
|
|
}, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-28 23:27:02 +08:00
|
|
|
|
// 调用厂商API
|
|
|
|
|
|
resp, err := t.provider.Translate(timeoutCtx, req)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, fmt.Errorf("翻译失败: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-29 18:41:25 +08:00
|
|
|
|
translatedText := resp.Text
|
|
|
|
|
|
|
|
|
|
|
|
// 如果包含代码且解析成功,重构结果
|
|
|
|
|
|
if parseErr == nil && parseResult.HasCode {
|
|
|
|
|
|
translatedText = t.contentParser.Reconstruct(parseResult, resp.Text)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-29 21:10:28 +08:00
|
|
|
|
// 保存到缓存
|
|
|
|
|
|
if t.cache != nil {
|
|
|
|
|
|
cacheKey := cache.GenerateCacheKey(filteredText, options.FromLang, options.ToLang)
|
|
|
|
|
|
cacheEntry := &cache.CacheEntry{
|
|
|
|
|
|
CacheKey: cacheKey,
|
|
|
|
|
|
OriginalText: filteredText,
|
|
|
|
|
|
TranslatedText: translatedText,
|
|
|
|
|
|
FromLang: resp.FromLang,
|
|
|
|
|
|
ToLang: resp.ToLang,
|
|
|
|
|
|
Model: resp.Model,
|
|
|
|
|
|
PromptName: options.PromptName,
|
|
|
|
|
|
PromptContent: prompt,
|
|
|
|
|
|
PromptTokens: resp.Usage.PromptTokens,
|
|
|
|
|
|
CompletionTokens: resp.Usage.CompletionTokens,
|
|
|
|
|
|
TotalTokens: resp.Usage.TotalTokens,
|
|
|
|
|
|
}
|
|
|
|
|
|
// 异步保存缓存,不阻塞翻译结果返回
|
|
|
|
|
|
go t.cache.Set(context.Background(), cacheEntry)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-28 23:27:02 +08:00
|
|
|
|
// 构建结果
|
|
|
|
|
|
return &TranslateResult{
|
|
|
|
|
|
Original: text,
|
2026-03-29 18:41:25 +08:00
|
|
|
|
Translated: translatedText,
|
2026-03-28 23:27:02 +08:00
|
|
|
|
FromLang: resp.FromLang,
|
|
|
|
|
|
ToLang: resp.ToLang,
|
|
|
|
|
|
Model: resp.Model,
|
|
|
|
|
|
Usage: resp.Usage,
|
|
|
|
|
|
}, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// TranslateWithProvider 使用指定厂商执行翻译
|
|
|
|
|
|
func (t *Translator) TranslateWithProvider(ctx context.Context, text string, providerName string, options *TranslateOptions) (*TranslateResult, error) {
|
|
|
|
|
|
// 创建指定厂商实例
|
|
|
|
|
|
providerConfig, err := t.config.GetProviderConfig(providerName)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, fmt.Errorf("获取厂商配置失败: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 创建厂商实例
|
|
|
|
|
|
providerInstance, err := provider.CreateProvider(providerName, provider.ProviderConfig{
|
|
|
|
|
|
APIHost: providerConfig.APIHost,
|
|
|
|
|
|
APIKey: providerConfig.APIKey,
|
|
|
|
|
|
Model: providerConfig.Model,
|
|
|
|
|
|
})
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, fmt.Errorf("创建厂商实例失败: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 临时切换厂商
|
|
|
|
|
|
originalProvider := t.provider
|
|
|
|
|
|
t.provider = providerInstance
|
|
|
|
|
|
defer func() {
|
|
|
|
|
|
t.provider = originalProvider
|
|
|
|
|
|
}()
|
|
|
|
|
|
|
|
|
|
|
|
// 执行翻译
|
|
|
|
|
|
return t.Translate(ctx, text, options)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// selectModel 选择模型
|
|
|
|
|
|
func (t *Translator) selectModel(model string) string {
|
|
|
|
|
|
if model != "" {
|
|
|
|
|
|
return model
|
|
|
|
|
|
}
|
|
|
|
|
|
return t.config.DefaultModel
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// GetProvider 获取当前厂商
|
|
|
|
|
|
func (t *Translator) GetProvider() provider.Provider {
|
|
|
|
|
|
return t.provider
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// GetConfig 获取配置
|
|
|
|
|
|
func (t *Translator) GetConfig() *config.Config {
|
|
|
|
|
|
return t.config
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// GetPromptManager 获取Prompt管理器
|
|
|
|
|
|
func (t *Translator) GetPromptManager() *PromptManager {
|
|
|
|
|
|
return t.prompt
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// SetTimeout 设置超时时间
|
|
|
|
|
|
func (t *Translator) SetTimeout(seconds int) {
|
|
|
|
|
|
t.config.Timeout = seconds
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// TranslateOptions 翻译选项
|
|
|
|
|
|
type TranslateOptions struct {
|
|
|
|
|
|
FromLang string
|
|
|
|
|
|
ToLang string
|
|
|
|
|
|
PromptName string
|
|
|
|
|
|
Model string
|
|
|
|
|
|
Temperature float64
|
|
|
|
|
|
ExtraOptions map[string]interface{}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// TranslateResult 翻译结果
|
|
|
|
|
|
type TranslateResult struct {
|
|
|
|
|
|
Original string
|
|
|
|
|
|
Translated string
|
|
|
|
|
|
FromLang string
|
|
|
|
|
|
ToLang string
|
|
|
|
|
|
Model string
|
|
|
|
|
|
Usage *provider.Usage
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// String 返回翻译结果的字符串表示
|
|
|
|
|
|
func (r *TranslateResult) String() string {
|
|
|
|
|
|
return r.Translated
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// TranslateResultWithInfo 带详细信息的翻译结果
|
|
|
|
|
|
type TranslateResultWithInfo struct {
|
|
|
|
|
|
Result *TranslateResult
|
|
|
|
|
|
Duration time.Duration
|
|
|
|
|
|
Provider string
|
|
|
|
|
|
Timestamp time.Time
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// BatchTranslate 批量翻译结果
|
|
|
|
|
|
type BatchTranslateRequest struct {
|
|
|
|
|
|
Texts []string
|
|
|
|
|
|
Options *TranslateOptions
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// BatchTranslateResult 批量翻译结果
|
|
|
|
|
|
type BatchTranslateResult struct {
|
|
|
|
|
|
Results []*TranslateResult
|
|
|
|
|
|
Errors []error
|
|
|
|
|
|
Summary BatchTranslateSummary
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// BatchTranslateSummary 批量翻译摘要
|
|
|
|
|
|
type BatchTranslateSummary struct {
|
|
|
|
|
|
Total int
|
|
|
|
|
|
Success int
|
|
|
|
|
|
Failed int
|
|
|
|
|
|
Duration time.Duration
|
|
|
|
|
|
AvgTokens int
|
|
|
|
|
|
}
|