immosquare-translate 0.1.13 → 0.1.14
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:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 87dc92e836e2e404cccfa85c01fac2b8edf396aecc1a8a74558f66532942d6fd
|
4
|
+
data.tar.gz: 449976467c208d55e4dc7bf95c1089fae2889a954d64c385186789aabb1bd228
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c89194b90aa0b774308113059769a2d3021a13d7359df80baeab1539562acbd0dc1e8f5088f48d5bb7d66efcf5a4214cf49aa71cbfa609da6013dd080d8bf2be
|
7
|
+
data.tar.gz: 2eb2ee90027ebbbfe1fb38aee306c070b785de2aed44001290225d67f83b25ad872efd09d2910832e68bff65e91507b200ea9bfa865fd8cbbc67bc85a9514c89
|
@@ -5,11 +5,18 @@ module ImmosquareTranslate
|
|
5
5
|
SIMPLE_QUOTE = "'".freeze
|
6
6
|
DOUBLE_QUOTE = '"'.freeze
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
8
|
+
##============================================================##
|
9
|
+
## https://platform.openai.com/docs/pricing
|
10
|
+
## List updated on : 21/05/2025
|
11
|
+
##============================================================##
|
12
|
+
OPEN_AI_MODELS = [
|
13
|
+
{:nickname => "gpt-3.5", :name => "gpt-3.5-turbo-0125", :default => false, :window_tokens => 16_385, :output_tokens => 4_096, :input_price_for_1m => 0.50, :output_price_for_1m => 1.50, :group_size => 75},
|
14
|
+
{:nickname => "gpt-4", :name => "gpt-4-turbo-2024-04-09", :default => false, :window_tokens => 128_000, :output_tokens => 4_096, :input_price_for_1m => 10.00, :output_price_for_1m => 30.00, :group_size => 75},
|
15
|
+
{:nickname => "gpt-4o-mini", :name => "gpt-4o-mini", :default => false, :window_tokens => 128_000, :output_tokens => 16_384, :input_price_for_1m => 0.15, :output_price_for_1m => 0.60, :group_size => 75},
|
16
|
+
{:nickname => "gpt-4o", :name => "gpt-4o-2024-08-06", :default => false, :window_tokens => 128_000, :output_tokens => 16_384, :input_price_for_1m => 2.50, :output_price_for_1m => 10.00, :group_size => 75},
|
17
|
+
{:nickname => "gpt-4.1-nano", :name => "gpt-4.1-nano", :default => false, :window_tokens => 1_000_000, :output_tokens => 32_768, :input_price_for_1m => 0.10, :output_price_for_1m => 0.40, :group_size => 75},
|
18
|
+
{:nickname => "gpt-4.1-mini", :name => "gpt-4.1-mini", :default => false, :window_tokens => 1_000_000, :output_tokens => 32_768, :input_price_for_1m => 0.40, :output_price_for_1m => 1.60, :group_size => 75},
|
19
|
+
{:nickname => "gpt-4.1", :name => "gpt-4.1-2025-04-14", :default => true, :window_tokens => 1_000_000, :output_tokens => 32_768, :input_price_for_1m => 2.00, :output_price_for_1m => 8.00, :group_size => 75}
|
13
20
|
].freeze
|
14
21
|
end
|
15
22
|
end
|
@@ -21,8 +21,14 @@ module ImmosquareTranslate
|
|
21
21
|
raise("Error: locales is not an array of locales") if !to.is_a?(Array) || to.empty? || to.any? {|l| !l.is_a?(String) }
|
22
22
|
|
23
23
|
model_name = ImmosquareTranslate.configuration.openai_model
|
24
|
-
model = OPEN_AI_MODELS.find {|m| m[:name] == model_name }
|
25
|
-
model = OPEN_AI_MODELS.find {|m| m[:
|
24
|
+
model = OPEN_AI_MODELS.find {|m| m[:name] == model_name || m[:nickname] == model_name }
|
25
|
+
model = OPEN_AI_MODELS.find {|m| m[:default] == true } if model.nil?
|
26
|
+
|
27
|
+
puts("To translate, we will use the model: #{model[:nickname]}")
|
28
|
+
|
29
|
+
##============================================================##
|
30
|
+
## We get the language name and the country name for each locale
|
31
|
+
##============================================================##
|
26
32
|
from_language_name = ISO_639.find_by_code(from).english_name.split(";").first
|
27
33
|
to_iso = to
|
28
34
|
.reject {|code| ImmosquareConstants::Locale.native_name_for_locale(code).nil? }
|
@@ -35,13 +41,17 @@ module ImmosquareTranslate
|
|
35
41
|
[iso, language_english_name, country_english_name]
|
36
42
|
end
|
37
43
|
|
38
|
-
|
39
|
-
|
44
|
+
##============================================================##
|
45
|
+
## Request headers
|
46
|
+
##============================================================##
|
40
47
|
headers = {
|
41
48
|
"Content-Type" => "application/json",
|
42
49
|
"Authorization" => "Bearer #{ImmosquareTranslate.configuration.openai_api_key}"
|
43
50
|
}
|
44
51
|
|
52
|
+
##============================================================##
|
53
|
+
## System prompt
|
54
|
+
##============================================================##
|
45
55
|
prompt_system = "As a sophisticated translation AI, your role is to translate sentences from a specified source language to multiple target languages.\n" \
|
46
56
|
"We pass you target languages as an array of arrays with this format: [iso_code to use (2 or 4 letters), language target name, country name (country vocabulary to use, this parameter is optional, can be null)].\n" \
|
47
57
|
"Rules to respect:\n" \
|
@@ -59,7 +69,9 @@ module ImmosquareTranslate
|
|
59
69
|
"- For multiple input strings, return an array of objects, where each object corresponds to an input string and contains all its translations.\n" \
|
60
70
|
"- Example output for two input strings 'Hello' and 'Goodbye' with target languages ['en', 'es', 'fr']: [{\"en\":\"Hello\",\"es\":\"Hola\",\"fr\":\"Bonjour\"},{\"en\":\"Goodbye\",\"es\":\"Adiós\",\"fr\":\"Au revoir\"}].\n"
|
61
71
|
|
62
|
-
|
72
|
+
##============================================================##
|
73
|
+
## User prompt
|
74
|
+
##============================================================##
|
63
75
|
prompt = "Translate the #{texts.size} following #{texts.size == 1 ? "text" : "texts"} from the source language: #{from_language_name} to the target languages specified: #{to_iso}."
|
64
76
|
|
65
77
|
##============================================================##
|
@@ -71,8 +83,9 @@ module ImmosquareTranslate
|
|
71
83
|
prompt += "\n#{index + 1}: #{sentence.gsub("\n", "___").gsub("\t", "____")}"
|
72
84
|
end
|
73
85
|
|
74
|
-
|
75
|
-
|
86
|
+
##============================================================##
|
87
|
+
## Request body
|
88
|
+
##============================================================##
|
76
89
|
body = {
|
77
90
|
:model => model[:name],
|
78
91
|
:messages => [
|
@@ -84,10 +97,11 @@ module ImmosquareTranslate
|
|
84
97
|
|
85
98
|
|
86
99
|
t0 = Time.now
|
87
|
-
|
100
|
+
url = "https://api.openai.com/v1/chat/completions"
|
101
|
+
call = HTTParty.post(url, :body => body.to_json, :headers => headers, :timeout => 500)
|
88
102
|
|
89
103
|
|
90
|
-
puts("
|
104
|
+
puts("OpenAI api response in #{(Time.now - t0).round(2)} seconds")
|
91
105
|
raise(call["error"]["message"]) if call.code != 200
|
92
106
|
|
93
107
|
##============================================================##
|
@@ -97,6 +111,7 @@ module ImmosquareTranslate
|
|
97
111
|
choice = response["choices"][0]
|
98
112
|
raise("Result is not complete") if choice["finish_reason"] != "stop"
|
99
113
|
|
114
|
+
|
100
115
|
##============================================================##
|
101
116
|
## We calculate the estimate price of the call
|
102
117
|
##============================================================##
|
@@ -105,21 +120,17 @@ module ImmosquareTranslate
|
|
105
120
|
price = input_price + output_price
|
106
121
|
puts("Estimate price => #{input_price.round(3)} + #{output_price.round(3)} = #{price.round(3)} USD")
|
107
122
|
|
108
|
-
|
109
123
|
##============================================================##
|
110
124
|
## On s'assure de ne renvoyer que les locales demandées
|
111
125
|
## car l'API peut renvoyer des locales non demandées...
|
112
126
|
##============================================================##
|
113
|
-
|
114
|
-
datas = content["datas"]
|
127
|
+
datas = JSON.parse(choice["message"]["content"])
|
115
128
|
datas.map do |hash|
|
116
129
|
hash
|
117
130
|
.select {|key, _| to.map(&:downcase).include?(key.downcase) }
|
118
131
|
.transform_values {|value| value.gsub("____", "\t").gsub("___", "\n") }
|
119
132
|
.transform_keys do |key|
|
120
|
-
key.to_s.split("-").map.with_index
|
121
|
-
index == 0 ? part.downcase : part.upcase
|
122
|
-
end.join("-").to_sym
|
133
|
+
key.to_s.split("-").map.with_index {|part, index| index == 0 ? part.downcase : part.upcase }.join("-").to_sym
|
123
134
|
end
|
124
135
|
end.reject(&:empty?)
|
125
136
|
rescue StandardError => e
|
@@ -10,18 +10,18 @@ module ImmosquareTranslate
|
|
10
10
|
|
11
11
|
def translate(file_path, locale_to, options = {})
|
12
12
|
begin
|
13
|
-
|
13
|
+
##============================================================##
|
14
14
|
## options
|
15
|
-
|
15
|
+
##============================================================##
|
16
16
|
options = {
|
17
17
|
:reset_translations => false
|
18
18
|
}.merge(options)
|
19
19
|
options[:reset_translations] = false if ![true, false].include?(options[:reset_translations])
|
20
20
|
|
21
21
|
|
22
|
-
|
22
|
+
##============================================================##
|
23
23
|
## Load config keys from config_dev.yml
|
24
|
-
|
24
|
+
##============================================================##
|
25
25
|
raise("Error: openai_api_key not found in config_dev.yml") if ImmosquareTranslate.configuration.openai_api_key.nil?
|
26
26
|
raise("Error: File #{file_path} not found") if !File.exist?(file_path)
|
27
27
|
raise("Error: locale is not a locale") if !locale_to.is_a?(String) || locale_to.size != 2
|
@@ -124,7 +124,8 @@ module ImmosquareTranslate
|
|
124
124
|
## format = "string" and keys_only = true => ["fr.demo1", "fr.demo2.demo2-1"]
|
125
125
|
## format = "array" and keys_only = false => [[["fr", "demo1"], "demo1"], [["fr", "demo2", "demo2-1"], "demo2-1"]]
|
126
126
|
## format = "array" and keys_only = true => [["fr", "demo1"], ["fr", "demo2", "demo2-1"]]
|
127
|
-
##
|
127
|
+
## ---------
|
128
|
+
##============================================================##
|
128
129
|
def translatable_array(hash, key = nil, result = [], **options)
|
129
130
|
options = {
|
130
131
|
:format => "string",
|
@@ -162,12 +163,8 @@ module ImmosquareTranslate
|
|
162
163
|
|
163
164
|
##============================================================##
|
164
165
|
## Translate with OpenAI
|
165
|
-
##
|
166
|
-
## [
|
167
|
-
## ["en.mlsconnect.contact_us", "Nous contacter", "Contact us"],
|
168
|
-
## ["en.mlsconnect.description", "Description", nil],
|
169
|
-
## ...
|
170
|
-
## ]
|
166
|
+
## [["en.mlsconnect.contact_us", "Nous contacter", "Contact us"],
|
167
|
+
## ["en.mlsconnect.description", "Description", nil]]
|
171
168
|
##============================================================##
|
172
169
|
def translate_with_open_ai(array, from, to)
|
173
170
|
##============================================================##
|
@@ -175,8 +172,8 @@ module ImmosquareTranslate
|
|
175
172
|
## https://openai.com/pricing
|
176
173
|
##============================================================##
|
177
174
|
model_name = ImmosquareTranslate.configuration.openai_model
|
178
|
-
model = OPEN_AI_MODELS.find {|m| m[:name] == model_name }
|
179
|
-
model = OPEN_AI_MODELS.find {|m| m[:
|
175
|
+
model = OPEN_AI_MODELS.find {|m| m[:name] == model_name || m[:nickname] == model_name }
|
176
|
+
model = OPEN_AI_MODELS.find {|m| m[:default] == true } if model.nil?
|
180
177
|
|
181
178
|
##============================================================##
|
182
179
|
## Manage blank values
|
@@ -192,7 +189,7 @@ module ImmosquareTranslate
|
|
192
189
|
## we want to send as little data as possible to openAI because
|
193
190
|
## we pay for the volume of data sent. So we're going to send. We put
|
194
191
|
## a number rather than a string for the translations to be made.
|
195
|
-
##
|
192
|
+
## ---------
|
196
193
|
## Remove the translations that have already been made
|
197
194
|
##============================================================##
|
198
195
|
data_open_ai = array.clone
|
@@ -247,7 +244,7 @@ module ImmosquareTranslate
|
|
247
244
|
## https://platform.openai.com/tokenizer
|
248
245
|
## English: 75 words => 100 tokens
|
249
246
|
## French : 55 words => 100 tokens
|
250
|
-
##
|
247
|
+
## ---------
|
251
248
|
## For each array value we add 5 tokens for the array format.
|
252
249
|
## [1, "my_word"],
|
253
250
|
## [ => first token
|
@@ -255,8 +252,8 @@ module ImmosquareTranslate
|
|
255
252
|
## , => third token
|
256
253
|
## " => fourth token
|
257
254
|
## ]" => fifth token
|
258
|
-
##
|
259
|
-
|
255
|
+
## ---------
|
256
|
+
## data_open_ai.inspect.size => to get the total number of characters in the array
|
260
257
|
## with the array structure [""],
|
261
258
|
##============================================================##
|
262
259
|
estimation_for_100_tokens = from == "fr" ? 55 : 75
|
@@ -269,7 +266,7 @@ module ImmosquareTranslate
|
|
269
266
|
##============================================================##
|
270
267
|
## Now each slice of the array should no be more than window_tokens
|
271
268
|
## of the model.... We can now translate each slice.
|
272
|
-
##
|
269
|
+
## ---------
|
273
270
|
## Normally we could send the whole slice at once and tell the api to continue if its response is not tarnished...
|
274
271
|
## But it should manage if a word is cut etc...
|
275
272
|
## For the moment we cut it into small group for which we are sure not to exceed the limit
|
@@ -291,7 +288,8 @@ module ImmosquareTranslate
|
|
291
288
|
:temperature => 0.0
|
292
289
|
}
|
293
290
|
t0 = Time.now
|
294
|
-
|
291
|
+
url = "https://api.openai.com/v1/chat/completions"
|
292
|
+
call = HTTParty.post(url, :body => body.to_json, :headers => headers, :timeout => 500)
|
295
293
|
|
296
294
|
puts("responded in #{(Time.now - t0).round(2)} seconds")
|
297
295
|
raise(call["error"]["message"]) if call.code != 200
|
data/lib/immosquare-translate.rb
CHANGED
@@ -5,15 +5,12 @@ require_relative "immosquare-translate/translator"
|
|
5
5
|
require_relative "immosquare-translate/railtie" if defined?(Rails)
|
6
6
|
|
7
7
|
|
8
|
-
##===========================================================================##
|
9
|
-
##
|
10
|
-
##===========================================================================##
|
11
8
|
module ImmosquareTranslate
|
12
9
|
class << self
|
13
10
|
|
14
|
-
|
11
|
+
##============================================================##
|
15
12
|
## Gem configuration
|
16
|
-
|
13
|
+
##============================================================##
|
17
14
|
attr_writer :configuration
|
18
15
|
|
19
16
|
def configuration
|
@@ -25,6 +22,5 @@ module ImmosquareTranslate
|
|
25
22
|
end
|
26
23
|
|
27
24
|
|
28
|
-
|
29
25
|
end
|
30
26
|
end
|
metadata
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: immosquare-translate
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.14
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- immosquare
|
8
|
-
autorequire:
|
9
8
|
bindir: bin
|
10
9
|
cert_chain: []
|
11
|
-
date:
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
12
11
|
dependencies:
|
13
12
|
- !ruby/object:Gem::Dependency
|
14
13
|
name: httparty
|
@@ -133,7 +132,6 @@ homepage: https://github.com/immosquare/immosquare-translate
|
|
133
132
|
licenses:
|
134
133
|
- MIT
|
135
134
|
metadata: {}
|
136
|
-
post_install_message:
|
137
135
|
rdoc_options: []
|
138
136
|
require_paths:
|
139
137
|
- lib
|
@@ -141,15 +139,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
141
139
|
requirements:
|
142
140
|
- - ">="
|
143
141
|
- !ruby/object:Gem::Version
|
144
|
-
version: 2.
|
142
|
+
version: 3.2.6
|
145
143
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
146
144
|
requirements:
|
147
145
|
- - ">="
|
148
146
|
- !ruby/object:Gem::Version
|
149
147
|
version: '0'
|
150
148
|
requirements: []
|
151
|
-
rubygems_version: 3.
|
152
|
-
signing_key:
|
149
|
+
rubygems_version: 3.6.9
|
153
150
|
specification_version: 4
|
154
151
|
summary: AI-powered translations for Ruby applications, supporting a wide range of
|
155
152
|
formats.
|