llm_translate 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.rspec_status +14 -0
- data/README.md +301 -0
- data/README.zh.md +209 -0
- data/Rakefile +12 -0
- data/content/changelog-1.md +12 -0
- data/content/changelog-2.md +12 -0
- data/content/llm_translate.yml +189 -0
- data/content/prompt.md +8 -0
- data/content/todo.md +115 -0
- data/exe/llm_translate +6 -0
- data/lib/llm_translate/ai_client.rb +95 -0
- data/lib/llm_translate/cli.rb +205 -0
- data/lib/llm_translate/config.rb +279 -0
- data/lib/llm_translate/file_finder.rb +153 -0
- data/lib/llm_translate/logger.rb +170 -0
- data/lib/llm_translate/translator_engine.rb +233 -0
- data/lib/llm_translate/version.rb +5 -0
- data/lib/llm_translate.rb +16 -0
- data/llm_translate.gemspec +41 -0
- data/llm_translate.yml +189 -0
- data/test_config.yml +52 -0
- data/test_docs/sample.md +22 -0
- data/test_docs_translated/sample.zh.md +22 -0
- data/test_llm_translate.yml +180 -0
- data/test_new_config.yml +189 -0
- metadata +143 -0
@@ -0,0 +1,189 @@
|
|
1
|
+
# llm_translate.yml - 翻译工具配置文件
|
2
|
+
|
3
|
+
# AI 模型配置
|
4
|
+
ai:
|
5
|
+
# API 密钥
|
6
|
+
api_key: xxxx
|
7
|
+
|
8
|
+
# API 主机地址
|
9
|
+
host: https://aihubmix.com
|
10
|
+
|
11
|
+
# 模型提供商
|
12
|
+
provider: "claude"
|
13
|
+
|
14
|
+
# 模型名称
|
15
|
+
model: "claude-3-7-sonnet-20250219"
|
16
|
+
|
17
|
+
# 模型参数
|
18
|
+
temperature: 0.3
|
19
|
+
max_tokens: 4000
|
20
|
+
top_p: 1.0
|
21
|
+
|
22
|
+
# 请求重试配置
|
23
|
+
retry_attempts: 3
|
24
|
+
retry_delay: 2 # 秒
|
25
|
+
|
26
|
+
# 请求超时时间
|
27
|
+
timeout: 60 # 秒
|
28
|
+
|
29
|
+
# 翻译配置
|
30
|
+
translation:
|
31
|
+
# 默认翻译 prompt
|
32
|
+
default_prompt: |
|
33
|
+
请将以下 Markdown 内容翻译为中文,保持所有格式不变:
|
34
|
+
- 保留代码块、链接、图片等 Markdown 语法
|
35
|
+
- 保留英文的专业术语和产品名称
|
36
|
+
- 确保翻译自然流畅
|
37
|
+
|
38
|
+
内容:
|
39
|
+
{content}
|
40
|
+
|
41
|
+
# 目标语言
|
42
|
+
target_language: "zh-CN"
|
43
|
+
|
44
|
+
# 源语言(auto 为自动检测)
|
45
|
+
source_language: "auto"
|
46
|
+
|
47
|
+
# 是否保留原文格式
|
48
|
+
preserve_formatting: true
|
49
|
+
|
50
|
+
# 是否翻译代码注释
|
51
|
+
translate_code_comments: false
|
52
|
+
|
53
|
+
# 需要保留不翻译的内容模式
|
54
|
+
preserve_patterns:
|
55
|
+
- "```[\\s\\S]*?```" # 代码块
|
56
|
+
- "`[^`]+`" # 行内代码
|
57
|
+
- "\\[.*?\\]\\(.*?\\)" # 链接
|
58
|
+
- "!\\[.*?\\]\\(.*?\\)" # 图片
|
59
|
+
|
60
|
+
# 文件处理配置
|
61
|
+
files:
|
62
|
+
# 输入目录
|
63
|
+
input_directory: "./docs"
|
64
|
+
|
65
|
+
# 输出目录
|
66
|
+
output_directory: "./docs-translated"
|
67
|
+
|
68
|
+
# 输入文件
|
69
|
+
input_file: "./README.md"
|
70
|
+
|
71
|
+
# 输出文件
|
72
|
+
output_file: "./README.zh.md"
|
73
|
+
|
74
|
+
# 文件名后缀策略
|
75
|
+
filename_strategy: "suffix" # suffix, replace, directory
|
76
|
+
filename_suffix: ".zh" # 仅当 strategy 为 suffix 时使用
|
77
|
+
|
78
|
+
# 包含的文件模式
|
79
|
+
include_patterns:
|
80
|
+
- "**/*.md"
|
81
|
+
- "**/*.markdown"
|
82
|
+
|
83
|
+
# 排除的文件模式
|
84
|
+
exclude_patterns:
|
85
|
+
- "**/node_modules/**"
|
86
|
+
- "**/.*"
|
87
|
+
- "**/*.tmp"
|
88
|
+
- "**/README.md" # 示例:排除 README 文件
|
89
|
+
|
90
|
+
# 是否保持目录结构
|
91
|
+
preserve_directory_structure: true
|
92
|
+
|
93
|
+
# 文件覆盖策略
|
94
|
+
overwrite_policy: "ask" # ask, overwrite, skip, backup
|
95
|
+
|
96
|
+
# 备份目录(当 overwrite_policy 为 backup 时)
|
97
|
+
backup_directory: "./backups"
|
98
|
+
|
99
|
+
# 日志配置
|
100
|
+
logging:
|
101
|
+
# 日志级别
|
102
|
+
level: "info" # debug, info, warn, error
|
103
|
+
|
104
|
+
# 日志输出位置
|
105
|
+
output: "console" # console, file, both
|
106
|
+
|
107
|
+
# 日志文件路径(当 output 包含 file 时)
|
108
|
+
file_path: "./logs/llm_translate.log"
|
109
|
+
|
110
|
+
# 是否记录详细的翻译过程
|
111
|
+
verbose_translation: false
|
112
|
+
|
113
|
+
# 错误日志文件
|
114
|
+
error_log_path: "./logs/errors.log"
|
115
|
+
|
116
|
+
# 错误处理配置
|
117
|
+
error_handling:
|
118
|
+
# 遇到错误时的行为
|
119
|
+
on_error: "log_and_continue" # stop, log_and_continue, skip_file
|
120
|
+
|
121
|
+
# 最大连续错误数(超过则停止)
|
122
|
+
max_consecutive_errors: 5
|
123
|
+
|
124
|
+
# 错误重试次数
|
125
|
+
retry_on_failure: 2
|
126
|
+
|
127
|
+
# 生成错误报告
|
128
|
+
generate_error_report: true
|
129
|
+
error_report_path: "./logs/error_report.md"
|
130
|
+
|
131
|
+
# 性能配置
|
132
|
+
performance:
|
133
|
+
# 并发处理文件数
|
134
|
+
concurrent_files: 3
|
135
|
+
|
136
|
+
# 批处理大小(同时翻译的文件数)
|
137
|
+
batch_size: 5
|
138
|
+
|
139
|
+
# 请求间隔(避免 API 限流)
|
140
|
+
request_interval: 1 # 秒
|
141
|
+
|
142
|
+
# 内存使用限制
|
143
|
+
max_memory_mb: 500
|
144
|
+
|
145
|
+
# 输出配置
|
146
|
+
output:
|
147
|
+
# 是否显示进度条
|
148
|
+
show_progress: true
|
149
|
+
|
150
|
+
# 是否显示翻译统计
|
151
|
+
show_statistics: true
|
152
|
+
|
153
|
+
# 是否生成翻译报告
|
154
|
+
generate_report: true
|
155
|
+
report_path: "./reports/translation_report.md"
|
156
|
+
|
157
|
+
# 输出格式
|
158
|
+
format: "markdown" # markdown, json, yaml
|
159
|
+
|
160
|
+
# 是否保留元数据
|
161
|
+
include_metadata: true
|
162
|
+
|
163
|
+
# 预设配置(可通过 --preset 参数使用)
|
164
|
+
presets:
|
165
|
+
chinese:
|
166
|
+
translation:
|
167
|
+
target_language: "zh-CN"
|
168
|
+
default_prompt: "翻译为简体中文,保持技术术语的准确性"
|
169
|
+
|
170
|
+
japanese:
|
171
|
+
translation:
|
172
|
+
target_language: "ja"
|
173
|
+
default_prompt: "日本語に翻訳してください。技術用語は正確に保ってください"
|
174
|
+
|
175
|
+
english:
|
176
|
+
translation:
|
177
|
+
target_language: "en"
|
178
|
+
default_prompt: "Translate to English, maintaining technical accuracy"
|
179
|
+
|
180
|
+
# 自定义 Hook(高级功能)
|
181
|
+
hooks:
|
182
|
+
# 翻译前处理
|
183
|
+
pre_translation: null
|
184
|
+
|
185
|
+
# 翻译后处理
|
186
|
+
post_translation: null
|
187
|
+
|
188
|
+
# 文件处理完成后
|
189
|
+
post_file_processing: null
|
data/content/prompt.md
ADDED
data/content/todo.md
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
# Translator Ruby Gem 开发计划
|
2
|
+
|
3
|
+
## 项目概述
|
4
|
+
实现一个 Ruby Gem,用于将 Markdown 文件通过 AI 进行翻译并生成新文件。
|
5
|
+
|
6
|
+
## 详细任务列表
|
7
|
+
|
8
|
+
### 1. 项目基础设施
|
9
|
+
|
10
|
+
#### 1.1 设置 Ruby Gem 项目结构
|
11
|
+
- [ ] 创建 gemspec 文件
|
12
|
+
- [ ] 创建 Gemfile 和依赖管理
|
13
|
+
- [ ] 设置 lib 目录结构
|
14
|
+
- [ ] 创建 bin 目录和可执行文件
|
15
|
+
- [ ] 配置 .gitignore 和基础文件
|
16
|
+
|
17
|
+
#### 1.2 实现命令行界面
|
18
|
+
- [ ] 使用 OptionParser 或 Thor 创建 CLI
|
19
|
+
- [ ] 支持指定输入目录参数
|
20
|
+
- [ ] 支持指定输出目录参数
|
21
|
+
- [ ] 支持自定义翻译 prompt 参数
|
22
|
+
- [ ] 添加帮助信息和版本显示
|
23
|
+
|
24
|
+
### 2. 核心功能开发
|
25
|
+
|
26
|
+
#### 2.1 实现 Markdown 文件发现功能
|
27
|
+
- [ ] 递归扫描指定目录中的所有 .md 文件
|
28
|
+
- [ ] 支持文件过滤和排除规则
|
29
|
+
- [ ] 维护原始目录结构信息
|
30
|
+
|
31
|
+
#### 2.2 集成 rubyllm gem
|
32
|
+
- [ ] 添加 rubyllm 到依赖列表 https://rubyllm.com/
|
33
|
+
- [ ] 配置 AI 模型连接参数
|
34
|
+
- [ ] 实现 API 调用封装
|
35
|
+
- [ ] 处理 API 限流和重试机制
|
36
|
+
|
37
|
+
#### 2.3 实现翻译核心逻辑
|
38
|
+
- [ ] 读取 Markdown 文件内容
|
39
|
+
- [ ] 解析 Markdown 格式结构
|
40
|
+
- [ ] 调用 AI 进行翻译
|
41
|
+
- [ ] 保持 Markdown 格式完整性(代码块、链接、图片等)
|
42
|
+
- [ ] 处理多语言内容混合情况
|
43
|
+
|
44
|
+
#### 2.4 实现可自定义翻译 prompt 功能
|
45
|
+
- [ ] 支持从配置文件读取 prompt
|
46
|
+
- [ ] 提供默认翻译 prompt 模板
|
47
|
+
- [ ] 支持 prompt 变量替换(如目标语言)
|
48
|
+
|
49
|
+
#### 2.5 实现输出文件管理
|
50
|
+
- [ ] 在指定目录生成翻译后的文件
|
51
|
+
- [ ] 保持原始目录结构
|
52
|
+
- [ ] 支持文件名后缀配置(如 .zh.md)
|
53
|
+
- [ ] 处理文件覆盖和备份策略
|
54
|
+
|
55
|
+
### 3. 质量保证
|
56
|
+
|
57
|
+
#### 3.1 添加错误处理和日志
|
58
|
+
- [ ] 文件读写错误处理
|
59
|
+
- [ ] AI 请求失败处理
|
60
|
+
- [ ] 网络连接异常处理
|
61
|
+
- [ ] 添加详细的日志记录
|
62
|
+
- [ ] 实现优雅的错误恢复机制
|
63
|
+
|
64
|
+
#### 3.2 实现配置系统
|
65
|
+
- [ ] 支持配置文件(YAML)
|
66
|
+
- [ ] API key 环境变量
|
67
|
+
- [ ] 模型参数配置(温度、最大长度等)
|
68
|
+
- [ ] 翻译选项配置
|
69
|
+
- [ ] 环境变量支持
|
70
|
+
|
71
|
+
#### 3.3 编写测试用例
|
72
|
+
- [ ] 单元测试:核心模块测试
|
73
|
+
- [ ] Mock AI 请求进行离线测试
|
74
|
+
- [ ] 测试各种 Markdown 格式
|
75
|
+
|
76
|
+
#### 3.4 执行结果记录
|
77
|
+
- [] 中间出错了不跳出,而是记录在报错日志中
|
78
|
+
|
79
|
+
|
80
|
+
### 4. 文档和发布
|
81
|
+
|
82
|
+
#### 4.1 完善文档
|
83
|
+
- [ ] 更新 README.md 文件
|
84
|
+
- [ ] 编写详细的使用说明
|
85
|
+
- [ ] API 文档和代码注释
|
86
|
+
- [ ] 配置文件示例
|
87
|
+
- [ ] 常见问题解答
|
88
|
+
|
89
|
+
#### 4.2 打包发布
|
90
|
+
- [ ] 配置 gem 打包流程
|
91
|
+
- [ ] 设置版本管理策略
|
92
|
+
- [ ] 准备发布到 RubyGems
|
93
|
+
- [ ] 创建 GitHub Release
|
94
|
+
- [ ] 设置持续集成流程
|
95
|
+
|
96
|
+
## 技术栈
|
97
|
+
- **语言**: Ruby
|
98
|
+
- **CLI 框架**: OptionParser 或 Thor
|
99
|
+
- **AI 集成**: rubyllm gem
|
100
|
+
- **测试框架**: RSpec
|
101
|
+
- **配置管理**: YAML
|
102
|
+
- **日志**: Ruby Logger
|
103
|
+
|
104
|
+
## 开发优先级
|
105
|
+
1. **高优先级**: 项目结构设置、基础 CLI、文件发现、AI 集成
|
106
|
+
2. **中优先级**: 翻译逻辑、输出管理、错误处理
|
107
|
+
3. **低优先级**: 高级配置、测试完善、文档编写、发布准备
|
108
|
+
|
109
|
+
## 预期功能
|
110
|
+
```bash
|
111
|
+
|
112
|
+
# 使用配置文件
|
113
|
+
translator --config ./translator.yml
|
114
|
+
|
115
|
+
```
|
data/exe/llm_translate
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'ruby_llm'
|
4
|
+
module LlmTranslate
|
5
|
+
class AiClient
|
6
|
+
attr_reader :config, :logger
|
7
|
+
|
8
|
+
def initialize(config, logger)
|
9
|
+
@config = config
|
10
|
+
@logger = logger
|
11
|
+
@client = initialize_client
|
12
|
+
end
|
13
|
+
|
14
|
+
def translate(content, custom_prompt = nil)
|
15
|
+
prompt = build_prompt(content, custom_prompt)
|
16
|
+
|
17
|
+
logger.log_ai_request(prompt.length, config.ai_model)
|
18
|
+
|
19
|
+
retries = 0
|
20
|
+
begin
|
21
|
+
response = make_request(prompt)
|
22
|
+
|
23
|
+
raise TranslationError, 'Empty response from AI service' unless response && !response.empty?
|
24
|
+
|
25
|
+
logger.log_ai_response(response.length)
|
26
|
+
response.strip
|
27
|
+
rescue StandardError => e
|
28
|
+
retries += 1
|
29
|
+
unless retries <= config.retry_attempts
|
30
|
+
raise TranslationError, "AI translation failed after #{config.retry_attempts} attempts: #{e.message}"
|
31
|
+
end
|
32
|
+
|
33
|
+
logger.warn "AI request failed (attempt #{retries}/#{config.retry_attempts}): #{e.message}"
|
34
|
+
sleep(config.retry_delay * retries) # Exponential backoff
|
35
|
+
retry
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_connection
|
40
|
+
test_prompt = 'Hello, world!'
|
41
|
+
|
42
|
+
begin
|
43
|
+
response = make_request(test_prompt)
|
44
|
+
!response.nil? && !response.empty?
|
45
|
+
rescue StandardError => e
|
46
|
+
logger.error "AI connection test failed: #{e.message}"
|
47
|
+
false
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def initialize_client
|
54
|
+
configure_ruby_llm
|
55
|
+
end
|
56
|
+
|
57
|
+
def configure_ruby_llm
|
58
|
+
RubyLLM.configure do |config_obj|
|
59
|
+
# For aihubmix.com or any custom host, use OpenAI-compatible API
|
60
|
+
config_obj.openai_api_key = config.api_key
|
61
|
+
config_obj.openai_api_base = config.ai_host
|
62
|
+
config_obj.default_model = config.ai_model
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def make_request(prompt)
|
67
|
+
chat = RubyLLM.chat
|
68
|
+
.with_model(config.ai_model)
|
69
|
+
.with_temperature(config.temperature)
|
70
|
+
|
71
|
+
response = chat.ask(prompt)
|
72
|
+
|
73
|
+
# Handle different response formats
|
74
|
+
case response
|
75
|
+
when RubyLLM::Message
|
76
|
+
response.content
|
77
|
+
when String
|
78
|
+
response
|
79
|
+
when Hash
|
80
|
+
response['content'] || response[:content] || response.dig('choices', 0, 'message', 'content')
|
81
|
+
else
|
82
|
+
response.to_s
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def build_prompt(content, custom_prompt = nil)
|
87
|
+
template = custom_prompt || config.default_prompt
|
88
|
+
|
89
|
+
# Replace {content} placeholder with actual content
|
90
|
+
template.gsub('{content}', content)
|
91
|
+
.gsub('{target_language}', config.target_language)
|
92
|
+
.gsub('{source_language}', config.source_language)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,205 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'thor'
|
4
|
+
require 'yaml'
|
5
|
+
|
6
|
+
module LlmTranslate
|
7
|
+
class CLI < Thor
|
8
|
+
desc 'translate', 'Translate markdown files using AI'
|
9
|
+
option :config, aliases: '-c', type: :string, default: './llm_translate.yml',
|
10
|
+
desc: 'Path to configuration file'
|
11
|
+
option :input, aliases: '-i', type: :string,
|
12
|
+
desc: 'Input directory (overrides config)'
|
13
|
+
option :output, aliases: '-o', type: :string,
|
14
|
+
desc: 'Output directory (overrides config)'
|
15
|
+
option :prompt, aliases: '-p', type: :string,
|
16
|
+
desc: 'Custom translation prompt (overrides config)'
|
17
|
+
option :verbose, aliases: '-v', type: :boolean, default: false,
|
18
|
+
desc: 'Enable verbose output'
|
19
|
+
option :dry_run, aliases: '-d', type: :boolean, default: false,
|
20
|
+
desc: 'Perform a dry run without actual translation'
|
21
|
+
|
22
|
+
def translate
|
23
|
+
config_path = options[:config]
|
24
|
+
|
25
|
+
unless File.exist?(config_path)
|
26
|
+
say "Configuration file not found: #{config_path}", :red
|
27
|
+
say 'Please create a configuration file or specify a valid path with --config', :yellow
|
28
|
+
exit 1
|
29
|
+
end
|
30
|
+
|
31
|
+
begin
|
32
|
+
config = Config.load(config_path, options)
|
33
|
+
logger = Logger.new(config)
|
34
|
+
|
35
|
+
logger.info 'Starting translation process...'
|
36
|
+
if config.single_file_mode?
|
37
|
+
logger.info "Input file: #{config.input_file}"
|
38
|
+
logger.info "Output file: #{config.output_file}"
|
39
|
+
else
|
40
|
+
logger.info "Input directory: #{config.input_directory}"
|
41
|
+
logger.info "Output directory: #{config.output_directory}"
|
42
|
+
end
|
43
|
+
|
44
|
+
logger.info 'DRY RUN MODE - No files will be translated' if options[:dry_run]
|
45
|
+
|
46
|
+
# Initialize components
|
47
|
+
ai_client = AiClient.new(config, logger)
|
48
|
+
translator_engine = TranslatorEngine.new(config, logger, ai_client)
|
49
|
+
|
50
|
+
# Determine files to translate
|
51
|
+
if config.single_file_mode?
|
52
|
+
# Single file mode
|
53
|
+
unless File.exist?(config.input_file)
|
54
|
+
logger.error "Input file not found: #{config.input_file}"
|
55
|
+
return
|
56
|
+
end
|
57
|
+
files = [config.input_file]
|
58
|
+
logger.info "Single file mode: translating #{config.input_file}"
|
59
|
+
else
|
60
|
+
# Directory mode
|
61
|
+
file_finder = FileFinder.new(config, logger)
|
62
|
+
files = file_finder.find_markdown_files
|
63
|
+
|
64
|
+
if files.empty?
|
65
|
+
logger.warn "No markdown files found in #{config.input_directory}"
|
66
|
+
return
|
67
|
+
end
|
68
|
+
|
69
|
+
logger.info "Found #{files.length} markdown files to translate"
|
70
|
+
end
|
71
|
+
|
72
|
+
# Translate files
|
73
|
+
success_count = 0
|
74
|
+
error_count = 0
|
75
|
+
|
76
|
+
files.each_with_index do |file_path, index|
|
77
|
+
logger.info "[#{index + 1}/#{files.length}] Processing: #{file_path}"
|
78
|
+
|
79
|
+
translator_engine.translate_file(file_path) unless options[:dry_run]
|
80
|
+
|
81
|
+
success_count += 1
|
82
|
+
logger.info "✓ Successfully processed: #{file_path}"
|
83
|
+
rescue StandardError => e
|
84
|
+
error_count += 1
|
85
|
+
logger.error "✗ Failed to process #{file_path}: #{e.message}"
|
86
|
+
|
87
|
+
if config.should_stop_on_error?(error_count)
|
88
|
+
logger.error 'Stopping due to too many consecutive errors'
|
89
|
+
break
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# Summary
|
94
|
+
logger.info 'Translation completed!'
|
95
|
+
logger.info "Success: #{success_count}, Errors: #{error_count}"
|
96
|
+
|
97
|
+
generate_report(config, success_count, error_count, files) if config.generate_report?
|
98
|
+
rescue ConfigurationError => e
|
99
|
+
say "Configuration error: #{e.message}", :red
|
100
|
+
exit 1
|
101
|
+
rescue StandardError => e
|
102
|
+
say "Unexpected error: #{e.message}", :red
|
103
|
+
say e.backtrace.join("\n") if options[:verbose]
|
104
|
+
exit 1
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
desc 'version', 'Show version'
|
109
|
+
def version
|
110
|
+
say "LlmTranslate #{LlmTranslate::VERSION}"
|
111
|
+
end
|
112
|
+
|
113
|
+
desc 'init', 'Initialize a new configuration file'
|
114
|
+
option :output, aliases: '-o', type: :string, default: './llm_translate.yml',
|
115
|
+
desc: 'Output path for configuration file'
|
116
|
+
|
117
|
+
def init
|
118
|
+
config_path = options[:output]
|
119
|
+
|
120
|
+
return if File.exist?(config_path) && !yes?('Configuration file already exists. Overwrite? (y/N)')
|
121
|
+
|
122
|
+
# Copy the sample configuration
|
123
|
+
sample_config = File.join(__dir__, '../../content/llm_translate.yml')
|
124
|
+
if File.exist?(sample_config)
|
125
|
+
FileUtils.cp(sample_config, config_path)
|
126
|
+
say "Configuration file created: #{config_path}", :green
|
127
|
+
say 'Please edit the file to configure your API keys and preferences', :yellow
|
128
|
+
else
|
129
|
+
# Create a minimal config if sample doesn't exist
|
130
|
+
create_minimal_config(config_path)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
private
|
135
|
+
|
136
|
+
def generate_report(config, success_count, error_count, files)
|
137
|
+
return unless config.generate_report?
|
138
|
+
|
139
|
+
report_path = config.report_path
|
140
|
+
FileUtils.mkdir_p(File.dirname(report_path))
|
141
|
+
|
142
|
+
File.write(report_path, <<~REPORT)
|
143
|
+
# Translation Report
|
144
|
+
|
145
|
+
**Date**: #{Time.now.strftime('%Y-%m-%d %H:%M:%S')}
|
146
|
+
|
147
|
+
## Summary
|
148
|
+
- Total files: #{files.length}
|
149
|
+
- Successfully translated: #{success_count}
|
150
|
+
- Errors: #{error_count}
|
151
|
+
- Success rate: #{(success_count.to_f / files.length * 100).round(2)}%
|
152
|
+
|
153
|
+
## Configuration
|
154
|
+
- Input directory: #{config.input_directory}
|
155
|
+
- Output directory: #{config.output_directory}
|
156
|
+
- Target language: #{config.target_language}
|
157
|
+
- AI Provider: #{config.ai_provider}
|
158
|
+
- Model: #{config.ai_model}
|
159
|
+
REPORT
|
160
|
+
|
161
|
+
say "Report generated: #{report_path}", :green
|
162
|
+
end
|
163
|
+
|
164
|
+
def create_minimal_config(config_path)
|
165
|
+
config_content = <<~YAML
|
166
|
+
# LlmTranslate Configuration
|
167
|
+
ai:
|
168
|
+
api_key: sk-
|
169
|
+
host: https://aihubmix.com/v1
|
170
|
+
provider: "openai"
|
171
|
+
model: "gpt-4o-mini"
|
172
|
+
temperature: 0.3
|
173
|
+
|
174
|
+
translation:
|
175
|
+
target_language: "zh-CN"
|
176
|
+
default_prompt: |
|
177
|
+
Please translate the following Markdown content to Chinese, keeping all formatting intact:
|
178
|
+
- Preserve code blocks, links, images, and other Markdown syntax
|
179
|
+
- Keep English technical terms and product names
|
180
|
+
- Ensure natural and fluent translation
|
181
|
+
#{' '}
|
182
|
+
Content:
|
183
|
+
{content}
|
184
|
+
|
185
|
+
files:
|
186
|
+
# Directory mode (default)
|
187
|
+
input_directory: "./docs"
|
188
|
+
output_directory: "./docs-translated"
|
189
|
+
filename_suffix: ".zh"
|
190
|
+
#{' '}
|
191
|
+
# Single file mode (uncomment to use)
|
192
|
+
# input_file: "./README.md"
|
193
|
+
# output_file: "./README.zh.md"
|
194
|
+
|
195
|
+
logging:
|
196
|
+
level: "info"
|
197
|
+
output: "console"
|
198
|
+
YAML
|
199
|
+
|
200
|
+
File.write(config_path, config_content)
|
201
|
+
say "Minimal configuration file created: #{config_path}", :green
|
202
|
+
say 'Please edit the file to configure your API keys and preferences', :yellow
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|