Files
yoyo/internal/translator/translator.go
titor b04092fd68 feat: 发布 v1.0.0-beta 版本
- 添加 ASCII 艺术 Logo 带渐变效果
- 改造输入框使用 ::: 紫色分隔符
- 改造信息栏合并显示语言/模型/记录数
- 添加 Spinner 翻译状态动画
- 优化翻译卡片样式
- 版本号三方同步规则
2026-04-07 07:12:00 +08:00

267 lines
6.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package translator
import (
"context"
"fmt"
"time"
"github.com/titor/fanyi/internal/cache"
"github.com/titor/fanyi/internal/config"
"github.com/titor/fanyi/internal/content"
"github.com/titor/fanyi/internal/provider"
)
// Translator 核心翻译类
type Translator struct {
config *config.Config
provider provider.Provider
prompt *PromptManager
contentParser *content.Parser
cache cache.Cache
}
func (t *Translator) GetCache() cache.Cache {
return t.cache
}
// NewTranslator 创建翻译器实例
func NewTranslator(config *config.Config, provider provider.Provider) *Translator {
translator := &Translator{
config: config,
provider: provider,
prompt: NewPromptManager(config.Prompts),
contentParser: content.NewParser(config.SkipKeywords),
}
// 初始化缓存(如果启用)
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
}
// 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()
// 基础字符过滤
filteredText := content.FilterBasic(text, nil)
// 内容解析(包含代码检测)
parseResult, parseErr := t.contentParser.Parse(filteredText)
// 选择Prompt
prompt := ""
if options.PromptName != "" {
prompt = t.prompt.GetPrompt(options.PromptName)
}
// 如果包含代码且解析成功使用增强的Prompt
if parseErr == nil && parseResult.HasCode {
enhancedPrompt := t.contentParser.BuildPrompt(parseResult)
if enhancedPrompt != "" {
if prompt != "" {
prompt = prompt + "\n\n" + enhancedPrompt
} else {
prompt = enhancedPrompt
}
}
}
// 构建请求
req := &provider.TranslateRequest{
Text: filteredText,
FromLang: options.FromLang,
ToLang: options.ToLang,
Prompt: prompt,
Model: t.selectModel(options.Model),
Options: options.ExtraOptions,
}
// 检查缓存
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
}
}
// 调用厂商API
resp, err := t.provider.Translate(timeoutCtx, req)
if err != nil {
return nil, fmt.Errorf("翻译失败: %w", err)
}
translatedText := resp.Text
// 如果包含代码且解析成功,重构结果
if parseErr == nil && parseResult.HasCode {
translatedText = t.contentParser.Reconstruct(parseResult, resp.Text)
}
// 保存到缓存
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)
}
// 构建结果
return &TranslateResult{
Original: text,
Translated: translatedText,
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
}