llm_translate 0.2.0 → 0.3.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 +4 -4
- data/README.zh.md +6 -6
- data/lib/llm_translate/cli.rb +11 -16
- data/lib/llm_translate/config.rb +0 -40
- data/lib/llm_translate/logger.rb +4 -11
- data/lib/llm_translate/translator_engine.rb +61 -0
- data/lib/llm_translate/version.rb +1 -1
- data/llm_translate.gemspec +1 -0
- metadata +16 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b3d7bffb10cabd77729e1e806a256a16bd7a036faa0ea52b7024e3a521dada5e
|
4
|
+
data.tar.gz: 50fcf8a22940afb311387913d2455dc519812abe8618be54c47541808193afd6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 76e635e0838f893377ed9ba05e8e4e04c36053cbb4c68b2ab09cbd8c69d8a7433400886e1f817647d9b322fc7dad1e4643cc53b6471fc7ed2b6538df015108d7
|
7
|
+
data.tar.gz: 713bb859602fd5f511444f8e491bcd556beb7954f2f03cb36ff79938ff1e07e60c5419dd2c9818dc4d1f95136af8d4722b649a267e4217128598a26036b50532
|
data/README.zh.md
CHANGED
@@ -1,17 +1,17 @@
|
|
1
1
|
# LlmTranslate
|
2
2
|
|
3
|
-
AI 驱动的 Markdown
|
3
|
+
AI 驱动的 Markdown 翻译工具,在使用各种 AI 提供商翻译内容的同时保持格式不变。
|
4
4
|
|
5
5
|
## 功能特性
|
6
6
|
|
7
7
|
- 🤖 **AI 驱动翻译**:支持 OpenAI、Anthropic 和 Ollama
|
8
|
-
- 📝 **Markdown
|
8
|
+
- 📝 **Markdown 格式保留**:保持代码块、链接、图片和格式不变
|
9
9
|
- 🔧 **灵活配置**:基于 YAML 的配置,支持环境变量
|
10
10
|
- 📁 **批量处理**:递归处理整个目录结构
|
11
|
-
- 🚀 **CLI 界面**:使用 Thor
|
11
|
+
- 🚀 **CLI 界面**:使用 Thor 实现的易用命令行界面
|
12
12
|
- 📊 **进度跟踪**:内置日志记录和报告功能
|
13
|
-
- ⚡
|
14
|
-
- 🎯
|
13
|
+
- ⚡ **错误处理**:具有重试机制的强大错误处理
|
14
|
+
- 🎯 **可定制性**:自定义提示、文件模式和输出策略
|
15
15
|
|
16
16
|
## 安装
|
17
17
|
|
@@ -149,7 +149,7 @@ Options:
|
|
149
149
|
-o, --output PATH 输出目录(覆盖配置)
|
150
150
|
-p, --prompt TEXT 自定义翻译提示(覆盖配置)
|
151
151
|
-v, --verbose 启用详细输出
|
152
|
-
-d, --dry-run
|
152
|
+
-d, --dry-run 执行试运行而不进行实际翻译
|
153
153
|
|
154
154
|
Other Commands:
|
155
155
|
llm_translate init 初始化新的配置文件
|
data/lib/llm_translate/cli.rb
CHANGED
@@ -70,24 +70,19 @@ module LlmTranslate
|
|
70
70
|
end
|
71
71
|
|
72
72
|
# Translate files
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
translator_engine.translate_file(file_path) unless options[:dry_run]
|
73
|
+
if options[:dry_run]
|
74
|
+
logger.info "DRY RUN: Would translate #{files.length} files with #{config.concurrent_files} concurrent threads"
|
75
|
+
success_count = files.length
|
76
|
+
error_count = 0
|
77
|
+
else
|
78
|
+
logger.info "Starting translation with #{config.concurrent_files} concurrent files"
|
80
79
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
error_count += 1
|
85
|
-
logger.error "✗ Failed to process #{file_path}: #{e.message}"
|
80
|
+
results = translator_engine.translate_files_concurrently(files)
|
81
|
+
success_count = results[:success].length
|
82
|
+
error_count = results[:error].length
|
86
83
|
|
87
|
-
if
|
88
|
-
|
89
|
-
break
|
90
|
-
end
|
84
|
+
# Check if we should stop on too many errors
|
85
|
+
logger.error "Stopping due to too many errors (#{error_count})" if config.should_stop_on_error?(error_count)
|
91
86
|
end
|
92
87
|
|
93
88
|
# Summary
|
data/lib/llm_translate/config.rb
CHANGED
@@ -120,10 +120,6 @@ module LlmTranslate
|
|
120
120
|
data.dig('files', 'overwrite_policy') || 'ask'
|
121
121
|
end
|
122
122
|
|
123
|
-
def backup_directory
|
124
|
-
data.dig('files', 'backup_directory') || './backups'
|
125
|
-
end
|
126
|
-
|
127
123
|
# Logging Configuration
|
128
124
|
def log_level
|
129
125
|
cli_options[:verbose] ? 'debug' : (data.dig('logging', 'level') || 'info')
|
@@ -133,18 +129,10 @@ module LlmTranslate
|
|
133
129
|
data.dig('logging', 'output') || 'console'
|
134
130
|
end
|
135
131
|
|
136
|
-
def log_file_path
|
137
|
-
data.dig('logging', 'file_path') || './logs/llm_translate.log'
|
138
|
-
end
|
139
|
-
|
140
132
|
def verbose_translation?
|
141
133
|
cli_options[:verbose] || data.dig('logging', 'verbose_translation') == true
|
142
134
|
end
|
143
135
|
|
144
|
-
def error_log_path
|
145
|
-
data.dig('logging', 'error_log_path') || './logs/errors.log'
|
146
|
-
end
|
147
|
-
|
148
136
|
# Error Handling Configuration
|
149
137
|
def on_error
|
150
138
|
data.dig('error_handling', 'on_error') || 'log_and_continue'
|
@@ -162,10 +150,6 @@ module LlmTranslate
|
|
162
150
|
data.dig('error_handling', 'generate_error_report') != false
|
163
151
|
end
|
164
152
|
|
165
|
-
def error_report_path
|
166
|
-
data.dig('error_handling', 'error_report_path') || './logs/error_report.md'
|
167
|
-
end
|
168
|
-
|
169
153
|
def should_stop_on_error?(error_count)
|
170
154
|
on_error == 'stop' || error_count >= max_consecutive_errors
|
171
155
|
end
|
@@ -175,27 +159,11 @@ module LlmTranslate
|
|
175
159
|
data.dig('performance', 'concurrent_files') || 3
|
176
160
|
end
|
177
161
|
|
178
|
-
def batch_size
|
179
|
-
data.dig('performance', 'batch_size') || 5
|
180
|
-
end
|
181
|
-
|
182
162
|
def request_interval
|
183
163
|
data.dig('performance', 'request_interval') || 1
|
184
164
|
end
|
185
165
|
|
186
|
-
def max_memory_mb
|
187
|
-
data.dig('performance', 'max_memory_mb') || 500
|
188
|
-
end
|
189
|
-
|
190
166
|
# Output Configuration
|
191
|
-
def show_progress?
|
192
|
-
data.dig('output', 'show_progress') != false
|
193
|
-
end
|
194
|
-
|
195
|
-
def show_statistics?
|
196
|
-
data.dig('output', 'show_statistics') != false
|
197
|
-
end
|
198
|
-
|
199
167
|
def generate_report?
|
200
168
|
data.dig('output', 'generate_report') != false
|
201
169
|
end
|
@@ -204,14 +172,6 @@ module LlmTranslate
|
|
204
172
|
data.dig('output', 'report_path') || './reports/translation_report.md'
|
205
173
|
end
|
206
174
|
|
207
|
-
def output_format
|
208
|
-
data.dig('output', 'format') || 'markdown'
|
209
|
-
end
|
210
|
-
|
211
|
-
def include_metadata?
|
212
|
-
data.dig('output', 'include_metadata') != false
|
213
|
-
end
|
214
|
-
|
215
175
|
private
|
216
176
|
|
217
177
|
def load_config_file(config_path)
|
data/lib/llm_translate/logger.rb
CHANGED
@@ -67,7 +67,7 @@ module LlmTranslate
|
|
67
67
|
when 'console'
|
68
68
|
create_console_logger
|
69
69
|
when 'file'
|
70
|
-
create_file_logger(
|
70
|
+
create_file_logger('./logs/llm_translate.log')
|
71
71
|
when 'both'
|
72
72
|
create_multi_logger
|
73
73
|
else
|
@@ -76,15 +76,8 @@ module LlmTranslate
|
|
76
76
|
end
|
77
77
|
|
78
78
|
def create_error_logger
|
79
|
-
return nil
|
80
|
-
|
81
|
-
FileUtils.mkdir_p(File.dirname(config.error_log_path))
|
82
|
-
error_logger = ::Logger.new(config.error_log_path)
|
83
|
-
error_logger.level = log_level_constant
|
84
|
-
error_logger.formatter = proc do |severity, datetime, _progname, msg|
|
85
|
-
"[#{datetime.strftime('%Y-%m-%d %H:%M:%S')}] #{severity}: #{msg}\n"
|
86
|
-
end
|
87
|
-
error_logger
|
79
|
+
# Error logger is no longer supported, return nil
|
80
|
+
nil
|
88
81
|
end
|
89
82
|
|
90
83
|
def create_console_logger
|
@@ -119,7 +112,7 @@ module LlmTranslate
|
|
119
112
|
|
120
113
|
def create_multi_logger
|
121
114
|
console_logger = create_console_logger
|
122
|
-
file_logger = create_file_logger(
|
115
|
+
file_logger = create_file_logger('./logs/llm_translate.log')
|
123
116
|
|
124
117
|
MultiLogger.new([console_logger, file_logger])
|
125
118
|
end
|
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require 'pathname'
|
4
4
|
require 'fileutils'
|
5
|
+
require 'async'
|
5
6
|
|
6
7
|
module LlmTranslate
|
7
8
|
class TranslatorEngine
|
@@ -53,6 +54,66 @@ module LlmTranslate
|
|
53
54
|
sleep(config.request_interval) if config.request_interval.positive?
|
54
55
|
end
|
55
56
|
|
57
|
+
def translate_files_concurrently(file_paths)
|
58
|
+
return translate_files_sequentially(file_paths) if config.concurrent_files <= 1
|
59
|
+
|
60
|
+
results = { success: [], error: [] }
|
61
|
+
|
62
|
+
# Use Async to run concurrent translation tasks
|
63
|
+
Async do |task|
|
64
|
+
# Process files in batches to limit concurrency
|
65
|
+
file_paths.each_slice(config.concurrent_files) do |batch|
|
66
|
+
# Create async tasks for the current batch
|
67
|
+
batch_tasks = batch.map.with_index do |file_path, _batch_index|
|
68
|
+
# Calculate overall index
|
69
|
+
overall_index = file_paths.index(file_path) + 1
|
70
|
+
|
71
|
+
task.async do
|
72
|
+
logger.info "[#{overall_index}/#{file_paths.length}] Processing: #{file_path}"
|
73
|
+
|
74
|
+
# Translate the file
|
75
|
+
translate_file(file_path)
|
76
|
+
|
77
|
+
# Collect successful result
|
78
|
+
results[:success] << file_path
|
79
|
+
|
80
|
+
logger.info "✓ Successfully processed: #{file_path}"
|
81
|
+
{ status: :success, file: file_path }
|
82
|
+
rescue StandardError => e
|
83
|
+
# Collect error result
|
84
|
+
results[:error] << { file: file_path, error: e.message }
|
85
|
+
|
86
|
+
logger.error "✗ Failed to process #{file_path}: #{e.message}"
|
87
|
+
{ status: :error, file: file_path, error: e.message }
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
# Wait for all tasks in this batch to complete before starting the next batch
|
92
|
+
batch_tasks.each(&:wait)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
results
|
97
|
+
end
|
98
|
+
|
99
|
+
def translate_files_sequentially(file_paths)
|
100
|
+
results = { success: [], error: [] }
|
101
|
+
|
102
|
+
file_paths.each_with_index do |file_path, index|
|
103
|
+
logger.info "[#{index + 1}/#{file_paths.length}] Processing: #{file_path}"
|
104
|
+
|
105
|
+
translate_file(file_path)
|
106
|
+
|
107
|
+
results[:success] << file_path
|
108
|
+
logger.info "✓ Successfully processed: #{file_path}"
|
109
|
+
rescue StandardError => e
|
110
|
+
results[:error] << { file: file_path, error: e.message }
|
111
|
+
logger.error "✗ Failed to process #{file_path}: #{e.message}"
|
112
|
+
end
|
113
|
+
|
114
|
+
results
|
115
|
+
end
|
116
|
+
|
56
117
|
def translate_content(content, file_path = nil)
|
57
118
|
if config.preserve_formatting?
|
58
119
|
translate_with_format_preservation(content)
|
data/llm_translate.gemspec
CHANGED
metadata
CHANGED
@@ -1,15 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: llm_translate
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- LlmTranslate Team
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-
|
11
|
+
date: 2025-09-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: async
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2.0'
|
13
27
|
- !ruby/object:Gem::Dependency
|
14
28
|
name: ruby_llm
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|