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.
Files changed (100) hide show
  1. checksums.yaml +4 -4
  2. data/.env.example +14 -0
  3. data/.rspec +3 -0
  4. data/.rubocop.yml +8 -0
  5. data/.yardopts +10 -0
  6. data/CHANGELOG.md +125 -114
  7. data/CLAUDE.md +385 -0
  8. data/README.md +629 -244
  9. data/Rakefile +7 -1
  10. data/Steepfile +29 -0
  11. data/docs/implementation/00-overview.md +220 -0
  12. data/docs/implementation/01-setup_dependencies.md +668 -0
  13. data/docs/implementation/02-error_handling.md +65 -0
  14. data/docs/implementation/03-core_components.md +457 -0
  15. data/docs/implementation/03.5-variable_preservation.md +509 -0
  16. data/docs/implementation/04-provider_architecture.md +571 -0
  17. data/docs/implementation/05-translation_logic.md +1065 -0
  18. data/docs/implementation/06-main_module_api.md +122 -0
  19. data/docs/implementation/07-direct_translation_helpers.md +582 -0
  20. data/docs/implementation/08-rails_integration.md +323 -0
  21. data/docs/implementation/09-testing_suite.md +228 -0
  22. data/docs/implementation/10-documentation_examples.md +150 -0
  23. data/docs/implementation/11-quality_security.md +65 -0
  24. data/docs/implementation/12-cli_standalone.md +698 -0
  25. data/exe/better_translate +9 -0
  26. data/lib/better_translate/cache.rb +125 -0
  27. data/lib/better_translate/cli.rb +304 -0
  28. data/lib/better_translate/configuration.rb +201 -0
  29. data/lib/better_translate/direct_translator.rb +131 -0
  30. data/lib/better_translate/errors.rb +101 -0
  31. data/lib/better_translate/progress_tracker.rb +157 -0
  32. data/lib/better_translate/provider_factory.rb +45 -0
  33. data/lib/better_translate/providers/anthropic_provider.rb +154 -0
  34. data/lib/better_translate/providers/base_http_provider.rb +239 -0
  35. data/lib/better_translate/providers/chatgpt_provider.rb +138 -44
  36. data/lib/better_translate/providers/gemini_provider.rb +123 -61
  37. data/lib/better_translate/railtie.rb +18 -0
  38. data/lib/better_translate/rate_limiter.rb +90 -0
  39. data/lib/better_translate/strategies/base_strategy.rb +58 -0
  40. data/lib/better_translate/strategies/batch_strategy.rb +56 -0
  41. data/lib/better_translate/strategies/deep_strategy.rb +45 -0
  42. data/lib/better_translate/strategies/strategy_selector.rb +43 -0
  43. data/lib/better_translate/translator.rb +115 -284
  44. data/lib/better_translate/utils/hash_flattener.rb +104 -0
  45. data/lib/better_translate/validator.rb +105 -0
  46. data/lib/better_translate/variable_extractor.rb +259 -0
  47. data/lib/better_translate/version.rb +2 -9
  48. data/lib/better_translate/yaml_handler.rb +168 -0
  49. data/lib/better_translate.rb +97 -73
  50. data/lib/generators/better_translate/analyze/USAGE +12 -0
  51. data/lib/generators/better_translate/analyze/analyze_generator.rb +94 -0
  52. data/lib/generators/better_translate/install/USAGE +13 -0
  53. data/lib/generators/better_translate/install/install_generator.rb +71 -0
  54. data/lib/generators/better_translate/install/templates/README +20 -0
  55. data/lib/generators/better_translate/install/templates/initializer.rb.tt +47 -0
  56. data/lib/generators/better_translate/translate/USAGE +13 -0
  57. data/lib/generators/better_translate/translate/translate_generator.rb +114 -0
  58. data/lib/tasks/better_translate.rake +136 -0
  59. data/sig/better_translate/cache.rbs +28 -0
  60. data/sig/better_translate/cli.rbs +24 -0
  61. data/sig/better_translate/configuration.rbs +78 -0
  62. data/sig/better_translate/direct_translator.rbs +18 -0
  63. data/sig/better_translate/errors.rbs +46 -0
  64. data/sig/better_translate/progress_tracker.rbs +29 -0
  65. data/sig/better_translate/provider_factory.rbs +8 -0
  66. data/sig/better_translate/providers/anthropic_provider.rbs +27 -0
  67. data/sig/better_translate/providers/base_http_provider.rbs +44 -0
  68. data/sig/better_translate/providers/chatgpt_provider.rbs +25 -0
  69. data/sig/better_translate/providers/gemini_provider.rbs +22 -0
  70. data/sig/better_translate/railtie.rbs +7 -0
  71. data/sig/better_translate/rate_limiter.rbs +20 -0
  72. data/sig/better_translate/strategies/base_strategy.rbs +19 -0
  73. data/sig/better_translate/strategies/batch_strategy.rbs +13 -0
  74. data/sig/better_translate/strategies/deep_strategy.rbs +11 -0
  75. data/sig/better_translate/strategies/strategy_selector.rbs +10 -0
  76. data/sig/better_translate/translator.rbs +24 -0
  77. data/sig/better_translate/utils/hash_flattener.rbs +14 -0
  78. data/sig/better_translate/validator.rbs +14 -0
  79. data/sig/better_translate/variable_extractor.rbs +40 -0
  80. data/sig/better_translate/version.rbs +4 -0
  81. data/sig/better_translate/yaml_handler.rbs +29 -0
  82. data/sig/better_translate.rbs +32 -2
  83. data/sig/faraday.rbs +22 -0
  84. data/sig/generators/better_translate/analyze/analyze_generator.rbs +18 -0
  85. data/sig/generators/better_translate/install/install_generator.rbs +14 -0
  86. data/sig/generators/better_translate/translate/translate_generator.rbs +10 -0
  87. data/sig/optparse.rbs +9 -0
  88. data/sig/psych.rbs +5 -0
  89. data/sig/rails.rbs +34 -0
  90. metadata +89 -203
  91. data/lib/better_translate/helper.rb +0 -83
  92. data/lib/better_translate/providers/base_provider.rb +0 -102
  93. data/lib/better_translate/service.rb +0 -144
  94. data/lib/better_translate/similarity_analyzer.rb +0 -218
  95. data/lib/better_translate/utils.rb +0 -55
  96. data/lib/better_translate/writer.rb +0 -75
  97. data/lib/generators/better_translate/analyze_generator.rb +0 -57
  98. data/lib/generators/better_translate/install_generator.rb +0 -14
  99. data/lib/generators/better_translate/templates/better_translate.rb +0 -56
  100. data/lib/generators/better_translate/translate_generator.rb +0 -84
