fastlane-plugin-translate_gpt 0.1.0

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 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: []