fastlane-plugin-translate_gpt 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: d61950fe5af16ec1511687b4cab646b8397be0d4870a5e7c2a8d7f22adc06687
4
+ data.tar.gz: a04a4b37ba73505bdfea8d6ca7a1b6b0b63cd95f8e2856c9d237a21f513d0a91
5
+ SHA512:
6
+ metadata.gz: 15b62929364a8be8f709c950d728a22ee72179a65aeffcb7a1156a23d3883404daf36219101b9aab8c2f0c8b12e22316d93a60407c9addaa72fdac5a1a68ca42
7
+ data.tar.gz: 6ac27ed65e2acca6a533ada325f1fe3e0756befd2fb803429b79864092a66eadaefff681a1eefd5fcd74e3f2b65bb87b301ded820a6541e628183df816a2a8ee
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2023 Aleksei Cherepanov <ftp27host@gmail.com>
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 all
13
+ 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 THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,105 @@
1
+ ![logo](images/logo.png)
2
+
3
+ # translate-gpt plugin
4
+
5
+ [![fastlane Plugin Badge](https://rawcdn.githack.com/fastlane/fastlane/master/fastlane/assets/plugin-badge.svg)](https://rubygems.org/gems/fastlane-plugin-translate_gpt)
6
+ [![Gem Version](https://badge.fury.io/rb/fastlane-plugin-translate_gpt.svg)](https://badge.fury.io/rb/fastlane-plugin-translate_gpt)
7
+
8
+ ## Getting Started
9
+
10
+ This project is a [fastlane](https://github.com/fastlane/fastlane) plugin. To get started with `fastlane-plugin-translate_gpt`, add it to your project by running:
11
+
12
+ ```bash
13
+ fastlane add_plugin translate_gpt
14
+ ```
15
+
16
+ ## About translate-gpt
17
+
18
+ `translate-gpt` is a fastlane plugin that allows you to easily translate your iOS app's strings using the OpenAI GPT API.
19
+
20
+
21
+ ## Features
22
+
23
+ - Automatically detects the source language and translates to the desired target language.
24
+ - Can take contextual information, such as comments in your code, into account to improve translation accuracy.
25
+ - Can automatically skip strings that are already translated, improving performance and reducing costs.
26
+
27
+ ## Example
28
+
29
+ The following example demonstrates how to use `translate-gpt` in a `Fastfile` to translate an app's strings from English to French:
30
+
31
+ ```ruby
32
+ lane :translate_strings do
33
+ translate_gpt(
34
+ api_key: 'YOUR_API_KEY',
35
+ target_language: 'fr'
36
+ )
37
+ end
38
+ ```
39
+
40
+ ## Options
41
+
42
+ The following options are available for `translate-gpt`:
43
+
44
+ | Key | Description | Environment Variable |
45
+ | --- | --- | --- |
46
+ | `api_key` | The API key for your OpenAI GPT account. | `GPT_API_KEY` |
47
+ | `model_name` | Name of the ChatGPT model to use | `GPT_MODEL_NAME` |
48
+ | `temperature` | What sampling temperature to use, between 0 and 2. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic. Defaults to 0.5 | `GPT_TEMPERATURE` |
49
+ | `request_timeout` | Timeout for the request in seconds. Defaults to 30 seconds | `GPT_REQUEST_TIMEOUT` |
50
+ | `skip_translated` | Whether to skip strings that have already been translated. Defaults to `true`. | `GPT_SKIP_TRANSLATED` |
51
+ | `source_language` | The source language of the strings to be translated. Defaults to auto-detection. | `GPT_SOURCE_LANGUAGE` |
52
+ | `target_language` | The target language of the translated strings. Required. | `GPT_TARGET_LANGUAGE` |
53
+ | `source_file` | The path to the `Localizable.strings` file to be translated. Defaults to `./Resources/Localizable.strings`. | `GPT_SOURCE_FILE` |
54
+ | `target_file` | The path to the output file for the translated strings. Defaults to `./Resources/Localizable.strings.<target_language>`. | `GPT_TARGET_FILE` |
55
+
56
+ ## Authentication
57
+
58
+ `translate-gpt` supports multiple authentication methods for the OpenAI GPT API:
59
+
60
+ ### API Key
61
+
62
+ You can provide your API key directly as an option to `translate-gpt`:
63
+
64
+ ```ruby
65
+ translate-gpt(
66
+ api_key: 'YOUR_API_KEY',
67
+ target_language: 'fr'
68
+ )
69
+ ```
70
+
71
+ ### Environment Variable
72
+
73
+ Alternatively, you can set the `GPT_API_KEY` environment variable with your API key:
74
+
75
+ ```bash
76
+ export GPT_API_KEY='YOUR_API_KEY'
77
+ ```
78
+
79
+ And then call `translate-gpt` without specifying an API key:
80
+
81
+ ```ruby
82
+ translate-gpt(
83
+ target_language: 'fr'
84
+ )
85
+ ```
86
+
87
+ ## Issues and Feedback
88
+
89
+ If you have trouble using plugins, check out the [Plugins Troubleshooting](https://docs.fastlane.tools/plugins/plugins-troubleshooting/) guide. For any other issues and feedback about this plugin, please submit it to this repository or contact the maintainers on [Twitter](https://twitter.com/ftp27host).
90
+
91
+ ## Using _fastlane_ Plugins
92
+
93
+ For more information about how the `fastlane` plugin system works, check out the [Plugins documentation](https://docs.fastlane.tools/plugins/create-plugin/).
94
+
95
+ ## About _fastlane_
96
+
97
+ _fastlane_ is the easiest way to automate beta deployments and releases for your iOS and Android apps. To learn more, check out [fastlane.tools](https://fastlane.tools).
98
+
99
+ ## Contributing
100
+
101
+ If you'd like to contribute to this plugin, please fork the repository and make your changes. When you're ready, submit a pull request explaining your changes.
102
+
103
+ ## License
104
+
105
+ This action is released under the [MIT License](LICENSE).
@@ -0,0 +1,167 @@
1
+ require 'fastlane/action'
2
+ require 'openai'
3
+ require_relative '../helper/translate_gpt_helper'
4
+
5
+ module Fastlane
6
+ module Actions
7
+ class TranslateGptAction < Action
8
+ def self.run(params)
9
+ client = OpenAI::Client.new(
10
+ access_token: params[:api_token],
11
+ request_timeout: params[:request_timeout]
12
+ )
13
+
14
+ input_hash = Helper::TranslateGptHelper.get_strings(params[:source_file])
15
+ output_hash = Helper::TranslateGptHelper.get_strings(params[:target_file])
16
+
17
+ if params[:skip_translated]
18
+ to_translate = input_hash.reject { |k, v| output_hash[k] }
19
+ else
20
+ to_translate = input_hash
21
+ end
22
+
23
+ UI.message "Translating #{input_hash.size} strings..."
24
+
25
+ to_translate.each do |key, value|
26
+ prompt = "Translate the following string from #{params[:source_language]} to #{params[:target_language]}:\n#{value}"
27
+ context = Helper::TranslateGptHelper.get_context(params[:source_file], key)
28
+ if context && !context.empty?
29
+ prompt += "\n\nAdditional context:\n#{context}"
30
+ end
31
+ # translate the source string to the target language
32
+ response = client.chat(
33
+ parameters: {
34
+ model: params[:model_name],
35
+ messages: [{ role: "user", content: prompt}],
36
+ temperature: params[:temperature],
37
+ }
38
+ )
39
+ #puts response
40
+ # extract the translated string from the response
41
+ error = response.dig("error", "message")
42
+ if error
43
+ UI.error "Error translating #{key}: #{error}"
44
+ else
45
+ target_string = response.dig("choices", 0, "message", "content")
46
+ if target_string && !target_string.empty?
47
+ UI.message "Translating #{key} - #{value} -> #{target_string}"
48
+ output_hash[key] = target_string
49
+ else
50
+ UI.warning "Unable to translate #{key} - #{value}"
51
+ end
52
+ end
53
+ Helper::TranslateGptHelper.timeout params[:request_timeout]
54
+ end
55
+
56
+ UI.message "Writing #{output_hash.size} strings to #{params[:target_file]}..."
57
+
58
+ # write the output hash to the output file
59
+ File.open(params[:target_file], "w") do |file|
60
+ output_hash.each do |key, value|
61
+ file.puts "\"#{key}\" = \"#{value}\";"
62
+ end
63
+ end
64
+ end
65
+
66
+ #####################################################
67
+ # @!group Documentation
68
+ #####################################################
69
+
70
+ def self.description
71
+ "Translate a strings file using OpenAI's GPT API"
72
+ end
73
+
74
+ def self.available_options
75
+ [
76
+ FastlaneCore::ConfigItem.new(
77
+ key: :api_token,
78
+ env_name: "GPT_API_KEY",
79
+ description: "API token for ChatGPT",
80
+ sensitive: true,
81
+ code_gen_sensitive: true,
82
+ default_value: ""
83
+ ),
84
+ FastlaneCore::ConfigItem.new(
85
+ key: :model_name,
86
+ env_name: "GPT_MODEL_NAME",
87
+ description: "Name of the ChatGPT model to use",
88
+ default_value: "gpt-3.5-turbo"
89
+ ),
90
+ FastlaneCore::ConfigItem.new(
91
+ key: :request_timeout,
92
+ env_name: "GPT_REQUEST_TIMEOUT",
93
+ description: "Timeout for the request in seconds",
94
+ default_value: 30
95
+ ),
96
+ FastlaneCore::ConfigItem.new(
97
+ key: :temperature,
98
+ env_name: "GPT_TEMPERATURE",
99
+ description: "What sampling temperature to use, between 0 and 2. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic",
100
+ type: Float,
101
+ optional: true,
102
+ default_value: 0.5
103
+ ),
104
+ FastlaneCore::ConfigItem.new(
105
+ key: :skip_translated,
106
+ env_name: "GPT_SKIP_TRANSLATED",
107
+ description: "Whether to skip strings that have already been translated",
108
+ type: Boolean,
109
+ optional: true,
110
+ default_value: true
111
+ ),
112
+ FastlaneCore::ConfigItem.new(
113
+ key: :source_language,
114
+ env_name: "GPT_SOURCE_LANGUAGE",
115
+ description: "Source language to translate from",
116
+ default_value: "auto"
117
+ ),
118
+ FastlaneCore::ConfigItem.new(
119
+ key: :target_language,
120
+ env_name: "GPT_TARGET_LANGUAGE",
121
+ description: "Target language to translate to",
122
+ default_value: "en"
123
+ ),
124
+ FastlaneCore::ConfigItem.new(
125
+ key: :source_file,
126
+ env_name: "GPT_SOURCE_FILE",
127
+ description: "The path to the Localizable.strings file to be translated",
128
+ verify_block: proc do |value|
129
+ UI.user_error!("Invalid file path: #{value}") unless File.exist?(value)
130
+ UI.user_error!("Translation file must have .strings extension") unless File.extname(value) == ".strings"
131
+ end
132
+ ),
133
+ FastlaneCore::ConfigItem.new(
134
+ key: :target_file,
135
+ env_name: "GPT_TARGET_FILE",
136
+ description: "Path to the translation file to update",
137
+ verify_block: proc do |value|
138
+ UI.user_error!("Invalid file path: #{value}") unless File.exist?(value)
139
+ UI.user_error!("Translation file must have .strings extension") unless File.extname(value) == ".strings"
140
+ end
141
+ ),
142
+ ]
143
+ end
144
+
145
+ def self.output
146
+ [
147
+ ['TRANSLATED_STRING', 'The translated string'],
148
+ ['SOURCE_LANGUAGE', 'The source language of the string'],
149
+ ['TARGET_LANGUAGE', 'The target language of the translation']
150
+ ]
151
+ end
152
+
153
+ def self.return_value
154
+ # This action doesn't return any specific value, so we return nil
155
+ nil
156
+ end
157
+
158
+ def self.authors
159
+ ["ftp27"]
160
+ end
161
+
162
+ def self.is_supported?(platform)
163
+ [:ios, :mac].include?(platform)
164
+ end
165
+ end
166
+ end
167
+ end
@@ -0,0 +1,67 @@
1
+ require 'fastlane_core/ui/ui'
2
+
3
+ module Fastlane
4
+ UI = FastlaneCore::UI unless Fastlane.const_defined?("UI")
5
+
6
+ module Helper
7
+ class TranslateGptHelper
8
+ # Read the strings file into a hash
9
+ # @param localization_file [String] The path to the strings file
10
+ # @return [Hash] The strings file as a hash
11
+ def self.get_strings(localization_file)
12
+ # Read the strings file into a hash
13
+ strings_file = File.read(localization_file)
14
+ strings_hash = strings_file.split(/\n/).reduce({}) do |hash, line|
15
+ match = line.match(/^"(.+)" = "(.+)";$/)
16
+ if match
17
+ hash[match[1]] = match[2]
18
+ end
19
+ hash
20
+ end
21
+ return strings_hash
22
+ end
23
+
24
+ # Get the context associated with a localization key
25
+ # @param localization_file [String] The path to the strings file
26
+ # @param localization_key [String] The localization key
27
+ # @return [String] The context associated with the localization key
28
+ def self.get_context(localization_file, localization_key)
29
+ # read the localization file
30
+ content = File.read(localization_file)
31
+
32
+ # search for the comments associated with the localization key
33
+ regex = /\/\*\*\n\s*\* @key\s+#{localization_key}\n((\s*\*.*\n)+)\s*\*\/\n\s*"#{localization_key}"/
34
+ match = content.match(regex)
35
+
36
+ # return the comments, if found
37
+ if match && match.captures.size > 0
38
+ comments = match.captures[0].strip
39
+ return comments.gsub(/\n\s*\*\s?/, "\n").strip
40
+ else
41
+ return nil
42
+ end
43
+ end
44
+
45
+ # Sleep for a specified number of seconds, displaying a progress bar
46
+ # @param seconds [Integer] The number of seconds to sleep
47
+ def self.timeout(total)
48
+ sleep_time = 0
49
+ while sleep_time < total
50
+ percent_complete = (sleep_time.to_f / total.to_f) * 100.0
51
+ progress_bar_width = 20
52
+ completed_width = (progress_bar_width * percent_complete / 100.0).round
53
+ remaining_width = progress_bar_width - completed_width
54
+ print "\rTimeout ["
55
+ print "=" * completed_width
56
+ print " " * remaining_width
57
+ print "] %.2f%%" % percent_complete
58
+ $stdout.flush
59
+ sleep(1)
60
+ sleep_time += 1
61
+ end
62
+ print "\r"
63
+ $stdout.flush
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,5 @@
1
+ module Fastlane
2
+ module TranslateGpt
3
+ VERSION = "0.1.0"
4
+ end
5
+ end
@@ -0,0 +1,16 @@
1
+ require 'fastlane/plugin/translate_gpt/version'
2
+
3
+ module Fastlane
4
+ module TranslateGpt
5
+ # Return all .rb files inside the "actions" and "helper" directory
6
+ def self.all_classes
7
+ Dir[File.expand_path('**/{actions,helper}/*.rb', File.dirname(__FILE__))]
8
+ end
9
+ end
10
+ end
11
+
12
+ # By default we want to import all available actions and helpers
13
+ # A plugin can contain any number of actions and plugins
14
+ Fastlane::TranslateGpt.all_classes.each do |current|
15
+ require current
16
+ end
metadata ADDED
@@ -0,0 +1,203 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fastlane-plugin-translate_gpt
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Aleksei Cherepanov
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2023-04-23 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: ruby-openai
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '3.7'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '3.7'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
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: fastlane
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: 2.212.2
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: 2.212.2
55
+ - !ruby/object:Gem::Dependency
56
+ name: pry
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '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: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rspec
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rspec_junit_formatter
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: rubocop
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - '='
116
+ - !ruby/object:Gem::Version
117
+ version: 1.12.1
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - '='
123
+ - !ruby/object:Gem::Version
124
+ version: 1.12.1
125
+ - !ruby/object:Gem::Dependency
126
+ name: rubocop-performance
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: rubocop-require_tools
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ - !ruby/object:Gem::Dependency
154
+ name: simplecov
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - ">="
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - ">="
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
167
+ description:
168
+ email: ftp27host@gmail.com
169
+ executables: []
170
+ extensions: []
171
+ extra_rdoc_files: []
172
+ files:
173
+ - LICENSE
174
+ - README.md
175
+ - lib/fastlane/plugin/translate_gpt.rb
176
+ - lib/fastlane/plugin/translate_gpt/actions/translate_gpt_action.rb
177
+ - lib/fastlane/plugin/translate_gpt/helper/translate_gpt_helper.rb
178
+ - lib/fastlane/plugin/translate_gpt/version.rb
179
+ homepage: https://github.com/ftp27/fastlane-plugin-translate_gpt
180
+ licenses:
181
+ - MIT
182
+ metadata: {}
183
+ post_install_message:
184
+ rdoc_options: []
185
+ require_paths:
186
+ - lib
187
+ required_ruby_version: !ruby/object:Gem::Requirement
188
+ requirements:
189
+ - - ">="
190
+ - !ruby/object:Gem::Version
191
+ version: '2.6'
192
+ required_rubygems_version: !ruby/object:Gem::Requirement
193
+ requirements:
194
+ - - ">="
195
+ - !ruby/object:Gem::Version
196
+ version: '0'
197
+ requirements: []
198
+ rubygems_version: 3.4.10
199
+ signing_key:
200
+ specification_version: 4
201
+ summary: This fastlane plugin provides an easy way to use the OpenAI GPT language
202
+ model to translate strings in your iOS application.
203
+ test_files: []