fastlane-plugin-translate_gpt 0.1.3 → 0.1.4
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: 8389bf90c62946cf66e3abb95d1fc9422a622146488f56c83985270a692c91f3
|
4
|
+
data.tar.gz: 9980483ba5032745071ad8f04ec9227c10385626cc678d5655b861294b2e2520
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4da07c2656000de7cbfa28333fbf2c7949d9211166a89624a928f734cdfe4a2ece7ecacdd3aedc265cff9241a9998b2b722906b5bbeda43ff50e54a772620c83
|
7
|
+
data.tar.gz: a91b4055259f18d927cb0d1a2ec29ccf39e7385b646821018f462bea16afba69f03450ecb2b54c118a011c90b2a64bf4caefc41243c5188e2455dd8cf394c84b
|
@@ -7,73 +7,11 @@ module Fastlane
|
|
7
7
|
module Actions
|
8
8
|
class TranslateGptAction < Action
|
9
9
|
def self.run(params)
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
)
|
14
|
-
|
15
|
-
input_hash = Helper::TranslateGptHelper.get_strings(params[:source_file])
|
16
|
-
output_hash = Helper::TranslateGptHelper.get_strings(params[:target_file])
|
17
|
-
|
18
|
-
if params[:skip_translated]
|
19
|
-
to_translate = input_hash.reject { |k, v| output_hash[k] }
|
20
|
-
else
|
21
|
-
to_translate = input_hash
|
22
|
-
end
|
23
|
-
|
24
|
-
UI.message "Translating #{to_translate.size} strings..."
|
25
|
-
|
26
|
-
to_translate.each_with_index do |(key, string), index|
|
27
|
-
prompt = "I want you to act as a translator for a mobile application strings. " + \
|
28
|
-
"You need to answer only with translation and nothing else. No commentaries. " + \
|
29
|
-
"Try to keep length of the translated text. " + \
|
30
|
-
"I will send you a text and you translate it from #{params[:source_language]} to #{params[:target_language]}. "
|
31
|
-
if params[:context] && !params[:context].empty?
|
32
|
-
prompt += "This app is #{params[:context]}. "
|
33
|
-
end
|
34
|
-
context = string.comment
|
35
|
-
if context && !context.empty?
|
36
|
-
prompt += "Additional context is #{context}. "
|
37
|
-
end
|
38
|
-
prompt += "Source text:\n#{string.value}"
|
39
|
-
|
40
|
-
# translate the source string to the target language
|
41
|
-
response = client.chat(
|
42
|
-
parameters: {
|
43
|
-
model: params[:model_name],
|
44
|
-
messages: [
|
45
|
-
{ role: "user", content: prompt }
|
46
|
-
],
|
47
|
-
temperature: params[:temperature],
|
48
|
-
}
|
49
|
-
)
|
50
|
-
# extract the translated string from the response
|
51
|
-
error = response.dig("error", "message")
|
52
|
-
if error
|
53
|
-
UI.error "Error translating #{key}: #{error}"
|
54
|
-
else
|
55
|
-
target_string = response.dig("choices", 0, "message", "content")
|
56
|
-
if target_string && !target_string.empty?
|
57
|
-
UI.message "Translating #{key} - #{string.value} -> #{target_string}"
|
58
|
-
string.value = target_string
|
59
|
-
output_hash[key] = string
|
60
|
-
else
|
61
|
-
UI.warning "Unable to translate #{key} - #{string.value}"
|
62
|
-
end
|
63
|
-
end
|
64
|
-
if index < to_translate.size - 1
|
65
|
-
Helper::TranslateGptHelper.timeout params[:request_timeout]
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
69
|
-
UI.message "Writing #{output_hash.size} strings to #{params[:target_file]}..."
|
70
|
-
|
71
|
-
file = LocoStrings.load(params[:target_file])
|
72
|
-
file.read
|
73
|
-
output_hash.each do |key, value|
|
74
|
-
file.update(key, value.value, value.comment)
|
75
|
-
end
|
76
|
-
file.write
|
10
|
+
helper = Helper::TranslateGptHelper.new(params)
|
11
|
+
helper.prepare_hashes()
|
12
|
+
helper.log_input()
|
13
|
+
helper.translate_strings()
|
14
|
+
helper.write_output()
|
77
15
|
end
|
78
16
|
|
79
17
|
#####################################################
|
@@ -6,10 +6,122 @@ module Fastlane
|
|
6
6
|
|
7
7
|
module Helper
|
8
8
|
class TranslateGptHelper
|
9
|
+
def initialize(params)
|
10
|
+
@params = params
|
11
|
+
@client = OpenAI::Client.new(
|
12
|
+
access_token: params[:api_token],
|
13
|
+
request_timeout: params[:request_timeout]
|
14
|
+
)
|
15
|
+
@timeout = params[:request_timeout]
|
16
|
+
end
|
17
|
+
|
18
|
+
# Get the strings from a file
|
19
|
+
def prepare_hashes()
|
20
|
+
@input_hash = get_strings(@params[:source_file])
|
21
|
+
@output_hash = get_strings(@params[:target_file])
|
22
|
+
@to_translate = filter_translated(@params[:skip_translated], @input_hash, @output_hash)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Log information about the input strings
|
26
|
+
def log_input()
|
27
|
+
@translation_count = @to_translate.size
|
28
|
+
number_of_strings = Colorizer::colorize("#{@translation_count}", :blue)
|
29
|
+
UI.message "Translating #{number_of_strings} strings..."
|
30
|
+
if @translation_count > 0
|
31
|
+
estimated_string = Colorizer::colorize("#{@translation_count * @params[:request_timeout]}", :white)
|
32
|
+
UI.message "Estimated time: #{estimated_string} seconds"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Cycle through the input strings and translate them
|
37
|
+
def translate_strings()
|
38
|
+
@to_translate.each_with_index do |(key, string), index|
|
39
|
+
prompt = prepare_prompt string
|
40
|
+
|
41
|
+
max_retries = 10
|
42
|
+
times_retried = 0
|
43
|
+
|
44
|
+
# translate the source string to the target language
|
45
|
+
begin
|
46
|
+
request_translate(key, string, prompt, index)
|
47
|
+
rescue Net::ReadTimeout => error
|
48
|
+
if times_retried < max_retries
|
49
|
+
times_retried += 1
|
50
|
+
UI.important "Failed to request translation, retry #{times_retried}/#{max_retries}"
|
51
|
+
wait 1
|
52
|
+
retry
|
53
|
+
else
|
54
|
+
UI.error "Can't translate #{key}: #{error}"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
if index < @translation_count - 1 then wait end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Prepare the prompt for the GPT API
|
62
|
+
def prepare_prompt(string)
|
63
|
+
prompt = "I want you to act as a translator for a mobile application strings. " + \
|
64
|
+
"Try to keep length of the translated text. " + \
|
65
|
+
"You need to answer only with the translation and nothing else until I say to stop it. No commentaries."
|
66
|
+
if @params[:context] && !@params[:context].empty?
|
67
|
+
prompt += "This app is #{@params[:context]}. "
|
68
|
+
end
|
69
|
+
context = string.comment
|
70
|
+
if context && !context.empty?
|
71
|
+
prompt += "Additional context is #{context}. "
|
72
|
+
end
|
73
|
+
prompt += "Translate next text from #{@params[:source_language]} to #{@params[:target_language]}:\n" +
|
74
|
+
"#{string.value}"
|
75
|
+
return prompt
|
76
|
+
end
|
77
|
+
|
78
|
+
# Request a translation from the GPT API
|
79
|
+
def request_translate(key, string, prompt, index)
|
80
|
+
response = @client.chat(
|
81
|
+
parameters: {
|
82
|
+
model: @params[:model_name],
|
83
|
+
messages: [
|
84
|
+
{ role: "user", content: prompt }
|
85
|
+
],
|
86
|
+
temperature: @params[:temperature],
|
87
|
+
}
|
88
|
+
)
|
89
|
+
# extract the translated string from the response
|
90
|
+
error = response.dig("error", "message")
|
91
|
+
key_log = Colorizer::colorize(key, :blue)
|
92
|
+
index_log = Colorizer::colorize("[#{index + 1}/#{@translation_count}]", :white)
|
93
|
+
if error
|
94
|
+
UI.error "#{index_log} Error translating #{key_log}: #{error}"
|
95
|
+
else
|
96
|
+
target_string = response.dig("choices", 0, "message", "content")
|
97
|
+
if target_string && !target_string.empty?
|
98
|
+
UI.message "#{index_log} Translating #{key_log} - #{string.value} -> #{target_string}"
|
99
|
+
string.value = target_string
|
100
|
+
@output_hash[key] = string
|
101
|
+
else
|
102
|
+
UI.important "#{index_log} Unable to translate #{key_log} - #{string.value}"
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
# Write the translated strings to the target file
|
108
|
+
def write_output()
|
109
|
+
number_of_strings = Colorizer::colorize("#{@output_hash.size}", :blue)
|
110
|
+
target_string = Colorizer::colorize(@params[:target_file], :white)
|
111
|
+
UI.message "Writing #{number_of_strings} strings to #{target_string}..."
|
112
|
+
|
113
|
+
file = LocoStrings.load(@params[:target_file])
|
114
|
+
file.read
|
115
|
+
@output_hash.each do |key, value|
|
116
|
+
file.update(key, value.value, value.comment)
|
117
|
+
end
|
118
|
+
file.write
|
119
|
+
end
|
120
|
+
|
9
121
|
# Read the strings file into a hash
|
10
122
|
# @param localization_file [String] The path to the strings file
|
11
123
|
# @return [Hash] The strings file as a hash
|
12
|
-
def
|
124
|
+
def get_strings(localization_file)
|
13
125
|
file = LocoStrings.load(localization_file)
|
14
126
|
return file.read
|
15
127
|
end
|
@@ -18,25 +130,36 @@ module Fastlane
|
|
18
130
|
# @param localization_file [String] The path to the strings file
|
19
131
|
# @param localization_key [String] The localization key
|
20
132
|
# @return [String] The context associated with the localization key
|
21
|
-
def
|
133
|
+
def get_context(localization_file, localization_key)
|
22
134
|
file = LocoStrings.load(localization_file)
|
23
135
|
string = file.read[localization_key]
|
24
136
|
return string.comment
|
25
137
|
end
|
26
138
|
|
139
|
+
def filter_translated(need_to_skip, base, target)
|
140
|
+
if need_to_skip
|
141
|
+
return base.reject { |k, v| target[k] }
|
142
|
+
else
|
143
|
+
return base
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
27
147
|
# Sleep for a specified number of seconds, displaying a progress bar
|
28
148
|
# @param seconds [Integer] The number of seconds to sleep
|
29
|
-
def
|
149
|
+
def wait(seconds = @timeout)
|
30
150
|
sleep_time = 0
|
31
|
-
while sleep_time <
|
32
|
-
percent_complete = (sleep_time.to_f /
|
151
|
+
while sleep_time < seconds
|
152
|
+
percent_complete = (sleep_time.to_f / seconds.to_f) * 100.0
|
33
153
|
progress_bar_width = 20
|
34
154
|
completed_width = (progress_bar_width * percent_complete / 100.0).round
|
35
155
|
remaining_width = progress_bar_width - completed_width
|
36
|
-
print "\rTimeout ["
|
156
|
+
print "\rTimeout ["
|
157
|
+
print Colorizer::code(:green)
|
37
158
|
print "=" * completed_width
|
38
159
|
print " " * remaining_width
|
39
|
-
print
|
160
|
+
print Colorizer::code(:reset)
|
161
|
+
print "]"
|
162
|
+
print " %.2f%%" % percent_complete
|
40
163
|
$stdout.flush
|
41
164
|
sleep(1)
|
42
165
|
sleep_time += 1
|
@@ -45,5 +168,28 @@ module Fastlane
|
|
45
168
|
$stdout.flush
|
46
169
|
end
|
47
170
|
end
|
171
|
+
|
172
|
+
# Helper class for bash colors
|
173
|
+
class Colorizer
|
174
|
+
COLORS = {
|
175
|
+
black: 30,
|
176
|
+
red: 31,
|
177
|
+
green: 32,
|
178
|
+
yellow: 33,
|
179
|
+
blue: 34,
|
180
|
+
magenta: 35,
|
181
|
+
cyan: 36,
|
182
|
+
white: 37,
|
183
|
+
reset: 0,
|
184
|
+
}
|
185
|
+
|
186
|
+
def self.colorize(text, color)
|
187
|
+
color_code = COLORS[color.to_sym]
|
188
|
+
"\e[#{color_code}m#{text}\e[0m"
|
189
|
+
end
|
190
|
+
def self.code(color)
|
191
|
+
"\e[#{COLORS[color.to_sym]}m"
|
192
|
+
end
|
193
|
+
end
|
48
194
|
end
|
49
195
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fastlane-plugin-translate_gpt
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Aleksei Cherepanov
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-05-
|
11
|
+
date: 2023-05-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: ruby-openai
|