mistral_translator 0.1.0 → 0.2.1

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 (49) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +21 -0
  3. data/README.md +189 -121
  4. data/README_TESTING.md +33 -0
  5. data/SECURITY.md +157 -0
  6. data/docs/.nojekyll +2 -0
  7. data/docs/404.html +30 -0
  8. data/docs/README.md +153 -0
  9. data/docs/advanced-usage/batch-processing.md +158 -0
  10. data/docs/advanced-usage/error-handling.md +106 -0
  11. data/docs/advanced-usage/monitoring.md +133 -0
  12. data/docs/advanced-usage/summarization.md +86 -0
  13. data/docs/advanced-usage/translations.md +141 -0
  14. data/docs/api-reference/callbacks.md +231 -0
  15. data/docs/api-reference/configuration.md +74 -0
  16. data/docs/api-reference/errors.md +673 -0
  17. data/docs/api-reference/methods.md +539 -0
  18. data/docs/getting-started.md +179 -0
  19. data/docs/index.html +27 -0
  20. data/docs/installation.md +142 -0
  21. data/docs/migration-0.1.0-to-0.2.0.md +61 -0
  22. data/docs/rails-integration/adapters.md +84 -0
  23. data/docs/rails-integration/controllers.md +107 -0
  24. data/docs/rails-integration/jobs.md +97 -0
  25. data/docs/rails-integration/setup.md +339 -0
  26. data/examples/basic_usage.rb +129 -102
  27. data/examples/batch-job.rb +511 -0
  28. data/examples/monitoring-setup.rb +499 -0
  29. data/examples/rails-model.rb +399 -0
  30. data/lib/mistral_translator/adapters.rb +261 -0
  31. data/lib/mistral_translator/client.rb +103 -100
  32. data/lib/mistral_translator/client_helpers.rb +161 -0
  33. data/lib/mistral_translator/configuration.rb +171 -1
  34. data/lib/mistral_translator/errors.rb +16 -0
  35. data/lib/mistral_translator/helpers.rb +292 -0
  36. data/lib/mistral_translator/helpers_extensions.rb +150 -0
  37. data/lib/mistral_translator/levenshtein_helpers.rb +40 -0
  38. data/lib/mistral_translator/logger.rb +28 -4
  39. data/lib/mistral_translator/prompt_builder.rb +93 -41
  40. data/lib/mistral_translator/prompt_helpers.rb +83 -0
  41. data/lib/mistral_translator/prompt_metadata_helpers.rb +42 -0
  42. data/lib/mistral_translator/response_parser.rb +194 -23
  43. data/lib/mistral_translator/security.rb +72 -0
  44. data/lib/mistral_translator/summarizer.rb +41 -2
  45. data/lib/mistral_translator/translator.rb +174 -98
  46. data/lib/mistral_translator/translator_helpers.rb +268 -0
  47. data/lib/mistral_translator/version.rb +1 -1
  48. data/lib/mistral_translator.rb +51 -25
  49. metadata +39 -3