metadata CHANGED
@@ -1,249 +1,135 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: better_translate
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
- - Alessio Bussolari
7
+ - alessiobussolari
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-03-11 00:00:00.000000000 Z
11
+ date: 2025-10-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: ruby-progressbar
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - "~>"
18
- - !ruby/object:Gem::Version
19
- version: '1.13'
20
- type: :runtime
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - "~>"
25
- - !ruby/object:Gem::Version
26
- version: '1.13'
27
- - !ruby/object:Gem::Dependency
28
- name: httparty
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - "~>"
32
- - !ruby/object:Gem::Version
33
- version: 0.21.0
34
- type: :runtime
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - "~>"
39
- - !ruby/object:Gem::Version
40
- version: 0.21.0
41
- - !ruby/object:Gem::Dependency
42
- name: zeitwerk
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - "~>"
46
- - !ruby/object:Gem::Version
47
- version: '2.6'
48
- type: :runtime
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - "~>"
53
- - !ruby/object:Gem::Version
54
- version: '2.6'
55
- - !ruby/object:Gem::Dependency
56
- name: bundler
14
+ name: faraday
57
15
  requirement: !ruby/object:Gem::Requirement
58
16
  requirements:
59
17
  - - "~>"
60
18
  - !ruby/object:Gem::Version
61
19
  version: '2.0'
62
- type: :development
20
+ type: :runtime
63
21
  prerelease: false
64
22
  version_requirements: !ruby/object:Gem::Requirement
65
23
  requirements:
66
24
  - - "~>"
67
25
  - !ruby/object:Gem::Version
68
26
  version: '2.0'
