better_translate 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +87 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/README.md +90 -30
- data/Rakefile +12 -0
- data/lib/better_translate/helper.rb +1 -5
- data/lib/better_translate/providers/base_provider.rb +47 -5
- data/lib/better_translate/providers/chatgpt_provider.rb +5 -5
- data/lib/better_translate/providers/gemini_provider.rb +1 -1
- data/lib/better_translate/service.rb +46 -2
- data/lib/better_translate/similarity_analyzer.rb +151 -0
- data/lib/better_translate/translator.rb +117 -37
- data/lib/better_translate/utils.rb +23 -6
- data/lib/better_translate/version.rb +1 -1
- data/lib/better_translate/writer.rb +17 -15
- data/lib/better_translate.rb +11 -8
- data/lib/generators/better_translate/analyze_generator.rb +43 -0
- data/lib/generators/better_translate/install_generator.rb +1 -1
- data/lib/generators/better_translate/templates/better_translate.rb +12 -12
- data/lib/generators/better_translate/translate_generator.rb +5 -3
- data/sig/better_translate.rbs +4 -0
- metadata +149 -24
@@ -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
|
@@ -2,65 +2,75 @@ module BetterTranslate
|
|
2
2
|
class Translator
|
3
3
|
class << self
|
4
4
|
def work
|
5
|
-
|
5
|
+
message = "Starting file translation..."
|
6
|
+
BetterTranslate::Utils.logger(message: message)
|
6
7
|
|
7
8
|
translations = read_yml_source
|
8
9
|
|
9
|
-
#
|
10
|
+
# Removes the keys to exclude (global_exclusions) from the read structure
|
10
11
|
global_filtered_translations = remove_exclusions(
|
11
12
|
translations, BetterTranslate.configuration.global_exclusions
|
12
13
|
)
|
13
14
|
|
14
|
-
|
15
|
+
start_time = Time.now
|
16
|
+
threads = BetterTranslate.configuration.target_languages.map do |target_lang|
|
17
|
+
Thread.new do
|
15
18
|
|
16
|
-
#
|
19
|
+
# Phase 2: Apply the target language specific filter
|
17
20
|
lang_exclusions = BetterTranslate.configuration.exclusions_per_language[target_lang[:short_name]] || []
|
18
21
|
filtered_translations = remove_exclusions(
|
19
22
|
global_filtered_translations, lang_exclusions
|
20
23
|
)
|
21
24
|
|
22
|
-
|
25
|
+
message = "Starting translation from #{BetterTranslate.configuration.source_language} to #{target_lang[:short_name]}"
|
26
|
+
BetterTranslate::Utils.logger(message: message)
|
23
27
|
service = BetterTranslate::Service.new
|
24
28
|
translated_data = translate_with_progress(filtered_translations, service, target_lang[:short_name], target_lang[:name])
|
25
29
|
BetterTranslate::Writer.write_translations(translated_data, target_lang[:short_name])
|
26
|
-
|
30
|
+
end_time = Time.now
|
31
|
+
duration = end_time - start_time
|
32
|
+
BetterTranslate::Utils.track_metric("translation_duration", duration)
|
33
|
+
|
34
|
+
message = "Translation completed from #{BetterTranslate.configuration.source_language} to #{target_lang[:short_name]} in #{duration.round(2)} seconds"
|
35
|
+
BetterTranslate::Utils.logger(message: message)
|
36
|
+
end
|
27
37
|
end
|
28
38
|
end
|
29
39
|
|
30
40
|
private
|
31
41
|
|
32
|
-
#
|
42
|
+
# Reads the YAML file based on the provided path.
|
33
43
|
#
|
34
|
-
# @param file_path [String]
|
35
|
-
# @return [Hash]
|
36
|
-
# @raise [StandardError]
|
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
|
37
47
|
def read_yml_source
|
38
48
|
file_path = BetterTranslate.configuration.input_file
|
39
49
|
unless File.exist?(file_path)
|
40
|
-
raise "File
|
50
|
+
raise "File not found: #{file_path}"
|
41
51
|
end
|
42
52
|
|
43
53
|
YAML.load_file(file_path)
|
44
54
|
end
|
45
55
|
|
46
|
-
#
|
47
|
-
#
|
56
|
+
# Removes the global keys to exclude from the data structure,
|
57
|
+
# calculating paths starting from the source language content.
|
48
58
|
#
|
49
|
-
#
|
59
|
+
# For example, if the YAML file is:
|
50
60
|
# { "en" => { "sample" => { "valid" => "valid", "excluded" => "Excluded" } } }
|
51
|
-
#
|
52
|
-
#
|
61
|
+
# and global_exclusions = ["sample.excluded"],
|
62
|
+
# the result will be:
|
53
63
|
# { "en" => { "sample" => { "valid" => "valid" } } }
|
54
64
|
#
|
55
|
-
# @param data [Hash, Array, Object]
|
56
|
-
# @param global_exclusions [Array<String>]
|
57
|
-
# @param current_path [Array]
|
58
|
-
# @return [Hash, Array, Object]
|
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.
|
59
69
|
def remove_exclusions(data, exclusion_list, current_path = [])
|
60
70
|
if data.is_a?(Hash)
|
61
71
|
data.each_with_object({}) do |(key, value), result|
|
62
|
-
#
|
63
|
-
#
|
72
|
+
# If we are at the top-level and the key matches the source language,
|
73
|
+
# reset the path (to exclude "en" from the final path)
|
64
74
|
new_path = if current_path.empty? && key == BetterTranslate.configuration.source_language
|
65
75
|
[]
|
66
76
|
else
|
@@ -81,14 +91,14 @@ module BetterTranslate
|
|
81
91
|
end
|
82
92
|
end
|
83
93
|
|
84
|
-
#
|
94
|
+
# Recursive method that traverses the structure, translating each string and updating progress.
|
85
95
|
#
|
86
|
-
# @param data [Hash, Array, String]
|
87
|
-
# @param provider [Object]
|
88
|
-
# @param target_lang_code [String]
|
89
|
-
# @param target_lang_name [String]
|
90
|
-
# @param progress [Hash]
|
91
|
-
# @return [Hash, Array, String]
|
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.
|
92
102
|
def deep_translate_with_progress(data, service, target_lang_code, target_lang_name, progress)
|
93
103
|
if data.is_a?(Hash)
|
94
104
|
data.each_with_object({}) do |(key, value), result|
|
@@ -106,20 +116,90 @@ module BetterTranslate
|
|
106
116
|
end
|
107
117
|
end
|
108
118
|
|
109
|
-
#
|
119
|
+
# Main method to translate the entire data structure, with progress monitoring.
|
110
120
|
#
|
111
|
-
# @param data [Hash, Array, String]
|
112
|
-
# @param provider [Object]
|
113
|
-
# @param target_lang_code [String]
|
114
|
-
# @param target_lang_name [String]
|
115
|
-
# @return
|
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
|
116
126
|
def translate_with_progress(data, service, target_lang_code, target_lang_name)
|
117
127
|
total = count_strings(data)
|
118
128
|
progress = ProgressBar.create(total: total, format: '%a %B %p%% %t')
|
119
|
-
|
129
|
+
|
130
|
+
start_time = Time.now
|
131
|
+
result = if total > 50 # Usa il batch processing per dataset grandi
|
132
|
+
batch_translate_with_progress(data, service, target_lang_code, target_lang_name, progress)
|
133
|
+
else
|
134
|
+
deep_translate_with_progress(data, service, target_lang_code, target_lang_name, progress)
|
135
|
+
end
|
136
|
+
|
137
|
+
duration = Time.now - start_time
|
138
|
+
BetterTranslate::Utils.track_metric("translation_method_duration", {
|
139
|
+
method: total > 50 ? 'batch' : 'deep',
|
140
|
+
duration: duration,
|
141
|
+
total_strings: total
|
142
|
+
})
|
143
|
+
|
144
|
+
result
|
145
|
+
end
|
146
|
+
|
147
|
+
def batch_translate_with_progress(data, service, target_lang_code, target_lang_name, progress)
|
148
|
+
texts = extract_translatable_texts(data)
|
149
|
+
translations = {}
|
150
|
+
|
151
|
+
texts.each_slice(10).each_with_index do |batch, index|
|
152
|
+
batch_start = Time.now
|
153
|
+
|
154
|
+
batch_translations = batch.map do |text|
|
155
|
+
translated = service.translate(text, target_lang_code, target_lang_name)
|
156
|
+
progress.increment
|
157
|
+
[text, translated]
|
158
|
+
end.to_h
|
159
|
+
|
160
|
+
translations.merge!(batch_translations)
|
161
|
+
|
162
|
+
batch_duration = Time.now - batch_start
|
163
|
+
BetterTranslate::Utils.track_metric("batch_translation_duration", {
|
164
|
+
batch_number: index + 1,
|
165
|
+
size: batch.size,
|
166
|
+
duration: batch_duration
|
167
|
+
})
|
168
|
+
end
|
169
|
+
|
170
|
+
replace_translations(data, translations)
|
171
|
+
end
|
172
|
+
|
173
|
+
def extract_translatable_texts(data)
|
174
|
+
texts = Set.new
|
175
|
+
traverse_structure(data) do |value|
|
176
|
+
texts.add(value) if value.is_a?(String) && !value.strip.empty?
|
177
|
+
end
|
178
|
+
texts.to_a
|
179
|
+
end
|
180
|
+
|
181
|
+
def replace_translations(data, translations)
|
182
|
+
traverse_structure(data) do |value|
|
183
|
+
if value.is_a?(String) && !value.strip.empty? && translations.key?(value)
|
184
|
+
translations[value]
|
185
|
+
else
|
186
|
+
value
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
def traverse_structure(data, &block)
|
192
|
+
case data
|
193
|
+
when Hash
|
194
|
+
data.transform_values { |v| traverse_structure(v, &block) }
|
195
|
+
when Array
|
196
|
+
data.map { |v| traverse_structure(v, &block) }
|
197
|
+
else
|
198
|
+
yield data
|
199
|
+
end
|
120
200
|
end
|
121
201
|
|
122
|
-
#
|
202
|
+
# Recursively counts the number of translatable strings in the data structure.
|
123
203
|
def count_strings(data)
|
124
204
|
if data.is_a?(Hash)
|
125
205
|
data.values.sum { |v| count_strings(v) }
|
@@ -4,21 +4,21 @@
|
|
4
4
|
#
|
5
5
|
# = BetterSeeder::Utils
|
6
6
|
#
|
7
|
-
#
|
8
|
-
#
|
9
|
-
#
|
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
10
|
|
11
11
|
module BetterTranslate
|
12
12
|
class Utils
|
13
13
|
class << self
|
14
14
|
##
|
15
|
-
#
|
15
|
+
# Logs a message using the Rails logger if available, otherwise prints it to standard output.
|
16
16
|
#
|
17
17
|
# ==== Parametri
|
18
|
-
# * +message+ -
|
18
|
+
# * +message+ - The message to log (can be a string or nil).
|
19
19
|
#
|
20
20
|
# ==== Ritorno
|
21
|
-
#
|
21
|
+
# Does not return a significant value.
|
22
22
|
#
|
23
23
|
def logger(message: nil)
|
24
24
|
if defined?(Rails) && Rails.respond_to?(:logger) && Rails.logger
|
@@ -27,6 +27,23 @@ module BetterTranslate
|
|
27
27
|
puts message
|
28
28
|
end
|
29
29
|
end
|
30
|
+
|
31
|
+
def track_metric(name, value)
|
32
|
+
@metrics ||= {}
|
33
|
+
@metrics[name] ||= []
|
34
|
+
@metrics[name] << { value: value, timestamp: Time.now }
|
35
|
+
|
36
|
+
# Mantieni solo le ultime 1000 metriche per tipo
|
37
|
+
@metrics[name] = @metrics[name].last(1000) if @metrics[name].size > 1000
|
38
|
+
end
|
39
|
+
|
40
|
+
def get_metrics
|
41
|
+
@metrics || {}
|
42
|
+
end
|
43
|
+
|
44
|
+
def clear_metrics
|
45
|
+
@metrics = {}
|
46
|
+
end
|
30
47
|
end
|
31
48
|
end
|
32
49
|
end
|
@@ -1,19 +1,19 @@
|
|
1
1
|
module BetterTranslate
|
2
2
|
class Writer
|
3
3
|
class << self
|
4
|
-
#
|
5
|
-
#
|
6
|
-
#
|
4
|
+
# Writes the translation file for a target language.
|
5
|
+
# If the translation method is :override, the file is rewritten from scratch;
|
6
|
+
# if it's :incremental, the existing file is updated by inserting new translations in the correct positions.
|
7
7
|
#
|
8
|
-
# @param translated_data [Hash]
|
9
|
-
# @param target_lang_code [String]
|
8
|
+
# @param translated_data [Hash] The translated data structure (e.g. { "sample" => { "valid" => "translated", "new_key" => "new translation" } }).
|
9
|
+
# @param target_lang_code [String] Target language code (e.g. "ru").
|
10
10
|
def write_translations(translated_data, target_lang_code)
|
11
11
|
output_folder = BetterTranslate.configuration.output_folder
|
12
12
|
output_file = File.join(output_folder, "#{target_lang_code}.yml")
|
13
13
|
|
14
|
-
#
|
14
|
+
# Reformats the structure to use the target code as the main key.
|
15
15
|
output_content = if translated_data.is_a?(Hash) && translated_data.key?(BetterTranslate.configuration.source_language)
|
16
|
-
#
|
16
|
+
# Replaces the source language key with the target one.
|
17
17
|
{ target_lang_code => translated_data[BetterTranslate.configuration.source_language] }
|
18
18
|
else
|
19
19
|
{ target_lang_code => translated_data }
|
@@ -23,11 +23,13 @@ module BetterTranslate
|
|
23
23
|
existing_data = YAML.load_file(output_file)
|
24
24
|
merged = deep_merge(existing_data, output_content)
|
25
25
|
File.write(output_file, merged.to_yaml(line_width: -1))
|
26
|
-
|
26
|
+
message = "File updated in incremental mode: #{output_file}"
|
27
|
+
BetterTranslate::Utils.logger(message: message)
|
27
28
|
else
|
28
29
|
FileUtils.mkdir_p(output_folder) unless Dir.exist?(output_folder)
|
29
30
|
File.write(output_file, output_content.to_yaml(line_width: -1))
|
30
|
-
|
31
|
+
message = "File rewritten in override mode: #{output_file}"
|
32
|
+
BetterTranslate::Utils.logger(message: message)
|
31
33
|
end
|
32
34
|
end
|
33
35
|
|
@@ -35,12 +37,12 @@ module BetterTranslate
|
|
35
37
|
|
36
38
|
# Metodo di deep merge: unisce in modo ricorsivo i due hash.
|
37
39
|
# Se una chiave esiste in entrambi gli hash e i valori sono hash, li unisce ricorsivamente.
|
38
|
-
#
|
40
|
+
# Otherwise, if the key already exists in the existing hash, it keeps it and doesn't overwrite it.
|
39
41
|
# Se la chiave non esiste, la aggiunge.
|
40
42
|
#
|
41
|
-
# @param existing [Hash] hash
|
42
|
-
# @param new_data [Hash]
|
43
|
-
# @return [Hash] hash
|
43
|
+
# @param existing [Hash] existing hash (current file)
|
44
|
+
# @param new_data [Hash] new hash with translations to merge
|
45
|
+
# @return [Hash] merged hash
|
44
46
|
def deep_merge(existing, new_data)
|
45
47
|
merged = existing.dup
|
46
48
|
new_data.each do |key, value|
|
@@ -48,8 +50,8 @@ module BetterTranslate
|
|
48
50
|
if merged[key].is_a?(Hash) && value.is_a?(Hash)
|
49
51
|
merged[key] = deep_merge(merged[key], value)
|
50
52
|
else
|
51
|
-
#
|
52
|
-
#
|
53
|
+
# If the key already exists, don't overwrite the value (incremental mode)
|
54
|
+
# or you might decide to update anyway, depending on the requirements.
|
53
55
|
merged[key] = merged[key]
|
54
56
|
end
|
55
57
|
else
|
data/lib/better_translate.rb
CHANGED
@@ -7,50 +7,53 @@ 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'
|
13
14
|
require 'better_translate/providers/gemini_provider'
|
14
15
|
|
16
|
+
require 'ostruct'
|
17
|
+
|
15
18
|
module BetterTranslate
|
16
19
|
class << self
|
17
20
|
attr_accessor :configuration
|
18
21
|
|
19
|
-
#
|
22
|
+
# Method to configure the gem
|
20
23
|
def configure
|
21
24
|
self.configuration ||= OpenStruct.new
|
22
25
|
yield(configuration) if block_given?
|
23
26
|
end
|
24
27
|
|
25
|
-
#
|
28
|
+
# Install method to generate the configuration file (initializer)
|
26
29
|
def install
|
27
30
|
unless defined?(Rails) && Rails.respond_to?(:root)
|
28
|
-
message = "
|
31
|
+
message = "The install method is only available in a Rails application."
|
29
32
|
BetterTranslate::Utils.logger(message: message)
|
30
33
|
return
|
31
34
|
end
|
32
35
|
|
33
|
-
#
|
36
|
+
# Builds the path to the template folder inside the gem
|
34
37
|
source = File.expand_path("../generators/better_translate/templates/better_translate.rb", __dir__)
|
35
38
|
destination = File.join(Rails.root, "config", "initializers", "better_translate.rb")
|
36
39
|
|
37
40
|
if File.exist?(destination)
|
38
|
-
message = "
|
41
|
+
message = "The initializer file already exists: #{destination}"
|
39
42
|
BetterTranslate::Utils.logger(message: message)
|
40
43
|
else
|
41
44
|
FileUtils.mkdir_p(File.dirname(destination))
|
42
45
|
FileUtils.cp(source, destination)
|
43
|
-
message = "
|
46
|
+
message = "The initializer file already exists: #{destination}"
|
44
47
|
BetterTranslate::Utils.logger(message: message)
|
45
48
|
end
|
46
49
|
end
|
47
50
|
|
48
51
|
def magic
|
49
|
-
message = "
|
52
|
+
message = "Magic method invoked: Translation will begin..."
|
50
53
|
BetterTranslate::Utils.logger(message: message)
|
51
54
|
|
52
55
|
BetterTranslate::Translator.work
|
53
|
-
message = "
|
56
|
+
message = "Magic method invoked: Translation completed successfully!"
|
54
57
|
BetterTranslate::Utils.logger(message: message)
|
55
58
|
end
|
56
59
|
|
@@ -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
|
@@ -4,7 +4,7 @@ module BetterTranslate
|
|
4
4
|
module Generators
|
5
5
|
class InstallGenerator < Rails::Generators::Base
|
6
6
|
source_root File.expand_path('templates', __dir__)
|
7
|
-
desc "
|
7
|
+
desc "Generates the config/initializers/better_translate.rb file with default configuration"
|
8
8
|
|
9
9
|
def copy_initializer
|
10
10
|
template "better_translate.rb", "config/initializers/better_translate.rb"
|