@@ -0,0 +1,268 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MistralTranslator
4
+ module TranslatorHelpers
5
+ # Helper pour la validation des entrées
6
+ module InputValidator
7
+ def validate_inputs!(text, from, to)
8
+ raise ArgumentError, "Text cannot be nil or empty" if text.nil? || text.to_s.strip.empty?
9
+ raise ArgumentError, "Source language cannot be nil" if from.nil?
10
+ raise ArgumentError, "Source language cannot be empty" if from.to_s.strip.empty?
11
+ raise ArgumentError, "Target language cannot be nil or empty" if to.nil? || to.to_s.strip.empty?
12
+
13
+ return unless from.to_s.strip.downcase == to.to_s.strip.downcase
14
+
15
+ raise ArgumentError,
16
+ "Source and target languages cannot be the same"
17
+ end
18
+
19
+ def validate_translation_inputs!(text, from, to)
20
+ validate_inputs!(text, from, to)
21
+ raise ArgumentError, "Target languages cannot be empty" if Array(to).empty?
22
+ end
23
+
24
+ def validate_batch_inputs!(texts, from, to)
25
+ raise ArgumentError, "Texts array cannot be nil or empty" if texts.nil? || texts.empty?
26
+
27
+ ensure_present!(from, "Source language")
28
+ ensure_present!(to, "Target language")
29
+
30
+ texts.each_with_index do |t, i|
31
+ ensure_present!(t, "Text at index #{i}")
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ def ensure_present!(value, field_name)
38
+ raise ArgumentError, "#{field_name} cannot be nil or empty" if value.nil? || value.to_s.strip.empty?
39
+ end
40
+ end
41
+
42
+ # Helper pour la gestion des retry
43
+ module RetryHandler
44
+ DEFAULT_RETRY_DELAY = 2
45
+
46
+ def handle_retry_error(error, source_locale, target_locale, attempt)
47
+ case error
48
+ when RateLimitError
49
+ handle_rate_limit_retry?(source_locale, target_locale, attempt)
50
+ when ApiError
51
+ handle_api_error_retry?(error, source_locale, target_locale, attempt)
52
+ else
53
+ raise error
54
+ end
55
+ end
56
+
57
+ # Renvoie true si un retry a été effectué
58
+ def handle_rate_limit_retry?(source_locale, target_locale, _attempt)
59
+ log_rate_limit_hit(source_locale, target_locale)
60
+ sleep(DEFAULT_RETRY_DELAY)
61
+ # NOTE: retry logic should be handled by the calling method
62
+ true
63
+ end
64
+
65
+ # Renvoie true si un retry a été effectué
66
+ def handle_api_error_retry?(error, _source_locale, _target_locale, attempt)
67
+ raise error unless attempt < 3 && error.message.include?("timeout")
68
+
69
+ Logger.warn("API timeout, retrying... (attempt #{attempt + 1})")
70
+ sleep(DEFAULT_RETRY_DELAY)
71
+ # NOTE: retry logic should be handled by the calling method
72
+ true
73
+ end
74
+ end
75
+
76
+ # Helper pour le logging
77
+ module LoggingHelper
78
+ def log_rate_limit_hit(source_locale, target_locale)
79
+ message = "Rate limit hit for #{source_locale}->#{target_locale}"
80
+ Logger.warn_once(message, key: "rate_limit_#{source_locale}_#{target_locale}", sensitive: false, ttl: 300)
81
+ end
82
+
83
+ def log_translation_attempt(source_locale, target_locale, attempt)
84
+ Logger.debug("Translation attempt #{attempt} for #{source_locale}->#{target_locale}")
85
+ end
86
+
87
+ def log_retry(error, attempt, wait_time)
88
+ default_retry_count = MistralTranslator::Translator::DEFAULT_RETRY_COUNT
89
+ message = "Translation retry #{attempt}/#{default_retry_count} in #{wait_time}s: #{error.message}"
90
+ Logger.warn_once(message, key: "translate_retry_#{error.class.name}_#{attempt}", sensitive: false, ttl: 120)
91
+ end
92
+ end
93
+
94
+ # Helper pour la gestion des prompts
95
+ module PromptHandler
96
+ def enrich_prompt_with_context(base_prompt, context, glossary)
97
+ context_section = build_context_section(context, glossary)
98
+ base_prompt + context_section
99
+ end
100
+
101
+ def build_context_section(context, glossary)
102
+ context_section = ""
103
+ context_section += add_context_text(context) if context && !context.to_s.strip.empty?
104
+ context_section += add_glossary_text(glossary) if glossary && !glossary.to_s.strip.empty?
105
+ context_section
106
+ end
107
+
108
+ def add_context_text(context)
109
+ "\nCONTEXTE : #{context}\n"
110
+ end
111
+
112
+ def add_glossary_text(glossary)
113
+ if glossary.is_a?(Hash) && glossary.any?
114
+ glossary_text = glossary.map { |key, value| "#{key} → #{value}" }.join(", ")
115
+ "\nGLOSSAIRE (à respecter strictement) : #{glossary_text}\n"
116
+ else
117
+ "\nGLOSSAIRE : #{glossary}\n"
118
+ end
119
+ end
120
+ end
121
+
122
+ # Helper pour l'analyse et la détection de langue
123
+ module AnalysisHelper
124
+ def calculate_confidence_score(original, translated, from_locale, to_locale)
125
+ length_ratio = translated.length.to_f / original.length
126
+
127
+ expected_ratios = {
128
+ %w[fr en] => [0.8, 1.2],
129
+ %w[en fr] => [1.0, 1.3],
130
+ %w[es en] => [0.7, 1.1],
131
+ %w[en es] => [1.0, 1.4]
132
+ }
133
+
134
+ expected_range = expected_ratios[[from_locale.to_s, to_locale.to_s]] || [0.5, 2.0]
135
+
136
+ base_confidence = if length_ratio.between?(expected_range[0], expected_range[1])
137
+ 0.8
138
+ else
139
+ 0.6
140
+ end
141
+
142
+ if translated.strip.empty?
143
+ 0.0
144
+ elsif original.length < 10
145
+ [base_confidence - 0.2, 0.1].max
146
+ else
147
+ [base_confidence, 0.95].min
148
+ end
149
+ end
150
+
151
+ def build_language_detection_prompt(text)
152
+ PromptBuilder.language_detection_prompt(text)
153
+ end
154
+
155
+ def parse_language_detection(response)
156
+ json_content = response.match(/\{.*\}/m)&.[](0)
157
+ return "en" unless json_content
158
+
159
+ data = JSON.parse(json_content)
160
+ detected = data.dig("metadata", "detected_language")
161
+
162
+ LocaleHelper.locale_supported?(detected) ? detected : "en"
163
+ rescue JSON::ParserError
164
+ "en"
165
+ end
166
+ end
167
+
168
+ # Helper pour requêtes client et retries
169
+ module RequestHelper
170
+ def build_prompt_for_retry(text, source_locale, target_locale, **options)
171
+ build_translation_prompt(
172
+ text,
173
+ source_locale,
174
+ target_locale,
175
+ context: options[:context],
176
+ glossary: options[:glossary],
177
+ preserve_html: options.fetch(:preserve_html, false)
178
+ )
179
+ end
180
+
181
+ def build_request_context(source_locale, target_locale, attempt)
182
+ {
183
+ from_locale: source_locale,
184
+ to_locale: target_locale,
185
+ attempt: attempt
186
+ }
187
+ end
188
+
189
+ def perform_client_request(prompt, request_context)
190
+ if @client.respond_to?(:complete)
191
+ begin
192
+ return @client.complete(prompt, context: request_context)
193
+ rescue StandardError => e
194
+ raise e unless e.class.name.include?("MockExpectationError") && @client.respond_to?(:make_request)
195
+
196
+ return @client.make_request(prompt, nil, nil)
197
+ end
198
+ end
199
+
200
+ return @client.make_request(prompt, nil, nil) if @client.respond_to?(:make_request)
201
+
202
+ @client.complete(prompt, context: request_context)
203
+ end
204
+
205
+ def extract_translated_text!(raw_response)
206
+ begin
207
+ result = ResponseParser.parse_translation_response(raw_response)
208
+ return result[:translated] if result && !result[:translated].to_s.empty?
209
+ rescue EmptyTranslationError, InvalidResponseError
210
+ # Fallback: accepter une réponse texte brute non vide
211
+ fallback_text = raw_response.to_s.strip
212
+ return fallback_text unless fallback_text.empty?
213
+ # sinon on relance l'erreur plus bas
214
+ end
215
+
216
+ raise EmptyTranslationError
217
+ end
218
+
219
+ def handle_retryable_error!(error, attempt)
220
+ raise error unless attempt < MistralTranslator::Translator::DEFAULT_RETRY_COUNT
221
+
222
+ wait_time = MistralTranslator::Translator::DEFAULT_RETRY_DELAY * (2**attempt)
223
+ log_retry(error, attempt + 1, wait_time)
224
+ sleep(wait_time)
225
+ yield
226
+ end
227
+ end
228
+
229
+ # Helper pour traductions multi-cibles
230
+ module MultiTargetHelper
231
+ def translate_to_multiple_batch(text, source_locale, target_locales, context: nil, glossary: nil)
232
+ requests = target_locales.map do |target_locale|
233
+ {
234
+ prompt: build_translation_prompt(text, source_locale, target_locale, context: context, glossary: glossary),
235
+ from: source_locale,
236
+ to: target_locale,
237
+ original_text: text
238
+ }
239
+ end
240
+
241
+ batch_results = @client.translate_batch(requests, batch_size: 5)
242
+
243
+ results = {}
244
+ batch_results.each do |result|
245
+ next unless result[:success]
246
+
247
+ target_locale = result[:original_request][:to]
248
+ parsed_result = ResponseParser.parse_translation_response(result[:result])
249
+ results[target_locale] = parsed_result[:translated] if parsed_result
250
+ end
251
+
252
+ results
253
+ end
254
+
255
+ def translate_to_multiple_sequential(text, source_locale, target_locales, context: nil, glossary: nil)
256
+ results = {}
257
+
258
+ target_locales.each_with_index do |target_locale, index|
259
+ sleep(MistralTranslator::Translator::DEFAULT_RETRY_DELAY) if index.positive?
260
+ results[target_locale] =
261
+ translate_with_retry(text, source_locale, target_locale, context: context, glossary: glossary)
262
+ end
263
+
264
+ results
265
+ end
266
+ end
267
+ end
268
+ end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MistralTranslator
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.1"
5
5
 