69
- - !ruby/object:Gem::Dependency
70
- name: rake
71
- requirement: !ruby/object:Gem::Requirement
72
- requirements:
73
- - - "~>"
74
- - !ruby/object:Gem::Version
75
- version: '13.0'
76
- type: :development
77
- prerelease: false
78
- version_requirements: !ruby/object:Gem::Requirement
79
- requirements:
80
- - - "~>"
81
- - !ruby/object:Gem::Version
82
- version: '13.0'
83
- - !ruby/object:Gem::Dependency
84
- name: rspec
85
- requirement: !ruby/object:Gem::Requirement
86
- requirements:
87
- - - "~>"
88
- - !ruby/object:Gem::Version
89
- version: '3.12'
90
- type: :development
91
- prerelease: false
92
- version_requirements: !ruby/object:Gem::Requirement
93
- requirements:
94
- - - "~>"
95
- - !ruby/object:Gem::Version
96
- version: '3.12'
97
- - !ruby/object:Gem::Dependency
98
- name: rubocop
99
- requirement: !ruby/object:Gem::Requirement
100
- requirements:
101
- - - "~>"
102
- - !ruby/object:Gem::Version
103
- version: '1.50'
104
- type: :development
105
- prerelease: false
106
- version_requirements: !ruby/object:Gem::Requirement
107
- requirements:
108
- - - "~>"
109
- - !ruby/object:Gem::Version
110
- version: '1.50'
111
- - !ruby/object:Gem::Dependency
112
- name: rubocop-rake
113
- requirement: !ruby/object:Gem::Requirement
114
- requirements:
115
- - - "~>"
116
- - !ruby/object:Gem::Version
117
- version: '0.6'
118
- type: :development
119
- prerelease: false
120
- version_requirements: !ruby/object:Gem::Requirement
121
- requirements:
122
- - - "~>"
123
- - !ruby/object:Gem::Version
124
- version: '0.6'
125
- - !ruby/object:Gem::Dependency
126
- name: rubocop-rspec
127
- requirement: !ruby/object:Gem::Requirement
128
- requirements:
129
- - - "~>"
130
- - !ruby/object:Gem::Version
131
- version: '2.22'
132
- type: :development
133
- prerelease: false
134
- version_requirements: !ruby/object:Gem::Requirement
135
- requirements:
136
- - - "~>"
137
- - !ruby/object:Gem::Version
138
- version: '2.22'
139
- - !ruby/object:Gem::Dependency
140
- name: yard
141
- requirement: !ruby/object:Gem::Requirement
142
- requirements:
143
- - - "~>"
144
- - !ruby/object:Gem::Version
145
- version: '0.9'
146
- type: :development
147
- prerelease: false
148
- version_requirements: !ruby/object:Gem::Requirement
149
- requirements:
150
- - - "~>"
151
- - !ruby/object:Gem::Version
152
- version: '0.9'
153
- - !ruby/object:Gem::Dependency
154
- name: simplecov
155
- requirement: !ruby/object:Gem::Requirement
156
- requirements:
157
- - - "~>"
158
- - !ruby/object:Gem::Version
159
- version: '0.22'
160
- type: :development
161
- prerelease: false
162
- version_requirements: !ruby/object:Gem::Requirement
163
- requirements:
164
- - - "~>"
165
- - !ruby/object:Gem::Version
166
- version: '0.22'
167
- - !ruby/object:Gem::Dependency
168
- name: webmock
169
- requirement: !ruby/object:Gem::Requirement
170
- requirements:
171
- - - "~>"
172
- - !ruby/object:Gem::Version
173
- version: '3.19'
174
- type: :development
175
- prerelease: false
176
- version_requirements: !ruby/object:Gem::Requirement
177
- requirements:
178
- - - "~>"
179
- - !ruby/object:Gem::Version
180
- version: '3.19'
181
- - !ruby/object:Gem::Dependency
182
- name: dotenv
183
- requirement: !ruby/object:Gem::Requirement
184
- requirements:
185
- - - "~>"
186
- - !ruby/object:Gem::Version
187
- version: '2.8'
188
- type: :development
189
- prerelease: false
190
- version_requirements: !ruby/object:Gem::Requirement
191
- requirements:
192
- - - "~>"
193
- - !ruby/object:Gem::Version
194
- version: '2.8'
195
- description: |
196
- BetterTranslate is a powerful Ruby gem for translating YAML files using AI providers.
197
-
198
- Key features:
199
- * Multiple AI providers support (ChatGPT and Google Gemini)
200
- * Smart translation modes (override/incremental)
201
- * LRU caching for performance
202
- * Precise key exclusion control
203
- * Rails integration with generators
204
- * Progress tracking with progress bar
205
- * Comprehensive test coverage
206
-
207
- Perfect for internationalizing Ruby/Rails applications with minimal effort.
27
+ description: Automatically translate YAML locale files using AI providers (ChatGPT,
28
+ Gemini, Claude). Features intelligent caching, batch processing, and Rails integration.
208
29
  email:
