better_translate 0.4.1 โ†’ 0.5.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cf5e2fea9a6f6702124591e888a922072386b7dc3eca153008828448eae45357
4
- data.tar.gz: 5c11456be0bda2b86ea385235a769d405af58233c096d306f710d451eecf6f2a
3
+ metadata.gz: '08c6af60f2eb66076d7f9fe0e603d13ae497f637380fba4a261961d570097312'
4
+ data.tar.gz: 7973a4254e17ae0a714e7fea96124916ffd27539e1610ef9ad1409627854af3b
5
5
  SHA512:
6
- metadata.gz: 8ca97b7c5796b961e80de8a3247aecaf7e781965dfed89197977be2fc59326f8800d6a4b82ed91fbff592c82958b23088e09493d50c42f82458a697628a2a607
7
- data.tar.gz: 6404f6b92fa55708db6127ade139d6f3f12170ef5c6617553ccbe383be3cb0971d31b2f956b9ac1bf0f8b4bc65bc46f702f55be13cc49cfc11541af319c72811
6
+ metadata.gz: 0bee7264dfe205b313a3326602c81590cda09ab283fc6a98145452c14c8d4ef4f756f1a2cfae6836c88a8fb9240f35549b7a8dc4ffdeb782d7fcfbc34d738bb0
7
+ data.tar.gz: ac9dc01081105b202448ac4dd0cec510b8f7bff7eb72426a009650a756cd9ff74ca97b2916fa569f14569f9963d3ce0798f7dadbfc1b7411e51ea975b645dd0a
data/CHANGELOG.md CHANGED
@@ -5,6 +5,37 @@ All notable changes to BetterTranslate will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.5.0] - 2025-03-12
9
+
10
+ ### Added
11
+ - Custom Translation Provider support:
12
+ - New generator: `rails generate better_translate:provider YourProviderName`
13
+ - Provider registration system via `BetterTranslate::Service.register_provider`
14
+ - Dynamic API key configuration for custom providers
15
+ - Comprehensive documentation and examples for creating custom providers
16
+ - Template files with implementation instructions
17
+ - Enhanced Service class:
18
+ - Provider registry for managing custom providers
19
+ - Improved error handling with better error messages
20
+ - Method to list all available providers
21
+ - Updated documentation:
22
+ - New section in README for custom providers
23
+ - Step-by-step guide for implementing custom translation services
24
+ - Example implementation for DeepL integration
25
+
26
+ ## [0.4.2] - 2025-03-11
27
+
28
+ ### Added
29
+ - Comprehensive YARD-style documentation across the entire codebase:
30
+ - Added detailed class and method documentation for all core components
31
+ - Added parameter and return type documentation
32
+ - Added usage examples for key classes and methods
33
+ - Improved inline comments for complex logic
34
+
35
+ ### Changed
36
+ - Improved code readability and maintainability through better documentation
37
+ - Enhanced developer experience with clearer API documentation
38
+
8
39
  ## [0.4.1] - 2025-03-11
9
40
 
10
41
  ### Fixed
data/README.md CHANGED
@@ -15,10 +15,11 @@ BetterTranslate simplifies the translation of YAML files in your Ruby/Rails appl
15
15
  - ๐Ÿงช Comprehensive test coverage
16
16
  - โšก๏ธ LRU caching for performance
17
17
  - ๐Ÿ” Translation similarity analysis
18
+ - ๐Ÿ“š Extensive YARD documentation
18
19
 
19
20
  ## Why BetterTranslate? ๐Ÿค”
20
21
 
21
- - ๐ŸŒ **AI-Powered Translation**: Leverage ChatGPT and Google Gemini for high-quality translations
22
+ - ๐ŸŒ **AI-Powered Translation**: Leverage ChatGPT, Google Gemini, and custom providers for high-quality translations
22
23
  - ๐Ÿ”„ **Smart Translation Modes**:
23
24
  - `Override`: Full file rewrite
24
25
  - `Incremental`: Update only new/modified keys
@@ -28,9 +29,11 @@ BetterTranslate simplifies the translation of YAML files in your Ruby/Rails appl
28
29
  - Dot notation support
29
30
  - ๐Ÿ›  **Developer-Friendly**:
30
31
  - Rails generators included
32
+ - Custom provider support
31
33
  - Comprehensive test suite
32
34
  - LRU caching for performance
33
35
  - Progress tracking
36
+ - Detailed YARD documentation
34
37
  - ๐Ÿ” **Translation Analysis**:
35
38
  - Similarity detection
36
39
  - Detailed reports
@@ -45,7 +48,7 @@ BetterTranslate simplifies the translation of YAML files in your Ruby/Rails appl
45
48
  Add the gem to your Gemfile:
46
49
 
47
50
  ```ruby
48
- gem 'better_translate', '~> 0.1.0'
51
+ gem 'better_translate', '~> 0.5.0'
49
52
  ```
50
53
 
51
54
  Then run:
@@ -157,6 +160,12 @@ The gem includes generators to simplify tasks:
157
160
  rails generate better_translate:analyze