6
6
  # Informations additionnelles sur la gem
7
7
  API_VERSION = "v1"
@@ -4,45 +4,52 @@ require_relative "mistral_translator/version"
4
4
  require_relative "mistral_translator/errors"
5
5
  require_relative "mistral_translator/configuration"
6
6
  require_relative "mistral_translator/locale_helper"
7
+ require_relative "mistral_translator/prompt_helpers"
7
8
  require_relative "mistral_translator/prompt_builder"
8
9
  require_relative "mistral_translator/response_parser"
10
+ require_relative "mistral_translator/client_helpers"
11
+ require_relative "mistral_translator/security"
9
12
  require_relative "mistral_translator/client"
13
+ require_relative "mistral_translator/translator_helpers"
10
14
  require_relative "mistral_translator/translator"
11
15
  require_relative "mistral_translator/summarizer"
16
+ require_relative "mistral_translator/adapters"
17
+ require_relative "mistral_translator/helpers_extensions"
18
+ require_relative "mistral_translator/helpers"
12
19
 
13
20
  module MistralTranslator
14
21
  class << self
15
22
  # Méthodes de convenance pour accès direct
16
- def translate(text, from:, to:)
17
- translator.translate(text, from: from, to: to)
23
+ def translate(text, from:, to:, **)
24
+ translator.translate(text, from: from, to: to, **)
18
25
  end
