better_translate 0.5.0 → 1.0.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/.env.example +14 -0
- data/.rspec +3 -0
- data/.rubocop.yml +8 -0
- data/.yardopts +10 -0
- data/CHANGELOG.md +125 -114
- data/CLAUDE.md +385 -0
- data/README.md +629 -244
- data/Rakefile +7 -1
- data/Steepfile +29 -0
- data/docs/implementation/00-overview.md +220 -0
- data/docs/implementation/01-setup_dependencies.md +668 -0
- data/docs/implementation/02-error_handling.md +65 -0
- data/docs/implementation/03-core_components.md +457 -0
- data/docs/implementation/03.5-variable_preservation.md +509 -0
- data/docs/implementation/04-provider_architecture.md +571 -0
- data/docs/implementation/05-translation_logic.md +1065 -0
- data/docs/implementation/06-main_module_api.md +122 -0
- data/docs/implementation/07-direct_translation_helpers.md +582 -0
- data/docs/implementation/08-rails_integration.md +323 -0
- data/docs/implementation/09-testing_suite.md +228 -0
- data/docs/implementation/10-documentation_examples.md +150 -0
- data/docs/implementation/11-quality_security.md +65 -0
- data/docs/implementation/12-cli_standalone.md +698 -0
- data/exe/better_translate +9 -0
- data/lib/better_translate/cache.rb +125 -0
- data/lib/better_translate/cli.rb +304 -0
- data/lib/better_translate/configuration.rb +201 -0
- data/lib/better_translate/direct_translator.rb +131 -0
- data/lib/better_translate/errors.rb +101 -0
- data/lib/better_translate/progress_tracker.rb +157 -0
- data/lib/better_translate/provider_factory.rb +45 -0
- data/lib/better_translate/providers/anthropic_provider.rb +154 -0
- data/lib/better_translate/providers/base_http_provider.rb +239 -0
- data/lib/better_translate/providers/chatgpt_provider.rb +138 -44
- data/lib/better_translate/providers/gemini_provider.rb +123 -61
- data/lib/better_translate/railtie.rb +18 -0
- data/lib/better_translate/rate_limiter.rb +90 -0
- data/lib/better_translate/strategies/base_strategy.rb +58 -0
- data/lib/better_translate/strategies/batch_strategy.rb +56 -0
- data/lib/better_translate/strategies/deep_strategy.rb +45 -0
- data/lib/better_translate/strategies/strategy_selector.rb +43 -0
- data/lib/better_translate/translator.rb +115 -284
- data/lib/better_translate/utils/hash_flattener.rb +104 -0
- data/lib/better_translate/validator.rb +105 -0
- data/lib/better_translate/variable_extractor.rb +259 -0
- data/lib/better_translate/version.rb +2 -9
- data/lib/better_translate/yaml_handler.rb +168 -0
- data/lib/better_translate.rb +97 -73
- data/lib/generators/better_translate/analyze/USAGE +12 -0
- data/lib/generators/better_translate/analyze/analyze_generator.rb +94 -0
- data/lib/generators/better_translate/install/USAGE +13 -0
- data/lib/generators/better_translate/install/install_generator.rb +71 -0
- data/lib/generators/better_translate/install/templates/README +20 -0
- data/lib/generators/better_translate/install/templates/initializer.rb.tt +47 -0
- data/lib/generators/better_translate/translate/USAGE +13 -0
- data/lib/generators/better_translate/translate/translate_generator.rb +114 -0
- data/lib/tasks/better_translate.rake +136 -0
- data/sig/better_translate/cache.rbs +28 -0
- data/sig/better_translate/cli.rbs +24 -0
- data/sig/better_translate/configuration.rbs +78 -0
- data/sig/better_translate/direct_translator.rbs +18 -0
- data/sig/better_translate/errors.rbs +46 -0
- data/sig/better_translate/progress_tracker.rbs +29 -0
- data/sig/better_translate/provider_factory.rbs +8 -0
- data/sig/better_translate/providers/anthropic_provider.rbs +27 -0
- data/sig/better_translate/providers/base_http_provider.rbs +44 -0
- data/sig/better_translate/providers/chatgpt_provider.rbs +25 -0
- data/sig/better_translate/providers/gemini_provider.rbs +22 -0
- data/sig/better_translate/railtie.rbs +7 -0
- data/sig/better_translate/rate_limiter.rbs +20 -0
- data/sig/better_translate/strategies/base_strategy.rbs +19 -0
- data/sig/better_translate/strategies/batch_strategy.rbs +13 -0
- data/sig/better_translate/strategies/deep_strategy.rbs +11 -0
- data/sig/better_translate/strategies/strategy_selector.rbs +10 -0
- data/sig/better_translate/translator.rbs +24 -0
- data/sig/better_translate/utils/hash_flattener.rbs +14 -0
- data/sig/better_translate/validator.rbs +14 -0
- data/sig/better_translate/variable_extractor.rbs +40 -0
- data/sig/better_translate/version.rbs +4 -0
- data/sig/better_translate/yaml_handler.rbs +29 -0
- data/sig/better_translate.rbs +32 -2
- data/sig/faraday.rbs +22 -0
- data/sig/generators/better_translate/analyze/analyze_generator.rbs +18 -0
- data/sig/generators/better_translate/install/install_generator.rbs +14 -0
- data/sig/generators/better_translate/translate/translate_generator.rbs +10 -0
- data/sig/optparse.rbs +9 -0
- data/sig/psych.rbs +5 -0
- data/sig/rails.rbs +34 -0
- metadata +89 -203
- data/lib/better_translate/helper.rb +0 -83
- data/lib/better_translate/providers/base_provider.rb +0 -102
- data/lib/better_translate/service.rb +0 -144
- data/lib/better_translate/similarity_analyzer.rb +0 -218
- data/lib/better_translate/utils.rb +0 -55
- data/lib/better_translate/writer.rb +0 -75
- data/lib/generators/better_translate/analyze_generator.rb +0 -57
- data/lib/generators/better_translate/install_generator.rb +0 -14
- data/lib/generators/better_translate/templates/better_translate.rb +0 -56
- data/lib/generators/better_translate/translate_generator.rb +0 -84
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# 06 - Main Module & API
|
|
2
|
+
|
|
3
|
+
[← Previous: 05-Translation Logic](./05-translation_logic.md) | [Back to Index](../../IMPLEMENTATION_PLAN.md) | [Next: 07-Direct Translation Helpers →](./07-direct_translation_helpers.md)
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Main Module & API
|
|
8
|
+
|
|
9
|
+
### 6.1 Aggiornare `lib/better_translate.rb`
|
|
10
|
+
|
|
11
|
+
```ruby
|
|
12
|
+
# frozen_string_literal: true
|
|
13
|
+
|
|
14
|
+
require_relative "better_translate/version"
|
|
15
|
+
require_relative "better_translate/errors"
|
|
16
|
+
require_relative "better_translate/configuration"
|
|
17
|
+
require_relative "better_translate/validator"
|
|
18
|
+
require_relative "better_translate/cache"
|
|
19
|
+
require_relative "better_translate/rate_limiter"
|
|
20
|
+
require_relative "better_translate/utils/hash_flattener"
|
|
21
|
+
require_relative "better_translate/yaml_handler"
|
|
22
|
+
require_relative "better_translate/progress_tracker"
|
|
23
|
+
require_relative "better_translate/providers/base_http_provider"
|
|
24
|
+
require_relative "better_translate/providers/chatgpt_provider"
|
|
25
|
+
require_relative "better_translate/providers/gemini_provider"
|
|
26
|
+
require_relative "better_translate/providers/anthropic_provider"
|
|
27
|
+
require_relative "better_translate/provider_factory"
|
|
28
|
+
require_relative "better_translate/strategies/base_strategy"
|
|
29
|
+
require_relative "better_translate/strategies/deep_strategy"
|
|
30
|
+
require_relative "better_translate/strategies/batch_strategy"
|
|
31
|
+
require_relative "better_translate/strategies/strategy_selector"
|
|
32
|
+
require_relative "better_translate/translator"
|
|
33
|
+
|
|
34
|
+
# BetterTranslate - AI-powered YAML locale file translator
|
|
35
|
+
#
|
|
36
|
+
# Automatically translate YAML locale files using AI providers (ChatGPT, Gemini, Claude).
|
|
37
|
+
# Features intelligent caching, batch processing, and Rails integration.
|
|
38
|
+
#
|
|
39
|
+
# @example Basic usage
|
|
40
|
+
# BetterTranslate.configure do |config|
|
|
41
|
+
# config.provider = :chatgpt
|
|
42
|
+
# config.openai_key = ENV['OPENAI_API_KEY']
|
|
43
|
+
# config.source_language = "en"
|
|
44
|
+
# config.target_languages = [{ short_name: "it", name: "Italian" }]
|
|
45
|
+
# config.input_file = "config/locales/en.yml"
|
|
46
|
+
# config.output_folder = "config/locales"
|
|
47
|
+
# end
|
|
48
|
+
#
|
|
49
|
+
# BetterTranslate.translate_all
|
|
50
|
+
#
|
|
51
|
+
# @example With advanced options
|
|
52
|
+
# BetterTranslate.configure do |config|
|
|
53
|
+
# config.provider = :anthropic
|
|
54
|
+
# config.anthropic_key = ENV['ANTHROPIC_API_KEY']
|
|
55
|
+
# config.source_language = "en"
|
|
56
|
+
# config.target_languages = [
|
|
57
|
+
# { short_name: "it", name: "Italian" },
|
|
58
|
+
# { short_name: "fr", name: "French" }
|
|
59
|
+
# ]
|
|
60
|
+
# config.input_file = "config/locales/en.yml"
|
|
61
|
+
# config.output_folder = "config/locales"
|
|
62
|
+
# config.translation_mode = :incremental
|
|
63
|
+
# config.translation_context = "E-commerce product descriptions"
|
|
64
|
+
# config.cache_enabled = true
|
|
65
|
+
# config.verbose = true
|
|
66
|
+
# config.global_exclusions = ["app.name"]
|
|
67
|
+
# end
|
|
68
|
+
#
|
|
69
|
+
# results = BetterTranslate.translate_all
|
|
70
|
+
# puts "Success: #{results[:success_count]}, Failures: #{results[:failure_count]}"
|
|
71
|
+
#
|
|
72
|
+
module BetterTranslate
|
|
73
|
+
class << self
|
|
74
|
+
# @return [Configuration, nil] Current configuration
|
|
75
|
+
attr_accessor :configuration
|
|
76
|
+
|
|
77
|
+
# Configure BetterTranslate
|
|
78
|
+
#
|
|
79
|
+
# @yieldparam config [Configuration] Configuration object to customize
|
|
80
|
+
# @return [Configuration] The configuration object
|
|
81
|
+
#
|
|
82
|
+
# @example
|
|
83
|
+
# BetterTranslate.configure do |config|
|
|
84
|
+
# config.provider = :chatgpt
|
|
85
|
+
# config.openai_key = ENV['OPENAI_API_KEY']
|
|
86
|
+
# end
|
|
87
|
+
def configure
|
|
88
|
+
self.configuration ||= Configuration.new
|
|
89
|
+
yield(configuration) if block_given?
|
|
90
|
+
configuration
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Translate all target languages
|
|
94
|
+
#
|
|
95
|
+
# @return [Hash] Results hash with :success_count, :failure_count, :errors
|
|
96
|
+
# @raise [ConfigurationError] if configuration is invalid
|
|
97
|
+
#
|
|
98
|
+
# @example
|
|
99
|
+
# results = BetterTranslate.translate_all
|
|
100
|
+
# puts "Translated #{results[:success_count]} languages"
|
|
101
|
+
def translate_all
|
|
102
|
+
raise ConfigurationError, "BetterTranslate not configured" unless configuration
|
|
103
|
+
|
|
104
|
+
translator = Translator.new(configuration)
|
|
105
|
+
translator.translate_all
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Reset configuration
|
|
109
|
+
#
|
|
110
|
+
# @return [void]
|
|
111
|
+
def reset!
|
|
112
|
+
self.configuration = nil
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
[← Previous: 05-Translation Logic](./05-translation_logic.md) | [Back to Index](../../IMPLEMENTATION_PLAN.md) | [Next: 07-Direct Translation Helpers →](./07-direct_translation_helpers.md)
|
|
@@ -0,0 +1,582 @@
|
|
|
1
|
+
# 07 - Direct Translation Helpers
|
|
2
|
+
|
|
3
|
+
[← Previous: 06-Main Module Api](./06-main_module_api.md) | [Back to Index](../../IMPLEMENTATION_PLAN.md) | [Next: 08-Rails Integration →](./08-rails_integration.md)
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Direct Translation Helpers
|
|
8
|
+
|
|
9
|
+
Questa sezione implementa helper pubblici per tradurre stringhe direttamente, senza usare file YAML.
|
|
10
|
+
|
|
11
|
+
### 6.5.1 `lib/better_translate/helpers.rb`
|
|
12
|
+
|
|
13
|
+
```ruby
|
|
14
|
+
# frozen_string_literal: true
|
|
15
|
+
|
|
16
|
+
module BetterTranslate
|
|
17
|
+
# Helper methods for direct text translation
|
|
18
|
+
#
|
|
19
|
+
# Provides convenient methods to translate text programmatically without YAML files.
|
|
20
|
+
module Helpers
|
|
21
|
+
# Translate a single text string
|
|
22
|
+
#
|
|
23
|
+
# @param text [String] Text to translate
|
|
24
|
+
# @param from [String] Source language code (e.g., "en")
|
|
25
|
+
# @param to [String, Array<String>] Target language code(s)
|
|
26
|
+
# @param provider [Symbol, nil] Provider to use (:chatgpt, :gemini, :anthropic). Uses configured provider if nil.
|
|
27
|
+
# @param context [String, nil] Optional translation context
|
|
28
|
+
# @return [String, Hash] Translated text (String if single target, Hash if multiple targets)
|
|
29
|
+
# @raise [ConfigurationError] if BetterTranslate is not configured and no provider specified
|
|
30
|
+
# @raise [ValidationError] if input is invalid
|
|
31
|
+
# @raise [TranslationError] if translation fails
|
|
32
|
+
#
|
|
33
|
+
# @example Single target language
|
|
34
|
+
# BetterTranslate.translate_text("Hello", from: "en", to: "it")
|
|
35
|
+
# #=> "Ciao"
|
|
36
|
+
#
|
|
37
|
+
# @example Multiple target languages
|
|
38
|
+
# BetterTranslate.translate_text("Hello", from: "en", to: ["it", "fr", "de"])
|
|
39
|
+
# #=> { "it" => "Ciao", "fr" => "Bonjour", "de" => "Hallo" }
|
|
40
|
+
#
|
|
41
|
+
# @example With custom provider
|
|
42
|
+
# BetterTranslate.translate_text("Hello", from: "en", to: "it", provider: :anthropic)
|
|
43
|
+
#
|
|
44
|
+
# @example With context
|
|
45
|
+
# BetterTranslate.translate_text(
|
|
46
|
+
# "The patient presents with symptoms",
|
|
47
|
+
# from: "en",
|
|
48
|
+
# to: "it",
|
|
49
|
+
# context: "Medical terminology"
|
|
50
|
+
# )
|
|
51
|
+
#
|
|
52
|
+
def self.translate_text(text, from:, to:, provider: nil, context: nil)
|
|
53
|
+
Validator.validate_text!(text)
|
|
54
|
+
Validator.validate_language_code!(from)
|
|
55
|
+
|
|
56
|
+
# Normalize to array
|
|
57
|
+
target_langs = Array(to)
|
|
58
|
+
target_langs.each { |lang| Validator.validate_language_code!(lang) }
|
|
59
|
+
|
|
60
|
+
# Get provider
|
|
61
|
+
translation_provider = get_provider(provider, context)
|
|
62
|
+
|
|
63
|
+
# Translate
|
|
64
|
+
if target_langs.size == 1
|
|
65
|
+
# Single target - return string
|
|
66
|
+
translate_single(text, target_langs.first, translation_provider)
|
|
67
|
+
else
|
|
68
|
+
# Multiple targets - return hash
|
|
69
|
+
translate_multiple(text, target_langs, translation_provider)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Translate multiple texts to a single target language
|
|
74
|
+
#
|
|
75
|
+
# @param texts [Array<String>] Texts to translate
|
|
76
|
+
# @param from [String] Source language code
|
|
77
|
+
# @param to [String] Target language code
|
|
78
|
+
# @param provider [Symbol, nil] Provider to use
|
|
79
|
+
# @param context [String, nil] Optional translation context
|
|
80
|
+
# @return [Array<String>] Translated texts
|
|
81
|
+
# @raise [ValidationError] if input is invalid
|
|
82
|
+
# @raise [TranslationError] if translation fails
|
|
83
|
+
#
|
|
84
|
+
# @example
|
|
85
|
+
# BetterTranslate.translate_texts(
|
|
86
|
+
# ["Hello", "Goodbye", "Thank you"],
|
|
87
|
+
# from: "en",
|
|
88
|
+
# to: "it"
|
|
89
|
+
# )
|
|
90
|
+
# #=> ["Ciao", "Arrivederci", "Grazie"]
|
|
91
|
+
#
|
|
92
|
+
def self.translate_texts(texts, from:, to:, provider: nil, context: nil)
|
|
93
|
+
raise ValidationError, "texts must be an Array" unless texts.is_a?(Array)
|
|
94
|
+
raise ValidationError, "texts cannot be empty" if texts.empty?
|
|
95
|
+
|
|
96
|
+
Validator.validate_language_code!(from)
|
|
97
|
+
Validator.validate_language_code!(to)
|
|
98
|
+
|
|
99
|
+
texts.each { |text| Validator.validate_text!(text) }
|
|
100
|
+
|
|
101
|
+
# Get provider
|
|
102
|
+
translation_provider = get_provider(provider, context)
|
|
103
|
+
|
|
104
|
+
# Translate each text
|
|
105
|
+
texts.map do |text|
|
|
106
|
+
translate_single(text, to, translation_provider)
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Translate a text to multiple languages (batch)
|
|
111
|
+
#
|
|
112
|
+
# @param text [String] Text to translate
|
|
113
|
+
# @param from [String] Source language code
|
|
114
|
+
# @param to [Array<Hash>] Target languages with format: [{ short_name: "it", name: "Italian" }]
|
|
115
|
+
# @param provider [Symbol, nil] Provider to use
|
|
116
|
+
# @param context [String, nil] Optional translation context
|
|
117
|
+
# @return [Hash] Hash with language codes as keys and translations as values
|
|
118
|
+
#
|
|
119
|
+
# @example
|
|
120
|
+
# languages = [
|
|
121
|
+
# { short_name: "it", name: "Italian" },
|
|
122
|
+
# { short_name: "fr", name: "French" }
|
|
123
|
+
# ]
|
|
124
|
+
# BetterTranslate.translate_text_to_languages("Hello", from: "en", to: languages)
|
|
125
|
+
# #=> { "it" => "Ciao", "fr" => "Bonjour" }
|
|
126
|
+
#
|
|
127
|
+
def self.translate_text_to_languages(text, from:, to:, provider: nil, context: nil)
|
|
128
|
+
raise ValidationError, "to must be an Array of Hashes" unless to.is_a?(Array)
|
|
129
|
+
|
|
130
|
+
Validator.validate_text!(text)
|
|
131
|
+
Validator.validate_language_code!(from)
|
|
132
|
+
|
|
133
|
+
# Get provider
|
|
134
|
+
translation_provider = get_provider(provider, context)
|
|
135
|
+
|
|
136
|
+
# Translate to each language
|
|
137
|
+
to.each_with_object({}) do |lang_hash, result|
|
|
138
|
+
lang_code = lang_hash[:short_name]
|
|
139
|
+
lang_name = lang_hash[:name] || lang_code
|
|
140
|
+
|
|
141
|
+
Validator.validate_language_code!(lang_code)
|
|
142
|
+
result[lang_code] = translation_provider.translate_text(text, lang_code, lang_name)
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Translate multiple texts to multiple languages
|
|
147
|
+
#
|
|
148
|
+
# @param texts [Array<String>] Texts to translate
|
|
149
|
+
# @param from [String] Source language code
|
|
150
|
+
# @param to [Array<Hash>] Target languages
|
|
151
|
+
# @param provider [Symbol, nil] Provider to use
|
|
152
|
+
# @param context [String, nil] Optional translation context
|
|
153
|
+
# @return [Hash] Nested hash: { "it" => ["Ciao", "Arrivederci"], "fr" => [...] }
|
|
154
|
+
#
|
|
155
|
+
# @example
|
|
156
|
+
# languages = [
|
|
157
|
+
# { short_name: "it", name: "Italian" },
|
|
158
|
+
# { short_name: "fr", name: "French" }
|
|
159
|
+
# ]
|
|
160
|
+
# BetterTranslate.translate_texts_to_languages(
|
|
161
|
+
# ["Hello", "Goodbye"],
|
|
162
|
+
# from: "en",
|
|
163
|
+
# to: languages
|
|
164
|
+
# )
|
|
165
|
+
# #=> { "it" => ["Ciao", "Arrivederci"], "fr" => ["Bonjour", "Au revoir"] }
|
|
166
|
+
#
|
|
167
|
+
def self.translate_texts_to_languages(texts, from:, to:, provider: nil, context: nil)
|
|
168
|
+
raise ValidationError, "texts must be an Array" unless texts.is_a?(Array)
|
|
169
|
+
raise ValidationError, "texts cannot be empty" if texts.empty?
|
|
170
|
+
raise ValidationError, "to must be an Array of Hashes" unless to.is_a?(Array)
|
|
171
|
+
|
|
172
|
+
Validator.validate_language_code!(from)
|
|
173
|
+
texts.each { |text| Validator.validate_text!(text) }
|
|
174
|
+
|
|
175
|
+
# Get provider
|
|
176
|
+
translation_provider = get_provider(provider, context)
|
|
177
|
+
|
|
178
|
+
# Translate to each language
|
|
179
|
+
to.each_with_object({}) do |lang_hash, result|
|
|
180
|
+
lang_code = lang_hash[:short_name]
|
|
181
|
+
lang_name = lang_hash[:name] || lang_code
|
|
182
|
+
|
|
183
|
+
Validator.validate_language_code!(lang_code)
|
|
184
|
+
|
|
185
|
+
# Translate all texts for this language
|
|
186
|
+
result[lang_code] = texts.map do |text|
|
|
187
|
+
translation_provider.translate_text(text, lang_code, lang_name)
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
private_class_method def self.translate_single(text, target_lang, translation_provider)
|
|
193
|
+
# Use language code as name if needed
|
|
194
|
+
lang_name = target_lang.upcase
|
|
195
|
+
|
|
196
|
+
translation_provider.translate_text(text, target_lang, lang_name)
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
private_class_method def self.translate_multiple(text, target_langs, translation_provider)
|
|
200
|
+
target_langs.each_with_object({}) do |lang_code, result|
|
|
201
|
+
result[lang_code] = translate_single(text, lang_code, translation_provider)
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
private_class_method def self.get_provider(provider_symbol, context)
|
|
206
|
+
if provider_symbol
|
|
207
|
+
# Use specified provider with temporary config
|
|
208
|
+
config = build_temp_config(provider_symbol, context)
|
|
209
|
+
ProviderFactory.create(provider_symbol, config)
|
|
210
|
+
elsif BetterTranslate.configuration
|
|
211
|
+
# Use existing configuration
|
|
212
|
+
config = BetterTranslate.configuration
|
|
213
|
+
config.translation_context = context if context
|
|
214
|
+
ProviderFactory.create(config.provider, config)
|
|
215
|
+
else
|
|
216
|
+
raise ConfigurationError, "BetterTranslate not configured. Either configure it or specify a provider."
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
private_class_method def self.build_temp_config(provider_symbol, context)
|
|
221
|
+
config = Configuration.new
|
|
222
|
+
config.provider = provider_symbol
|
|
223
|
+
config.translation_context = context if context
|
|
224
|
+
|
|
225
|
+
# Set API keys from environment
|
|
226
|
+
case provider_symbol
|
|
227
|
+
when :chatgpt
|
|
228
|
+
config.openai_key = ENV["OPENAI_API_KEY"]
|
|
229
|
+
when :gemini
|
|
230
|
+
config.google_gemini_key = ENV["GEMINI_API_KEY"]
|
|
231
|
+
when :anthropic
|
|
232
|
+
config.anthropic_key = ENV["ANTHROPIC_API_KEY"]
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
# Set minimal required fields for validation
|
|
236
|
+
config.source_language = "en"
|
|
237
|
+
config.target_languages = [{ short_name: "it", name: "Italian" }]
|
|
238
|
+
config.input_file = "/tmp/dummy.yml" # Not used for direct translation
|
|
239
|
+
config.output_folder = "/tmp"
|
|
240
|
+
|
|
241
|
+
config
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
### 6.5.2 Aggiornare `lib/better_translate.rb`
|
|
248
|
+
|
|
249
|
+
Aggiungere dopo la sezione dei require:
|
|
250
|
+
|
|
251
|
+
```ruby
|
|
252
|
+
require_relative "better_translate/helpers"
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
E aggiungere i metodi convenience al modulo principale:
|
|
256
|
+
|
|
257
|
+
```ruby
|
|
258
|
+
module BetterTranslate
|
|
259
|
+
class << self
|
|
260
|
+
# ... existing methods ...
|
|
261
|
+
|
|
262
|
+
# Translate a text string directly
|
|
263
|
+
#
|
|
264
|
+
# @see Helpers.translate_text
|
|
265
|
+
def translate_text(text, from:, to:, provider: nil, context: nil)
|
|
266
|
+
Helpers.translate_text(text, from: from, to: to, provider: provider, context: context)
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
# Translate multiple texts directly
|
|
270
|
+
#
|
|
271
|
+
# @see Helpers.translate_texts
|
|
272
|
+
def translate_texts(texts, from:, to:, provider: nil, context: nil)
|
|
273
|
+
Helpers.translate_texts(texts, from: from, to: to, provider: provider, context: context)
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
# Translate text to multiple languages
|
|
277
|
+
#
|
|
278
|
+
# @see Helpers.translate_text_to_languages
|
|
279
|
+
def translate_text_to_languages(text, from:, to:, provider: nil, context: nil)
|
|
280
|
+
Helpers.translate_text_to_languages(text, from: from, to: to, provider: provider, context: context)
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
# Translate multiple texts to multiple languages
|
|
284
|
+
#
|
|
285
|
+
# @see Helpers.translate_texts_to_languages
|
|
286
|
+
def translate_texts_to_languages(texts, from:, to:, provider: nil, context: nil)
|
|
287
|
+
Helpers.translate_texts_to_languages(texts, from: from, to: to, provider: provider, context: context)
|
|
288
|
+
end
|
|
289
|
+
end
|
|
290
|
+
end
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
### 6.5.3 Test: `spec/better_translate/helpers_spec.rb`
|
|
294
|
+
|
|
295
|
+
```ruby
|
|
296
|
+
# frozen_string_literal: true
|
|
297
|
+
|
|
298
|
+
RSpec.describe BetterTranslate::Helpers do
|
|
299
|
+
describe ".translate_text" do
|
|
300
|
+
let(:config) { build_config }
|
|
301
|
+
|
|
302
|
+
before do
|
|
303
|
+
BetterTranslate.configure do |c|
|
|
304
|
+
c.provider = :chatgpt
|
|
305
|
+
c.openai_key = "test-key"
|
|
306
|
+
c.source_language = "en"
|
|
307
|
+
c.target_languages = [{ short_name: "it", name: "Italian" }]
|
|
308
|
+
c.input_file = create_temp_yaml("en" => { "test" => "test" })
|
|
309
|
+
c.output_folder = Dir.mktmpdir
|
|
310
|
+
end
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
context "with single target language" do
|
|
314
|
+
it "returns translated string", :vcr do
|
|
315
|
+
result = described_class.translate_text("Hello", from: "en", to: "it")
|
|
316
|
+
expect(result).to be_a(String)
|
|
317
|
+
expect(result).to eq("Ciao")
|
|
318
|
+
end
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
context "with multiple target languages" do
|
|
322
|
+
it "returns hash of translations", :vcr do
|
|
323
|
+
result = described_class.translate_text("Hello", from: "en", to: ["it", "fr"])
|
|
324
|
+
expect(result).to be_a(Hash)
|
|
325
|
+
expect(result.keys).to contain_exactly("it", "fr")
|
|
326
|
+
expect(result["it"]).to eq("Ciao")
|
|
327
|
+
expect(result["fr"]).to eq("Bonjour")
|
|
328
|
+
end
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
context "with custom provider" do
|
|
332
|
+
it "uses specified provider", :vcr do
|
|
333
|
+
result = described_class.translate_text(
|
|
334
|
+
"Hello",
|
|
335
|
+
from: "en",
|
|
336
|
+
to: "it",
|
|
337
|
+
provider: :gemini
|
|
338
|
+
)
|
|
339
|
+
expect(result).to eq("Ciao")
|
|
340
|
+
end
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
context "with context" do
|
|
344
|
+
it "includes context in translation", :vcr do
|
|
345
|
+
result = described_class.translate_text(
|
|
346
|
+
"The patient presents with symptoms",
|
|
347
|
+
from: "en",
|
|
348
|
+
to: "it",
|
|
349
|
+
context: "Medical terminology"
|
|
350
|
+
)
|
|
351
|
+
expect(result).to be_a(String)
|
|
352
|
+
end
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
it "validates text input" do
|
|
356
|
+
expect {
|
|
357
|
+
described_class.translate_text("", from: "en", to: "it")
|
|
358
|
+
}.to raise_error(BetterTranslate::ValidationError, /cannot be empty/)
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
it "validates language codes" do
|
|
362
|
+
expect {
|
|
363
|
+
described_class.translate_text("Hello", from: "invalid", to: "it")
|
|
364
|
+
}.to raise_error(BetterTranslate::ValidationError, /must be 2 letters/)
|
|
365
|
+
end
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
describe ".translate_texts" do
|
|
369
|
+
before do
|
|
370
|
+
BetterTranslate.configure do |c|
|
|
371
|
+
c.provider = :chatgpt
|
|
372
|
+
c.openai_key = "test-key"
|
|
373
|
+
c.source_language = "en"
|
|
374
|
+
c.target_languages = [{ short_name: "it", name: "Italian" }]
|
|
375
|
+
c.input_file = create_temp_yaml("en" => { "test" => "test" })
|
|
376
|
+
c.output_folder = Dir.mktmpdir
|
|
377
|
+
end
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
it "translates array of texts", :vcr do
|
|
381
|
+
result = described_class.translate_texts(
|
|
382
|
+
["Hello", "Goodbye"],
|
|
383
|
+
from: "en",
|
|
384
|
+
to: "it"
|
|
385
|
+
)
|
|
386
|
+
expect(result).to be_an(Array)
|
|
387
|
+
expect(result).to eq(["Ciao", "Arrivederci"])
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
it "validates input is array" do
|
|
391
|
+
expect {
|
|
392
|
+
described_class.translate_texts("Hello", from: "en", to: "it")
|
|
393
|
+
}.to raise_error(BetterTranslate::ValidationError, /must be an Array/)
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
it "validates array is not empty" do
|
|
397
|
+
expect {
|
|
398
|
+
described_class.translate_texts([], from: "en", to: "it")
|
|
399
|
+
}.to raise_error(BetterTranslate::ValidationError, /cannot be empty/)
|
|
400
|
+
end
|
|
401
|
+
end
|
|
402
|
+
|
|
403
|
+
describe ".translate_text_to_languages" do
|
|
404
|
+
let(:languages) do
|
|
405
|
+
[
|
|
406
|
+
{ short_name: "it", name: "Italian" },
|
|
407
|
+
{ short_name: "fr", name: "French" }
|
|
408
|
+
]
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
before do
|
|
412
|
+
BetterTranslate.configure do |c|
|
|
413
|
+
c.provider = :chatgpt
|
|
414
|
+
c.openai_key = "test-key"
|
|
415
|
+
c.source_language = "en"
|
|
416
|
+
c.target_languages = languages
|
|
417
|
+
c.input_file = create_temp_yaml("en" => { "test" => "test" })
|
|
418
|
+
c.output_folder = Dir.mktmpdir
|
|
419
|
+
end
|
|
420
|
+
end
|
|
421
|
+
|
|
422
|
+
it "translates to multiple languages", :vcr do
|
|
423
|
+
result = described_class.translate_text_to_languages(
|
|
424
|
+
"Hello",
|
|
425
|
+
from: "en",
|
|
426
|
+
to: languages
|
|
427
|
+
)
|
|
428
|
+
expect(result).to be_a(Hash)
|
|
429
|
+
expect(result.keys).to contain_exactly("it", "fr")
|
|
430
|
+
expect(result["it"]).to eq("Ciao")
|
|
431
|
+
expect(result["fr"]).to eq("Bonjour")
|
|
432
|
+
end
|
|
433
|
+
end
|
|
434
|
+
|
|
435
|
+
describe ".translate_texts_to_languages" do
|
|
436
|
+
let(:languages) do
|
|
437
|
+
[
|
|
438
|
+
{ short_name: "it", name: "Italian" },
|
|
439
|
+
{ short_name: "fr", name: "French" }
|
|
440
|
+
]
|
|
441
|
+
end
|
|
442
|
+
|
|
443
|
+
before do
|
|
444
|
+
BetterTranslate.configure do |c|
|
|
445
|
+
c.provider = :chatgpt
|
|
446
|
+
c.openai_key = "test-key"
|
|
447
|
+
c.source_language = "en"
|
|
448
|
+
c.target_languages = languages
|
|
449
|
+
c.input_file = create_temp_yaml("en" => { "test" => "test" })
|
|
450
|
+
c.output_folder = Dir.mktmpdir
|
|
451
|
+
end
|
|
452
|
+
end
|
|
453
|
+
|
|
454
|
+
it "translates multiple texts to multiple languages", :vcr do
|
|
455
|
+
result = described_class.translate_texts_to_languages(
|
|
456
|
+
["Hello", "Goodbye"],
|
|
457
|
+
from: "en",
|
|
458
|
+
to: languages
|
|
459
|
+
)
|
|
460
|
+
expect(result).to be_a(Hash)
|
|
461
|
+
expect(result["it"]).to eq(["Ciao", "Arrivederci"])
|
|
462
|
+
expect(result["fr"]).to eq(["Bonjour", "Au revoir"])
|
|
463
|
+
end
|
|
464
|
+
end
|
|
465
|
+
end
|
|
466
|
+
|
|
467
|
+
# Test module-level convenience methods
|
|
468
|
+
RSpec.describe BetterTranslate do
|
|
469
|
+
describe ".translate_text" do
|
|
470
|
+
it "delegates to Helpers.translate_text" do
|
|
471
|
+
expect(BetterTranslate::Helpers).to receive(:translate_text).with(
|
|
472
|
+
"Hello",
|
|
473
|
+
from: "en",
|
|
474
|
+
to: "it",
|
|
475
|
+
provider: nil,
|
|
476
|
+
context: nil
|
|
477
|
+
)
|
|
478
|
+
|
|
479
|
+
BetterTranslate.translate_text("Hello", from: "en", to: "it")
|
|
480
|
+
end
|
|
481
|
+
end
|
|
482
|
+
end
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
### 6.5.4 Example: `examples/direct_translation.rb`
|
|
486
|
+
|
|
487
|
+
```ruby
|
|
488
|
+
#!/usr/bin/env ruby
|
|
489
|
+
# frozen_string_literal: true
|
|
490
|
+
|
|
491
|
+
require "bundler/setup"
|
|
492
|
+
require "better_translate"
|
|
493
|
+
|
|
494
|
+
# Configure BetterTranslate
|
|
495
|
+
BetterTranslate.configure do |config|
|
|
496
|
+
config.provider = :chatgpt
|
|
497
|
+
config.openai_key = ENV["OPENAI_API_KEY"]
|
|
498
|
+
# Minimal configuration for direct translation
|
|
499
|
+
config.source_language = "en"
|
|
500
|
+
config.target_languages = [{ short_name: "it", name: "Italian" }]
|
|
501
|
+
config.input_file = "/tmp/dummy.yml"
|
|
502
|
+
config.output_folder = "/tmp"
|
|
503
|
+
end
|
|
504
|
+
|
|
505
|
+
puts "=" * 80
|
|
506
|
+
puts "Direct Translation Examples"
|
|
507
|
+
puts "=" * 80
|
|
508
|
+
|
|
509
|
+
# Example 1: Single text, single target
|
|
510
|
+
puts "\n1. Translate to single language:"
|
|
511
|
+
result = BetterTranslate.translate_text("Hello, world!", from: "en", to: "it")
|
|
512
|
+
puts " EN: Hello, world!"
|
|
513
|
+
puts " IT: #{result}"
|
|
514
|
+
|
|
515
|
+
# Example 2: Single text, multiple targets
|
|
516
|
+
puts "\n2. Translate to multiple languages:"
|
|
517
|
+
result = BetterTranslate.translate_text("Good morning", from: "en", to: ["it", "fr", "de"])
|
|
518
|
+
puts " EN: Good morning"
|
|
519
|
+
result.each do |lang, translation|
|
|
520
|
+
puts " #{lang.upcase}: #{translation}"
|
|
521
|
+
end
|
|
522
|
+
|
|
523
|
+
# Example 3: Multiple texts, single target
|
|
524
|
+
puts "\n3. Translate multiple texts:"
|
|
525
|
+
texts = ["Hello", "Goodbye", "Thank you"]
|
|
526
|
+
results = BetterTranslate.translate_texts(texts, from: "en", to: "it")
|
|
527
|
+
texts.each_with_index do |text, i|
|
|
528
|
+
puts " #{text} => #{results[i]}"
|
|
529
|
+
end
|
|
530
|
+
|
|
531
|
+
# Example 4: With translation context
|
|
532
|
+
puts "\n4. Translation with context (medical):"
|
|
533
|
+
result = BetterTranslate.translate_text(
|
|
534
|
+
"The patient presents with acute symptoms",
|
|
535
|
+
from: "en",
|
|
536
|
+
to: "it",
|
|
537
|
+
context: "Medical terminology for healthcare professionals"
|
|
538
|
+
)
|
|
539
|
+
puts " EN: The patient presents with acute symptoms"
|
|
540
|
+
puts " IT: #{result}"
|
|
541
|
+
|
|
542
|
+
# Example 5: Using language hashes (like in YAML translation)
|
|
543
|
+
puts "\n5. Translate with full language info:"
|
|
544
|
+
languages = [
|
|
545
|
+
{ short_name: "it", name: "Italian" },
|
|
546
|
+
{ short_name: "fr", name: "French" },
|
|
547
|
+
{ short_name: "de", name: "German" }
|
|
548
|
+
]
|
|
549
|
+
|
|
550
|
+
result = BetterTranslate.translate_text_to_languages(
|
|
551
|
+
"Welcome to our application",
|
|
552
|
+
from: "en",
|
|
553
|
+
to: languages
|
|
554
|
+
)
|
|
555
|
+
|
|
556
|
+
puts " EN: Welcome to our application"
|
|
557
|
+
result.each do |lang_code, translation|
|
|
558
|
+
lang_name = languages.find { |l| l[:short_name] == lang_code }[:name]
|
|
559
|
+
puts " #{lang_name} (#{lang_code}): #{translation}"
|
|
560
|
+
end
|
|
561
|
+
|
|
562
|
+
# Example 6: Without global configuration (using provider parameter)
|
|
563
|
+
puts "\n6. Direct translation without global config:"
|
|
564
|
+
BetterTranslate.reset! # Clear configuration
|
|
565
|
+
|
|
566
|
+
result = BetterTranslate.translate_text(
|
|
567
|
+
"Hello",
|
|
568
|
+
from: "en",
|
|
569
|
+
to: "it",
|
|
570
|
+
provider: :anthropic # Uses ENV['ANTHROPIC_API_KEY']
|
|
571
|
+
)
|
|
572
|
+
puts " Using Anthropic provider directly"
|
|
573
|
+
puts " Hello => #{result}"
|
|
574
|
+
|
|
575
|
+
puts "\n" + "=" * 80
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
---
|
|
579
|
+
|
|
580
|
+
---
|
|
581
|
+
|
|
582
|
+
[← Previous: 06-Main Module Api](./06-main_module_api.md) | [Back to Index](../../IMPLEMENTATION_PLAN.md) | [Next: 08-Rails Integration →](./08-rails_integration.md)
|