158
161
  ```
159
162
 
163
+ - **Create Custom Provider:**
164
+
165
+ ```bash
166
+ rails generate better_translate:provider YourProviderName
167
+ ```
168
+
160
169
  The `better_translate:analyze` generator will:
161
170
  - Scan all YAML files in your locales directory
162
171
  - Find similar translations using Levenshtein distance
@@ -170,6 +179,76 @@ The gem includes generators to simplify tasks:
170
179
  - Optimize your translation files
171
180
  - Reduce translation costs
172
181
 
182
+ ### Custom Translation Providers
183
+
184
+ BetterTranslate allows you to create and use custom translation providers, enabling integration with any translation API or service. This feature is particularly useful if you want to use a translation service not natively supported by BetterTranslate, such as DeepL, Microsoft Translator, Amazon Translate, or any other API-based translation service.
185
+
186
+ 1. **Generate a custom provider template:**
187
+
188
+ ```bash
189
+ rails generate better_translate:provider DeepL
190
+ ```
191
+
192
+ This creates a new provider class in `app/providers/deep_l_provider.rb` and a README with implementation instructions.
193
+
194
+ 2. **Implement the translation logic:**
195
+
196
+ Edit the generated provider file to implement the `translate_text` method with your API-specific code:
197
+
198
+ ```ruby
199
+ # app/providers/deep_l_provider.rb
200
+ module Providers
201
+ class DeepLProvider < BetterTranslate::Providers::BaseProvider
202
+ def initialize(api_key)
203
+ @api_key = api_key
204
+ end
205
+
206
+ def translate_text(text, target_lang_code, target_lang_name)
207
+ # Implement your API call to DeepL here
208
+ # Return the translated text as a string
209
+ end
210
+ end
211
+ end
212
+ ```
213
+
214
+ 3. **Register your provider:**
215
+
216
+ Create an initializer to register your custom provider:
217
+
218
+ ```ruby
219
+ # config/initializers/better_translate_providers.rb
220
+ # Require the provider file to ensure it's loaded
221
+ require Rails.root.join('app', 'providers', 'deep_l_provider')
222
+
223
+ BetterTranslate::Service.register_provider(
224
+ :deepl,
225
+ ->(api_key) { Providers::DeepLProvider.new(api_key) }
226
+ )
227
+ ```
228
+
229
+ Note: The `require` statement is important to ensure the provider class is loaded before it's used.
230
+
231
+ 4. **Update your configuration:**
232
+
233
+ Add your API key to the BetterTranslate configuration:
234
+
235
+ ```ruby
236
+ # config/initializers/better_translate.rb
237
+ BetterTranslate.configure do |config|
238
+ config.provider = :deepl
239
+ config.deepl_key = ENV['DEEPL_API_KEY']
240
+ # ... other configuration
241
+ end
242
+ ```
243
+
244
+ 5. **Use your custom provider:**
245
+
246
+ Your custom provider will now be used when you call `BetterTranslate.magic` or any other translation method.
247
+
248
+ 6. **Troubleshooting:**
249
+
250
+ If you encounter a `NameError` related to the `Providers` module, make sure the provider file is properly required in your initializer as shown in step 3. The `require` statement ensures that the provider class is loaded before it's used.
251
+
173
252
  ### Translation Helpers
174
253
 
175
254
  BetterTranslate provides helper methods to simplify translation tasks.
@@ -1,4 +1,16 @@
1
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
+ # )
2
14
  class Helper
3
15
  class << self
4
16
  # Translates a given text into multiple target languages.
@@ -1,6 +1,21 @@
1
1
  module BetterTranslate
2
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
3
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
4
19
  def initialize(api_key)
5
20
  @api_key = api_key
6
21
  @last_request_time = Time.now - 1
@@ -8,25 +23,36 @@ module BetterTranslate
8
23
 
9
24
  private
10
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]
11
30
  def rate_limit
12
31
  time_since_last_request = Time.now - @last_request_time
13
32
  sleep(0.5 - time_since_last_request) if time_since_last_request < 0.5
14
33
  @last_request_time = Time.now
15
34
  end
16
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]
17
43
  def validate_input(text, target_lang_code)
18
44
  raise ArgumentError, "Text cannot be empty" if text.nil? || text.strip.empty?
19
45
  raise ArgumentError, "Invalid target language code" unless target_lang_code.match?(/^[a-z]{2}(-[A-Z]{2})?$/)
20
46
  end
21
47
 
22
- # Method to be implemented in derived classes.
23
- # @param text [String] text to translate.
24
- # @param target_lang_code [String] target language code (e.g. "en").
25
- # @param target_lang_name [String] target language name (e.g. "English").
26
- # @return [String] testo tradotto.
27
- MAX_RETRIES = 3
28
- RETRY_DELAY = 2 # seconds
29
-
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
30
56
  def translate(text, target_lang_code, target_lang_name)
31
57
  retries = 0
32
58
  begin
@@ -46,12 +72,28 @@ module BetterTranslate
46
72
  end
47
73
  end
48
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
49
82
  def perform_translation(text, target_lang_code, target_lang_name)
50
83
  validate_input(text, target_lang_code)
51
84
  rate_limit
52
85
  translate_text(text, target_lang_code, target_lang_name)
53
86
  end
54
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
55
97
  def translate_text(text, target_lang_code, target_lang_name)
56
98
  raise NotImplementedError, "The provider #{self.class} must implement the translate_text method"
57
99
  end
@@ -1,7 +1,23 @@
1
1
  module BetterTranslate
2
2
  module Providers
3
+ # Translation provider that uses OpenAI's ChatGPT API to perform translations.
4
+ # Implements the BaseProvider interface with ChatGPT-specific translation logic.
5
+ # Uses the gpt-3.5-turbo model with a specialized prompt for accurate translations.
6
+ #
7
+ # @example
8
+ # provider = BetterTranslate::Providers::ChatgptProvider.new(ENV['OPENAI_API_KEY'])
9
+ # translated_text = provider.translate("Hello world", "fr", "French")
3
10
  class ChatgptProvider < BaseProvider
4
- # Uses the OpenAI API to translate the text.
11
+ # Translates text using the OpenAI ChatGPT API.
12
+ # Implements the provider-specific translation logic using OpenAI's ChatGPT API.
13
+ # Sends a carefully crafted prompt to the API to ensure high-quality translations
14
+ # without explanations or additional text.
15
+ #
16
+ # @param text [String] The text to translate
17
+ # @param target_lang_code [String] The target language code (e.g., "fr")
18
+ # @param target_lang_name [String] The target language name (e.g., "French")
19
+ # @return [String] The translated text
20
+ # @raise [StandardError] If the API request fails or returns an error
5
21
  def translate_text(text, target_lang_code, target_lang_name)
6
22
  uri = URI("https://api.openai.com/v1/chat/completions")
7
23
  headers = {
@@ -6,9 +6,28 @@ require "uri"
6
6
 
7
7
  module BetterTranslate
8
8
  module Providers
9
+ # Translation provider that uses Google's Gemini API to perform translations.
10
+ # Implements the BaseProvider interface with Gemini-specific translation logic.
11
+ # Uses the gemini-2.0-flash model with a specialized prompt for accurate translations.
12
+ #
13
+ # @example
14
+ # provider = BetterTranslate::Providers::GeminiProvider.new(ENV['GOOGLE_GEMINI_KEY'])
15
+ # translated_text = provider.translate("Hello world", "fr", "French")
9
16
  class GeminiProvider < BaseProvider
17
+ # The base URL for the Gemini API
18
+ # @return [String] The API endpoint URL
10
19
  GEMINI_API_URL = "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent"
11
20
 
21
+ # Implements the provider-specific translation logic using Google's Gemini API.
22
+ # Sends a carefully crafted prompt to the API to ensure high-quality translations
23
+ # without explanations or additional text. Includes minimal text cleaning to handle
24
+ # any formatting issues in the response.
25
+ #
26
+ # @param text [String] The text to translate
27
+ # @param target_lang_code [String] The target language code (e.g., "fr")
28
+ # @param target_lang_name [String] The target language name (e.g., "French")
29
+ # @return [String] The translated text
30
+ # @raise [StandardError] If the API request fails or returns an error
12
31
  def translate_text(text, target_lang_code, target_lang_name)
13
32
  url = "#{GEMINI_API_URL}?key=#{@api_key}"
14
33
  uri = URI(url)
@@ -1,37 +1,66 @@
1
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")
2
10
  class Service
11
+ # Maximum number of translations to keep in the LRU cache
3
12
  MAX_CACHE_SIZE = 1000
4
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
5
21
  def initialize
6
22
  @provider_name = BetterTranslate.configuration.provider
7
23
  @translation_cache = {}
8
24
  @cache_order = []
9
25
  end
10
26
 
11
- # Method to translate a text using the selected provider.
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
12
36
  def translate(text, target_lang_code, target_lang_name)
13
37
  cache_key = "#{text}:#{target_lang_code}"
14
-
38
+
15
39
  # Prova a recuperare dalla cache
16
40
  cached = cache_get(cache_key)
17
41
  return cached if cached
18
-
42
+
19
43
  # Traduci e salva in cache
20
44
  start_time = Time.now
21
45
  result = provider_instance.translate_text(text, target_lang_code, target_lang_name)
22
46
  duration = Time.now - start_time
23
-
47
+
24
48
  BetterTranslate::Utils.track_metric("translation_request_duration", {
25
49
  provider: @provider_name,
26
50
  text_length: text.length,
27
51
  duration: duration
28
52
  })
29
-
53
+
30
54
  cache_set(cache_key, result)
31
55
  end
32
56
 
33
57
  private
34
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
35
64
  def cache_get(key)
36
65
  if @translation_cache.key?(key)
37
66
  # Aggiorna l'ordine LRU
@@ -41,13 +70,19 @@ module BetterTranslate
41
70
  end
42
71
  end
43
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
44
79
  def cache_set(key, value)
45
80
  if @translation_cache.size >= MAX_CACHE_SIZE
46
81
  # Rimuovi l'elemento meno recentemente usato
47
82
  oldest_key = @cache_order.shift
48
83
  @translation_cache.delete(oldest_key)
49
84
  end
50
-
85
+
51
86
  @translation_cache[key] = value
52
87
  @cache_order.push(key)
53
88
  value
@@ -55,15 +90,55 @@ module BetterTranslate
55
90
 
56
91
 
57
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
58
100
  def provider_instance
59
101
  @provider_instance ||= case @provider_name
60
102
  when :chatgpt
61
103
  Providers::ChatgptProvider.new(BetterTranslate.configuration.openai_key)
62
104
  when :gemini
63
- Providers::GeminiProvider.new(BetterTranslate.configuration.gemini_key)
105
+ Providers::GeminiProvider.new(BetterTranslate.configuration.google_gemini_key)
64
106
  else
65
- raise "Provider non supportato: #{@provider_name}"
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
66
119
  end
67
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
68
143
  end
69
144
  end
@@ -6,15 +6,37 @@ require "json"
6
6
  require "time"
7
7
 
8
8
  module BetterTranslate
9
+ # Analyzes translation YAML files to find similar content across keys.
10
+ # Uses the Levenshtein distance algorithm to identify strings that are similar
11
+ # but not identical, which could indicate redundant translations.
12
+ #
13
+ # @example
14
+ # analyzer = BetterTranslate::SimilarityAnalyzer.new(["config/locales/en.yml", "config/locales/fr.yml"])
15
+ # analyzer.analyze
9
16
  class SimilarityAnalyzer
17
+ # Default threshold for considering two strings similar (75%)
18
+ # @return [Float] The similarity threshold as a decimal between 0 and 1
10
19
  SIMILARITY_THRESHOLD = 0.75 # Abbassiamo la soglia per trovare piรน similaritร 
20
+
21
+ # Default filename for the detailed JSON report
22
+ # @return [String] The filename for the JSON report
11
23
  REPORT_FILE = "translation_similarities.json"
12
24
 
25
+ # Initializes a new SimilarityAnalyzer with the specified YAML files.
26
+ #
27
+ # @param yaml_files [Array<String>] An array of paths to YAML translation files
28
+ # @return [SimilarityAnalyzer] A new instance of the analyzer
13
29
  def initialize(yaml_files)
14
30
  @yaml_files = yaml_files
15
31
  @similarities = {}
16
32
  end
17
33
 
34
+ # Performs the complete analysis process:
35
+ # 1. Loads all translation files
36
+ # 2. Finds similarities between translations
37
+ # 3. Generates JSON and text reports
38
+ #
39
+ # @return [void]
18
40
  def analyze
19
41
  translations_by_language = load_translations
20
42
  find_similarities(translations_by_language)
@@ -23,6 +45,10 @@ module BetterTranslate
23
45
 
24
46
  private
25
47
 
48
+ # Loads all YAML translation files specified during initialization.
49
+ # Parses each file and organizes translations by language code.
50
+ #
51
+ # @return [Hash] A hash mapping language codes to their translation data structures
26
52
  def load_translations
27
53
  translations = {}
28
54
  puts "Loading YAML files..."
@@ -36,6 +62,12 @@ module BetterTranslate
36
62
  translations
37
63
  end
38
64
 
65
+ # Finds similar translations within each language file.
66
+ # For each language, flattens the translation structure and compares each pair of strings
67
+ # to identify similarities based on the Levenshtein distance algorithm.
68
+ #
69
+ # @param translations_by_language [Hash] A hash mapping language codes to their translation data
70
+ # @return [void]
39
71
  def find_similarities(translations_by_language)
40
72
  translations_by_language.each do |lang, translations|
41
73
  puts "\nAnalyzing #{lang} translations..."
@@ -62,6 +94,13 @@ module BetterTranslate
62
94
  end
63
95
  end
64
96
 
97
+ # Flattens a nested translation hash into a single-level hash with dot-notation keys.
98
+ # For example, {"en" => {"hello" => "world"}} becomes {"en.hello" => "world"}.
99
+ #
100
+ # @param hash [Hash] The nested hash to flatten
101
+ # @param prefix [String] The current key prefix (used recursively)
102
+ # @param result [Hash] The accumulator for flattened key-value pairs
103
+ # @return [Hash] A flattened hash with dot-notation keys
65
104
  def flatten_translations(hash, prefix = "", result = {})
66
105
  hash.each do |key, value|
67
106
  current_key = prefix.empty? ? key.to_s : "#{prefix}.#{key}"
@@ -76,6 +115,13 @@ module BetterTranslate
76
115
  result
77
116
  end
78
117
 
118
+ # Calculates the similarity between two strings using normalized Levenshtein distance.
119
+ # Returns a value between 0 (completely different) and 1 (identical).
120
+ # The normalization accounts for the length of the strings.
121
+ #
122
+ # @param str1 [String] The first string to compare
123
+ # @param str2 [String] The second string to compare
124
+ # @return [Float] A similarity score between 0 and 1
79
125
  def calculate_similarity(str1, str2)
80
126
  # Implementazione della distanza di Levenshtein normalizzata
81
127
  matrix = Array.new(str1.length + 1) { Array.new(str2.length + 1) }
@@ -98,6 +144,16 @@ module BetterTranslate
98
144
  1 - (matrix[str1.length][str2.length].to_f / max_length)
99
145
  end
100
146
 
147
+ # Records a similarity finding in the internal data structure.
148
+ # Organizes findings by language and stores all relevant information about the similar strings.
149
+ #
150
+ # @param lang [String] The language code
151
+ # @param key1 [String] The first translation key
152
+ # @param key2 [String] The second translation key
153
+ # @param value1 [String] The first translation value
154
+ # @param value2 [String] The second translation value
155
+ # @param similarity [Float] The calculated similarity score
156
+ # @return [void]
101
157
  def record_similarity(lang, key1, key2, value1, value2, similarity)
102
158
  @similarities[lang] ||= []
103
159
  @similarities[lang] << {
@@ -109,6 +165,11 @@ module BetterTranslate
109
165
  }
110
166
  end
111
167
 
168
+ # Generates both JSON and human-readable reports of the similarity findings.
169
+ # The JSON report contains detailed information about each similarity found.
170
+ # The text summary provides a more readable format for quick review.
171
+ #
172
+ # @return [void]
112
173
  def generate_report
113
174
  puts "\nGenerating reports..."
114
175
  report = {
@@ -125,6 +186,12 @@ module BetterTranslate
125
186
  puts " - Generated translation_similarities_summary.txt"
126
187
  end
127
188
 
189
+ # Generates a human-readable summary of the similarity findings.
190
+ # Creates a formatted text report organized by language, showing each pair of similar strings
191
+ # with their keys, values, and similarity percentage.
192
+ #
193
+ # @param report [Hash] The complete similarity report data
194
+ # @return [String] A formatted text summary
128
195
  def generate_summary(report)
129
196
  summary = ["Translation Similarities Report", "=" * 30, ""]
130
197
 
@@ -1,21 +1,33 @@
1
1
  module BetterTranslate
2
2
  class Translator
3
3
  class << self
4
+ # Main method that orchestrates the translation process.
5
+ # Reads the source file, applies exclusions, and translates the content
6
+ # to all configured target languages sequentially.
7
+ #
8
+ # @return [void]
4
9
  def work
5
- message = "Starting file translation..."
10
+ message = "\n[BetterTranslate] Reading source file: #{BetterTranslate.configuration.input_file}\n"
6
11
  BetterTranslate::Utils.logger(message: message)
7
12
 
8
13
  translations = read_yml_source
14
+ message = "[BetterTranslate] Source file loaded successfully."
15
+ BetterTranslate::Utils.logger(message: message)
9
16
 
10
17
  # Removes the keys to exclude (global_exclusions) from the read structure
18
+ message = "[BetterTranslate] Applying global exclusions..."
19
+ BetterTranslate::Utils.logger(message: message)
11
20
  global_filtered_translations = remove_exclusions(
12
21
  translations, BetterTranslate.configuration.global_exclusions
13
22
  )
23
+ message = "[BetterTranslate] Global exclusions applied."
24
+ BetterTranslate::Utils.logger(message: message)
14
25
 
15
26
  start_time = Time.now
16
- threads = BetterTranslate.configuration.target_languages.map do |target_lang|
17
- Thread.new do
18
-
27
+ results = []
28
+
29
+ # Elabora ogni lingua target in sequenza invece di utilizzare thread
30
+ BetterTranslate.configuration.target_languages.each do |target_lang|
19
31
  # Phase 2: Apply the target language specific filter
20
32
  lang_exclusions = BetterTranslate.configuration.exclusions_per_language[target_lang[:short_name]] || []
21
33
  filtered_translations = remove_exclusions(
@@ -24,26 +36,36 @@ module BetterTranslate
24
36
 
25
37
  message = "Starting translation from #{BetterTranslate.configuration.source_language} to #{target_lang[:short_name]}"
26
38
  BetterTranslate::Utils.logger(message: message)
39
+ message = "\n[BetterTranslate] Starting translation to #{target_lang[:name]} (#{target_lang[:short_name]})..."
40
+ BetterTranslate::Utils.logger(message: message)
41
+
27
42
  service = BetterTranslate::Service.new
28
43
  translated_data = translate_with_progress(filtered_translations, service, target_lang[:short_name], target_lang[:name])
44
+
45
+ message = "[BetterTranslate] Writing translations for #{target_lang[:short_name]}..."
46
+ BetterTranslate::Utils.logger(message: message)
29
47
  BetterTranslate::Writer.write_translations(translated_data, target_lang[:short_name])
30
- end_time = Time.now
31
- duration = end_time - start_time
48
+
49
+ lang_end_time = Time.now
50
+ duration = lang_end_time - start_time
32
51
  BetterTranslate::Utils.track_metric("translation_duration", duration)
33
52
 
34
53
  message = "Translation completed from #{BetterTranslate.configuration.source_language} to #{target_lang[:short_name]} in #{duration.round(2)} seconds"
35
54
  BetterTranslate::Utils.logger(message: message)
36
- end
55
+ message = "[BetterTranslate] Completed translation to #{target_lang[:name]} in #{duration.round(2)} seconds."
56
+ BetterTranslate::Utils.logger(message: message)
57
+
58
+ results << translated_data
37
59
  end
38
60
  end
39
61
 
40
62
  private
41
63
 
42
- # Reads the YAML file based on the provided path.
64
+ # Reads the YAML file specified in the configuration.
65
+ # The file path is taken from BetterTranslate.configuration.input_file.
43
66
  #
44
- # @param file_path [String] path of the YAML file to read
45
- # @return [Hash] data structure containing the translations
46
- # @raise [StandardError] if the file does not exist
67
+ # @return [Hash] The parsed YAML data structure containing the translations
68
+ # @raise [StandardError] If the input file does not exist or cannot be parsed
47
69
  def read_yml_source
48
70
  file_path = BetterTranslate.configuration.input_file
49
71
  unless File.exist?(file_path)
@@ -61,11 +83,14 @@ module BetterTranslate
61
83
  # and global_exclusions = ["sample.excluded"],
62
84
  # the result will be:
63
85
  # { "en" => { "sample" => { "valid" => "valid" } } }
86
+ # Removes specified keys from the translation data structure.
87
+ # Recursively traverses the data structure and excludes any keys that match the exclusion list.
88
+ # Keys can be excluded at any nesting level using dot notation paths.
64
89
  #
65
- # @param data [Hash, Array, Object] The data structure to filter.
66
- # @param global_exclusions [Array<String>] List of paths (in dot notation) to exclude globally.
67
- # @param current_path [Array] The current path (used recursively, default: []).
68
- # @return [Hash, Array, Object] The filtered data structure.
90
+ # @param data [Hash, Array, Object] The data structure to filter
91
+ # @param exclusion_list [Array<String>] List of dot-separated key paths to exclude
92
+ # @param current_path [Array] The current path in the traversal (used recursively, default: [])
93
+ # @return [Hash, Array, Object] The filtered data structure with excluded keys removed
69
94
  def remove_exclusions(data, exclusion_list, current_path = [])
70
95
  if data.is_a?(Hash)
71
96
  data.each_with_object({}) do |(key, value), result|
@@ -93,12 +118,16 @@ module BetterTranslate
93
118
 
94
119
  # Recursive method that traverses the structure, translating each string and updating progress.
95
120
  #
96
- # @param data [Hash, Array, String] The data structure to translate.
97
- # @param provider [Object] The provider that responds to the translate method.
98
- # @param target_lang_code [String] Target language code (e.g. "en").
99
- # @param target_lang_name [String] Target language name (e.g. "English").
100
- # @param progress [Hash] A hash with :count and :total keys to monitor progress.
101
- # @return [Hash, Array, String] The translated structure.
121
+ # Recursively translates a data structure by traversing it deeply.
122
+ # This method is used for smaller datasets (less than 50 strings) and translates each string individually.
123
+ # It maintains the original structure of the data while replacing string values with their translations.
124
+ #
125
+ # @param data [Hash, Array, String] The data structure to translate
126
+ # @param service [BetterTranslate::Service] The service instance to use for translation
127
+ # @param target_lang_code [String] The target language code (e.g., 'fr', 'es')
128
+ # @param target_lang_name [String] The target language name (e.g., 'French', 'Spanish')
129
+ # @param progress [ProgressBar] A progress bar instance to track translation progress
130
+ # @return [Hash, Array, String] The translated data structure with the same structure as the input
102
131
  def deep_translate_with_progress(data, service, target_lang_code, target_lang_name, progress)
103
132
  if data.is_a?(Hash)
104
133
  data.each_with_object({}) do |(key, value), result|
@@ -116,18 +145,27 @@ module BetterTranslate
116
145
  end
117
146
  end
118
147
 
119
- # Main method to translate the entire data structure, with progress monitoring.
148
+ # Translates the entire data structure with progress monitoring.
149
+ # Automatically selects between batch and deep translation methods based on the number of strings.
150
+ # For datasets with more than 50 strings, batch processing is used for better performance.
120
151
  #
121
- # @param data [Hash, Array, String] the data structure to translate
122
- # @param provider [Object] the provider to use for translation (must implement translate)
123
- # @param target_lang_code [String] target language code
124
- # @param target_lang_name [String] target language name
125
- # @return the translated structure
152
+ # @param data [Hash, Array, String] The data structure to translate
153
+ # @param service [BetterTranslate::Service] The service instance to use for translation
154
+ # @param target_lang_code [String] The target language code (e.g., 'fr', 'es')
155
+ # @param target_lang_name [String] The target language name (e.g., 'French', 'Spanish')
156
+ # @return [Hash, Array, String] The translated data structure with the same structure as the input
126
157
  def translate_with_progress(data, service, target_lang_code, target_lang_name)
127
158
  total = count_strings(data)
159
+ message = "[BetterTranslate] Found #{total} strings to translate to #{target_lang_name}"
160
+ BetterTranslate::Utils.logger(message: message)
161
+
162
+ # Creiamo la barra di progresso ma aggiungiamo anche output visibile
128
163
  progress = ProgressBar.create(total: total, format: '%a %B %p%% %t')
129
164
 
130
165
  start_time = Time.now
166
+ message = "[BetterTranslate] Using #{total > 50 ? 'batch' : 'deep'} translation method"
167
+ BetterTranslate::Utils.logger(message: message)
168
+
131
169
  result = if total > 50 # Usa il batch processing per dataset grandi
132
170
  batch_translate_with_progress(data, service, target_lang_code, target_lang_name, progress)
133
171
  else
@@ -135,6 +173,9 @@ module BetterTranslate
135
173
  end
136
174
 
137
175
  duration = Time.now - start_time
176
+ message = "[BetterTranslate] Translation processing completed in #{duration.round(2)} seconds"
177
+ BetterTranslate::Utils.logger(message: message)
178
+
138
179
  BetterTranslate::Utils.track_metric("translation_method_duration", {
139
180
  method: total > 50 ? 'batch' : 'deep',
140
181
  duration: duration,
@@ -144,6 +185,16 @@ module BetterTranslate
144
185
  result
145
186
  end
146
187
 
188
+ # Translates data in batches for improved performance with larger datasets.
189
+ # This method first extracts all translatable strings, processes them in batches of 10,
190
+ # and then reinserts the translations back into the original structure.
191
+ #
192
+ # @param data [Hash, Array, String] The data structure to translate
193
+ # @param service [BetterTranslate::Service] The service instance to use for translation
194
+ # @param target_lang_code [String] The target language code (e.g., 'fr', 'es')
195
+ # @param target_lang_name [String] The target language name (e.g., 'French', 'Spanish')
196
+ # @param progress [ProgressBar] A progress bar instance to track translation progress
197
+ # @return [Hash, Array, String] The translated data structure with the same structure as the input
147
198
  def batch_translate_with_progress(data, service, target_lang_code, target_lang_name, progress)
148
199
  texts = extract_translatable_texts(data)
149
200
  translations = {}
@@ -170,6 +221,12 @@ module BetterTranslate
170
221
  replace_translations(data, translations)
171
222
  end
172
223
 
224
+ # Extracts all unique translatable strings from a data structure.
225
+ # This is used by the batch translation method to collect all strings for efficient processing.
226
+ # Only non-empty strings are included in the result.
227
+ #
228
+ # @param data [Hash, Array, String] The data structure to extract strings from
229
+ # @return [Array<String>] An array of unique strings found in the data structure
173
230
  def extract_translatable_texts(data)
174
231
  texts = Set.new
175
232
  traverse_structure(data) do |value|
@@ -178,6 +235,13 @@ module BetterTranslate
178
235
  texts.to_a
179
236
  end
180
237
 
238
+ # Replaces strings in the original data structure with their translations.
239
+ # Used by the batch translation method to reinsert translated strings into the original structure.
240
+ # Only non-empty strings that have translations in the provided hash are replaced.
241
+ #
242
+ # @param data [Hash, Array, String] The original data structure
243
+ # @param translations [Hash] A hash mapping original strings to their translations
244
+ # @return [Hash, Array, String] The data structure with strings replaced by translations
181
245
  def replace_translations(data, translations)
182
246
  traverse_structure(data) do |value|
183
247
  if value.is_a?(String) && !value.strip.empty? && translations.key?(value)
@@ -188,6 +252,13 @@ module BetterTranslate
188
252
  end
189
253
  end
190
254
 
255
+ # Traverses a nested data structure and applies a block to each element.
256
+ # This is a utility method used by extract_translatable_texts and replace_translations.
257
+ # Handles Hash, Array, and scalar values recursively.
258
+ #
259
+ # @param data [Hash, Array, Object] The data structure to traverse
260
+ # @yield [Object] Yields each value in the data structure to the block
261
+ # @return [Hash, Array, Object] The transformed data structure after applying the block
191
262
  def traverse_structure(data, &block)
192
263
  case data
193
264
  when Hash
@@ -199,7 +270,12 @@ module BetterTranslate
199
270
  end
200
271
  end
201
272
 
202
- # Recursively counts the number of translatable strings in the data structure.
273
+ # Counts the number of translatable strings in a data structure.
274
+ # Used to determine the total number of strings for progress tracking and method selection.
275
+ # Recursively traverses Hash and Array structures, counting each String as 1.
276
+ #
277
+ # @param data [Hash, Array, String, Object] The data structure to count strings in
278
+ # @return [Integer] The total number of strings found in the data structure
203
279
  def count_strings(data)
204
280
  if data.is_a?(Hash)
205
281
  data.values.sum { |v| count_strings(v) }
@@ -1,25 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # lib/better_seeder/utils.rb
4
- #
5
- # = BetterSeeder::Utils
6
- #
7
- # This module provides utility methods for seed management. In particular,
8
- # allows transforming class names into snake_case format with the "_structure.rb" suffix,
9
- # manage log messages and configure the logger level for ActiveRecord.
10
-
11
3
  module BetterTranslate
4
+ # Utility class providing helper methods for logging and metrics tracking.
5
+ # Used throughout the BetterTranslate gem to standardize logging and performance monitoring.
12
6
  class Utils
13
7
  class << self
14
- ##
15
- # Logs a message using the Rails logger if available, otherwise prints it to standard output.
16
- #
17
- # ==== Parametri
18
- # * +message+ - The message to log (can be a string or nil).
19
- #
20
- # ==== Ritorno
21
- # Does not return a significant value.
8
+
9
+ # Logs a message using the Rails logger if available, otherwise prints to standard output.
10
+ # Provides a consistent logging interface across different environments.
22
11
  #
12
+ # @param message [String, nil] The message to log
13
+ # @return [void]
23
14
  def logger(message: nil)
24
15
  if defined?(Rails) && Rails.respond_to?(:logger) && Rails.logger
25
16
  Rails.logger.info message
@@ -28,6 +19,13 @@ module BetterTranslate
28
19
  end
29
20
  end
30
21
 
22
+ # Tracks a metric with the given name and value.
23
+ # Stores metrics in memory with timestamps for performance analysis.
24
+ # Automatically limits each metric type to the most recent 1000 entries.
25
+ #
26
+ # @param name [Symbol, String] The name of the metric to track
27
+ # @param value [Numeric] The value to record for this metric
28
+ # @return [void]
31
29
  def track_metric(name, value)
32
30
  @metrics ||= {}
33
31
  @metrics[name] ||= []
@@ -37,10 +35,18 @@ module BetterTranslate
37
35
  @metrics[name] = @metrics[name].last(1000) if @metrics[name].size > 1000
38
36
  end
39
37
 
38
+ # Retrieves all tracked metrics.
39
+ # Returns a hash where keys are metric names and values are arrays of recorded values with timestamps.
40
+ #
41
+ # @return [Hash] The collected metrics or an empty hash if no metrics have been tracked
40
42
  def get_metrics
41
43
  @metrics || {}
42
44
  end
43
45
 
46
+ # Clears all tracked metrics.
47
+ # Useful for resetting metrics between translation jobs or testing.
48
+ #
49
+ # @return [Hash] An empty hash representing the cleared metrics
44
50
  def clear_metrics
45
51
  @metrics = {}
46
52
  end
@@ -1,5 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module BetterTranslate
4
- VERSION = "0.4.1"
4
+ # Current version of the BetterTranslate gem.
5
+ #
6
+ # The versioning follows Semantic Versioning 2.0.0 (https://semver.org/):
7
+ # - MAJOR version for incompatible API changes
8
+ # - MINOR version for backwards-compatible functionality additions
9
+ # - PATCH version for backwards-compatible bug fixes
10
+ #
11
+ # @return [String] The current version in the format "MAJOR.MINOR.PATCH"
12
+ VERSION = "0.5.0"
5
13
  end
@@ -1,4 +1,8 @@
1
1
  module BetterTranslate
2
+ # Responsible for writing translated content to YAML files.
3
+ # Supports both incremental and override translation methods.
4
+ # Incremental mode preserves existing translations and adds new ones,
5
+ # while override mode completely replaces the target file.
2
6
  class Writer
3
7
  class << self
4
8
  # Writes the translation file for a target language.
@@ -35,14 +39,19 @@ module BetterTranslate
35
39
 
36
40
  private
37
41
 
38
- # Metodo di deep merge: unisce in modo ricorsivo i due hash.
39
- # Se una chiave esiste in entrambi gli hash e i valori sono hash, li unisce ricorsivamente.
40
- # Otherwise, if the key already exists in the existing hash, it keeps it and doesn't overwrite it.
41
- # Se la chiave non esiste, la aggiunge.
42
+ # Recursively merges two hashes while preserving existing values.
43
+ # If a key exists in both hashes and the values are hashes, they are merged recursively.
44
+ # If a key exists in both hashes but the values are not hashes, the existing value is preserved.
45
+ # If a key only exists in the new hash, it is added to the merged result.
42
46
  #
43
- # @param existing [Hash] existing hash (current file)
44
- # @param new_data [Hash] new hash with translations to merge
45
- # @return [Hash] merged hash
47
+ # @param existing [Hash] The existing hash (current file content)
48
+ # @param new_data [Hash] The new hash with translations to merge
49
+ # @return [Hash] The merged hash with preserved existing values
50
+ # @example
51
+ # existing = { "en" => { "hello" => "Hello", "nested" => { "key" => "Value" } } }
52
+ # new_data = { "en" => { "hello" => "New Hello", "nested" => { "key2" => "Value2" } } }
53
+ # deep_merge(existing, new_data)
54
+ # # => { "en" => { "hello" => "Hello", "nested" => { "key" => "Value", "key2" => "Value2" } } }
46
55
  def deep_merge(existing, new_data)
47
56
  merged = existing.dup
48
57
  new_data.each do |key, value|
@@ -15,17 +15,41 @@ require 'better_translate/providers/gemini_provider'
15
15
 
16
16
  require 'ostruct'
17
17
 
18
+ # Main module for the BetterTranslate gem.
19
+ # Provides functionality for translating YAML files using various AI providers.
20
+ #
21
+ # @example Basic configuration and usage
22
+ # BetterTranslate.configure do |config|
23
+ # config.provider = :chatgpt
24
+ # config.openai_key = ENV['OPENAI_API_KEY']
25
+ # config.input_file = 'config/locales/en.yml'
26
+ # config.target_languages = [{short_name: 'fr', name: 'French'}]
27
+ # end
28
+ #
29
+ # BetterTranslate.magic # Start the translation process
18
30
  module BetterTranslate
19
31
  class << self
32
+ # Configuration object for the gem
33
+ # @return [OpenStruct] The configuration object
20
34
  attr_accessor :configuration
21
35
 
22
- # Method to configure the gem
36
+ # Configures the gem with the provided block.
37
+ # Sets up the configuration object and yields it to the block.
38
+ #
39
+ # @yield [configuration] Yields the configuration object to the block
40
+ # @yieldparam configuration [OpenStruct] The configuration object
41
+ # @return [OpenStruct] The updated configuration object
23
42
  def configure
24
43
  self.configuration ||= OpenStruct.new
25
44
  yield(configuration) if block_given?
26
45
  end
27
46
 
28
- # Install method to generate the configuration file (initializer)
47
+ # Installs the gem configuration file (initializer) in a Rails application.
48
+ # Copies the template initializer to the Rails config/initializers directory.
49
+ # Only works in a Rails environment.
50
+ #
51
+ # @return [void]
52
+ # @note This method will log an error if not called from a Rails application
29
53
  def install
30
54
  unless defined?(Rails) && Rails.respond_to?(:root)
31
55
  message = "The install method is only available in a Rails application."
@@ -48,13 +72,27 @@ module BetterTranslate
48
72
  end
49
73
  end
50
74
 
75
+ # Starts the translation process using the configured settings.
76
+ # This is the main entry point for the translation functionality.
77
+ # Logs the start and completion of the translation process.
78
+ #
79
+ # @return [void]
80
+ # @example
81
+ # BetterTranslate.magic
51
82
  def magic
52
83
  message = "Magic method invoked: Translation will begin..."
53
84
  BetterTranslate::Utils.logger(message: message)
85
+ # Utilizziamo il logger per tutti i messaggi
86
+ message = "\n[BetterTranslate] Starting translation process...\n"
87
+ BetterTranslate::Utils.logger(message: message)
54
88
 
55
89
  BetterTranslate::Translator.work
90
+
56
91
  message = "Magic method invoked: Translation completed successfully!"
57
92
  BetterTranslate::Utils.logger(message: message)
93
+ # Utilizziamo il logger per tutti i messaggi
94
+ message = "\n[BetterTranslate] Translation completed successfully!\n"
95
+ BetterTranslate::Utils.logger(message: message)
58
96
  end
59
97
 
60
98
  end
@@ -4,9 +4,23 @@ require "rails/generators"
4
4
 
5
5
  module BetterTranslate
6
6
  module Generators
7
+ # Rails generator for analyzing translation files to find similar content.
8
+ # Uses the SimilarityAnalyzer to identify potentially redundant translations
9
+ # across locale files and generates both JSON and human-readable reports.
10
+ #
11
+ # @example
12
+ # rails generate better_translate:analyze
7
13
  class AnalyzeGenerator < Rails::Generators::Base
8
14
  desc "Analyze translation files for similarities"
9
15
 
16
+ # Main generator method that analyzes translation files for similarities.
17
+ # Finds all YAML files in the config/locales directory, runs the analysis,
18
+ # and displays a summary of the results.
19
+ #
20
+ # The analysis uses the Levenshtein distance algorithm to identify strings
21
+ # that are similar but not identical, which could indicate redundant translations.
22
+ #
23
+ # @return [void]
10
24
  def analyze_translations
11
25
  say "Starting translation similarity analysis...", :blue
12
26
 
@@ -1,5 +1,5 @@
1
1
  BetterTranslate.configure do |config|
2
- # Choose the provider to use: :chatgpt or :gemini
2
+ # Choose the provider to use: :chatgpt, :gemini, or any custom provider registered with BetterTranslate::Service.register_provider
3
3
  config.provider = :chatgpt
4
4
 
5
5
  # API key for OpenAI
@@ -7,6 +7,13 @@ BetterTranslate.configure do |config|
7
7
 
8
8
  # API key for Google Gemini
9
9
  config.google_gemini_key = ENV.fetch("GOOGLE_GEMINI_KEY") { "YOUR_GOOGLE_GEMINI_KEY" }
10
+
11
+ # Custom provider API keys
12
+ # If you've created a custom provider using 'rails generate better_translate:provider YourProvider',
13
+ # add its API key here following the naming convention: config.your_provider_key
14
+ #
15
+ # Example for a custom DeepL provider:
16
+ # config.deepl_key = ENV.fetch("DEEPL_API_KEY") { "YOUR_DEEPL_API_KEY" }
10
17
 
11
18
  # Source language (for example "en" if the source file is in English)
12
19
  config.source_language = "en"
@@ -1,16 +1,83 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rails/generators'
2
4
 
3
5
  module BetterTranslate
4
6
  module Generators
7
+ # Rails generator for executing translations with BetterTranslate.
8
+ # This generator validates the configuration and starts the translation process.
9
+ #
10
+ # @example
11
+ # rails generate better_translate:translate
5
12
  class TranslateGenerator < Rails::Generators::Base
6
13
  desc "Starts the translation process configured in BetterTranslate"
7
14
 
8
- def run_translation
9
- message = "Starting translation with BetterTranslate..."
10
- BetterTranslate::Utils.logger(message: message)
11
- BetterTranslate.magic
12
- message = "Translation completed."
13
- BetterTranslate::Utils.logger(message: message)
15
+ # Main generator method that initiates the translation process.
16
+ # Validates the configuration before starting and provides status messages.
17
+ #
18
+ # @return [void]
19
+ def translate
20
+ say "Starting translation with BetterTranslate...", :blue
21
+
22
+ # Verifica che la configurazione sia valida
23
+ if validate_configuration
24
+ say "Configuration validated. Starting translation...", :green
25
+ BetterTranslate.magic
26
+ say "Translation completed.", :green
27
+ else
28
+ say "Translation aborted due to configuration issues.", :red
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ # Validates the BetterTranslate configuration.
35
+ # Checks for required settings and reports any issues found.
36
+ #
37
+ # @return [Boolean] true if the configuration is valid, false otherwise
38
+ def validate_configuration
39
+ valid = true
40
+
41
+ # Verifica che il file di input esista
42
+ unless BetterTranslate.configuration.respond_to?(:input_file)
43
+ say "Error: input_file not configured. Please check your initializer.", :red
44
+ return false
45
+ end
46
+
47
+ input_file = BetterTranslate.configuration.input_file
48
+ unless File.exist?(input_file)
49
+ say "Error: Input file not found: #{input_file}", :red
50
+ valid = false
51
+ end
52
+
53
+ # Verifica che ci siano lingue target attive
54
+ if !BetterTranslate.configuration.respond_to?(:target_languages) ||
55
+ BetterTranslate.configuration.target_languages.empty?
56
+ say "Error: No target languages configured. Please uncomment or add target languages in your initializer.", :red
57
+ valid = false
58
+ end
59
+
60
+ # Verifica che il provider sia configurato
61
+ if !BetterTranslate.configuration.respond_to?(:provider)
62
+ say "Error: No provider configured. Please set config.provider in your initializer.", :red
63
+ valid = false
64
+ end
65
+
66
+ # Verifica che la chiave API sia configurata per il provider selezionato
67
+ if BetterTranslate.configuration.respond_to?(:provider)
68
+ provider = BetterTranslate.configuration.provider
69
+ if provider == :chatgpt && (!BetterTranslate.configuration.respond_to?(:openai_key) ||
70
+ BetterTranslate.configuration.openai_key == "YOUR_OPENAI_API_KEY")
71
+ say "Error: OpenAI API key not configured. Please set config.openai_key in your initializer.", :red
72
+ valid = false
73
+ elsif provider == :gemini && (!BetterTranslate.configuration.respond_to?(:google_gemini_key) ||
74
+ BetterTranslate.configuration.google_gemini_key == "YOUR_GOOGLE_GEMINI_KEY")
75
+ say "Error: Gemini API key not configured. Please set config.google_gemini_key in your initializer.", :red
76
+ valid = false
77
+ end
78
+ end
79
+
80
+ valid
14
81
  end
15
82
  end
16
83
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: better_translate
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.1
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alessio Bussolari