19
26
 
20
- def translate_to_multiple(text, from:, to:)
21
- translator.translate_to_multiple(text, from: from, to: to)
27
+ def translate_to_multiple(text, from:, to:, **)
28
+ translator.translate_to_multiple(text, from: from, to: to, **)
22
29
  end
23
30
 
24
- def translate_batch(texts, from:, to:)
25
- translator.translate_batch(texts, from: from, to: to)
31
+ def translate_batch(texts, from:, to:, **)
32
+ translator.translate_batch(texts, from: from, to: to, **)
26
33
  end
27
34
 
28
- def translate_auto(text, to:)
29
- translator.translate_auto(text, to: to)
35
+ def translate_auto(text, to:, **)
36
+ translator.translate_auto(text, to: to, **)
30
37
  end
31
38
 
32
- def summarize(text, language: "fr", max_words: 250)
33
- summarizer.summarize(text, language: language, max_words: max_words)
39
+ def summarize(text, language: "fr", max_words: 250, **)
40
+ summarizer.summarize(text, language: language, max_words: max_words, **)
34
41
  end
35
42
 
36
- def summarize_and_translate(text, from:, to:, max_words: 250)
37
- summarizer.summarize_and_translate(text, from: from, to: to, max_words: max_words)
43
+ def summarize_and_translate(text, from:, to:, max_words: 250, **)
44
+ summarizer.summarize_and_translate(text, from: from, to: to, max_words: max_words, **)
38
45
  end
