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
         |