Files
yoyo/AGENTS.md
z.to 24ba405d55 feat: add language support and onboard configuration wizard (v0.2.0)
- Add language code intelligent parsing module (internal/lang)
- Support --lang parameter for target language specification
- Support multiple language code formats (BCP47, aliases, Chinese names)
- Implement interactive onboard configuration wizard
- Update Config struct with language fields
- Add survey library dependency for interactive UI
- Improve CLI command interface
- Add comprehensive unit tests for language module
- Update documentation (AGENTS.md, changelog.md, taolun.md, memory.md)

Supported language codes:
- Standard: zh-CN, zh-TW, en-US, en-GB, ja, ko, es, fr, de
- Aliases: cn, en, jp, kr, es, fr, de
- Chinese names: chinese, english, japanese

Commands:
- yoyo "Hello world" - basic translation
- yoyo --lang=cn "Hello world" - specify target language
- yoyo onboard - start configuration wizard
- yoyo onboard --force - force reconfiguration

Version: 0.2.0
2026-03-29 01:30:42 +08:00

824 lines
22 KiB
Markdown
Raw Permalink 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.
# AGENTS.md - YOYO翻译工具开发指南
## 项目概述
YOYO是一个命令行翻译工具使用Go语言编写采用面向对象设计模式。它通过调用在线大模型API结合不同的Prompt配置实现多样化的翻译特色。
## OOP设计模式
### 核心类设计
项目采用以下面向对象设计,结合工厂模式和策略模式:
```
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Config │ │ Provider │ │ Translator │
│ (全局配置) │──────│ (厂商接口) │──────│ (核心翻译) │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│ │ │
│ │ │
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ ConfigLoader │ │ ProviderFactory │ │ TranslationTask │
│ (配置加载) │ │ (工厂模式) │ │ (任务管理) │
└─────────────────┘ └─────────────────┘ └─────────────────┘
```
### 1. 全局配置类 (Config)
负责读取YAML配置文件提供默认值。
```go
// internal/config/config.go
package config
import (
"os"
"path/filepath"
"gopkg.in/yaml.v3"
)
// Config 全局配置结构
type Config struct {
// 全局设置
DefaultProvider string `yaml:"default_provider"`
DefaultModel string `yaml:"default_model"`
Timeout int `yaml:"timeout"` // 秒
// 厂商配置
Providers map[string]ProviderConfig `yaml:"providers"`
// Prompt配置
Prompts map[string]string `yaml:"prompts"`
}
// ProviderConfig 厂商配置
type ProviderConfig struct {
APIHost string `yaml:"api_host"`
APIKey string `yaml:"api_key"`
Model string `yaml:"model"`
Enabled bool `yaml:"enabled"`
}
// ConfigLoader 配置加载器接口
type ConfigLoader interface {
Load(path string) (*Config, error)
Save(config *Config, path string) error
}
// YAMLConfigLoader YAML配置加载器实现
type YAMLConfigLoader struct{}
// Load 加载YAML配置文件
func (l *YAMLConfigLoader) Load(path string) (*Config, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("读取配置文件失败: %w", err)
}
config := &Config{}
if err := yaml.Unmarshal(data, config); err != nil {
return nil, fmt.Errorf("解析配置文件失败: %w", err)
}
// 设置默认值
config.setDefaults()
return config, nil
}
// setDefaults 设置默认值
func (c *Config) setDefaults() {
if c.DefaultProvider == "" {
c.DefaultProvider = "siliconflow"
}
if c.Timeout <= 0 {
c.Timeout = 30
}
// 为每个厂商设置默认值
for name, provider := range c.Providers {
if provider.Model == "" {
provider.Model = "gpt-3.5-turbo"
c.Providers[name] = provider
}
}
}
```
### 2. 大模型厂商接口 (Provider)
定义统一的厂商接口,采用策略模式。
```go
// internal/provider/provider.go
package provider
import (
"context"
)
// Provider 厂商接口
type Provider interface {
// Translate 调用厂商API进行翻译
Translate(ctx context.Context, req *TranslateRequest) (*TranslateResponse, error)
// Name 返回厂商名称
Name() string
// Validate 验证配置是否有效
Validate() error
}
// TranslateRequest 翻译请求
type TranslateRequest struct {
Text string `json:"text"`
FromLang string `json:"from_lang"`
ToLang string `json:"to_lang"`
Prompt string `json:"prompt"`
Model string `json:"model"`
Options map[string]interface{} `json:"options"`
}
// TranslateResponse 翻译响应
type TranslateResponse struct {
Text string `json:"text"`
FromLang string `json:"from_lang"`
ToLang string `json:"to_lang"`
Model string `json:"model"`
Usage *Usage `json:"usage"`
RawResponse []byte `json:"raw_response,omitempty"`
}
// Usage 用量统计
type Usage struct {
PromptTokens int `json:"prompt_tokens"`
CompletionTokens int `json:"completion_tokens"`
TotalTokens int `json:"total_tokens"`
}
```
### 3. 具体厂商实现示例
```go
// internal/provider/siliconflow.go
package provider
import (
"context"
"encoding/json"
"net/http"
)
// SiliconFlowProvider 硅基流动厂商实现
type SiliconFlowProvider struct {
config ProviderConfig
client *http.Client
}
// ProviderConfig 厂商配置从config包导入
type ProviderConfig struct {
APIHost string
APIKey string
Model string
}
// NewSiliconFlowProvider 创建硅基流动厂商实例
func NewSiliconFlowProvider(config ProviderConfig) *SiliconFlowProvider {
return &SiliconFlowProvider{
config: config,
client: &http.Client{},
}
}
// Name 返回厂商名称
func (p *SiliconFlowProvider) Name() string {
return "siliconflow"
}
// Validate 验证配置
func (p *SiliconFlowProvider) Validate() error {
if p.config.APIKey == "" {
return fmt.Errorf("siliconflow: API key 不能为空")
}
if p.config.APIHost == "" {
p.config.APIHost = "https://api.siliconflow.cn/v1"
}
return nil
}
// Translate 调用硅基流动API
func (p *SiliconFlowProvider) Translate(ctx context.Context, req *TranslateRequest) (*TranslateResponse, error) {
// 实现具体的API调用逻辑
// 1. 构建请求体
// 2. 发送HTTP请求
// 3. 解析响应
// 示例代码(简化)
url := p.config.APIHost + "/chat/completions"
requestBody := map[string]interface{}{
"model": p.config.Model,
"messages": []map[string]string{
{"role": "user", "content": req.Text},
},
}
// 实际实现需要完整的HTTP客户端代码
return &TranslateResponse{
Text: "翻译结果",
FromLang: req.FromLang,
ToLang: req.ToLang,
Model: p.config.Model,
}, nil
}
```
### 4. 工厂模式 - ProviderFactory
```go
// internal/provider/factory.go
package provider
import (
"fmt"
)
// ProviderFactory 厂商工厂
type ProviderFactory struct {
providers map[string]func(ProviderConfig) Provider
}
// NewProviderFactory 创建工厂实例
func NewProviderFactory() *ProviderFactory {
factory := &ProviderFactory{
providers: make(map[string]func(ProviderConfig) Provider),
}
// 注册所有厂商
factory.Register("siliconflow", NewSiliconFlowProvider)
factory.Register("volcano", NewVolcanoEngineProvider)
factory.Register("national", NewNationalSupercomputingProvider)
factory.Register("qwen", NewQwenProvider)
factory.Register("openai", NewOpenAICompatibleProvider)
return factory
}
// Register 注册厂商构造函数
func (f *ProviderFactory) Register(name string, creator func(ProviderConfig) Provider) {
f.providers[name] = creator
}
// Create 创建厂商实例
func (f *ProviderFactory) Create(name string, config ProviderConfig) (Provider, error) {
creator, exists := f.providers[name]
if !exists {
return nil, fmt.Errorf("不支持的厂商: %s", name)
}
provider := creator(config)
if err := provider.Validate(); err != nil {
return nil, fmt.Errorf("厂商配置验证失败: %w", err)
}
return provider, nil
}
```
### 5. 核心翻译类 (Translator)
```go
// internal/translator/translator.go
package translator
import (
"context"
"time"
)
// Translator 核心翻译类
type Translator struct {
config *config.Config
provider provider.Provider
prompt *PromptManager
}
// NewTranslator 创建翻译器实例
func NewTranslator(config *config.Config, provider provider.Provider) *Translator {
return &Translator{
config: config,
provider: provider,
prompt: NewPromptManager(config.Prompts),
}
}
// 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()
// 构建请求
req := &provider.TranslateRequest{
Text: text,
FromLang: options.FromLang,
ToLang: options.ToLang,
Prompt: t.prompt.GetPrompt(options.PromptName),
Model: t.selectModel(options.Model),
}
// 调用厂商API
resp, err := t.provider.Translate(timeoutCtx, req)
if err != nil {
return nil, fmt.Errorf("翻译失败: %w", err)
}
// 构建结果
return &TranslateResult{
Original: text,
Translated: resp.Text,
FromLang: resp.FromLang,
ToLang: resp.ToLang,
Model: resp.Model,
Usage: resp.Usage,
}, nil
}
// selectModel 选择模型
func (t *Translator) selectModel(model string) string {
if model != "" {
return model
}
return t.config.DefaultModel
}
// TranslateOptions 翻译选项
type TranslateOptions struct {
FromLang string
ToLang string
PromptName string
Model string
Temperature float64
}
// TranslateResult 翻译结果
type TranslateResult struct {
Original string
Translated string
FromLang string
ToLang string
Model string
Usage *provider.Usage
}
```
### 6. 设计模式应用
#### 工厂模式
- `ProviderFactory` 用于创建不同厂商实例
- 统一创建接口,隐藏具体实现细节
#### 策略模式
- `Provider` 接口定义统一行为
- 每个厂商实现不同的API调用策略
- 运行时动态选择策略
#### 单例模式
- 全局配置通常只需要一个实例
- 可通过包级变量或sync.Once实现
#### 依赖注入
- `Translator` 依赖 `Provider` 接口,而不是具体实现
- 通过构造函数注入依赖
## 项目结构
```
yoyo/
├── cmd/
│ └── yoyo/ # CLI入口
├── internal/
│ ├── config/ # 全局配置
│ │ ├── config.go
│ │ └── loader.go
│ ├── provider/ # 厂商实现
│ │ ├── provider.go # 接口定义
│ │ ├── factory.go # 工厂模式
│ │ ├── siliconflow.go
│ │ ├── volcano.go
│ │ ├── national.go
│ │ ├── qwen.go
│ │ └── openai.go
│ ├── translator/ # 核心翻译
│ │ ├── translator.go
│ │ └── prompt.go
│ └── prompt/ # Prompt管理
├── pkg/ # 公共工具
├── configs/ # 配置文件目录
│ └── config.yaml
├── go.mod
└── go.sum
```
## 配置文件示例 (YAML)
```yaml
# config.yaml
default_provider: "siliconflow"
default_model: "gpt-3.5-turbo"
timeout: 30
providers:
siliconflow:
api_host: "https://api.siliconflow.cn/v1"
api_key: "${SILICONFLOW_API_KEY}"
model: "siliconflow-base"
enabled: true
volcano:
api_host: "https://api.volcengine.com/v1"
api_key: "${VOLCANO_API_KEY}"
model: "volcano-chat"
enabled: true
national:
api_host: "https://api.nsc.gov.cn/v1"
api_key: "${NATIONAL_API_KEY}"
model: "nsc-base"
enabled: false
qwen:
api_host: "https://dashscope.aliyuncs.com/compatible-mode/v1"
api_key: "${QWEN_API_KEY}"
model: "qwen-turbo"
enabled: true
openai:
api_host: "https://api.openai.com/v1"
api_key: "${OPENAI_API_KEY}"
model: "gpt-3.5-turbo"
enabled: true
prompts:
technical: "你是一位专业的技术翻译,请准确翻译以下技术文档,保持专业术语的准确性。"
creative: "你是一位富有创造力的翻译家,请用优美流畅的语言翻译以下内容。"
academic: "你是一位学术翻译专家,请用严谨的学术语言翻译以下内容。"
simple: "请用简单易懂的语言翻译以下内容。"
```
## 开发顺序建议
1. 实现 `Config``ConfigLoader`
2. 实现 `Provider` 接口和工厂
3. 实现至少一个厂商如SiliconFlow
4. 实现 `Translator` 核心类
5. 集成测试
6. 实现其他厂商
## 测试策略
```go
// 内部测试示例
func TestTranslator_Translate(t *testing.T) {
// Mock provider
mockProvider := &MockProvider{
TranslateFunc: func(ctx context.Context, req *provider.TranslateRequest) (*provider.TranslateResponse, error) {
return &provider.TranslateResponse{Text: "翻译结果"}, nil
},
}
translator := NewTranslator(testConfig, mockProvider)
result, err := translator.Translate(context.Background(), "Hello", &TranslateOptions{
ToLang: "zh",
})
if err != nil {
t.Fatalf("翻译失败: %v", err)
}
if result.Translated != "翻译结果" {
t.Errorf("期望 '翻译结果', 得到 '%s'", result.Translated)
}
}
```
## 错误处理最佳实践
- 每个厂商实现应定义具体的错误类型
- 使用 `%w` 包装错误以保留原始错误信息
- 提供清晰的错误消息帮助调试
## 安全注意事项
- API密钥使用环境变量
- 配置文件中不存储真实密钥
- 使用 `os.Getenv` 读取敏感信息
- 定期轮换API密钥
## 开发规范
### 文档管理
#### 文档文件列表
1. **why.md** - 项目初衷文档(仅用户编辑)
2. **taolun.md** - 讨论记录(时间轴格式)
3. **changelog.md** - 版本记录(包含讨论链接)
4. **memory.md** - 知识纠正(踩坑记录)
#### 文档协作规范
| 文档 | 编辑者 | 内容 | 更新频率 |
|------|--------|------|----------|
| why.md | 用户 | 项目初衷、愿景、目标 | 随时 |
| taolun.md | AI+用户 | 讨论记录、决策过程 | 每次重要讨论后 |
| changelog.md | AI | 版本变更、任务状态 | 每次版本更新 |
| memory.md | AI | 经验总结、术语定义 | 遇到问题后 |
#### 特殊文档说明
**why.md**:
- 项目初衷文档,只能由用户编辑
- AI不应修改此文件内容
- 用于记录创始人的个人想法和项目愿景
- 位置:项目根目录
**AI责任边界**:
- ✅ 可以编辑taolun.md、changelog.md、memory.md
- ❌ 不应编辑why.md、用户个人配置文件
- ✅ 可以建议why.md的内容结构但不强制
### 文档链接
- 项目初衷: [why.md](why.md)
- 讨论记录: [taolun.md](taolun.md)
- 版本记录: [changelog.md](changelog.md)
- 知识纠正: [memory.md](memory.md)
### 版本号管理
- 格式:主版本.次版本.修订版本(00-99)
- 更新时机测试完成后git操作前
- 递增规则:小修复第三位+1新功能第二位+1重大变更第一位+1
### 分支策略
- main: 稳定上线版
- dev: 开发分支
- 功能开发从dev创建功能分支
### 提交规范
- 提交前更新相关文档
- 使用清晰的提交信息
- 版本更新时打标签
## 开发命令
### 构建
```bash
# 构建二进制文件
go build -o yoyo ./cmd/yoyo
# 交叉编译Linux
GOOS=linux GOARCH=amd64 go build -o yoyo-linux ./cmd/yoyo
# 交叉编译macOS
GOOS=darwin GOARCH=amd64 go build -o yoyo-mac ./cmd/yoyo
```
### 测试
```bash
# 运行所有测试
go test ./...
# 运行特定包的测试
go test ./internal/translator
# 运行单个测试函数
go test -run TestTranslate ./internal/translator
# 运行测试并显示详细输出
go test -v ./...
# 运行基准测试
go test -bench=. ./...
```
### 代码检查与格式化
```bash
# 代码格式化(必须)
gofmt -w .
# 代码检查
go vet ./...
# 使用golangci-lint推荐
golangci-lint run
# 生成依赖图
go mod graph
```
### 运行工具
```bash
# 直接运行
go run ./cmd/yoyo "This is translation content..."
# 使用构建的二进制
./yoyo "This is translation content..."
# 指定翻译模式
./yoyo --mode=technical "API documentation text"
```
## 代码风格指南
### 命名约定
- **包名**:小写单词,简洁明了(如`translator``config`
- **函数/方法名**:驼峰命名,动词开头(如`TranslateText``LoadConfig`
- **变量名**:驼峰命名,简洁(如`inputText``apiResponse`
- **常量**:全大写加下划线(如`MAX_RETRY_COUNT`
- **接口名**:以`-er`结尾或描述性名称(如`Translator``ConfigLoader`
### 导入顺序
```go
import (
// 标准库
"context"
"fmt"
// 第三方包
"github.com/spf13/cobra"
// 项目内部包
"github.com/username/yoyo/internal/api"
"github.com/username/yoyo/internal/config"
)
```
### 错误处理
```go
// 包装错误以提供更多上下文
if err := loadConfig(); err != nil {
return fmt.Errorf("failed to load config: %w", err)
}
// 定义哨兵错误
var (
ErrInvalidAPIKey = errors.New("invalid API key")
ErrNetworkFailed = errors.New("network request failed")
)
// 错误检查使用errors.Is
if errors.Is(err, ErrInvalidAPIKey) {
// 处理特定错误
}
```
### 类型使用
- 优先使用具体类型,必要时使用接口
- 避免使用`interface{}`,使用泛型或具体类型
- 结构体字段使用大写开头(导出)或小写开头(私有)
- 为复杂类型添加文档注释
### 并发处理
```go
// 使用context传递取消信号
func Translate(ctx context.Context, text string) (string, error) {
select {
case <-ctx.Done():
return "", ctx.Err()
default:
// 继续翻译
}
}
// 使用goroutine和channel时注意资源清理
go func() {
defer close(doneCh)
// 执行任务
}()
```
### 注释规范
- 包注释:描述包的功能和用途
- 函数注释:描述函数功能、参数、返回值
- 导出类型/函数必须有注释
- 使用Godoc格式
## 测试指南
- 每个公共函数都应有对应的测试
- 测试函数命名:`TestFunctionName`
- 使用表格驱动测试
- Mock外部依赖
- 测试文件放在同一目录,以`_test.go`结尾
```go
// 示例测试
func TestTranslator_Translate(t *testing.T) {
tests := []struct {
name string
input string
expected string
}{
{"simple text", "hello", "你好"},
{"empty string", "", ""},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := translator.Translate(tt.input)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if result != tt.expected {
t.Errorf("got %v, want %v", result, tt.expected)
}
})
}
}
```
## 依赖管理
- 使用Go Modules管理依赖
- 保持`go.mod``go.sum`整洁
- 定期更新依赖:`go get -u ./...`
- 移除未使用的依赖:`go mod tidy`
## 提交前检查清单
1. ✅ 运行`gofmt -w .`格式化代码
2. ✅ 运行`go vet ./...`检查代码
3. ✅ 运行`go test ./...`确保测试通过
4. ✅ 确保所有导出函数有注释
5. ✅ 检查错误处理是否完善
6. ✅ 验证API密钥等敏感信息已忽略
## 敏感信息处理
- API密钥使用环境变量或配置文件
-`.env`添加到`.gitignore`
- 不要在日志中打印敏感信息
- 使用`os.Getenv`读取环境变量
## 构建和发布
```bash
# 发布版本
git tag v1.0.0
git push origin v1.0.0
# 使用GoReleaser推荐
goreleaser release --clean
```
## 常见问题
### Q: 如何添加新的翻译模式?
A: 在`internal/prompt/`目录下创建新的Prompt配置然后在`internal/translator/`中注册。
### Q: 如何处理API限流
A: 使用指数退避重试,并在`internal/api/`中实现限流器。
### Q: 如何支持更多语言?
A: 在配置文件中添加语言映射,并更新翻译逻辑。
## 语言代码处理
### 支持的语言代码格式
项目支持多种语言代码格式,通过 `internal/lang` 模块处理:
1. **标准BCP47格式**: `zh-CN`, `zh-TW`, `en-US`, `en-GB`, `ja`, `ko`
2. **简短别名**: `cn`(中文), `en`(英文), `jp`(日文), `kr`(韩文) 等
3. **中文名称**: `chinese`(中文), `english`(英文), `japanese`(日文) 等
### 语言解析函数
```go
// 解析语言代码
lang.ParseLanguageCode("cn") // 返回 "zh-CN"
lang.ParseLanguageCode("en") // 返回 "en-US"
lang.ParseLanguageCode("zh-TW") // 返回 "zh-TW"
// 获取语言名称(用于显示)
lang.GetLanguageName("zh-CN") // 返回 "中文(简体)"
lang.GetLanguageName("en-US") // 返回 "English (US)"
```
## Onboard配置向导
### 配置流程
1. 选择主要翻译厂商
2. 配置厂商API密钥、HOST、模型
3. 设置全局配置(默认语言、超时)
4. 保存配置到 `configs/config.yaml`
### 使用方法
```bash
yoyo onboard # 启动配置向导
yoyo onboard --force # 强制重新配置
```
### 配置向导实现
- 使用 `github.com/AlecAivazis/survey/v2` 实现交互式界面
- 支持厂商选择、API配置、语言设置
- 生成标准YAML配置文件
## 分阶段迁移策略
### 第一阶段:开发阶段(当前)
- API密钥存储在 `.env` 文件
- 复杂配置存储在 `configs/config.yaml`
- 支持环境变量替换
### 第二阶段:上线前
- 实现配置文件路径查找机制
- 支持用户配置目录 `~/.config/yoo/yoo.yml`
- 提供配置迁移工具
### 第三阶段:最终优化
- 移除对 `.env` 文件依赖
- 完全使用配置文件
## 参考资源
- [Effective Go](https://go.dev/doc/effective_go)
- [Go Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments)
- [Go Style Guide](https://google.github.io/styleguide/go/)
- [Survey库文档](https://github.com/AlecAivazis/survey)