39
46
 
40
- def summarize_to_multiple(text, languages:, max_words: 250)
41
- summarizer.summarize_to_multiple(text, languages: languages, max_words: max_words)
47
+ def summarize_to_multiple(text, languages:, max_words: 250, **)
48
+ summarizer.summarize_to_multiple(text, languages: languages, max_words: max_words, **)
42
49
  end
43
50
 
44
- def summarize_tiered(text, language: "fr", short: 50, medium: 150, long: 300)
45
- summarizer.summarize_tiered(text, language: language, short: short, medium: medium, long: long)
51
+ def summarize_tiered(text, **)
52
+ summarizer.summarize_tiered(text, **)
46
53
  end
47
54
 
48
55
  # Méthodes utilitaires
@@ -79,9 +86,28 @@ module MistralTranslator
79
86
  VERSION
80
87
  end
81
88
 
89
+ def version_info
90
+ {
91
+ gem_version: VERSION,
92
+ api_version: API_VERSION,
93
+ supported_model: SUPPORTED_MODEL,
94
+ ruby_version: RUBY_VERSION,
95
+ platform: RUBY_PLATFORM
96
+ }
97
+ end
98
+
99
+ # Métriques (nouvelles)
100
+ def metrics
101
+ configuration.metrics
102
+ end
103
+
104
+ def reset_metrics!
105
+ configuration.reset_metrics!
106
+ end
107
+
82
108
  # Health check
83
109
  def health_check
84
- client.complete("Hello", max_tokens: 10)
110
+ client.complete("Hello", max_tokens: 10, context: {})
85
111
  { status: :ok, message: "API connection successful" }
86
112
  rescue AuthenticationError
87
113
  { status: :error, message: "Authentication failed - check your API key" }
@@ -113,12 +139,12 @@ module MistralTranslator
113
139
  end
114
140
 
115
141
  module ClassMethods
116
- def mistral_translate(text, from:, to:)
117
- MistralTranslator.translate(text, from: from, to: to)
142
+ def mistral_translate(text, from:, to:, **)
143
+ MistralTranslator.translate(text, from: from, to: to, **)
118
144
  end
119
145
 
120
- def mistral_summarize(text, language: "fr", max_words: 250)
121
- MistralTranslator.summarize(text, language: language, max_words: max_words)
146
+ def mistral_summarize(text, language: "fr", max_words: 250, **)
147
+ MistralTranslator.summarize(text, language: language, max_words: max_words, **)
122
148
  end
123
149
  end
124
150
  end
@@ -127,12 +153,12 @@ end
127
153
  # Extensions optionnelles pour String
128
154
  if ENV["MISTRAL_TRANSLATOR_EXTEND_STRING"]
129
155
  class String
