better_translate 0.3.1 → 0.4.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a2ff034a6db3498226ddff2111cf66e94a371f65741f82986b6ec8324daaf2dc
4
- data.tar.gz: 991a78781adfc647d789729f385bca6e7688a6269bf102e0bff8b40b7bf8aca7
3
+ metadata.gz: cf5e2fea9a6f6702124591e888a922072386b7dc3eca153008828448eae45357
4
+ data.tar.gz: 5c11456be0bda2b86ea385235a769d405af58233c096d306f710d451eecf6f2a
5
5
  SHA512:
6
- metadata.gz: 68f06a1006c8fc3e43184485d8f71ec9b080649fa68e2f8c5563d55b20c356b501a20a308f0c00f967b5387ac8d3f62abf11d837ca25b325a646658bf8126146
7
- data.tar.gz: 719cb1a9bb0217934ca0ecacd455bcc6c81afab081fbc43d24b8add5a50eb33b7947ab715a37938460d162fdcf88e9e629b8cc18d08e1f47ecec085101c35e8c
6
+ metadata.gz: 8ca97b7c5796b961e80de8a3247aecaf7e781965dfed89197977be2fc59326f8800d6a4b82ed91fbff592c82958b23088e09493d50c42f82458a697628a2a607
7
+ data.tar.gz: 6404f6b92fa55708db6127ade139d6f3f12170ef5c6617553ccbe383be3cb0971d31b2f956b9ac1bf0f8b4bc65bc46f702f55be13cc49cfc11541af319c72811
data/CHANGELOG.md CHANGED
@@ -5,6 +5,32 @@ 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.4.1] - 2025-03-11
9
+
10
+ ### Fixed
11
+ - Migliorati i test RSpec per garantire maggiore affidabilità:
12
+ - Corretti gli stub per i provider di traduzione
13
+ - Migliorata la gestione delle richieste HTTP nei test
14
+ - Ottimizzati i file YAML temporanei per i test di similarità
15
+ - Risolti problemi di compatibilità con WebMock
16
+
17
+ ### Changed
18
+ - Sostituito l'approccio di stubbing specifico con pattern più flessibili
19
+ - Migliorata la struttura dei test per il SimilarityAnalyzer
20
+
21
+ ## [0.4.0] - 2025-03-11
22
+
23
+ ### Added
24
+ - New Translation Similarity Analyzer:
25
+ - Identifies similar translations across language files
26
+ - Generates detailed JSON reports and human-readable summaries
27
+ - Uses Levenshtein distance for similarity calculation
28
+ - Configurable similarity threshold
29
+ - New Rails generator: `rails generate better_translate:analyze`
30
+ - Analyzes all YAML files in the locales directory
31
+ - Provides immediate feedback in the console
32
+ - Generates comprehensive similarity reports
33
+
8
34
  ## [0.3.1] - 2025-03-11
9
35
 
10
36
  ### Added
data/README.md CHANGED
@@ -14,6 +14,7 @@ BetterTranslate simplifies the translation of YAML files in your Ruby/Rails appl
14
14
  - 📊 Real-time progress tracking
15
15
  - 🧪 Comprehensive test coverage
16
16
  - ⚡️ LRU caching for performance
17
+ - 🔍 Translation similarity analysis
17
18
 
18
19
  ## Why BetterTranslate? 🤔
19
20
 
@@ -30,6 +31,10 @@ BetterTranslate simplifies the translation of YAML files in your Ruby/Rails appl
30
31
  - Comprehensive test suite
31
32
  - LRU caching for performance
32
33
  - Progress tracking
34
+ - 🔍 **Translation Analysis**:
35
+ - Similarity detection
36
+ - Detailed reports
37
+ - Optimization suggestions
33
38
  - 🔧 **Flexible Helpers**:
34
39
  - Single text translation
35
40
  - Bulk text translation
@@ -146,6 +151,25 @@ The gem includes generators to simplify tasks:
146
151
 
147
152
  The `better_translate:translate` generator will trigger the translation process (via `BetterTranslate.magic`) and display progress in the terminal.
148
153
 
154
+ - **Analyze Translations:**
155
+
156
+ ```bash
157
+ rails generate better_translate:analyze
158
+ ```
159
+
160
+ The `better_translate:analyze` generator will:
161
+ - Scan all YAML files in your locales directory
162
+ - Find similar translations using Levenshtein distance
163
+ - Generate two reports:
164
+ - `translation_similarities.json`: Detailed JSON report
165
+ - `translation_similarities_summary.txt`: Human-readable summary
166
+
167
+ This helps you:
168
+ - Identify potentially redundant translations
169
+ - Maintain consistency across your translations
170
+ - Optimize your translation files
171
+ - Reduce translation costs
172
+
149
173
  ### Translation Helpers