209
30
  - alessio.bussolari@pandev.it
210
- executables: []
31
+ executables:
32
+ - better_translate
211
33
  extensions: []
212
34
  extra_rdoc_files: []
213
35
  files:
36
+ - ".env.example"
37
+ - ".rspec"
38
+ - ".rubocop.yml"
39
+ - ".yardopts"
214
40
  - CHANGELOG.md
41
+ - CLAUDE.md
215
42
  - CODE_OF_CONDUCT.md
216
43
  - LICENSE.txt
217
44
  - README.md
218
45
  - Rakefile
46
+ - Steepfile
47
+ - docs/implementation/00-overview.md
48
+ - docs/implementation/01-setup_dependencies.md
49
+ - docs/implementation/02-error_handling.md
50
+ - docs/implementation/03-core_components.md
51
+ - docs/implementation/03.5-variable_preservation.md
52
+ - docs/implementation/04-provider_architecture.md
53
+ - docs/implementation/05-translation_logic.md
54
+ - docs/implementation/06-main_module_api.md
55
+ - docs/implementation/07-direct_translation_helpers.md
56
+ - docs/implementation/08-rails_integration.md
57
+ - docs/implementation/09-testing_suite.md
58
+ - docs/implementation/10-documentation_examples.md
59
+ - docs/implementation/11-quality_security.md
60
+ - docs/implementation/12-cli_standalone.md
61
+ - exe/better_translate
219
62
  - lib/better_translate.rb
220
- - lib/better_translate/helper.rb
221
- - lib/better_translate/providers/base_provider.rb
63
+ - lib/better_translate/cache.rb
64
+ - lib/better_translate/cli.rb
65
+ - lib/better_translate/configuration.rb
66
+ - lib/better_translate/direct_translator.rb
67
+ - lib/better_translate/errors.rb
68
+ - lib/better_translate/progress_tracker.rb
69
+ - lib/better_translate/provider_factory.rb
70
+ - lib/better_translate/providers/anthropic_provider.rb
71
+ - lib/better_translate/providers/base_http_provider.rb
222
72
  - lib/better_translate/providers/chatgpt_provider.rb
223
73
  - lib/better_translate/providers/gemini_provider.rb
224
- - lib/better_translate/service.rb
225
- - lib/better_translate/similarity_analyzer.rb
74
+ - lib/better_translate/railtie.rb
75
+ - lib/better_translate/rate_limiter.rb
76
+ - lib/better_translate/strategies/base_strategy.rb
77
+ - lib/better_translate/strategies/batch_strategy.rb
78
+ - lib/better_translate/strategies/deep_strategy.rb
79
+ - lib/better_translate/strategies/strategy_selector.rb
226
80
  - lib/better_translate/translator.rb
227
- - lib/better_translate/utils.rb
81
+ - lib/better_translate/utils/hash_flattener.rb
82
+ - lib/better_translate/validator.rb
83
+ - lib/better_translate/variable_extractor.rb
228
84
  - lib/better_translate/version.rb
229
- - lib/better_translate/writer.rb
230
- - lib/generators/better_translate/analyze_generator.rb
231
- - lib/generators/better_translate/install_generator.rb
232
- - lib/generators/better_translate/templates/better_translate.rb
233
- - lib/generators/better_translate/translate_generator.rb
85
+ - lib/better_translate/yaml_handler.rb
86
+ - lib/generators/better_translate/analyze/USAGE
87
+ - lib/generators/better_translate/analyze/analyze_generator.rb
88
+ - lib/generators/better_translate/install/USAGE
89
+ - lib/generators/better_translate/install/install_generator.rb
90
+ - lib/generators/better_translate/install/templates/README
91
+ - lib/generators/better_translate/install/templates/initializer.rb.tt
92
+ - lib/generators/better_translate/translate/USAGE
93
+ - lib/generators/better_translate/translate/translate_generator.rb
94
+ - lib/tasks/better_translate.rake
234
95
  - sig/better_translate.rbs