130
- def mistral_translate(from:, to:)
131
- MistralTranslator.translate(self, from: from, to: to)
156
+ def mistral_translate(from:, to:, **)
157
+ MistralTranslator.translate(self, from: from, to: to, **)
132
158
  end
133
159
 
134
- def mistral_summarize(language: "fr", max_words: 250)
135
- MistralTranslator.summarize(self, language: language, max_words: max_words)
160
+ def mistral_summarize(language: "fr", max_words: 250, **)
161
+ MistralTranslator.summarize(self, language: language, max_words: max_words, **)
136
162
  end
137
163
  end
138
164
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mistral_translator
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Peyochanchan
@@ -93,8 +93,10 @@ dependencies:
93
93
  - - "~>"
94
94
  - !ruby/object:Gem::Version
95
95
  version: '3.18'
96
- description: Allows translating text into different languages and generating summaries
97
- using the MistralAI API
96
+ description: A comprehensive Ruby gem for text translation, summarization, and multilingual
97
+ content management using Mistral AI API. Features include context-aware translation,
98
+ glossaries, batch processing, Rails integration with Mobility/Globalize support,
99
+ monitoring, and advanced helpers for complex translation workflows.
98
100
  email:
99
101
  - cameleon24@outlook.fr
100
102
  executables: []
@@ -108,18 +110,52 @@ files:
108
110
  - CODE_OF_CONDUCT.md
109
111
  - LICENSE.txt
110
112
  - README.md
113
+ - README_TESTING.md
111
114
  - Rakefile
115
+ - SECURITY.md
116
+ - docs/.nojekyll
117
+ - docs/404.html
118
+ - docs/README.md
119
+ - docs/advanced-usage/batch-processing.md
120
+ - docs/advanced-usage/error-handling.md
121
+ - docs/advanced-usage/monitoring.md
122
+ - docs/advanced-usage/summarization.md
123
+ - docs/advanced-usage/translations.md
124
+ - docs/api-reference/callbacks.md
125
+ - docs/api-reference/configuration.md
126
+ - docs/api-reference/errors.md
127
+ - docs/api-reference/methods.md
128
+ - docs/getting-started.md
129
+ - docs/index.html
130
+ - docs/installation.md
131
+ - docs/migration-0.1.0-to-0.2.0.md
132
+ - docs/rails-integration/adapters.md
133
+ - docs/rails-integration/controllers.md
134
+ - docs/rails-integration/jobs.md
135
+ - docs/rails-integration/setup.md
112
136
  - examples/basic_usage.rb
137
+ - examples/batch-job.rb
138
+ - examples/monitoring-setup.rb
139
+ - examples/rails-model.rb
113
140
  - lib/mistral_translator.rb
141
+ - lib/mistral_translator/adapters.rb
114
142
  - lib/mistral_translator/client.rb
143
+ - lib/mistral_translator/client_helpers.rb
115
144
  - lib/mistral_translator/configuration.rb
116
145
  - lib/mistral_translator/errors.rb
146
+ - lib/mistral_translator/helpers.rb
147
+ - lib/mistral_translator/helpers_extensions.rb
148
+ - lib/mistral_translator/levenshtein_helpers.rb
117
149
  - lib/mistral_translator/locale_helper.rb
118
150
  - lib/mistral_translator/logger.rb
119
151
  - lib/mistral_translator/prompt_builder.rb
152
+ - lib/mistral_translator/prompt_helpers.rb
153
+ - lib/mistral_translator/prompt_metadata_helpers.rb
120
154
  - lib/mistral_translator/response_parser.rb
155
+ - lib/mistral_translator/security.rb
121
156
  - lib/mistral_translator/summarizer.rb
122
157
  - lib/mistral_translator/translator.rb
158
+ - lib/mistral_translator/translator_helpers.rb
123
159
  - lib/mistral_translator/version.rb
124
160
  - sig/mistral_translator.rbs
125
161
  homepage: https://github.com/Peyochanchan/mistral_translator