better_translate 0.1.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 +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +116 -0
- data/lib/better_translate/providers/base_provider.rb +18 -0
- data/lib/better_translate/providers/chatgpt_provider.rb +38 -0
- data/lib/better_translate/providers/gemini_provider.rb +35 -0
- data/lib/better_translate/service.rb +25 -0
- data/lib/better_translate/translator.rb +131 -0
- data/lib/better_translate/version.rb +5 -0
- data/lib/better_translate/writer.rb +64 -0
- data/lib/better_translate.rb +51 -0
- data/lib/generators/better_translate/install_generator.rb +14 -0
- data/lib/generators/better_translate/templates/better_translate.rb +49 -0
- metadata +138 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: ac3bc8d98853820f1c6367bbbe4fb36733a872de35eef7ff8bc47349fc9376e5
|
4
|
+
data.tar.gz: fa486ddb0bf587faee2792bbca54982ddbdffcc44b4b183e9c0de1c849f917b7
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 643e1688cc0171ddee6bfeb848aa1d555922bf2c44907afc56981ea0b5c9dd9ce8fa5788b509596eb53735cd4ce63e1adbccaeb1592f5cb3d1653da794a0c415
|
7
|
+
data.tar.gz: ea2451b736c946d2bead72f3bf36dab06fb38e2037662c160a400f73a1854919314bfecb0a5081b63add8b7a66b1402ae85b3cfa42af10149cb9faf9677fba90
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2025 alessiobussolari
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
# BetterTranslate
|
2
|
+
|
3
|
+
BetterTranslate is a Ruby gem that enables you to translate YAML files from a source language into one or more target languages using translation providers such as ChatGPT (OpenAI) and Google Gemini. The gem supports two translation modes:
|
4
|
+
|
5
|
+
- **Override**: Completely rewrites the translated file.
|
6
|
+
- **Incremental**: Updates the translated file only with new or modified keys while keeping existing ones.
|
7
|
+
|
8
|
+
Configuration is centralized via an initializer (for example, in a Rails app), where you can set API keys, the source language, target languages, key exclusions, the output folder, and the translation mode. Additionally, BetterTranslate integrates progress tracking using the [ruby-progressbar](https://github.com/jfelchner/ruby-progressbar) gem.
|
9
|
+
|
10
|
+
## Features
|
11
|
+
|
12
|
+
- **Multi-language YAML Translation**: Translates YAML files from a source language into one or more target languages.
|
13
|
+
- **Multiple Providers**: Supports ChatGPT (OpenAI) and Google Gemini (with potential for extending to other providers in the future).
|
14
|
+
- **Translation Modes**:
|
15
|
+
- **Override**: Rewrites the file from scratch.
|
16
|
+
- **Incremental**: Updates only missing or modified keys.
|
17
|
+
- **Centralized Configuration**: Configured via an initializer with settings for API keys, source language, target languages, exclusions (using dot notation), and the output folder.
|
18
|
+
- **Progress Bar**: Displays the translation progress using ruby-progressbar.
|
19
|
+
|
20
|
+
## Installation
|
21
|
+
|
22
|
+
Add the gem to your Gemfile:
|
23
|
+
|
24
|
+
```ruby
|
25
|
+
gem 'better_translate'
|
26
|
+
|
27
|
+
Then run:
|
28
|
+
|
29
|
+
```bash
|
30
|
+
bundle install
|
31
|
+
```
|
32
|
+
|
33
|
+
Or install the gem manually:
|
34
|
+
|
35
|
+
```bash
|
36
|
+
gem install better_translate
|
37
|
+
```
|
38
|
+
|
39
|
+
## Configuration
|
40
|
+
|
41
|
+
In a Rails application, generate the initializer by running:
|
42
|
+
|
43
|
+
```bash
|
44
|
+
rails generate better_translate:install
|
45
|
+
```
|
46
|
+
|
47
|
+
This command creates the file `config/initializers/better_translate.rb` with a default configuration. An example configuration is:
|
48
|
+
|
49
|
+
```ruby
|
50
|
+
BetterTranslate.configure do |config|
|
51
|
+
# Choose the provider to use: :chatgpt or :gemini
|
52
|
+
config.provider = :chatgpt
|
53
|
+
|
54
|
+
# API key for ChatGPT (OpenAI)
|
55
|
+
config.openai_key = ENV.fetch("OPENAI_API_KEY") { "YOUR_OPENAI_API_KEY" }
|
56
|
+
|
57
|
+
# API key for Google Gemini
|
58
|
+
config.google_gemini_key = ENV.fetch("GOOGLE_GEMINI_KEY") { "YOUR_GOOGLE_GEMINI_KEY" }
|
59
|
+
|
60
|
+
# Source language (e.g., "en" if the source file is in English)
|
61
|
+
config.source_language = "en"
|
62
|
+
|
63
|
+
# Output folder where the translated files will be saved
|
64
|
+
config.output_folder = Rails.root.join("config", "locales", "translated").to_s
|
65
|
+
|
66
|
+
# List of target languages (short_name and name)
|
67
|
+
config.target_languages = [
|
68
|
+
# Example:
|
69
|
+
{ short_name: "it", name: "italian" }
|
70
|
+
]
|
71
|
+
|
72
|
+
# Global exclusions (keys in dot notation) to exclude from translation
|
73
|
+
config.global_exclusions = [
|
74
|
+
"key.child_key",
|
75
|
+
]
|
76
|
+
|
77
|
+
# Language-specific exclusions (optional)
|
78
|
+
config.exclusions_per_language = {
|
79
|
+
"ru" => []
|
80
|
+
}
|
81
|
+
|
82
|
+
# Path to the input file (e.g., en.yml)
|
83
|
+
config.input_file = Rails.root.join("config", "locales", "en.yml").to_s
|
84
|
+
|
85
|
+
# Translation mode: :override or :incremental
|
86
|
+
config.translation_method = :override
|
87
|
+
end
|
88
|
+
```
|
89
|
+
|
90
|
+
## Usage
|
91
|
+
|
92
|
+
### YAML File Translation
|
93
|
+
|
94
|
+
To start the translation process, simply call the `magic` method:
|
95
|
+
|
96
|
+
```ruby
|
97
|
+
BetterTranslate.magic
|
98
|
+
```
|
99
|
+
|
100
|
+
This will execute the process that:
|
101
|
+
1. Reads the input YAML file.
|
102
|
+
2. Applies any filters (exclusions).
|
103
|
+
3. Translates the strings from the source language to the configured target languages.
|
104
|
+
4. Writes the translated files to the output folder, either in **override** or **incremental** mode based on the configuration.
|
105
|
+
|
106
|
+
## Contributing
|
107
|
+
|
108
|
+
Pull requests are welcome! If you would like to suggest improvements or new features, please open an issue to discuss your ideas before submitting a pull request.
|
109
|
+
|
110
|
+
## Changelog
|
111
|
+
|
112
|
+
See the [CHANGELOG](https://github.com/alessiobussolari/better_translate/blob/main/CHANGELOG.md) for a summary of changes.
|
113
|
+
|
114
|
+
## License
|
115
|
+
|
116
|
+
BetterTranslate is distributed under the MIT license. See the [LICENSE](LICENSE) file for more details.
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module BetterTranslate
|
2
|
+
module Providers
|
3
|
+
class BaseProvider
|
4
|
+
def initialize(api_key)
|
5
|
+
@api_key = api_key
|
6
|
+
end
|
7
|
+
|
8
|
+
# Metodo da implementare nelle classi derivate.
|
9
|
+
# @param text [String] testo da tradurre.
|
10
|
+
# @param target_lang_code [String] codice della lingua di destinazione (es. "en").
|
11
|
+
# @param target_lang_name [String] nome della lingua di destinazione (es. "English").
|
12
|
+
# @return [String] testo tradotto.
|
13
|
+
def translate(text, target_lang_code, target_lang_name)
|
14
|
+
raise NotImplementedError, "Il provider #{self.class} deve implementare il metodo translate"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module BetterTranslate
|
2
|
+
module Providers
|
3
|
+
class ChatgptProvider < BaseProvider
|
4
|
+
# Utilizza l'API di OpenAI per tradurre il testo.
|
5
|
+
def translate(text, target_lang_code, target_lang_name)
|
6
|
+
uri = URI("https://api.openai.com/v1/chat/completions")
|
7
|
+
headers = {
|
8
|
+
"Content-Type" => "application/json",
|
9
|
+
"Authorization" => "Bearer #{@api_key}"
|
10
|
+
}
|
11
|
+
|
12
|
+
# Costruiamo il prompt per tradurre il testo.
|
13
|
+
body = {
|
14
|
+
model: "gpt-3.5-turbo",
|
15
|
+
messages: [
|
16
|
+
{ role: "system", content: "Sei un traduttore professionale. Traduci esattamente il seguente testo da #{BetterTranslate.configuration.source_language} a #{target_lang_name} senza aggiungere commenti, spiegazioni o alternative. Fornisci solamente la traduzione diretta:" },
|
17
|
+
{ role: "user", content: "#{text}" }
|
18
|
+
],
|
19
|
+
temperature: 0.3
|
20
|
+
}
|
21
|
+
|
22
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
23
|
+
http.use_ssl = true
|
24
|
+
request = Net::HTTP::Post.new(uri.path, headers)
|
25
|
+
request.body = body.to_json
|
26
|
+
|
27
|
+
response = http.request(request)
|
28
|
+
if response.is_a?(Net::HTTPSuccess)
|
29
|
+
json = JSON.parse(response.body)
|
30
|
+
translated_text = json.dig("choices", 0, "message", "content")
|
31
|
+
translated_text ? translated_text.strip : text
|
32
|
+
else
|
33
|
+
raise "Errore durante la traduzione con ChatGPT: #{response.body}"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module BetterTranslate
|
2
|
+
module Providers
|
3
|
+
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.
|
6
|
+
def translate(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
|
+
}
|
12
|
+
|
13
|
+
body = {
|
14
|
+
input_text: text,
|
15
|
+
target_language: target_lang_code,
|
16
|
+
model: "gemini" # oppure altri parametri richiesti dall'API
|
17
|
+
}
|
18
|
+
|
19
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
20
|
+
http.use_ssl = true
|
21
|
+
request = Net::HTTP::Post.new(uri.path, headers)
|
22
|
+
request.body = body.to_json
|
23
|
+
|
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}"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module BetterTranslate
|
2
|
+
class Service
|
3
|
+
def initialize
|
4
|
+
@provider_name = BetterTranslate.configuration.provider
|
5
|
+
end
|
6
|
+
|
7
|
+
# Metodo per tradurre un testo utilizzando il provider selezionato.
|
8
|
+
def translate(text, target_lang_code, target_lang_name)
|
9
|
+
provider_instance.translate(text, target_lang_code, target_lang_name)
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def provider_instance
|
15
|
+
@provider_instance ||= case @provider_name
|
16
|
+
when :chatgpt
|
17
|
+
Providers::ChatgptProvider.new(BetterTranslate.configuration.openai_key)
|
18
|
+
when :gemini
|
19
|
+
Providers::GeminiProvider.new(BetterTranslate.configuration.google_gemini_key)
|
20
|
+
else
|
21
|
+
raise "Provider non supportato: #{@provider_name}"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
module BetterTranslate
|
2
|
+
class Translator
|
3
|
+
class << self
|
4
|
+
def work
|
5
|
+
puts "Avvio della traduzione dei file..."
|
6
|
+
|
7
|
+
translations = read_yml_source
|
8
|
+
|
9
|
+
# Rimuove le chiavi da escludere (global_exclusions) dalla struttura letta
|
10
|
+
filtered_translations = remove_exclusions(
|
11
|
+
translations, BetterTranslate.configuration.global_exclusions
|
12
|
+
)
|
13
|
+
|
14
|
+
BetterTranslate.configuration.target_languages.each do |target_lang|
|
15
|
+
puts "Inizio traduzione da #{BetterTranslate.configuration.source_language} a #{target_lang[:short_name]}"
|
16
|
+
service = BetterTranslate::Service.new
|
17
|
+
translated_data = translate_with_progress(filtered_translations, service, target_lang[:short_name], target_lang[:name])
|
18
|
+
BetterTranslate::Writer.write_translations(translated_data, target_lang[:short_name])
|
19
|
+
puts "Traduzione completata da #{BetterTranslate.configuration.source_language} a #{target_lang[:short_name]}"
|
20
|
+
end
|
21
|
+
|
22
|
+
"Traduzione iniziata! #{filtered_translations.inspect}"
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
# Legge il file YAML in base al percorso fornito.
|
28
|
+
#
|
29
|
+
# @param file_path [String] percorso del file YAML da leggere
|
30
|
+
# @return [Hash] struttura dati contenente le traduzioni
|
31
|
+
# @raise [StandardError] se il file non esiste
|
32
|
+
def read_yml_source
|
33
|
+
file_path = BetterTranslate.configuration.input_file
|
34
|
+
unless File.exist?(file_path)
|
35
|
+
raise "File non trovato: #{file_path}"
|
36
|
+
end
|
37
|
+
|
38
|
+
YAML.load_file(file_path)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Rimuove le chiavi specificate in exclusion_list dalla struttura dati,
|
42
|
+
# calcolando i percorsi a partire dal contenuto della lingua di partenza.
|
43
|
+
#
|
44
|
+
# Ad esempio, se il file YAML è:
|
45
|
+
# { "en" => { "sample" => { "valid" => "valid", "excluded" => "Excluded" } } }
|
46
|
+
# e exclusion_list = ["sample.excluded"],
|
47
|
+
# il risultato sarà:
|
48
|
+
# { "en" => { "sample" => { "valid" => "valid" } } }
|
49
|
+
#
|
50
|
+
# @param data [Hash, Array, Object] La struttura dati da filtrare.
|
51
|
+
# @param exclusion_list [Array<String>] Lista dei percorsi (in dot notation) da escludere.
|
52
|
+
# @param current_path [Array] Il percorso corrente (usato in maniera ricorsiva).
|
53
|
+
# @return [Hash, Array, Object] La struttura dati filtrata.
|
54
|
+
def remove_exclusions(data, exclusion_list, current_path = [])
|
55
|
+
if data.is_a?(Hash)
|
56
|
+
data.each_with_object({}) do |(key, value), result|
|
57
|
+
# Se siamo al livello top-level e la chiave corrisponde alla lingua di partenza,
|
58
|
+
# resettare il percorso (così da escludere "en" dal percorso finale)
|
59
|
+
new_path = if current_path.empty? && key == BetterTranslate.configuration.source_language
|
60
|
+
[]
|
61
|
+
else
|
62
|
+
current_path + [key]
|
63
|
+
end
|
64
|
+
|
65
|
+
path_string = new_path.join(".")
|
66
|
+
unless exclusion_list.include?(path_string)
|
67
|
+
result[key] = remove_exclusions(value, exclusion_list, new_path)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
elsif data.is_a?(Array)
|
71
|
+
data.map.with_index do |item, index|
|
72
|
+
remove_exclusions(item, exclusion_list, current_path + [index])
|
73
|
+
end
|
74
|
+
else
|
75
|
+
data
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# Metodo ricorsivo che percorre la struttura, traducendo ogni stringa e aggiornando il progresso.
|
80
|
+
#
|
81
|
+
# @param data [Hash, Array, String] La struttura dati da tradurre.
|
82
|
+
# @param provider [Object] Il provider che risponde al metodo translate.
|
83
|
+
# @param target_lang_code [String] Codice della lingua target (es. "en").
|
84
|
+
# @param target_lang_name [String] Nome della lingua target (es. "English").
|
85
|
+
# @param progress [Hash] Un hash con le chiavi :count e :total per monitorare il progresso.
|
86
|
+
# @return [Hash, Array, String] La struttura tradotta.
|
87
|
+
def deep_translate_with_progress(data, service, target_lang_code, target_lang_name, progress)
|
88
|
+
if data.is_a?(Hash)
|
89
|
+
data.each_with_object({}) do |(key, value), result|
|
90
|
+
result[key] = deep_translate_with_progress(value, service, target_lang_code, target_lang_name, progress)
|
91
|
+
end
|
92
|
+
elsif data.is_a?(Array)
|
93
|
+
data.map do |item|
|
94
|
+
deep_translate_with_progress(item, service, target_lang_code, target_lang_name, progress)
|
95
|
+
end
|
96
|
+
elsif data.is_a?(String)
|
97
|
+
progress.increment
|
98
|
+
service.translate(data, target_lang_code, target_lang_name)
|
99
|
+
else
|
100
|
+
data
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# Metodo principale per tradurre l'intera struttura dati, con monitoraggio del progresso.
|
105
|
+
#
|
106
|
+
# @param data [Hash, Array, String] la struttura dati da tradurre
|
107
|
+
# @param provider [Object] il provider da usare per tradurre (deve implementare translate)
|
108
|
+
# @param target_lang_code [String] codice della lingua target
|
109
|
+
# @param target_lang_name [String] nome della lingua target
|
110
|
+
# @return la struttura tradotta
|
111
|
+
def translate_with_progress(data, service, target_lang_code, target_lang_name)
|
112
|
+
total = count_strings(data)
|
113
|
+
progress = ProgressBar.create(total: total, format: '%a %B %p%% %t')
|
114
|
+
deep_translate_with_progress(data, service, target_lang_code, target_lang_name, progress)
|
115
|
+
end
|
116
|
+
|
117
|
+
# Conta ricorsivamente il numero di stringhe traducibili nella struttura dati.
|
118
|
+
def count_strings(data)
|
119
|
+
if data.is_a?(Hash)
|
120
|
+
data.values.sum { |v| count_strings(v) }
|
121
|
+
elsif data.is_a?(Array)
|
122
|
+
data.sum { |item| count_strings(item) }
|
123
|
+
elsif data.is_a?(String)
|
124
|
+
1
|
125
|
+
else
|
126
|
+
0
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module BetterTranslate
|
2
|
+
class Writer
|
3
|
+
class << self
|
4
|
+
# Scrive il file di traduzione per una lingua target.
|
5
|
+
# Se il metodo di traduzione è :override, il file viene riscritto da zero;
|
6
|
+
# se è :incremental, il file esistente viene aggiornato inserendo le nuove traduzioni nelle posizioni corrette.
|
7
|
+
#
|
8
|
+
# @param translated_data [Hash] La struttura dati tradotta (ad es. { "sample" => { "valid" => "tradotto", "new_key" => "nuova traduzione" } }).
|
9
|
+
# @param target_lang_code [String] Codice della lingua target (es. "ru").
|
10
|
+
def write_translations(translated_data, target_lang_code)
|
11
|
+
output_folder = BetterTranslate.configuration.output_folder
|
12
|
+
output_file = File.join(output_folder, "#{target_lang_code}.yml")
|
13
|
+
|
14
|
+
# Riformatta la struttura per utilizzare il codice target come chiave principale.
|
15
|
+
output_content = if translated_data.is_a?(Hash) && translated_data.key?(BetterTranslate.configuration.source_language)
|
16
|
+
# Sostituisce la chiave della lingua di partenza con quella target.
|
17
|
+
{ target_lang_code => translated_data[BetterTranslate.configuration.source_language] }
|
18
|
+
else
|
19
|
+
{ target_lang_code => translated_data }
|
20
|
+
end
|
21
|
+
|
22
|
+
if BetterTranslate.configuration.translation_method.to_sym == :incremental && File.exist?(output_file)
|
23
|
+
existing_data = YAML.load_file(output_file)
|
24
|
+
merged = deep_merge(existing_data, output_content)
|
25
|
+
File.write(output_file, merged.to_yaml(line_width: -1))
|
26
|
+
puts "File aggiornato in modalità incremental: #{output_file}"
|
27
|
+
else
|
28
|
+
FileUtils.mkdir_p(output_folder) unless Dir.exist?(output_folder)
|
29
|
+
File.write(output_file, output_content.to_yaml(line_width: -1))
|
30
|
+
puts "File riscritto in modalità override: #{output_file}"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
# Metodo di deep merge: unisce in modo ricorsivo i due hash.
|
37
|
+
# Se una chiave esiste in entrambi gli hash e i valori sono hash, li unisce ricorsivamente.
|
38
|
+
# Altrimenti, se la chiave esiste già nell'hash esistente, la mantiene e non la sovrascrive.
|
39
|
+
# Se la chiave non esiste, la aggiunge.
|
40
|
+
#
|
41
|
+
# @param existing [Hash] hash esistente (file corrente)
|
42
|
+
# @param new_data [Hash] nuovo hash con le traduzioni da unire
|
43
|
+
# @return [Hash] hash unito
|
44
|
+
def deep_merge(existing, new_data)
|
45
|
+
merged = existing.dup
|
46
|
+
new_data.each do |key, value|
|
47
|
+
if merged.key?(key)
|
48
|
+
if merged[key].is_a?(Hash) && value.is_a?(Hash)
|
49
|
+
merged[key] = deep_merge(merged[key], value)
|
50
|
+
else
|
51
|
+
# Se la chiave esiste già, non sovrascrivo il valore (modalità incrementale)
|
52
|
+
# oppure potresti decidere di aggiornare comunque, a seconda delle esigenze.
|
53
|
+
merged[key] = merged[key]
|
54
|
+
end
|
55
|
+
else
|
56
|
+
merged[key] = value
|
57
|
+
end
|
58
|
+
end
|
59
|
+
merged
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "ruby-progressbar"
|
3
|
+
|
4
|
+
require "better_translate/version"
|
5
|
+
require "better_translate/translator"
|
6
|
+
require "better_translate/service"
|
7
|
+
require "better_translate/writer"
|
8
|
+
|
9
|
+
require 'better_translate/providers/base_provider'
|
10
|
+
require 'better_translate/providers/chatgpt_provider'
|
11
|
+
require 'better_translate/providers/gemini_provider'
|
12
|
+
|
13
|
+
module BetterTranslate
|
14
|
+
class << self
|
15
|
+
attr_accessor :configuration
|
16
|
+
|
17
|
+
# Metodo per configurare la gemma
|
18
|
+
def configure
|
19
|
+
self.configuration ||= OpenStruct.new
|
20
|
+
yield(configuration) if block_given?
|
21
|
+
end
|
22
|
+
|
23
|
+
# Metodo install per generare il file di configurazione (initializer)
|
24
|
+
def install
|
25
|
+
unless defined?(Rails) && Rails.respond_to?(:root)
|
26
|
+
puts "Il metodo install è disponibile solo in un'applicazione Rails."
|
27
|
+
return
|
28
|
+
end
|
29
|
+
|
30
|
+
# Costruisce il percorso della cartella template all'interno della gemma
|
31
|
+
source = File.expand_path("../generators/better_translate/templates/better_translate.rb", __dir__)
|
32
|
+
destination = File.join(Rails.root, "config", "initializers", "better_translate.rb")
|
33
|
+
|
34
|
+
if File.exist?(destination)
|
35
|
+
puts "Il file initializer esiste già: #{destination}"
|
36
|
+
else
|
37
|
+
FileUtils.mkdir_p(File.dirname(destination))
|
38
|
+
FileUtils.cp(source, destination)
|
39
|
+
puts "Initializer creato in: #{destination}"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def magic
|
44
|
+
puts "Metodo magic invocato: eseguirò la traduzione dei file..."
|
45
|
+
|
46
|
+
BetterTranslate::Translator.work
|
47
|
+
puts "Metodo magic invocato: Traduzione completata con successo!"
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,14 @@
|
|
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 "Genera il file config/initializers/better_translate.rb con la configurazione di default"
|
8
|
+
|
9
|
+
def copy_initializer
|
10
|
+
template "better_translate.rb", "config/initializers/better_translate.rb"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
BetterTranslate.configure do |config|
|
2
|
+
# Scegli il provider da utilizzare: :chatgpt oppure :gemini
|
3
|
+
config.provider = :chatgpt
|
4
|
+
|
5
|
+
# API key per OpenAI
|
6
|
+
config.openai_key = ENV.fetch("OPENAI_API_KEY") { "YOUR_OPENAI_API_KEY" }
|
7
|
+
|
8
|
+
# API key per Google Gemini
|
9
|
+
config.google_gemini_key = ENV.fetch("GOOGLE_GEMINI_KEY") { "YOUR_GOOGLE_GEMINI_KEY" }
|
10
|
+
|
11
|
+
# Lingua sorgente (ad esempio "en" se il file sorgente è in inglese)
|
12
|
+
config.source_language = "en"
|
13
|
+
|
14
|
+
# Lista delle lingue target (short_name e name)
|
15
|
+
config.target_languages = [
|
16
|
+
# { short_name: 'es', name: 'spagnolo' },
|
17
|
+
# { short_name: 'it', name: 'italiano' },
|
18
|
+
# { short_name: 'fr', name: 'francese' },
|
19
|
+
# { short_name: 'de', name: 'tedesco' },
|
20
|
+
# { short_name: 'pt', name: 'portoghese' },
|
21
|
+
{ short_name: "ru", name: "russian" }
|
22
|
+
]
|
23
|
+
|
24
|
+
# Esclusioni globali (percorsi in dot notation) da escludere per tutte le lingue
|
25
|
+
config.global_exclusions = [
|
26
|
+
"key.value"
|
27
|
+
]
|
28
|
+
|
29
|
+
# Esclusioni specifiche per lingua
|
30
|
+
config.exclusions_per_language = {
|
31
|
+
"es" => [],
|
32
|
+
"it" => [],
|
33
|
+
"fr" => [],
|
34
|
+
"de" => [],
|
35
|
+
"pt" => [],
|
36
|
+
"ru" => []
|
37
|
+
}
|
38
|
+
|
39
|
+
# Percorso del file di input (ad es. en.yml)
|
40
|
+
config.input_file = Rails.root.join("config", "locales", "en.yml").to_s
|
41
|
+
|
42
|
+
# Cartella di output dove verranno salvati i file tradotti
|
43
|
+
config.output_folder = Rails.root.join("config", "locales", "translated").to_s
|
44
|
+
|
45
|
+
# Metodo di traduzione:
|
46
|
+
# - :override => rigenera tutte le traduzioni
|
47
|
+
# - :incremental => aggiorna solo le chiavi mancanti (o quelle che hanno subito modifiche)
|
48
|
+
config.translation_method = :override
|
49
|
+
end
|
metadata
ADDED
@@ -0,0 +1,138 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: better_translate
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- alessio_bussolari
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2025-03-10 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: yaml
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: httparty
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: ruby-progressbar
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.11'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.11'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: bundler
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '2.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '2.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rake
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '13.0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '13.0'
|
83
|
+
description: "\nBetterTranslate è una gemma che consente di tradurre file YAML partendo
|
84
|
+
da una lingua sorgente verso una o più lingue target.\nLa gemma supporta differenti
|
85
|
+
provider di traduzione, attualmente ChatGPT (OpenAI) e Google Gemini, e permette
|
86
|
+
di scegliere\nla modalità di traduzione: \"override\" per rigenerare tutte le traduzioni
|
87
|
+
oppure \"incremental\" per aggiornare solo le chiavi mancanti.\n\nLa configurazione
|
88
|
+
della gemma è centralizzata tramite un initializer, dove è possibile definire API
|
89
|
+
key, lingue target, lingua sorgente,\nesclusioni di chiavi e la cartella di output.
|
90
|
+
BetterTranslate integra inoltre il monitoraggio del progresso della traduzione tramite
|
91
|
+
una progress bar.\n "
|
92
|
+
email:
|
93
|
+
- alessio.bussolari@pandev.it
|
94
|
+
executables: []
|
95
|
+
extensions: []
|
96
|
+
extra_rdoc_files: []
|
97
|
+
files:
|
98
|
+
- LICENSE.txt
|
99
|
+
- README.md
|
100
|
+
- lib/better_translate.rb
|
101
|
+
- lib/better_translate/providers/base_provider.rb
|
102
|
+
- lib/better_translate/providers/chatgpt_provider.rb
|
103
|
+
- lib/better_translate/providers/gemini_provider.rb
|
104
|
+
- lib/better_translate/service.rb
|
105
|
+
- lib/better_translate/translator.rb
|
106
|
+
- lib/better_translate/version.rb
|
107
|
+
- lib/better_translate/writer.rb
|
108
|
+
- lib/generators/better_translate/install_generator.rb
|
109
|
+
- lib/generators/better_translate/templates/better_translate.rb
|
110
|
+
homepage: https://github.com/alessiobussolari/better_translate
|
111
|
+
licenses:
|
112
|
+
- MIT
|
113
|
+
metadata:
|
114
|
+
allowed_push_host: https://rubygems.org
|
115
|
+
rubygems_mfa_required: 'true'
|
116
|
+
homepage_uri: https://github.com/alessiobussolari/better_translate
|
117
|
+
source_code_uri: https://github.com/alessiobussolari/better_translate
|
118
|
+
changelog_uri: https://github.com/alessiobussolari/better_translate/blob/main/CHANGELOG.md
|
119
|
+
post_install_message:
|
120
|
+
rdoc_options: []
|
121
|
+
require_paths:
|
122
|
+
- lib
|
123
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
124
|
+
requirements:
|
125
|
+
- - ">="
|
126
|
+
- !ruby/object:Gem::Version
|
127
|
+
version: 3.0.0
|
128
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
129
|
+
requirements:
|
130
|
+
- - ">="
|
131
|
+
- !ruby/object:Gem::Version
|
132
|
+
version: '0'
|
133
|
+
requirements: []
|
134
|
+
rubygems_version: 3.5.11
|
135
|
+
signing_key:
|
136
|
+
specification_version: 4
|
137
|
+
summary: Gemma per tradurre file YAML in più lingue tramite provider (ChatGPT e Gemini)
|
138
|
+
test_files: []
|