96
+ - sig/better_translate/cache.rbs
97
+ - sig/better_translate/cli.rbs
98
+ - sig/better_translate/configuration.rbs
99
+ - sig/better_translate/direct_translator.rbs
100
+ - sig/better_translate/errors.rbs
101
+ - sig/better_translate/progress_tracker.rbs
102
+ - sig/better_translate/provider_factory.rbs
103
+ - sig/better_translate/providers/anthropic_provider.rbs
104
+ - sig/better_translate/providers/base_http_provider.rbs
105
+ - sig/better_translate/providers/chatgpt_provider.rbs
106
+ - sig/better_translate/providers/gemini_provider.rbs
107
+ - sig/better_translate/railtie.rbs
108
+ - sig/better_translate/rate_limiter.rbs
109
+ - sig/better_translate/strategies/base_strategy.rbs
110
+ - sig/better_translate/strategies/batch_strategy.rbs
111
+ - sig/better_translate/strategies/deep_strategy.rbs
112
+ - sig/better_translate/strategies/strategy_selector.rbs
113
+ - sig/better_translate/translator.rbs
114
+ - sig/better_translate/utils/hash_flattener.rbs
115
+ - sig/better_translate/validator.rbs
116
+ - sig/better_translate/variable_extractor.rbs
117
+ - sig/better_translate/version.rbs
118
+ - sig/better_translate/yaml_handler.rbs
119
+ - sig/faraday.rbs
120
+ - sig/generators/better_translate/analyze/analyze_generator.rbs
121
+ - sig/generators/better_translate/install/install_generator.rbs
122
+ - sig/generators/better_translate/translate/translate_generator.rbs
123
+ - sig/optparse.rbs
124
+ - sig/psych.rbs
125
+ - sig/rails.rbs
235
126
  homepage: https://github.com/alessiobussolari/better_translate
236
127
  licenses:
237
128
  - MIT
238
129
  metadata:
239
- allowed_push_host: https://rubygems.org
240
- rubygems_mfa_required: 'true'
241
130
  homepage_uri: https://github.com/alessiobussolari/better_translate
242
131
  source_code_uri: https://github.com/alessiobussolari/better_translate
243
132
  changelog_uri: https://github.com/alessiobussolari/better_translate/blob/main/CHANGELOG.md
244
- bug_tracker_uri: https://github.com/alessiobussolari/better_translate/issues
245
- documentation_uri: https://github.com/alessiobussolari/better_translate
246
- wiki_uri: https://github.com/alessiobussolari/better_translate/wiki
247
133
  post_install_message:
248
134
  rdoc_options: []
249
135
  require_paths:
@@ -262,5 +148,5 @@ requirements: []
262
148
  rubygems_version: 3.5.11
263
149
  signing_key:
264
150
  specification_version: 4
265
- summary: AI-powered YAML translation with ChatGPT and Google Gemini
151
+ summary: AI-powered YAML locale file translator for Rails and Ruby projects
266
152
  test_files: []