150
174
 
151
175
  BetterTranslate provides helper methods to simplify translation tasks.
@@ -13,7 +13,7 @@ module BetterTranslate
13
13
  body = {
14
14
  model: "gpt-3.5-turbo",
15
15
  messages: [
16
- { role: "system", content: "You are a professional translator. Translate the following text exactly from #{BetterTranslate.configuration.source_language} to #{target_lang_name} without adding comments, explanations, or alternatives. Provide only the direct translation:" },
16
+ { role: "system", content: "You are a professional translator. Translate the following text exactly from #{BetterTranslate.configuration.source_language} to #{target_lang_name}. Provide ONLY the direct translation without any explanations, alternatives, or additional text. Do not include the original text. Do not use markdown formatting. Do not add any prefixes or notes. Just return the plain translated text." },
17
17
  { role: "user", content: "#{text}" }
18
18
  ],
19
19
  temperature: 0.3
@@ -30,8 +30,10 @@ module BetterTranslate
30
30
  translated_text = json.dig("choices", 0, "message", "content")
31
31
  translated_text ? translated_text.strip : text
32
32
  else
33
- raise "Error during translation with ChatGPT: #{response.body}"
33
+ raise "Errore HTTP #{response.code}: #{response.body}"
34
34
  end
35
+ rescue StandardError => e
36
+ raise "Errore durante la traduzione con ChatGPT: #{e.message}"
35
37
  end
36
38
  end
37
39
  end
@@ -1,33 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "net/http"
4
+ require "json"
5
+ require "uri"
6
+
1
7
  module BetterTranslate
2
8
  module Providers
3
9
  class GeminiProvider < BaseProvider
4
- # Esempio di implementazione per Google Gemini.
5
- # Nota: L'endpoint e i parametri sono ipotetici e vanno sostituiti con quelli reali secondo la documentazione ufficiale.
10
+ GEMINI_API_URL = "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent"
11
+
6
12
  def translate_text(text, target_lang_code, target_lang_name)
7
- uri = URI("https://gemini.googleapis.com/v1/translate")
8
- headers = {
9
- "Content-Type" => "application/json",
10
- "Authorization" => "Bearer #{@api_key}"
11
- }
13
+ url = "#{GEMINI_API_URL}?key=#{@api_key}"
14
+ uri = URI(url)
12
15
 
16
+ headers = { "Content-Type" => "application/json" }
13
17
  body = {
14
- input_text: text,
15
- target_language: target_lang_code,
16
- model: "gemini" # oppure altri parametri richiesti dall'API
18
+ contents: [{
19
+ parts: [{
20
+ text: "Translate the following text to #{target_lang_name}. Provide ONLY the direct translation without any explanations, alternatives, or additional text. Do not include the original text. Do not use markdown formatting. Do not add any prefixes or notes. Just return the plain translated text:\n\n#{text}"
21
+ }]
22
+ }]
17
23
  }
18
24
 
19
25
  http = Net::HTTP.new(uri.host, uri.port)
20
26
  http.use_ssl = true
21
- request = Net::HTTP::Post.new(uri.path, headers)
27
+ request = Net::HTTP::Post.new(uri.path + "?" + uri.query, headers)
22
28
  request.body = body.to_json
23
29
 
24
- response = http.request(request)
25
- if response.is_a?(Net::HTTPSuccess)
26
- json = JSON.parse(response.body)
27
- translated_text = json["translatedText"]
28
- translated_text ? translated_text.strip : text
29
- else
30
- raise "Errore durante la traduzione con Gemini: #{response.body}"
30
+ begin
31
+ response = http.request(request)
32
+
33
+ if response.is_a?(Net::HTTPSuccess)
34
+ json = JSON.parse(response.body)
35
+
36
+ if json["candidates"]&.any? && json["candidates"][0]["content"]["parts"]&.any?
37
+ translated_text = json["candidates"][0]["content"]["parts"][0]["text"]
38
+
39
+ # Pulizia minima del testo, dato che il prompt è già specifico
40
+ cleaned_text = translated_text.strip
41
+ .gsub(/[\*\`\n"]/, '') # Rimuovi markdown, newline e virgolette
42
+ .gsub(/\s+/, ' ') # Riduci spazi multipli a uno singolo
43
+
44
+ cleaned_text.empty? ? text : cleaned_text
45
+ else
46
+ raise "Risposta Gemini non valida: #{json}"
47
+ end
48
+ else
49
+ raise "Errore HTTP #{response.code}: #{response.body}"
50
+ end
51
+ rescue => e
52
+ raise "Errore durante la traduzione con Gemini: #{e.message}"
31
53
  end
32
54
  end
33
55
  end
@@ -60,7 +60,7 @@ module BetterTranslate
60
60
  when :chatgpt
61
61
  Providers::ChatgptProvider.new(BetterTranslate.configuration.openai_key)
62
62
  when :gemini
63
- Providers::GeminiProvider.new(BetterTranslate.configuration.google_gemini_key)
63
+ Providers::GeminiProvider.new(BetterTranslate.configuration.gemini_key)
64
64
  else
65
65
  raise "Provider non supportato: #{@provider_name}"
66
66
  end
@@ -0,0 +1,151 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "yaml"
4
+ require "set"
5
+ require "json"
6
+ require "time"
7
+
8
+ module BetterTranslate
9
+ class SimilarityAnalyzer
10
+ SIMILARITY_THRESHOLD = 0.75 # Abbassiamo la soglia per trovare più similarità
11
+ REPORT_FILE = "translation_similarities.json"
12
+
13
+ def initialize(yaml_files)
14
+ @yaml_files = yaml_files
15
+ @similarities = {}
16
+ end
17
+
18
+ def analyze
19
+ translations_by_language = load_translations
20
+ find_similarities(translations_by_language)
21
+ generate_report
22
+ end
23
+
24
+ private
25
+
26
+ def load_translations
27
+ translations = {}
28
+ puts "Loading YAML files..."
29
+
30
+ @yaml_files.each do |file|
31
+ lang_code = File.basename(file, ".yml")
32
+ translations[lang_code] = YAML.load_file(file)
33
+ puts " - Loaded #{lang_code}.yml"
34
+ end
35
+
36
+ translations
37
+ end
38
+
39
+ def find_similarities(translations_by_language)
40
+ translations_by_language.each do |lang, translations|
41
+ puts "\nAnalyzing #{lang} translations..."
42
+ flattened = flatten_translations(translations)
43
+ keys = flattened.keys
44
+ similar_found = 0
45
+
46
+ keys.each_with_index do |key1, i|
47
+ value1 = flattened[key1]
48
+
49
+ # Confronta solo con le chiavi successive per evitare duplicati
50
+ keys[(i + 1)..-1].each do |key2|
51
+ value2 = flattened[key2]
52
+
53
+ similarity = calculate_similarity(value1.to_s, value2.to_s)
54
+ if similarity >= SIMILARITY_THRESHOLD
55
+ record_similarity(lang, key1, key2, value1, value2, similarity)
56
+ similar_found += 1
57
+ end
58
+ end
59
+ end
60
+
61
+ puts " Found #{similar_found} similar translations"
62
+ end
63
+ end
64
+
65
+ def flatten_translations(hash, prefix = "", result = {})
66
+ hash.each do |key, value|
67
+ current_key = prefix.empty? ? key.to_s : "#{prefix}.#{key}"
68
+
69
+ if value.is_a?(Hash)
70
+ flatten_translations(value, current_key, result)
71
+ else
72
+ result[current_key] = value
73
+ end
74
+ end
75
+
76
+ result
77
+ end
78
+
79
+ def calculate_similarity(str1, str2)
80
+ # Implementazione della distanza di Levenshtein normalizzata
81
+ matrix = Array.new(str1.length + 1) { Array.new(str2.length + 1) }
82
+
83
+ (0..str1.length).each { |i| matrix[i][0] = i }
84
+ (0..str2.length).each { |j| matrix[0][j] = j }
85
+
86
+ (1..str1.length).each do |i|
87
+ (1..str2.length).each do |j|
88
+ cost = str1[i-1] == str2[j-1] ? 0 : 1
89
+ matrix[i][j] = [
90
+ matrix[i-1][j] + 1,
91
+ matrix[i][j-1] + 1,
92
+ matrix[i-1][j-1] + cost
93
+ ].min
94
+ end
95
+ end
96
+
97
+ max_length = [str1.length, str2.length].max
98
+ 1 - (matrix[str1.length][str2.length].to_f / max_length)
99
+ end
100
+
101
+ def record_similarity(lang, key1, key2, value1, value2, similarity)
102
+ @similarities[lang] ||= []
103
+ @similarities[lang] << {
104
+ key1: key1,
105
+ key2: key2,
106
+ value1: value1,
107
+ value2: value2,
108
+ similarity: similarity.round(2)
109
+ }
110
+ end
111
+
112
+ def generate_report
113
+ puts "\nGenerating reports..."
114
+ report = {
115
+ generated_at: Time.now.iso8601,
116
+ similarity_threshold: SIMILARITY_THRESHOLD,
117
+ findings: @similarities
118
+ }
119
+
120
+ File.write(REPORT_FILE, JSON.pretty_generate(report))
121
+ puts " - Generated #{REPORT_FILE}"
122
+
123
+ summary = generate_summary(report)
124
+ File.write("translation_similarities_summary.txt", summary)
125
+ puts " - Generated translation_similarities_summary.txt"
126
+ end
127
+
128
+ def generate_summary(report)
129
+ summary = ["Translation Similarities Report", "=" * 30, ""]
130
+
131
+ report[:findings].each do |lang, similarities|
132
+ summary << "Language: #{lang}"
133
+ summary << "-" * 20
134
+
135
+ similarities.each do |sim|
136
+ summary << "Similar translations found:"
137
+ summary << " Key 1: #{sim[:key1]}"
138
+ summary << " Value 1: #{sim[:value1]}"
139
+ summary << " Key 2: #{sim[:key2]}"
140
+ summary << " Value 2: #{sim[:value2]}"
141
+ summary << " Similarity: #{(sim[:similarity] * 100).round(1)}%"
142
+ summary << ""
143
+ end
144
+
145
+ summary << ""
146
+ end
147
+
148
+ summary.join("\n")
149
+ end
150
+ end
151
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module BetterTranslate
4
- VERSION = "0.3.1"
4
+ VERSION = "0.4.1"
5
5
  end
@@ -7,6 +7,7 @@ require "better_translate/translator"
7
7
  require "better_translate/service"
8
8
  require "better_translate/writer"
9
9
  require "better_translate/helper"
10
+ require "better_translate/similarity_analyzer"
10
11
 
11
12
  require 'better_translate/providers/base_provider'
12
13
  require 'better_translate/providers/chatgpt_provider'
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+
5
+ module BetterTranslate
6
+ module Generators
7
+ class AnalyzeGenerator < Rails::Generators::Base
8
+ desc "Analyze translation files for similarities"
9
+
10
+ def analyze_translations
11
+ say "Starting translation similarity analysis...", :blue
12
+
13
+ # Find all YAML files in the locales directory
14
+ locale_dir = Rails.root.join("config", "locales")
15
+ yaml_files = Dir[locale_dir.join("*.yml")]
16
+
17
+ if yaml_files.empty?
18
+ say "No YAML files found in #{locale_dir}", :red
19
+ return
20
+ end
21
+
22
+ say "Found #{yaml_files.length} YAML files to analyze", :green
23
+
24
+ # Run analysis
25
+ analyzer = BetterTranslate::SimilarityAnalyzer.new(yaml_files)
26
+ analyzer.analyze
27
+
28
+ # Show results
29
+ say "\nAnalysis complete!", :green
30
+ say "Reports generated:", :green
31
+ say " * #{BetterTranslate::SimilarityAnalyzer::REPORT_FILE} (detailed JSON report)"
32
+ say " * translation_similarities_summary.txt (human-readable summary)"
33
+
34
+ # Show quick summary from the text file
35
+ if File.exist?("translation_similarities_summary.txt")
36
+ summary = File.read("translation_similarities_summary.txt")
37
+ say "\nQuick Summary:", :blue
38
+ say summary
39
+ end
40
+ end
41
+ end
42
+ end
43
+ 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.3.1
4
+ version: 0.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alessio Bussolari
@@ -179,19 +179,19 @@ dependencies:
179
179
  - !ruby/object:Gem::Version
180
180
  version: '3.19'
181
181
  - !ruby/object:Gem::Dependency
182
- name: vcr
182
+ name: dotenv
183
183
  requirement: !ruby/object:Gem::Requirement
184
184
  requirements:
185
185
  - - "~>"
186
186
  - !ruby/object:Gem::Version
187
- version: '6.1'
187
+ version: '2.8'
188
188
  type: :development
189
189
  prerelease: false
190
190
  version_requirements: !ruby/object:Gem::Requirement
191
191
  requirements:
192
192
  - - "~>"
193
193
  - !ruby/object:Gem::Version
194
- version: '6.1'
194
+ version: '2.8'
195
195
  description: |
196
196
  BetterTranslate is a powerful Ruby gem for translating YAML files using AI providers.
197
197
 
@@ -222,10 +222,12 @@ files:
222
222
  - lib/better_translate/providers/chatgpt_provider.rb
223
223
  - lib/better_translate/providers/gemini_provider.rb
224
224
  - lib/better_translate/service.rb
225
+ - lib/better_translate/similarity_analyzer.rb
225
226
  - lib/better_translate/translator.rb
226
227
  - lib/better_translate/utils.rb
227
228
  - lib/better_translate/version.rb
228
229
  - lib/better_translate/writer.rb
230
+ - lib/generators/better_translate/analyze_generator.rb
229
231
  - lib/generators/better_translate/install_generator.rb
230
232
  - lib/generators/better_translate/templates/better_translate.rb
231
233
  - lib/generators/better_translate/translate_generator.rb