Class: Fastlane::Helper::TranslateGptHelper
- Inherits:
-
Object
- Object
- Fastlane::Helper::TranslateGptHelper
- Defined in:
- lib/fastlane/plugin/translate_gpt/helper/translate_gpt_helper.rb
Instance Method Summary collapse
- #check_value_for_translate(string, orignal_string) ⇒ Object
- #filter_translated(need_to_skip, base, target) ⇒ Object
-
#get_context(localization_file, localization_key) ⇒ String
Get the context associated with a localization key.
-
#get_strings(localization_file) ⇒ Hash
Read the strings file into a hash.
-
#initialize(params) ⇒ TranslateGptHelper
constructor
A new instance of TranslateGptHelper.
-
#log_input(bunch_size) ⇒ Object
Log information about the input strings.
- #prepare_bunch_prompt(strings) ⇒ Object
-
#prepare_hashes ⇒ Object
Get the strings from a file.
-
#prepare_prompt(string) ⇒ Object
Prepare the prompt for the GPT API.
- #prepare_strings ⇒ Object
- #prepare_xcstrings ⇒ Object
- #request_bunch_translate(strings, prompt, index, number_of_bunches) ⇒ Object
-
#request_translate(key, string, prompt, index) ⇒ Object
Request a translation from the GPT API.
- #transform_string(input_string) ⇒ Object
- #translate_bunch_of_strings(bunch_size) ⇒ Object
-
#translate_strings ⇒ Object
Cycle through the input strings and translate them.
-
#wait(seconds = @timeout) ⇒ Object
Sleep for a specified number of seconds, displaying a progress bar.
-
#write_output ⇒ Object
Write the translated strings to the target file.
Constructor Details
#initialize(params) ⇒ TranslateGptHelper
Returns a new instance of TranslateGptHelper.
11 12 13 14 15 16 17 18 |
# File 'lib/fastlane/plugin/translate_gpt/helper/translate_gpt_helper.rb', line 11 def initialize(params) @params = params @client = OpenAI::Client.new( access_token: params[:api_token], request_timeout: params[:request_timeout] ) @timeout = params[:request_timeout] end |
Instance Method Details
#check_value_for_translate(string, orignal_string) ⇒ Object
35 36 37 38 39 40 41 42 43 44 45 46 47 |
# File 'lib/fastlane/plugin/translate_gpt/helper/translate_gpt_helper.rb', line 35 def check_value_for_translate(string, orignal_string) return true unless string if string.is_a? LocoStrings::LocoString return false if orignal_string.value.nil? || orignal_string.value.empty? return string.value.empty? elsif string.is_a? LocoStrings::LocoVariantions orignal_string.strings.each do |key, _| return true unless string.strings.has_key?(key) return true if string.strings[key].value.empty? end end return false end |
#filter_translated(need_to_skip, base, target) ⇒ Object
334 335 336 337 338 339 340 |
# File 'lib/fastlane/plugin/translate_gpt/helper/translate_gpt_helper.rb', line 334 def filter_translated(need_to_skip, base, target) if need_to_skip return base.reject { |k, v| target[k] } else return base end end |
#get_context(localization_file, localization_key) ⇒ String
Get the context associated with a localization key
328 329 330 331 332 |
# File 'lib/fastlane/plugin/translate_gpt/helper/translate_gpt_helper.rb', line 328 def get_context(localization_file, localization_key) file = LocoStrings.load(localization_file) string = file.read[localization_key] return string.comment end |
#get_strings(localization_file) ⇒ Hash
Read the strings file into a hash
319 320 321 322 |
# File 'lib/fastlane/plugin/translate_gpt/helper/translate_gpt_helper.rb', line 319 def get_strings(localization_file) file = LocoStrings.load(localization_file) return file.read end |
#log_input(bunch_size) ⇒ Object
Log information about the input strings
65 66 67 68 69 70 71 72 73 74 75 76 77 |
# File 'lib/fastlane/plugin/translate_gpt/helper/translate_gpt_helper.rb', line 65 def log_input(bunch_size) @translation_count = @to_translate.size number_of_strings = Colorizer::colorize("#{@translation_count}", :blue) UI. "Translating #{number_of_strings} strings..." if bunch_size.nil? || bunch_size < 1 estimated_string = Colorizer::colorize("#{@translation_count * @params[:request_timeout]}", :white) UI. "Estimated time: #{estimated_string} seconds" else number_of_bunches = (@translation_count / bunch_size.to_f).ceil estimated_string = Colorizer::colorize("#{number_of_bunches * @params[:request_timeout]}", :white) UI. "Estimated time: #{estimated_string} seconds" end end |
#prepare_bunch_prompt(strings) ⇒ Object
152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 |
# File 'lib/fastlane/plugin/translate_gpt/helper/translate_gpt_helper.rb', line 152 def prepare_bunch_prompt(strings) prompt = "I want you to act as a translator for a mobile application strings. " + \ "Try to keep length of the translated text. " + \ "You need to response with a JSON only with the translation and nothing else until I say to stop it. " if @params[:context] && !@params[:context].empty? prompt += "This app is #{@params[:context]}. " end prompt += "Translate next text from #{@params[:source_language]} to #{@params[:target_language]}:\n" json_hash = [] strings.each do |key, string| UI. "Translating #{key} - #{string}" next if string.nil? string_hash = {} context = string.comment string_hash["context"] = context if context && !context.empty? key = transform_string(string.key) @keys_associations[key] = string.key string_hash["key"] = key if string.is_a? LocoStrings::LocoString next if string.value.nil? || string.value.empty? string_hash["string_to_translate"] = string.value elsif string.is_a? LocoStrings::LocoVariantions variants = {} string.strings.each do |key, variant| next if variant.nil? || variant.value.nil? || variant.value.empty? variants[key] = variant.value end string_hash["strings_to_translate"] = variants else UI.warning "Unknown type of string: #{string.key}" end json_hash << string_hash end return '' if json_hash.empty? prompt += "'''\n" prompt += json_hash.to_json prompt += "\n'''" return prompt end |
#prepare_hashes ⇒ Object
Get the strings from a file
56 57 58 59 60 61 62 |
# File 'lib/fastlane/plugin/translate_gpt/helper/translate_gpt_helper.rb', line 56 def prepare_hashes() if File.extname(@params[:source_file]) == ".xcstrings" prepare_xcstrings() else prepare_strings() end end |
#prepare_prompt(string) ⇒ Object
Prepare the prompt for the GPT API
136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 |
# File 'lib/fastlane/plugin/translate_gpt/helper/translate_gpt_helper.rb', line 136 def prepare_prompt(string) prompt = "I want you to act as a translator for a mobile application strings. " + \ "Try to keep length of the translated text. " + \ "You need to answer only with the translation and nothing else until I say to stop it. No commentaries." if @params[:context] && !@params[:context].empty? prompt += "This app is #{@params[:context]}. " end context = string.comment if context && !context.empty? prompt += "Additional context is #{context}. " end prompt += "Translate next text from #{@params[:source_language]} to #{@params[:target_language]}:\n" + "#{string.value}" return prompt end |
#prepare_strings ⇒ Object
49 50 51 52 53 |
# File 'lib/fastlane/plugin/translate_gpt/helper/translate_gpt_helper.rb', line 49 def prepare_strings() @input_hash = get_strings(@params[:source_file]) @output_hash = get_strings(@params[:target_file]) @to_translate = filter_translated(@params[:skip_translated], @input_hash, @output_hash) end |
#prepare_xcstrings ⇒ Object
20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
# File 'lib/fastlane/plugin/translate_gpt/helper/translate_gpt_helper.rb', line 20 def prepare_xcstrings() @xcfile = LocoStrings::XCStringsFile.new @params[:source_file] @output_hash = {} @to_translate = @xcfile.read if @params[:skip_translated] == true @to_translate = @to_translate.reject { |k, original| !check_value_for_translate( @xcfile.unit(k, @params[:target_language]), original ) } end end |
#request_bunch_translate(strings, prompt, index, number_of_bunches) ⇒ Object
231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 |
# File 'lib/fastlane/plugin/translate_gpt/helper/translate_gpt_helper.rb', line 231 def request_bunch_translate(strings, prompt, index, number_of_bunches) response = @client.chat( parameters: { model: @params[:model_name], messages: [ { role: "user", content: prompt } ], temperature: @params[:temperature], } ) # extract the translated string from the response error = response.dig("error", "message") #key_log = Colorizer::colorize(key, :blue) index_log = Colorizer::colorize("[#{index + 1}/#{number_of_bunches}]", :white) if error UI.error "#{index_log} Error translating: #{error}" else target_string = response.dig("choices", 0, "message", "content") json_string = target_string[/\[[^\[\]]*\]/m] begin json_hash = JSON.parse(json_string) rescue => error UI.error "#{index_log} Error parsing JSON: #{error}" UI.error "#{index_log} JSON: \"#{json_string}\"" return end keys_to_translate = json_hash.map { |string_hash| string_hash["key"] } json_hash.each do |string_hash| key = string_hash["key"] context = string_hash["context"] string_hash.delete("key") string_hash.delete("context") translated_string = string_hash.values.first return unless key && !key.empty? real_key = @keys_associations[key] if translated_string.is_a? Hash strings = {} translated_string.each do |pl_key, value| UI. "#{index_log} Translating #{real_key} > #{pl_key} - #{value}" strings[pl_key] = LocoStrings::LocoString.new(pl_key, value, context) end string = LocoStrings::LocoVariantions.new(real_key, strings, context) elsif translated_string && !translated_string.empty? UI. "#{index_log} Translating #{real_key} - #{translated_string}" string = LocoStrings::LocoString.new(real_key, translated_string, context) end @output_hash[real_key] = string keys_to_translate.delete(key) end if keys_to_translate.length > 0 UI.important "#{index_log} Unable to translate #{keys_to_translate.join(", ")}" end end end |
#request_translate(key, string, prompt, index) ⇒ Object
Request a translation from the GPT API
203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 |
# File 'lib/fastlane/plugin/translate_gpt/helper/translate_gpt_helper.rb', line 203 def request_translate(key, string, prompt, index) response = @client.chat( parameters: { model: @params[:model_name], messages: [ { role: "user", content: prompt } ], temperature: @params[:temperature], } ) # extract the translated string from the response error = response.dig("error", "message") key_log = Colorizer::colorize(key, :blue) index_log = Colorizer::colorize("[#{index + 1}/#{@translation_count}]", :white) if error UI.error "#{index_log} Error translating #{key_log}: #{error}" else target_string = response.dig("choices", 0, "message", "content") if target_string && !target_string.empty? UI. "#{index_log} Translating #{key_log} - #{string.value} -> #{target_string}" string.value = target_string @output_hash[key] = string else UI.important "#{index_log} Unable to translate #{key_log} - #{string.value}" end end end |
#transform_string(input_string) ⇒ Object
196 197 198 199 200 |
# File 'lib/fastlane/plugin/translate_gpt/helper/translate_gpt_helper.rb', line 196 def transform_string(input_string) uppercased_string = input_string.upcase escaped_string = uppercased_string.gsub(/[^0-9a-zA-Z]+/, '_') return escaped_string end |
#translate_bunch_of_strings(bunch_size) ⇒ Object
104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 |
# File 'lib/fastlane/plugin/translate_gpt/helper/translate_gpt_helper.rb', line 104 def translate_bunch_of_strings(bunch_size) bunch_index = 0 number_of_bunches = (@translation_count / bunch_size.to_f).ceil @keys_associations = {} @to_translate.each_slice(bunch_size) do |bunch| prompt = prepare_bunch_prompt bunch if prompt.empty? UI.important "Empty prompt, skipping bunch" next end max_retries = 10 times_retried = 0 # translate the source string to the target language begin request_bunch_translate(bunch, prompt, bunch_index, number_of_bunches) bunch_index += 1 rescue Net::ReadTimeout => error if times_retried < max_retries times_retried += 1 UI.important "Failed to request translation, retry #{times_retried}/#{max_retries}" wait 1 retry else UI.error "Can't translate the bunch: #{error}" end end if bunch_index < number_of_bunches - 1 then wait end end end |
#translate_strings ⇒ Object
Cycle through the input strings and translate them
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 |
# File 'lib/fastlane/plugin/translate_gpt/helper/translate_gpt_helper.rb', line 80 def translate_strings() @to_translate.each_with_index do |(key, string), index| prompt = prepare_prompt string max_retries = 10 times_retried = 0 # translate the source string to the target language begin request_translate(key, string, prompt, index) rescue Net::ReadTimeout => error if times_retried < max_retries times_retried += 1 UI.important "Failed to request translation, retry #{times_retried}/#{max_retries}" wait 1 retry else UI.error "Can't translate #{key}: #{error}" end end if index < @translation_count - 1 then wait end end end |
#wait(seconds = @timeout) ⇒ Object
Sleep for a specified number of seconds, displaying a progress bar
344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 |
# File 'lib/fastlane/plugin/translate_gpt/helper/translate_gpt_helper.rb', line 344 def wait(seconds = @timeout) sleep_time = 0 while sleep_time < seconds percent_complete = (sleep_time.to_f / seconds.to_f) * 100.0 = 20 completed_width = ( * percent_complete / 100.0).round remaining_width = - completed_width print "\rTimeout [" print Colorizer::code(:green) print "=" * completed_width print " " * remaining_width print Colorizer::code(:reset) print "]" print " %.2f%%" % percent_complete $stdout.flush sleep(1) sleep_time += 1 end print "\r" $stdout.flush end |
#write_output ⇒ Object
Write the translated strings to the target file
289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 |
# File 'lib/fastlane/plugin/translate_gpt/helper/translate_gpt_helper.rb', line 289 def write_output() number_of_strings = Colorizer::colorize("#{@output_hash.size}", :blue) target_string = Colorizer::colorize(@params[:target_file], :white) UI. "Writing #{number_of_strings} strings to #{target_string}..." if @xcfile.nil? file = LocoStrings.load(@params[:target_file]) file.read @output_hash.each do |key, value| file.update(key, value.value, value.comment) end file.write else @xcfile.update_file_path(@params[:target_file]) @output_hash.each do |key, value| if value.is_a? LocoStrings::LocoString @xcfile.update(key, value.value, value.comment, "translated", @params[:target_language]) elsif value.is_a? LocoStrings::LocoVariantions value.strings.each do |pl_key, variant| @xcfile.update_variation(key, pl_key, variant.value, variant.comment, "translated", @params[:target_language]) end end end @xcfile.write end end |