@@ -1,83 +0,0 @@
1
- module BetterTranslate
2
- # Helper class that provides utility methods for translating text and arrays of text
3
- # to multiple target languages using different translation providers.
4
- # This class simplifies the process of translating content by abstracting away
5
- # the provider-specific implementation details.
6
- #
7
- # @example Translating a single text to multiple languages
8
- # BetterTranslate::Helper.translate_text_to_languages(
9
- # "Hello world!",
10
- # [{ short_name: "it", name: "Italian" }, { short_name: "fr", name: "French" }],
11
- # "en",
12
- # :chatgpt
13
- # )
14
- class Helper
15
- class << self
16
- # Translates a given text into multiple target languages.
17
- #
18
- # @param text [String] The text to be translated.
19
- # @param target_languages [Array<Hash>] Array of target language hashes,
20
- # e.g. [{ short_name: "it", name: "Italian" }, { short_name: "fr", name: "French" }]
21
- # @param source_language [String] The source language code (e.g., "en").
22
- # @param provider_name [Symbol] The provider to use (e.g., :chatgpt or :gemini).
23
- # @return [Hash] A hash where each key is a target language code and the value is the translated text.
24
- #
25
- # Example:
26
- # translated = BetterTranslate::Helpers.translate_text_to_languages(
27
- # "Hello world!",
28
- # [{ short_name: "it", name: "Italian" }, { short_name: "fr", name: "French" }],
29
- # "en",
30
- # :chatgpt
31
- # )
32
- # # => { "it" => "Ciao mondo!", "fr" => "Bonjour le monde!" }
33
- def translate_text_to_languages(text, target_languages, source_language, provider_name)
34
- provider_instance = case provider_name
35
- when :chatgpt
36
- Providers::ChatgptProvider.new(BetterTranslate.configuration.openai_key)
37
- when :gemini
38
- Providers::GeminiProvider.new(BetterTranslate.configuration.google_gemini_key)
39
- else
40
- raise "Provider not supported: #{provider_name}"
41
- end
42
-
43
- result = {}
44
- target_languages.each do |lang|
45
- # Optionally, you could also pass the source_language if needed by your provider.
46
- translated_text = provider_instance.translate(text, lang[:short_name], lang[:name])
47
- result[lang[:short_name]] = translated_text
48
- end
49
- result
50
- end
51
-
52
- # Translates an array of texts into multiple target languages using the translate_text_to_languages helper.
53
- #
54
- # @param texts [Array<String>] An array of texts to translate.
55
- # @param target_languages [Array<Hash>] Array of target language hashes,
56
- # e.g. [{ short_name: "it", name: "Italian" }, { short_name: "fr", name: "French" }].
57
- # @param source_language [String] The source language code (e.g., "en").
58
- # @param provider_name [Symbol] The provider to use (e.g., :chatgpt or :gemini).
59
- # @return [Hash] A hash where each key is a target language code and the value is an array of translated texts.
60
- #
61
- # Example:
62
- # texts = ["Hello world!", "How are you?"]
63
- # result = BetterTranslate::TranslationHelper.translate_texts_to_languages(
64
- # texts,
65
- # [{ short_name: "it", name: "Italian" }, { short_name: "fr", name: "French" }],
66
- # "en",
67
- # :chatgpt
68
- # )
69
- # # => { "it" => ["Ciao mondo!", "Come stai?"], "fr" => ["Bonjour le monde!", "Comment ça va?"] }
70
- def translate_texts_to_languages(texts, target_languages, source_language, provider_name)
71
- result = {}
72
- target_languages.each do |lang|
73
- # For each target language, translate each text and collect translations into an array.
74
- result[lang[:short_name]] = texts.map do |text|
75
- translation_hash = translate_text_to_languages(text, [lang], source_language, provider_name)
76
- translation_hash[lang[:short_name]]
77
- end
78
- end
79
- result
80
- end
81
- end
82
- end
83
- end
@@ -1,102 +0,0 @@
1
- module BetterTranslate
2
- module Providers
3
- # Abstract base class for translation providers.
4
- # Provides common functionality and defines the interface that all providers must implement.
5
- # Handles rate limiting, input validation, and retry logic for failed translations.
6
- #
7
- # @abstract Subclass and override {#translate_text} to implement a provider
8
- class BaseProvider
9
- # Number of retry attempts for failed translations
10
- MAX_RETRIES = 3
11
-
12
- # Delay in seconds between retry attempts
13
- RETRY_DELAY = 2 # seconds
14
-
15
- # Initializes a new provider instance with the specified API key.
16
- #
17
- # @param api_key [String] The API key for the translation service
18
- # @return [BaseProvider] A new instance of the provider
19
- def initialize(api_key)
20
- @api_key = api_key
21
- @last_request_time = Time.now - 1
22
- end
23
-
24
- private
25
-
26
- # Implements a simple rate limiting mechanism to prevent overloading the API.
27
- # Ensures at least 0.5 seconds between consecutive requests.
28
- #
29
- # @return [void]
30
- def rate_limit
31
- time_since_last_request = Time.now - @last_request_time
32
- sleep(0.5 - time_since_last_request) if time_since_last_request < 0.5
33
- @last_request_time = Time.now
34
- end
35
-
36
- # Validates the input parameters for translation.
37
- # Ensures that the text is not empty and the language code is in a valid format.
38
- #
39
- # @param text [String] The text to translate
40
- # @param target_lang_code [String] The target language code (e.g., "en", "fr-FR")
41
- # @raise [ArgumentError] If the text is empty or the language code is invalid
42
- # @return [void]
43
- def validate_input(text, target_lang_code)
44
- raise ArgumentError, "Text cannot be empty" if text.nil? || text.strip.empty?
45
- raise ArgumentError, "Invalid target language code" unless target_lang_code.match?(/^[a-z]{2}(-[A-Z]{2})?$/)
46
- end
47
-
48
- # Public method to translate text with built-in retry logic.
49
- # Attempts to translate the text and retries on failure up to MAX_RETRIES times.
50
- #
51
- # @param text [String] The text to translate
52
- # @param target_lang_code [String] The target language code (e.g., "en")
53
- # @param target_lang_name [String] The target language name (e.g., "English")
54
- # @return [String] The translated text
55
- # @raise [StandardError] If translation fails after all retry attempts
56
- def translate(text, target_lang_code, target_lang_name)
57
- retries = 0
58
- begin
59
- perform_translation(text, target_lang_code, target_lang_name)
60
- rescue StandardError => e
61
- retries += 1
62
- if retries <= MAX_RETRIES
63
- message = "Translation attempt #{retries} failed. Retrying in #{RETRY_DELAY} seconds..."
64
- BetterTranslate::Utils.logger(message: message)
65
- sleep(RETRY_DELAY)
66
- retry
67
- else
68
- message = "Translation failed after #{MAX_RETRIES} attempts: #{e.message}"
69
- BetterTranslate::Utils.logger(message: message)
70
- raise
71
- end
72
- end
73
- end
74
-
75
- # Performs the actual translation process.
76
- # Validates input, applies rate limiting, and calls the provider-specific translation method.
77
- #
78
- # @param text [String] The text to translate
79
- # @param target_lang_code [String] The target language code
80
- # @param target_lang_name [String] The target language name
81
- # @return [String] The translated text
82
- def perform_translation(text, target_lang_code, target_lang_name)
83
- validate_input(text, target_lang_code)
84
- rate_limit
85
- translate_text(text, target_lang_code, target_lang_name)
86
- end
87
-
88
- # Provider-specific implementation of the translation logic.
89
- # Must be overridden by subclasses to implement the actual translation.
90
- #
91
- # @abstract
92
- # @param text [String] The text to translate
93
- # @param target_lang_code [String] The target language code
94
- # @param target_lang_name [String] The target language name
95
- # @return [String] The translated text
96
- # @raise [NotImplementedError] If the method is not overridden by a subclass
97
- def translate_text(text, target_lang_code, target_lang_name)
98
- raise NotImplementedError, "The provider #{self.class} must implement the translate_text method"
99
- end
100
- end
101
- end
102
- end
@@ -1,144 +0,0 @@
1
- module BetterTranslate
2
- # Service class that handles translation requests using the configured provider.
3
- # Implements a Least Recently Used (LRU) cache to avoid redundant translation requests.
4
- # Supports built-in providers (ChatGPT, Gemini) and custom providers registered via
5
- # the register_provider class method.
6
- #
7
- # @example
8
- # service = BetterTranslate::Service.new
9
- # translated_text = service.translate("Hello world", "fr", "French")
10
- class Service
11
- # Maximum number of translations to keep in the LRU cache
12
- MAX_CACHE_SIZE = 1000
13
-
14
- # Registry for custom providers
15
- @@provider_registry = {}
16
-
17
- # Initializes a new Service instance.
18
- # Sets up the translation provider based on configuration and initializes the LRU cache.
19
- #
20
- # @return [BetterTranslate::Service] A new Service instance
21
- def initialize
22
- @provider_name = BetterTranslate.configuration.provider
23
- @translation_cache = {}
24
- @cache_order = []
25
- end
26
-
27
- # Translates text using the configured provider with caching support.
28
- # First checks if the translation is already in the cache. If not, it uses the
29
- # provider to translate the text and then caches the result for future use.
30
- # Also tracks metrics about the translation request duration.
31
- #
32
- # @param text [String] The text to translate
33
- # @param target_lang_code [String] The target language code (e.g., 'fr', 'es')
34
- # @param target_lang_name [String] The target language name (e.g., 'French', 'Spanish')
35
- # @return [String] The translated text
36
- def translate(text, target_lang_code, target_lang_name)
37
- cache_key = "#{text}:#{target_lang_code}"
38
-
39
- # Prova a recuperare dalla cache
40
- cached = cache_get(cache_key)
41
- return cached if cached
42
-
43
- # Traduci e salva in cache
44
- start_time = Time.now
45
- result = provider_instance.translate_text(text, target_lang_code, target_lang_name)
46
- duration = Time.now - start_time
47
-
48
- BetterTranslate::Utils.track_metric("translation_request_duration", {
49
- provider: @provider_name,
50
- text_length: text.length,
51
- duration: duration
52
- })
53
-
54
- cache_set(cache_key, result)
55
- end
56
-
57
- private
58
-
59
- # Retrieves a translation from the LRU cache if it exists.
60
- # Updates the cache order to mark this key as most recently used.
61
- #
62
- # @param key [String] The cache key in the format "text:target_lang_code"
63
- # @return [String, nil] The cached translation or nil if not found
64
- def cache_get(key)
65
- if @translation_cache.key?(key)
66
- # Aggiorna l'ordine LRU
67
- @cache_order.delete(key)
68
- @cache_order.push(key)
69
- @translation_cache[key]
70
- end
71
- end
72
-
73
- # Stores a translation in the LRU cache.
74
- # If the cache is full, removes the least recently used item before adding the new one.
75
- #
76
- # @param key [String] The cache key in the format "text:target_lang_code"
77
- # @param value [String] The translated text to cache
78
- # @return [String] The value that was cached
79
- def cache_set(key, value)
80
- if @translation_cache.size >= MAX_CACHE_SIZE
81
- # Rimuovi l'elemento meno recentemente usato
82
- oldest_key = @cache_order.shift
83
- @translation_cache.delete(oldest_key)
84
- end
85
-
86
- @translation_cache[key] = value
87
- @cache_order.push(key)
88
- value
89
- end
90
-
91
-
92
-
93
- # Creates or returns a cached instance of the translation provider.
94
- # The provider is determined by the configuration and instantiated with the appropriate API key.
95
- # Supports built-in providers (ChatGPT, Gemini) and custom providers registered via
96
- # the register_provider class method.
97
- #
98
- # @return [BetterTranslate::Providers::BaseProvider] An instance of the configured translation provider
99
- # @raise [RuntimeError] If the configured provider is not supported
100
- def provider_instance
101
- @provider_instance ||= case @provider_name
102
- when :chatgpt
103
- Providers::ChatgptProvider.new(BetterTranslate.configuration.openai_key)
104
- when :gemini
105
- Providers::GeminiProvider.new(BetterTranslate.configuration.google_gemini_key)
106
- else
107
- if @@provider_registry.key?(@provider_name)
108
- # Get the API key from configuration dynamically
109
- api_key_method = "#{@provider_name}_key".to_sym
110
- if BetterTranslate.configuration.respond_to?(api_key_method)
111
- api_key = BetterTranslate.configuration.send(api_key_method)
112
- @@provider_registry[@provider_name].call(api_key)
113
- else
114
- raise "API key configuration missing for provider: #{@provider_name}. Add config.#{@provider_name}_key to your BetterTranslate configuration."
115
- end
116
- else
117
- raise "Provider not supported: #{@provider_name}. Available providers: #{available_providers.join(', ')}"
118
- end
119
- end
120
- end
121
-
122
- # Returns a list of all available provider names
123
- # @return [Array<Symbol>] List of available provider names
124
- def available_providers
125
- [:chatgpt, :gemini] + @@provider_registry.keys
126
- end
127
-
128
- # Registers a custom provider for use with BetterTranslate
129
- #
130
- # @param name [Symbol] The name of the provider to register
131
- # @param factory [Proc] A proc that takes an API key and returns a provider instance
132
- # @return [Symbol] The name of the registered provider
133
- #
134
- # @example Register a custom DeepL provider
135
- # BetterTranslate::Service.register_provider(
136
- # :deepl,
137
- # ->(api_key) { Providers::DeepLProvider.new(api_key) }
138
- # )
139
- def self.register_provider(name, factory)
140
- @@provider_registry[name] = factory
141
- name
142
- end
143
- end
144
- end