better_translate 0.5.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.env.example +14 -0
- data/.rspec +3 -0
- data/.rubocop.yml +8 -0
- data/.yardopts +10 -0
- data/CHANGELOG.md +125 -114
- data/CLAUDE.md +385 -0
- data/README.md +629 -244
- data/Rakefile +7 -1
- data/Steepfile +29 -0
- data/docs/implementation/00-overview.md +220 -0
- data/docs/implementation/01-setup_dependencies.md +668 -0
- data/docs/implementation/02-error_handling.md +65 -0
- data/docs/implementation/03-core_components.md +457 -0
- data/docs/implementation/03.5-variable_preservation.md +509 -0
- data/docs/implementation/04-provider_architecture.md +571 -0
- data/docs/implementation/05-translation_logic.md +1065 -0
- data/docs/implementation/06-main_module_api.md +122 -0
- data/docs/implementation/07-direct_translation_helpers.md +582 -0
- data/docs/implementation/08-rails_integration.md +323 -0
- data/docs/implementation/09-testing_suite.md +228 -0
- data/docs/implementation/10-documentation_examples.md +150 -0
- data/docs/implementation/11-quality_security.md +65 -0
- data/docs/implementation/12-cli_standalone.md +698 -0
- data/exe/better_translate +9 -0
- data/lib/better_translate/cache.rb +125 -0
- data/lib/better_translate/cli.rb +304 -0
- data/lib/better_translate/configuration.rb +201 -0
- data/lib/better_translate/direct_translator.rb +131 -0
- data/lib/better_translate/errors.rb +101 -0
- data/lib/better_translate/progress_tracker.rb +157 -0
- data/lib/better_translate/provider_factory.rb +45 -0
- data/lib/better_translate/providers/anthropic_provider.rb +154 -0
- data/lib/better_translate/providers/base_http_provider.rb +239 -0
- data/lib/better_translate/providers/chatgpt_provider.rb +138 -44
- data/lib/better_translate/providers/gemini_provider.rb +123 -61
- data/lib/better_translate/railtie.rb +18 -0
- data/lib/better_translate/rate_limiter.rb +90 -0
- data/lib/better_translate/strategies/base_strategy.rb +58 -0
- data/lib/better_translate/strategies/batch_strategy.rb +56 -0
- data/lib/better_translate/strategies/deep_strategy.rb +45 -0
- data/lib/better_translate/strategies/strategy_selector.rb +43 -0
- data/lib/better_translate/translator.rb +115 -284
- data/lib/better_translate/utils/hash_flattener.rb +104 -0
- data/lib/better_translate/validator.rb +105 -0
- data/lib/better_translate/variable_extractor.rb +259 -0
- data/lib/better_translate/version.rb +2 -9
- data/lib/better_translate/yaml_handler.rb +168 -0
- data/lib/better_translate.rb +97 -73
- data/lib/generators/better_translate/analyze/USAGE +12 -0
- data/lib/generators/better_translate/analyze/analyze_generator.rb +94 -0
- data/lib/generators/better_translate/install/USAGE +13 -0
- data/lib/generators/better_translate/install/install_generator.rb +71 -0
- data/lib/generators/better_translate/install/templates/README +20 -0
- data/lib/generators/better_translate/install/templates/initializer.rb.tt +47 -0
- data/lib/generators/better_translate/translate/USAGE +13 -0
- data/lib/generators/better_translate/translate/translate_generator.rb +114 -0
- data/lib/tasks/better_translate.rake +136 -0
- data/sig/better_translate/cache.rbs +28 -0
- data/sig/better_translate/cli.rbs +24 -0
- data/sig/better_translate/configuration.rbs +78 -0
- data/sig/better_translate/direct_translator.rbs +18 -0
- data/sig/better_translate/errors.rbs +46 -0
- data/sig/better_translate/progress_tracker.rbs +29 -0
- data/sig/better_translate/provider_factory.rbs +8 -0
- data/sig/better_translate/providers/anthropic_provider.rbs +27 -0
- data/sig/better_translate/providers/base_http_provider.rbs +44 -0
- data/sig/better_translate/providers/chatgpt_provider.rbs +25 -0
- data/sig/better_translate/providers/gemini_provider.rbs +22 -0
- data/sig/better_translate/railtie.rbs +7 -0
- data/sig/better_translate/rate_limiter.rbs +20 -0
- data/sig/better_translate/strategies/base_strategy.rbs +19 -0
- data/sig/better_translate/strategies/batch_strategy.rbs +13 -0
- data/sig/better_translate/strategies/deep_strategy.rbs +11 -0
- data/sig/better_translate/strategies/strategy_selector.rbs +10 -0
- data/sig/better_translate/translator.rbs +24 -0
- data/sig/better_translate/utils/hash_flattener.rbs +14 -0
- data/sig/better_translate/validator.rbs +14 -0
- data/sig/better_translate/variable_extractor.rbs +40 -0
- data/sig/better_translate/version.rbs +4 -0
- data/sig/better_translate/yaml_handler.rbs +29 -0
- data/sig/better_translate.rbs +32 -2
- data/sig/faraday.rbs +22 -0
- data/sig/generators/better_translate/analyze/analyze_generator.rbs +18 -0
- data/sig/generators/better_translate/install/install_generator.rbs +14 -0
- data/sig/generators/better_translate/translate/translate_generator.rbs +10 -0
- data/sig/optparse.rbs +9 -0
- data/sig/psych.rbs +5 -0
- data/sig/rails.rbs +34 -0
- metadata +89 -203
- data/lib/better_translate/helper.rb +0 -83
- data/lib/better_translate/providers/base_provider.rb +0 -102
- data/lib/better_translate/service.rb +0 -144
- data/lib/better_translate/similarity_analyzer.rb +0 -218
- data/lib/better_translate/utils.rb +0 -55
- data/lib/better_translate/writer.rb +0 -75
- data/lib/generators/better_translate/analyze_generator.rb +0 -57
- data/lib/generators/better_translate/install_generator.rb +0 -14
- data/lib/generators/better_translate/templates/better_translate.rb +0 -56
- data/lib/generators/better_translate/translate_generator.rb +0 -84
|
@@ -1,218 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "yaml"
|
|
4
|
-
require "set"
|
|
5
|
-
require "json"
|
|
6
|
-
require "time"
|
|
7
|
-
|
|
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
|
|
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
|
|
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
|
|
23
|
-
REPORT_FILE = "translation_similarities.json"
|
|
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
|
|
29
|
-
def initialize(yaml_files)
|
|
30
|
-
@yaml_files = yaml_files
|
|
31
|
-
@similarities = {}
|
|
32
|
-
end
|
|
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]
|
|
40
|
-
def analyze
|
|
41
|
-
translations_by_language = load_translations
|
|
42
|
-
find_similarities(translations_by_language)
|
|
43
|
-
generate_report
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
private
|
|
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
|
|
52
|
-
def load_translations
|
|
53
|
-
translations = {}
|
|
54
|
-
puts "Loading YAML files..."
|
|
55
|
-
|
|
56
|
-
@yaml_files.each do |file|
|
|
57
|
-
lang_code = File.basename(file, ".yml")
|
|
58
|
-
translations[lang_code] = YAML.load_file(file)
|
|
59
|
-
puts " - Loaded #{lang_code}.yml"
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
translations
|
|
63
|
-
end
|
|
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]
|
|
71
|
-
def find_similarities(translations_by_language)
|
|
72
|
-
translations_by_language.each do |lang, translations|
|
|
73
|
-
puts "\nAnalyzing #{lang} translations..."
|
|
74
|
-
flattened = flatten_translations(translations)
|
|
75
|
-
keys = flattened.keys
|
|
76
|
-
similar_found = 0
|
|
77
|
-
|
|
78
|
-
keys.each_with_index do |key1, i|
|
|
79
|
-
value1 = flattened[key1]
|
|
80
|
-
|
|
81
|
-
# Confronta solo con le chiavi successive per evitare duplicati
|
|
82
|
-
keys[(i + 1)..-1].each do |key2|
|
|
83
|
-
value2 = flattened[key2]
|
|
84
|
-
|
|
85
|
-
similarity = calculate_similarity(value1.to_s, value2.to_s)
|
|
86
|
-
if similarity >= SIMILARITY_THRESHOLD
|
|
87
|
-
record_similarity(lang, key1, key2, value1, value2, similarity)
|
|
88
|
-
similar_found += 1
|
|
89
|
-
end
|
|
90
|
-
end
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
puts " Found #{similar_found} similar translations"
|
|
94
|
-
end
|
|
95
|
-
end
|
|
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
|
|
104
|
-
def flatten_translations(hash, prefix = "", result = {})
|
|
105
|
-
hash.each do |key, value|
|
|
106
|
-
current_key = prefix.empty? ? key.to_s : "#{prefix}.#{key}"
|
|
107
|
-
|
|
108
|
-
if value.is_a?(Hash)
|
|
109
|
-
flatten_translations(value, current_key, result)
|
|
110
|
-
else
|
|
111
|
-
result[current_key] = value
|
|
112
|
-
end
|
|
113
|
-
end
|
|
114
|
-
|
|
115
|
-
result
|
|
116
|
-
end
|
|
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
|
|
125
|
-
def calculate_similarity(str1, str2)
|
|
126
|
-
# Implementazione della distanza di Levenshtein normalizzata
|
|
127
|
-
matrix = Array.new(str1.length + 1) { Array.new(str2.length + 1) }
|
|
128
|
-
|
|
129
|
-
(0..str1.length).each { |i| matrix[i][0] = i }
|
|
130
|
-
(0..str2.length).each { |j| matrix[0][j] = j }
|
|
131
|
-
|
|
132
|
-
(1..str1.length).each do |i|
|
|
133
|
-
(1..str2.length).each do |j|
|
|
134
|
-
cost = str1[i-1] == str2[j-1] ? 0 : 1
|
|
135
|
-
matrix[i][j] = [
|
|
136
|
-
matrix[i-1][j] + 1,
|
|
137
|
-
matrix[i][j-1] + 1,
|
|
138
|
-
matrix[i-1][j-1] + cost
|
|
139
|
-
].min
|
|
140
|
-
end
|
|
141
|
-
end
|
|
142
|
-
|
|
143
|
-
max_length = [str1.length, str2.length].max
|
|
144
|
-
1 - (matrix[str1.length][str2.length].to_f / max_length)
|
|
145
|
-
end
|
|
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]
|
|
157
|
-
def record_similarity(lang, key1, key2, value1, value2, similarity)
|
|
158
|
-
@similarities[lang] ||= []
|
|
159
|
-
@similarities[lang] << {
|
|
160
|
-
key1: key1,
|
|
161
|
-
key2: key2,
|
|
162
|
-
value1: value1,
|
|
163
|
-
value2: value2,
|
|
164
|
-
similarity: similarity.round(2)
|
|
165
|
-
}
|
|
166
|
-
end
|
|
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]
|
|
173
|
-
def generate_report
|
|
174
|
-
puts "\nGenerating reports..."
|
|
175
|
-
report = {
|
|
176
|
-
generated_at: Time.now.iso8601,
|
|
177
|
-
similarity_threshold: SIMILARITY_THRESHOLD,
|
|
178
|
-
findings: @similarities
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
File.write(REPORT_FILE, JSON.pretty_generate(report))
|
|
182
|
-
puts " - Generated #{REPORT_FILE}"
|
|
183
|
-
|
|
184
|
-
summary = generate_summary(report)
|
|
185
|
-
File.write("translation_similarities_summary.txt", summary)
|
|
186
|
-
puts " - Generated translation_similarities_summary.txt"
|
|
187
|
-
end
|
|
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
|
|
195
|
-
def generate_summary(report)
|
|
196
|
-
summary = ["Translation Similarities Report", "=" * 30, ""]
|
|
197
|
-
|
|
198
|
-
report[:findings].each do |lang, similarities|
|
|
199
|
-
summary << "Language: #{lang}"
|
|
200
|
-
summary << "-" * 20
|
|
201
|
-
|
|
202
|
-
similarities.each do |sim|
|
|
203
|
-
summary << "Similar translations found:"
|
|
204
|
-
summary << " Key 1: #{sim[:key1]}"
|
|
205
|
-
summary << " Value 1: #{sim[:value1]}"
|
|
206
|
-
summary << " Key 2: #{sim[:key2]}"
|
|
207
|
-
summary << " Value 2: #{sim[:value2]}"
|
|
208
|
-
summary << " Similarity: #{(sim[:similarity] * 100).round(1)}%"
|
|
209
|
-
summary << ""
|
|
210
|
-
end
|
|
211
|
-
|
|
212
|
-
summary << ""
|
|
213
|
-
end
|
|
214
|
-
|
|
215
|
-
summary.join("\n")
|
|
216
|
-
end
|
|
217
|
-
end
|
|
218
|
-
end
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
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.
|
|
6
|
-
class Utils
|
|
7
|
-
class << self
|
|
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.
|
|
11
|
-
#
|
|
12
|
-
# @param message [String, nil] The message to log
|
|
13
|
-
# @return [void]
|
|
14
|
-
def logger(message: nil)
|
|
15
|
-
if defined?(Rails) && Rails.respond_to?(:logger) && Rails.logger
|
|
16
|
-
Rails.logger.info message
|
|
17
|
-
else
|
|
18
|
-
puts message
|
|
19
|
-
end
|
|
20
|
-
end
|
|
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]
|
|
29
|
-
def track_metric(name, value)
|
|
30
|
-
@metrics ||= {}
|
|
31
|
-
@metrics[name] ||= []
|
|
32
|
-
@metrics[name] << { value: value, timestamp: Time.now }
|
|
33
|
-
|
|
34
|
-
# Mantieni solo le ultime 1000 metriche per tipo
|
|
35
|
-
@metrics[name] = @metrics[name].last(1000) if @metrics[name].size > 1000
|
|
36
|
-
end
|
|
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
|
|
42
|
-
def get_metrics
|
|
43
|
-
@metrics || {}
|
|
44
|
-
end
|
|
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
|
|
50
|
-
def clear_metrics
|
|
51
|
-
@metrics = {}
|
|
52
|
-
end
|
|
53
|
-
end
|
|
54
|
-
end
|
|
55
|
-
end
|
|
@@ -1,75 +0,0 @@
|
|
|
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.
|
|
6
|
-
class Writer
|
|
7
|
-
class << self
|
|
8
|
-
# Writes the translation file for a target language.
|
|
9
|
-
# If the translation method is :override, the file is rewritten from scratch;
|
|
10
|
-
# if it's :incremental, the existing file is updated by inserting new translations in the correct positions.
|
|
11
|
-
#
|
|
12
|
-
# @param translated_data [Hash] The translated data structure (e.g. { "sample" => { "valid" => "translated", "new_key" => "new translation" } }).
|
|
13
|
-
# @param target_lang_code [String] Target language code (e.g. "ru").
|
|
14
|
-
def write_translations(translated_data, target_lang_code)
|
|
15
|
-
output_folder = BetterTranslate.configuration.output_folder
|
|
16
|
-
output_file = File.join(output_folder, "#{target_lang_code}.yml")
|
|
17
|
-
|
|
18
|
-
# Reformats the structure to use the target code as the main key.
|
|
19
|
-
output_content = if translated_data.is_a?(Hash) && translated_data.key?(BetterTranslate.configuration.source_language)
|
|
20
|
-
# Replaces the source language key with the target one.
|
|
21
|
-
{ target_lang_code => translated_data[BetterTranslate.configuration.source_language] }
|
|
22
|
-
else
|
|
23
|
-
{ target_lang_code => translated_data }
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
if BetterTranslate.configuration.translation_method.to_sym == :incremental && File.exist?(output_file)
|
|
27
|
-
existing_data = YAML.load_file(output_file)
|
|
28
|
-
merged = deep_merge(existing_data, output_content)
|
|
29
|
-
File.write(output_file, merged.to_yaml(line_width: -1))
|
|
30
|
-
message = "File updated in incremental mode: #{output_file}"
|
|
31
|
-
BetterTranslate::Utils.logger(message: message)
|
|
32
|
-
else
|
|
33
|
-
FileUtils.mkdir_p(output_folder) unless Dir.exist?(output_folder)
|
|
34
|
-
File.write(output_file, output_content.to_yaml(line_width: -1))
|
|
35
|
-
message = "File rewritten in override mode: #{output_file}"
|
|
36
|
-
BetterTranslate::Utils.logger(message: message)
|
|
37
|
-
end
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
private
|
|
41
|
-
|
|
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.
|
|
46
|
-
#
|
|
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" } } }
|
|
55
|
-
def deep_merge(existing, new_data)
|
|
56
|
-
merged = existing.dup
|
|
57
|
-
new_data.each do |key, value|
|
|
58
|
-
if merged.key?(key)
|
|
59
|
-
if merged[key].is_a?(Hash) && value.is_a?(Hash)
|
|
60
|
-
merged[key] = deep_merge(merged[key], value)
|
|
61
|
-
else
|
|
62
|
-
# If the key already exists, don't overwrite the value (incremental mode)
|
|
63
|
-
# or you might decide to update anyway, depending on the requirements.
|
|
64
|
-
merged[key] = merged[key]
|
|
65
|
-
end
|
|
66
|
-
else
|
|
67
|
-
merged[key] = value
|
|
68
|
-
end
|
|
69
|
-
end
|
|
70
|
-
merged
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
end
|
|
74
|
-
end
|
|
75
|
-
end
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "rails/generators"
|
|
4
|
-
|
|
5
|
-
module BetterTranslate
|
|
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
|
|
13
|
-
class AnalyzeGenerator < Rails::Generators::Base
|
|
14
|
-
desc "Analyze translation files for similarities"
|
|
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]
|
|
24
|
-
def analyze_translations
|
|
25
|
-
say "Starting translation similarity analysis...", :blue
|
|
26
|
-
|
|
27
|
-
# Find all YAML files in the locales directory
|
|
28
|
-
locale_dir = Rails.root.join("config", "locales")
|
|
29
|
-
yaml_files = Dir[locale_dir.join("*.yml")]
|
|
30
|
-
|
|
31
|
-
if yaml_files.empty?
|
|
32
|
-
say "No YAML files found in #{locale_dir}", :red
|
|
33
|
-
return
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
say "Found #{yaml_files.length} YAML files to analyze", :green
|
|
37
|
-
|
|
38
|
-
# Run analysis
|
|
39
|
-
analyzer = BetterTranslate::SimilarityAnalyzer.new(yaml_files)
|
|
40
|
-
analyzer.analyze
|
|
41
|
-
|
|
42
|
-
# Show results
|
|
43
|
-
say "\nAnalysis complete!", :green
|
|
44
|
-
say "Reports generated:", :green
|
|
45
|
-
say " * #{BetterTranslate::SimilarityAnalyzer::REPORT_FILE} (detailed JSON report)"
|
|
46
|
-
say " * translation_similarities_summary.txt (human-readable summary)"
|
|
47
|
-
|
|
48
|
-
# Show quick summary from the text file
|
|
49
|
-
if File.exist?("translation_similarities_summary.txt")
|
|
50
|
-
summary = File.read("translation_similarities_summary.txt")
|
|
51
|
-
say "\nQuick Summary:", :blue
|
|
52
|
-
say summary
|
|
53
|
-
end
|
|
54
|
-
end
|
|
55
|
-
end
|
|
56
|
-
end
|
|
57
|
-
end
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
require 'rails/generators'
|
|
2
|
-
|
|
3
|
-
module BetterTranslate
|
|
4
|
-
module Generators
|
|
5
|
-
class InstallGenerator < Rails::Generators::Base
|
|
6
|
-
source_root File.expand_path('templates', __dir__)
|
|
7
|
-
desc "Generates the config/initializers/better_translate.rb file with default configuration"
|
|
8
|
-
|
|
9
|
-
def copy_initializer
|
|
10
|
-
template "better_translate.rb", "config/initializers/better_translate.rb"
|
|
11
|
-
end
|
|
12
|
-
end
|
|
13
|
-
end
|
|
14
|
-
end
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
BetterTranslate.configure do |config|
|
|
2
|
-
# Choose the provider to use: :chatgpt, :gemini, or any custom provider registered with BetterTranslate::Service.register_provider
|
|
3
|
-
config.provider = :chatgpt
|
|
4
|
-
|
|
5
|
-
# API key for OpenAI
|
|
6
|
-
config.openai_key = ENV.fetch("OPENAI_API_KEY") { "YOUR_OPENAI_API_KEY" }
|
|
7
|
-
|
|
8
|
-
# API key for Google Gemini
|
|
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" }
|
|
17
|
-
|
|
18
|
-
# Source language (for example "en" if the source file is in English)
|
|
19
|
-
config.source_language = "en"
|
|
20
|
-
|
|
21
|
-
# List of target languages (short_name and name)
|
|
22
|
-
config.target_languages = [
|
|
23
|
-
# { short_name: 'es', name: 'spagnolo' },
|
|
24
|
-
# { short_name: 'it', name: 'italiano' },
|
|
25
|
-
# { short_name: 'fr', name: 'francese' },
|
|
26
|
-
# { short_name: 'de', name: 'tedesco' },
|
|
27
|
-
# { short_name: 'pt', name: 'portoghese' },
|
|
28
|
-
{ short_name: "ru", name: "russian" }
|
|
29
|
-
]
|
|
30
|
-
|
|
31
|
-
# Global exclusions (paths in dot notation) to exclude for all languages
|
|
32
|
-
config.global_exclusions = [
|
|
33
|
-
"key.value"
|
|
34
|
-
]
|
|
35
|
-
|
|
36
|
-
# Language-specific exclusions
|
|
37
|
-
config.exclusions_per_language = {
|
|
38
|
-
"es" => [],
|
|
39
|
-
"it" => [],
|
|
40
|
-
"fr" => [],
|
|
41
|
-
"de" => [],
|
|
42
|
-
"pt" => [],
|
|
43
|
-
"ru" => []
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
# Input file path (e.g. en.yml)
|
|
47
|
-
config.input_file = Rails.root.join("config", "locales", "en.yml").to_s
|
|
48
|
-
|
|
49
|
-
# Output folder where translated files will be saved
|
|
50
|
-
config.output_folder = Rails.root.join("config", "locales", "translated").to_s
|
|
51
|
-
|
|
52
|
-
# Translation method:
|
|
53
|
-
# - :override => regenerates all translations
|
|
54
|
-
# - :incremental => updates only missing keys (or those that have been modified)
|
|
55
|
-
config.translation_method = :override
|
|
56
|
-
end
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'rails/generators'
|
|
4
|
-
|
|
5
|
-
module BetterTranslate
|
|
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
|
|
12
|
-
class TranslateGenerator < Rails::Generators::Base
|
|
13
|
-
desc "Starts the translation process configured in BetterTranslate"
|
|
14
|
-
|
|
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
|
|
81
|
-
end
|
|
82
|
-
end
|
|
83
|
-
end
|
|
84
|
-
end
|