fastlane-plugin-lokalise 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: a5e7e187badad268439404f393a092153774eb6778d8732ce92e4379db96546f
4
+ data.tar.gz: 195d0918db2c6cdfe1d92c721106172c15d5d51214884ed2b04c63d8f12e550f
5
+ SHA512:
6
+ metadata.gz: ecf0b5fda98a53918cb169384e007f7c27bedc6217926fbd3635d9bfc563867ee1e563a6fc62794cc51e216bb95e52bf6b749fdd46e1ab3f67be4af6f0ced5cd
7
+ data.tar.gz: c63f815688f58cf1344e84db50f67bc7d3a807cefe8f2a8ca673e06c731eb1054efcfccd3ecb6bbbfd94fe1bd6e1dff85d3be864eac706b6519532874b98400e
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2019 Abbas Jaffery <basoukazuma@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.
@@ -0,0 +1,70 @@
1
+ # Lokalise Fastlane Plugin
2
+
3
+ This project is a port of the [lokalise-fastlane-actions](https://github.com/lokalise/lokalise-fastlane-actions) project. The goal is for it to be packaged as a Fastlane Plugin that can be imported as a gem.
4
+
5
+
6
+ ## Install
7
+
8
+ This can be added with the following command:
9
+ ```bash
10
+ fastlane add_plugin lokalise
11
+ ```
12
+
13
+ Or added to the Pluginfile as:
14
+ ```
15
+ gem 'fastlane-plugin-lokalise', git: 'https://github.com/BasouKazuma/fastlane-plugin-lokalise'
16
+ ```
17
+
18
+
19
+ ## Actions
20
+
21
+ ### lokalise
22
+
23
+ This action downloads .strings and .stringsdict files to destination folder.
24
+
25
+ Parameters:
26
+
27
+ - `api_token`. Your API token. Can be set up using enviromental parameter `LOKALISE_API_TOKEN`
28
+ - `project_identifier`. Your Project ID. Can be set up using enviromental parameter `LOKALISE_PROJECT_ID`
29
+ - `destination`. Localization files destination.
30
+ - `clean_destination`. Cleans destination folder if set to `true` *(`false` by default)*.
31
+ - `languages`. Languages to download *(must be passed as array of strings, leave empty to download all)*.
32
+ - `include_comments`. Include comments in exported files.
33
+ - `use_original`. Use original filenames/formats.
34
+
35
+ ### lokalise_metadata
36
+
37
+ This action imports metadata from files generated by Deliver action and uploads iTunes Connect metadata using information from Lokalise.
38
+
39
+ - `api_token`. Your API token. Can be set up using enviromental parameter `LOKALISE_API_TOKEN`
40
+ - `project_identifier`. Your Project ID. Can be set up using enviromental parameter `LOKALISE_PROJECT_ID`
41
+ - `action`. Action to perform *(can be `update_lokalise_itunes`, `download_lokalise_itunes`, `update_itunes`, `update_lokalise_googleplay` or `update_googleplay`)*.
42
+ - `add_languages`. Add missing languages to lokalise *(`false` by default)*.
43
+ - `override_translation`. Override translations in lokalise.
44
+ - `release_number`. Application release number. Required with `update_googleplay` action.
45
+ - `validate_only`. Only validate the metadata. Used with `update_googleplay` action *(`false` by default)*.
46
+
47
+ ### add_keys_to_lokalise
48
+
49
+ This actions allow you upload keys to Lokalise.
50
+
51
+ Parameters:
52
+
53
+ - `api_token`. Your API token. Can be set up using enviromental parameter `LOKALISE_API_TOKEN`
54
+ - `project_identifier`. Your Project ID. Can be set up using enviromental parameter `LOKALISE_PROJECT_ID`
55
+ - `platform_mask`. Platform mask to asign to keys *(1 is iOS, 2 is Android, 4 is Web and 16 is Other)*.
56
+ - `keys`. Keys to add *(must be passed as array of strings)*.
57
+
58
+
59
+ ## Development
60
+
61
+ This can be locally built and installed as a gem by doing the following on the repo:
62
+ ```bash
63
+ bundle install
64
+ rake install
65
+ ```
66
+
67
+ This can be published using:
68
+ ```bash
69
+ rake release
70
+ ```
@@ -0,0 +1,16 @@
1
+ require 'fastlane/plugin/lokalise/version'
2
+
3
+ module Fastlane
4
+ module Lokalise
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::Lokalise.all_classes.each do |current|
15
+ require current
16
+ end
@@ -0,0 +1,115 @@
1
+ require 'net/http'
2
+
3
+ module Fastlane
4
+ module Actions
5
+ class AddKeysToLokaliseAction < Action
6
+
7
+
8
+ def self.run(params)
9
+
10
+ token = params[:api_token]
11
+ project_identifier = params[:project_identifier]
12
+ keys = params[:keys]
13
+ platform_mask = params[:platform_mask]
14
+
15
+ keysObjects = []
16
+
17
+ keys.each do |key|
18
+ keysObjects << {
19
+ key: key,
20
+ platform_mask: platform_mask
21
+ }
22
+ end
23
+
24
+ request_data = {
25
+ api_token: token,
26
+ id: project_identifier,
27
+ data: keysObjects.to_json
28
+ }
29
+
30
+ uri = URI("https://api.lokalise.co/api/string/set")
31
+ request = Net::HTTP::Post.new(uri)
32
+ request.set_form_data(request_data)
33
+
34
+ http = Net::HTTP.new(uri.host, uri.port)
35
+ http.use_ssl = true
36
+ response = http.request(request)
37
+
38
+ jsonResponse = JSON.parse(response.body)
39
+ raise "Bad response 🉐\n#{response.body}".red unless jsonResponse.kind_of? Hash
40
+ if jsonResponse["response"]["status"] == "success" then
41
+ inserted = jsonResponse["result"]["inserted"]
42
+ updated = jsonResponse["result"]["updated"]
43
+ Helper.log.info "Keys uploaded. #{inserted} inserted and #{updated} updated 🚀".green
44
+ elsif jsonResponse["response"]["status"] == "error"
45
+ code = jsonResponse["response"]["code"]
46
+ message = jsonResponse["response"]["message"]
47
+ raise "Response error code #{code} (#{message}) 📟".red
48
+ else
49
+ raise "Bad response 🉐\n#{jsonResponse}".red
50
+ end
51
+ end
52
+
53
+
54
+ #####################################################
55
+ # @!group Documentation
56
+ #####################################################
57
+
58
+
59
+ def self.description
60
+ "Add keys to lokalise"
61
+ end
62
+
63
+
64
+ def self.available_options
65
+ [
66
+ FastlaneCore::ConfigItem.new(key: :api_token,
67
+ env_name: "LOKALISE_API_TOKEN",
68
+ description: "API Token for Lokalise",
69
+ is_string: true,
70
+ verify_block: proc do |value|
71
+ raise "No API token for Lokalise given, pass using `api_token: 'token'`".red unless (value and not value.empty?)
72
+ end),
73
+ FastlaneCore::ConfigItem.new(key: :project_identifier,
74
+ env_name: "LOKALISE_PROJECT_ID",
75
+ description: "Lokalise Project Identifier",
76
+ is_string: true,
77
+ verify_block: proc do |value|
78
+ raise "No Project Identifier for Lokalise given, pass using `project_identifier: 'identifier'`".red unless (value and not value.empty?)
79
+ end),
80
+ FastlaneCore::ConfigItem.new(key: :platform_mask,
81
+ description: "Platform mask where 1 is iOS, 2 is Android, 4 is Web and 16 is Other",
82
+ optional: true,
83
+ is_string: false,
84
+ default_value: 1,
85
+ verify_block: proc do |value|
86
+ raise "Platfrom mask is an integer value".red unless value.is_a?(Integer)
87
+ end),
88
+ FastlaneCore::ConfigItem.new(key: :keys,
89
+ description: "Keys to add",
90
+ optional: false,
91
+ is_string: false,
92
+ verify_block: proc do |value|
93
+ raise "Keys must be passed as array of strings".red unless (value.kind_of? Array and not value.empty?)
94
+ value.each_with_index do |key, index|
95
+ raise "Key at index #{index} must be string".red unless key.kind_of? String
96
+ raise "Key at index #{index} can't be empty".red if key.empty?
97
+ end
98
+ end)
99
+ ]
100
+ end
101
+
102
+
103
+ def self.authors
104
+ "Fedya-L"
105
+ end
106
+
107
+
108
+ def self.is_supported?(platform)
109
+ [:ios, :mac].include? platform
110
+ end
111
+
112
+
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,184 @@
1
+ require 'net/http'
2
+ require 'zip'
3
+ require 'rubygems'
4
+
5
+ module Fastlane
6
+ module Actions
7
+ class LokaliseAction < Action
8
+
9
+ def self.run(params)
10
+
11
+ token = params[:api_token]
12
+ project_identifier = params[:project_identifier]
13
+ destination = params[:destination]
14
+ clean_destination = params[:clean_destination]
15
+ include_comments = params[:include_comments] ? 1 : 0
16
+ use_original = params[:use_original] ? 1 : 0
17
+
18
+ request_data = {
19
+ api_token: token,
20
+ id: project_identifier,
21
+ type: "strings",
22
+ use_original: use_original,
23
+ bundle_filename: "Localization.zip",
24
+ bundle_structure: "%LANG_ISO%.lproj/Localizable.%FORMAT%",
25
+ ota_plugin_bundle: 0,
26
+ export_empty: "base",
27
+ include_comments: include_comments
28
+ }
29
+
30
+ languages = params[:languages]
31
+ if languages.kind_of? Array then
32
+ request_data["langs"] = languages.to_json
33
+ end
34
+
35
+ tags = params[:tags]
36
+ if tags.kind_of? Array then
37
+ request_data["include_tags"] = tags.to_json
38
+ end
39
+
40
+ uri = URI("https://api.lokalise.co/api/project/export")
41
+ request = Net::HTTP::Post.new(uri)
42
+ request.set_form_data(request_data)
43
+
44
+ http = Net::HTTP.new(uri.host, uri.port)
45
+ http.use_ssl = true
46
+ response = http.request(request)
47
+
48
+
49
+ jsonResponse = JSON.parse(response.body)
50
+ UI.error "Bad response 🉐\n#{response.body}" unless jsonResponse.kind_of? Hash
51
+ if jsonResponse["response"]["status"] == "success" && jsonResponse["bundle"]["file"].kind_of?(String) then
52
+ UI.message "Downloading localizations archive 📦"
53
+ FileUtils.mkdir_p("lokalisetmp")
54
+ fileURL = jsonResponse["bundle"]["full_file"]
55
+ uri = URI(fileURL)
56
+ http = Net::HTTP.new(uri.host, uri.port)
57
+ http.use_ssl = true
58
+ zipRequest = Net::HTTP::Get.new(uri)
59
+ response = http.request(zipRequest)
60
+ if response.content_type == "application/zip" or response.content_type == "application/octet-stream" then
61
+ FileUtils.mkdir_p("lokalisetmp")
62
+ open("lokalisetmp/a.zip", "wb") { |file|
63
+ file.write(response.body)
64
+ }
65
+ unzip_file("lokalisetmp/a.zip", destination, clean_destination)
66
+ FileUtils.remove_dir("lokalisetmp")
67
+ UI.success "Localizations extracted to #{destination} 📗 📕 📘"
68
+ else
69
+ UI.error "Response did not include ZIP"
70
+ end
71
+ elsif jsonResponse["response"]["status"] == "error"
72
+ code = jsonResponse["response"]["code"]
73
+ message = jsonResponse["response"]["message"]
74
+ UI.error "Response error code #{code} (#{message}) 📟"
75
+ else
76
+ UI.error "Bad response 🉐\n#{jsonResponse}"
77
+ end
78
+ end
79
+
80
+
81
+ def self.unzip_file(file, destination, clean_destination)
82
+ Zip::File.open(file) { |zip_file|
83
+ if clean_destination then
84
+ UI.message "Cleaning destination folder ♻️"
85
+ FileUtils.remove_dir(destination)
86
+ FileUtils.mkdir_p(destination)
87
+ end
88
+ UI.message "Unarchiving localizations to destination 📚"
89
+ zip_file.each { |f|
90
+ f_path= File.join(destination, f.name)
91
+ FileUtils.mkdir_p(File.dirname(f_path))
92
+ FileUtils.rm(f_path) if File.file? f_path
93
+ zip_file.extract(f, f_path)
94
+ }
95
+ }
96
+ end
97
+
98
+
99
+ #####################################################
100
+ # @!group Documentation
101
+ #####################################################
102
+
103
+
104
+ def self.description
105
+ "Download Lokalise localization"
106
+ end
107
+
108
+
109
+ def self.available_options
110
+ [
111
+ FastlaneCore::ConfigItem.new(key: :api_token,
112
+ env_name: "LOKALISE_API_TOKEN",
113
+ description: "API Token for Lokalise",
114
+ verify_block: proc do |value|
115
+ UI.user_error! "No API token for Lokalise given, pass using `api_token: 'token'`" unless (value and not value.empty?)
116
+ end),
117
+ FastlaneCore::ConfigItem.new(key: :project_identifier,
118
+ env_name: "LOKALISE_PROJECT_ID",
119
+ description: "Lokalise Project ID",
120
+ verify_block: proc do |value|
121
+ UI.user_error! "No Project Identifier for Lokalise given, pass using `project_identifier: 'identifier'`" unless (value and not value.empty?)
122
+ end),
123
+ FastlaneCore::ConfigItem.new(key: :destination,
124
+ description: "Localization destination",
125
+ verify_block: proc do |value|
126
+ UI.user_error! "Things are pretty bad" unless (value and not value.empty?)
127
+ UI.user_error! "Directory you passed is in your imagination" unless File.directory?(value)
128
+ end),
129
+ FastlaneCore::ConfigItem.new(key: :clean_destination,
130
+ description: "Clean destination folder",
131
+ optional: true,
132
+ is_string: false,
133
+ default_value: false,
134
+ verify_block: proc do |value|
135
+ UI.user_error! "Clean destination should be true or false" unless [true, false].include? value
136
+ end),
137
+ FastlaneCore::ConfigItem.new(key: :languages,
138
+ description: "Languages to download",
139
+ optional: true,
140
+ is_string: false,
141
+ verify_block: proc do |value|
142
+ UI.user_error! "Language codes should be passed as array" unless value.kind_of? Array
143
+ end),
144
+ FastlaneCore::ConfigItem.new(key: :include_comments,
145
+ description: "Include comments in exported files",
146
+ optional: true,
147
+ is_string: false,
148
+ default_value: false,
149
+ verify_block: proc do |value|
150
+ UI.user_error! "Include comments should be true or false" unless [true, false].include? value
151
+ end),
152
+ FastlaneCore::ConfigItem.new(key: :use_original,
153
+ description: "Use original filenames/formats (bundle_structure parameter is ignored then)",
154
+ optional: true,
155
+ is_string: false,
156
+ default_value: false,
157
+ verify_block: proc do |value|
158
+ UI.user_error! "Use original should be true of false." unless [true, false].include?(value)
159
+ end),
160
+ FastlaneCore::ConfigItem.new(key: :tags,
161
+ description: "Include only the keys tagged with a given set of tags",
162
+ optional: true,
163
+ is_string: false,
164
+ verify_block: proc do |value|
165
+ UI.user_error! "Tags should be passed as array" unless value.kind_of? Array
166
+ end),
167
+
168
+ ]
169
+ end
170
+
171
+
172
+ def self.authors
173
+ "Fedya-L"
174
+ end
175
+
176
+
177
+ def self.is_supported?(platform)
178
+ [:ios, :mac].include? platform
179
+ end
180
+
181
+
182
+ end
183
+ end
184
+ end
@@ -0,0 +1,687 @@
1
+ require 'net/http'
2
+
3
+ module Fastlane
4
+ module Actions
5
+ class LokaliseMetadataAction < Action
6
+
7
+ @params
8
+ def self.run(params)
9
+ @params = params
10
+ action = params[:action]
11
+
12
+ case action
13
+ when "update_itunes"
14
+ key_file = metadata_key_file_itunes()
15
+ metadata = get_metadata_from_lokalise_itunes()
16
+ run_deliver_action(metadata)
17
+ when "download_lokalise_itunes"
18
+ key_file = metadata_key_file_itunes()
19
+ metadata = get_metadata_from_lokalise_itunes()
20
+ write_lokalise_translations_to_itunes_metadata(metadata)
21
+ when "update_googleplay"
22
+ release_number = params[:release_number]
23
+ UI.user_error! "Release number is required when using `update_googleplay` action (should be an integer and greater that 0)" unless (release_number and release_number.is_a?(Integer) and release_number > 0)
24
+ key_file = metadata_key_file_googleplay
25
+ metadata = get_metadata_from_lokalise_googleplay()
26
+ save_metadata_to_files(metadata, release_number)
27
+ run_supply_action(params[:validate_only])
28
+ when "update_lokalise_itunes"
29
+ metadata = get_metadata_itunes_connect()
30
+ add_languages = params[:add_languages]
31
+ override_translation = params[:override_translation]
32
+ if add_languages == true
33
+ create_languages(metadata.keys, true)
34
+ end
35
+ if override_translation == true
36
+ upload_metadata_itunes(metadata) unless metadata.empty?
37
+ else
38
+ lokalise_metadata = get_metadata_from_lokalise_itunes()
39
+ filtered_metadata = filter_metadata(metadata, lokalise_metadata)
40
+ upload_metadata_itunes(filtered_metadata) unless filtered_metadata.empty?
41
+ end
42
+ when "update_lokalise_googleplay"
43
+ metadata = get_metadata_google_play()
44
+ add_languages = params[:add_languages]
45
+ override_translation = params[:override_translation]
46
+ if add_languages == true
47
+ create_languages(metadata.keys, false)
48
+ end
49
+ if override_translation == true
50
+ upload_metadata_google_play(metadata) unless metadata.empty?
51
+ else
52
+ lokalise_metadata = get_metadata_from_lokalise_googleplay()
53
+ filtered_metadata = filter_metadata(metadata, lokalise_metadata)
54
+ upload_metadata_google_play(filtered_metadata) unless filtered_metadata.empty?
55
+ end
56
+ end
57
+ end
58
+
59
+
60
+ def self.create_languages(languages, for_itunes)
61
+ data = {
62
+ iso: languages.map { |language| fix_language_name(language, for_itunes, true) } .to_json
63
+ }
64
+ make_request("language/add", data)
65
+ end
66
+
67
+
68
+ def self.filter_metadata(metadata, other_metadata)
69
+ filtered_metadata = {}
70
+ metadata.each { |language, translations|
71
+ other_translations = other_metadata[language]
72
+ filtered_translations = {}
73
+
74
+ if other_translations != nil && other_translations.empty? == false
75
+ translations.each { |key, value|
76
+ other_value = other_translations[key]
77
+ filtered_translations[key] = value unless other_value != nil && other_value.empty? == false
78
+ }
79
+ else
80
+ filtered_translations = translations
81
+ end
82
+
83
+ filtered_metadata[language] = filtered_translations unless filtered_translations.empty?
84
+ }
85
+ return filtered_metadata
86
+ end
87
+
88
+
89
+ def self.write_lokalise_translations_to_itunes_metadata(metadata)
90
+ metadata_key_file_itunes().each { |key, parameter|
91
+ final_translations = {}
92
+ metadata.each { |lang, translations|
93
+ if translations.empty? == false
94
+ translation = translations[key]
95
+ final_translations[lang] = translation if translation != nil && translation.empty? == false
96
+ path = File.join('.', 'fastlane', 'metadata', lang)
97
+ filename = "#{parameter}.txt"
98
+ output_file = File.join(path, filename)
99
+ FileUtils.mkdir_p(path) unless File.exist?(path)
100
+ puts "Updating '#{output_file}'..."
101
+ File.open(output_file, 'wb') do |file|
102
+ file.write(final_translations[lang])
103
+ end
104
+ end
105
+ }
106
+ }
107
+ end
108
+
109
+
110
+ def self.run_deliver_action(metadata)
111
+ config = FastlaneCore::Configuration.create(Actions::DeliverAction.available_options, {})
112
+ config.load_configuration_file("Deliverfile")
113
+ config[:metadata_path] = "./fastlane/no_metadata"
114
+ config[:screenshots_path] = "./fastlane/no_screenshot"
115
+ config[:skip_screenshots] = true
116
+ config[:run_precheck_before_submit] = false
117
+ config[:skip_binary_upload] = true
118
+ config[:skip_app_version_update] = true
119
+ config[:force] = true
120
+
121
+ metadata_key_file_itunes().each { |key, parameter|
122
+ final_translations = {}
123
+
124
+ metadata.each { |lang, translations|
125
+ if translations.empty? == false
126
+ translation = translations[key]
127
+ final_translations[lang] = translation if translation != nil && translation.empty? == false
128
+ end
129
+ }
130
+
131
+ config[parameter.to_sym] = final_translations
132
+ }
133
+
134
+ Actions::DeliverAction.run(config)
135
+ end
136
+
137
+
138
+ def self.run_supply_action(validate_only)
139
+ config = FastlaneCore::Configuration.create(Actions::SupplyAction.available_options, {})
140
+ config[:skip_upload_apk] = true
141
+ config[:skip_upload_aab] = true
142
+ config[:skip_upload_screenshots] = true
143
+ config[:skip_upload_images] = true
144
+ config[:validate_only] = validate_only
145
+
146
+ Actions::SupplyAction.run(config)
147
+ end
148
+
149
+
150
+ def self.save_metadata_to_files(metadata, release_number)
151
+
152
+ translations = {}
153
+
154
+ metadata_key_file_googleplay().each { |key, parameter|
155
+ final_translations = {}
156
+
157
+ metadata.each { |lang, translations|
158
+ if translations.empty? == false
159
+ translation = translations[key]
160
+ final_translations[lang] = translation if translation != nil && translation.empty? == false
161
+ end
162
+ }
163
+
164
+ translations[parameter.to_sym] = final_translations
165
+ }
166
+
167
+ FileUtils.rm_rf(Dir['fastlane/metadata/android/*'])
168
+
169
+ translations.each { |key, parameter|
170
+ parameter.each { |lang, text|
171
+ path = "fastlane/metadata/android/#{lang}/#{key}.txt"
172
+ if "#{key}" == "changelogs"
173
+ path = "fastlane/metadata/android/#{lang}/changelogs/#{release_number}.txt"
174
+ end
175
+ dirname = File.dirname(path)
176
+ unless File.directory?(dirname)
177
+ FileUtils.mkdir_p(dirname)
178
+ end
179
+ File.write(path, text)
180
+ }
181
+ }
182
+
183
+ end
184
+
185
+
186
+ def self.make_request(path, data)
187
+
188
+ request_data = {
189
+ api_token: @params[:api_token],
190
+ id: @params[:project_identifier]
191
+ }.merge(data)
192
+
193
+ uri = URI("https://api.lokalise.co/api/#{path}")
194
+ request = Net::HTTP::Post.new(uri)
195
+ request.set_form_data(request_data)
196
+
197
+ http = Net::HTTP.new(uri.host, uri.port)
198
+ http.use_ssl = true
199
+ response = http.request(request)
200
+
201
+ jsonResponse = JSON.parse(response.body)
202
+ raise "Bad response 🉐\n#{response.body}" unless jsonResponse.kind_of? Hash
203
+ if jsonResponse["response"]["status"] == "success" then
204
+ UI.success "Response #{jsonResponse} 🚀"
205
+ elsif jsonResponse["response"]["status"] == "error"
206
+ code = jsonResponse["response"]["code"]
207
+ message = jsonResponse["response"]["message"]
208
+ raise "Response error code #{code} (#{message}) 📟"
209
+ else
210
+ raise "Bad response 🉐\n#{jsonResponse}"
211
+ end
212
+ return jsonResponse
213
+ end
214
+
215
+
216
+ def self.upload_metadata(metadata_keys, for_itunes, metadata)
217
+
218
+ keys = []
219
+
220
+ metadata_keys.each do |key, value|
221
+ key = make_key_object_from_metadata(key, metadata, for_itunes)
222
+ if key
223
+ keys << key
224
+ end
225
+ end
226
+
227
+ data = {
228
+ data: keys.to_json
229
+ }
230
+
231
+ make_request("string/set", data)
232
+ end
233
+
234
+
235
+ def self.upload_metadata_itunes(metadata)
236
+ upload_metadata(metadata_key_file_itunes, true, metadata)
237
+ end
238
+
239
+
240
+ def self.upload_metadata_google_play(metadata)
241
+ upload_metadata(metadata_key_file_googleplay, false, metadata)
242
+ end
243
+
244
+
245
+ def self.make_key_object_from_metadata(key, metadata, for_itunes)
246
+ key_data = {
247
+ "key" => key,
248
+ "platform_mask" => 16,
249
+ "translations" => {}
250
+ }
251
+ metadata.each { |iso_code, data|
252
+ translation = data[key]
253
+ unless translation == nil || translation.empty?
254
+ key_data["translations"][fix_language_name(iso_code, for_itunes, true)] = translation
255
+ end
256
+ }
257
+ unless key_data["translations"].empty?
258
+ return key_data
259
+ else
260
+ return nil
261
+ end
262
+ end
263
+
264
+
265
+ def self.get_metadata_google_play()
266
+ available_languages = google_play_languages
267
+ return get_metadata(available_languages, "fastlane/metadata/android/", false)
268
+ end
269
+
270
+
271
+ def self.get_metadata_itunes_connect()
272
+ available_languages = itunes_connect_languages
273
+ return get_metadata(available_languages, "fastlane/metadata/", true)
274
+ end
275
+
276
+
277
+ def self.get_metadata(available_languages, folder, for_itunes)
278
+ complete_metadata = {}
279
+
280
+ available_languages.each { |iso_code|
281
+ language_directory = "#{folder}#{iso_code}"
282
+ if Dir.exist? language_directory
283
+ language_metadata = {}
284
+ if for_itunes
285
+ metadata_key_file_itunes().each { |key, file|
286
+ populate_hash_key_from_file(language_metadata, key, language_directory + "/#{file}.txt")
287
+ }
288
+ else
289
+ metadata_key_file_googleplay().each { |key, file|
290
+ if file == "changelogs"
291
+ changelog_directory = "#{folder}#{iso_code}/changelogs"
292
+ files = Dir.entries("#{changelog_directory}")
293
+ collectedFiles = files.collect { |s| s.partition(".").first.to_i }
294
+ sortedFiles = collectedFiles.sort
295
+ populate_hash_key_from_file(language_metadata, key, language_directory + "/changelogs/#{sortedFiles.last}.txt")
296
+ else
297
+ populate_hash_key_from_file(language_metadata, key, language_directory + "/#{file}.txt")
298
+ end
299
+ }
300
+ end
301
+ complete_metadata[iso_code] = language_metadata
302
+ end
303
+ }
304
+
305
+ return complete_metadata
306
+ end
307
+
308
+
309
+ def self.get_metadata_from_lokalise(valid_keys, for_itunes)
310
+
311
+ data = {
312
+ platform_mask: 16,
313
+ keys: valid_keys.to_json,
314
+ }
315
+
316
+ response = make_request("string/list", data)
317
+
318
+ if for_itunes
319
+ valid_languages = itunes_connect_languages_in_lokalise()
320
+ else
321
+ valid_languages = google_play_languages_in_lokalise()
322
+ end
323
+ metadata = {}
324
+
325
+ response["strings"].each { |lang, translation_objects|
326
+ if valid_languages.include?(lang)
327
+ translations = {}
328
+ translation_objects.each { |object|
329
+ if for_itunes
330
+ key = object["key_ios"]
331
+ else
332
+ key = object["key_android"]
333
+ end
334
+ translation = object["translation"]
335
+ if valid_keys.include?(key) && translation != nil && translation.empty? == false
336
+ translations[key] = translation
337
+ end
338
+ }
339
+ if translations.empty? == false
340
+ metadata[fix_language_name(lang, for_itunes)] = translations
341
+ end
342
+ end
343
+ }
344
+ return metadata
345
+
346
+ end
347
+
348
+
349
+ def self.get_metadata_from_lokalise_itunes()
350
+ valid_keys = metadata_keys_itunes()
351
+ return get_metadata_from_lokalise(valid_keys, true)
352
+ end
353
+
354
+
355
+ def self.get_metadata_from_lokalise_googleplay()
356
+ valid_keys = metadata_keys_googleplay()
357
+ return get_metadata_from_lokalise(valid_keys, false)
358
+ end
359
+
360
+
361
+ def self.populate_hash_key_from_file(hash, key, filepath)
362
+ begin
363
+ text = File.read filepath
364
+ text.chomp!
365
+ hash[key] = text unless text.empty?
366
+ rescue => exception
367
+ raise exception
368
+ end
369
+ end
370
+
371
+
372
+ def self.metadata_keys_itunes()
373
+ return metadata_key_file_itunes().keys
374
+ end
375
+
376
+
377
+ def self.metadata_keys_googleplay()
378
+ return metadata_key_file_googleplay().keys
379
+ end
380
+
381
+
382
+ def self.metadata_key_file_itunes()
383
+ return {
384
+ "appstore.app.name" => "name",
385
+ "appstore.app.description" => "description",
386
+ "appstore.app.keywords" => "keywords",
387
+ "appstore.app.promotional_text" => "promotional_text",
388
+ "appstore.app.release_notes" => "release_notes",
389
+ "appstore.app.subtitle" => "subtitle",
390
+ "appstore.app.marketing_url" => "marketing_url",
391
+ "appstore.app.privacy_url" => "privacy_url",
392
+ "appstore.app.support_url" => "support_url",
393
+ }
394
+ end
395
+
396
+
397
+ def self.metadata_key_file_googleplay()
398
+ return {
399
+ "googleplay.app.title" => "title",
400
+ "googleplay.app.full_description" => "full_description",
401
+ "googleplay.app.short_description" => "short_description",
402
+ "googleplay.app.changelogs" => "changelogs",
403
+ }
404
+ end
405
+
406
+
407
+ def self.itunes_connect_languages_in_lokalise()
408
+ return itunes_connect_languages().map { |lang|
409
+ fix_language_name(lang, true, true)
410
+ }
411
+ end
412
+
413
+
414
+ def self.google_play_languages_in_lokalise()
415
+ return google_play_languages().map { |lang|
416
+ fix_language_name(lang, false, true)
417
+ }
418
+ end
419
+
420
+
421
+ def self.itunes_connect_languages()
422
+ return [
423
+ "cs",
424
+ "da",
425
+ "de-DE",
426
+ "el",
427
+ "en-AU",
428
+ "en-CA",
429
+ "en-GB",
430
+ "en-US",
431
+ "es-MX",
432
+ "es-ES",
433
+ "fi",
434
+ "fr-FR",
435
+ "fr-CA",
436
+ "id",
437
+ "it",
438
+ "ja",
439
+ "ko",
440
+ "ms",
441
+ "nl-NL",
442
+ "no",
443
+ "pt-BR",
444
+ "pt-PT",
445
+ "ru",
446
+ "sv",
447
+ "th",
448
+ "tr",
449
+ "vi",
450
+ "zh-Hans",
451
+ "zh-Hant"
452
+ ]
453
+ end
454
+
455
+
456
+ def self.google_play_languages()
457
+ return [
458
+ 'af',
459
+ 'am',
460
+ 'ar',
461
+ 'hy',
462
+ 'az-AZ',
463
+ 'eu-ES',
464
+ 'be',
465
+ 'bn-BD',
466
+ 'bg',
467
+ 'my',
468
+ 'ca',
469
+ 'zh-CN',
470
+ 'zh-TW',
471
+ 'zh-HK',
472
+ 'hr',
473
+ 'cs',
474
+ 'da',
475
+ 'nl-NL',
476
+ 'en-AU',
477
+ 'en-CA',
478
+ 'en-IN',
479
+ 'en-SG',
480
+ 'en-ZA',
481
+ 'en-GB',
482
+ 'en-US',
483
+ 'et-EE',
484
+ 'fil',
485
+ 'fi',
486
+ 'fr-CA',
487
+ 'fr-FR',
488
+ 'gl-ES',
489
+ 'ka-GE',
490
+ 'de-DE',
491
+ 'el-GR',
492
+ 'he',
493
+ 'hi-IN',
494
+ 'hu',
495
+ 'is-IS',
496
+ 'id',
497
+ 'it-IT',
498
+ 'ja',
499
+ 'kn-IN',
500
+ 'km-KH',
501
+ 'ko',
502
+ 'ky',
503
+ 'lo',
504
+ 'lv-LV',
505
+ 'lt-LT',
506
+ 'mk-MK',
507
+ 'ms',
508
+ 'ml-IN',
509
+ 'mr',
510
+ 'mn-MN',
511
+ 'ne-NP',
512
+ 'no',
513
+ 'fa',
514
+ 'pl',
515
+ 'pt-BR',
516
+ 'pt-PT',
517
+ 'ro',
518
+ 'ru-RU',
519
+ 'sr',
520
+ 'si',
521
+ 'sk',
522
+ 'sl-SI',
523
+ 'es-419',
524
+ 'es-ES',
525
+ 'es-US',
526
+ 'sw',
527
+ 'sv-SE',
528
+ 'ta-IN',
529
+ 'te-IN',
530
+ 'th',
531
+ 'tr',
532
+ 'uk',
533
+ 'vi',
534
+ 'zu'
535
+ ]
536
+ end
537
+
538
+
539
+ def self.fix_language_name(name, for_itunes, for_lokalise = false)
540
+ if for_itunes
541
+ if for_lokalise
542
+ name = name.gsub("-","_")
543
+ name = "en" if name == "en_US"
544
+ name = "de" if name == "de_DE"
545
+ name = "es" if name == "es_ES"
546
+ name = "fr" if name == "fr_FR"
547
+ else
548
+ name = name.gsub("_","-")
549
+ name = "en-US" if name == "en"
550
+ name = "de-DE" if name == "de"
551
+ name = "es-ES" if name == "es"
552
+ name = "fr-FR" if name == "fr"
553
+ end
554
+ else
555
+ if for_lokalise
556
+ name = name.gsub("-","_")
557
+ name = "tr" if name == "tr_TR"
558
+ name = "hy" if name == "hy_AM"
559
+ name = "my" if name == "my_MM"
560
+ name = "ms" if name == "ms_MY"
561
+ name = "cs" if name == "cs_CZ"
562
+ name = "da" if name == "da_DK"
563
+ name = "et_EE" if name == "et"
564
+ name = "fi" if name == "fi_FI"
565
+ name = "he" if name == "iw_IL"
566
+ name = "hu" if name == "hu_HU"
567
+ name = "ja" if name == "ja_JP"
568
+ name = "ko" if name == "ko_KR"
569
+ name = "ky" if name == "ky_KG"
570
+ name = "lo" if name == "lo_LA"
571
+ name = "lv_LV" if name == "lv"
572
+ name = "lt_LT" if name == "lt"
573
+ name = "mr" if name == "mr_IN"
574
+ name = "no" if name == "no_NO"
575
+ name = "pl" if name == "pl_PL"
576
+ name = "si" if name == "si_LK"
577
+ name = "sl_SI" if name == "sl"
578
+ else
579
+ name = name.gsub("_","-")
580
+ name = "tr-TR" if name == "tr"
581
+ name = "hy-AM" if name == "hy"
582
+ name = "my-MM" if name == "my"
583
+ name = "ms-MY" if name == "ms"
584
+ name = "cs-CZ" if name == "cs"
585
+ name = "da-DK" if name == "da"
586
+ name = "et" if name == "et-EE"
587
+ name = "fi-FI" if name == "fi"
588
+ name = "iw-IL" if name == "he"
589
+ name = "hu-HU" if name == "hu"
590
+ name = "ja-JP" if name == "ja"
591
+ name = "ko-KR" if name == "ko"
592
+ name = "ky-KG" if name == "ky"
593
+ name = "lo-LA" if name == "lo"
594
+ name = "lv" if name == "lv-LV"
595
+ name = "lt" if name == "lt-LT"
596
+ name = "mr-IN" if name == "mr"
597
+ name = "no-NO" if name == "no"
598
+ name = "pl-PL" if name == "pl"
599
+ name = "si-LK" if name == "si"
600
+ name = "sl" if name == "sl-SI"
601
+ end
602
+ end
603
+ return name
604
+ end
605
+
606
+
607
+ #####################################################
608
+ # @!group Documentation
609
+ #####################################################
610
+
611
+
612
+ def self.description
613
+ "Upload metadata to lokalise."
614
+ end
615
+
616
+
617
+ def self.details
618
+ "This action scans fastlane/metadata folder and uploads metadata to lokalise.co"
619
+ end
620
+
621
+
622
+ def self.available_options
623
+ [
624
+ FastlaneCore::ConfigItem.new(key: :api_token,
625
+ env_name: "LOKALISE_API_TOKEN",
626
+ description: "API Token for Lokalise",
627
+ verify_block: proc do |value|
628
+ UI.user_error! "No API token for Lokalise given, pass using `api_token: 'token'`" unless (value and not value.empty?)
629
+ end),
630
+ FastlaneCore::ConfigItem.new(key: :project_identifier,
631
+ env_name: "LOKALISE_PROJECT_ID",
632
+ description: "Lokalise Project ID",
633
+ verify_block: proc do |value|
634
+ UI.user_error! "No Project Identifier for Lokalise given, pass using `project_identifier: 'identifier'`" unless (value and not value.empty?)
635
+ end),
636
+ FastlaneCore::ConfigItem.new(key: :add_languages,
637
+ description: "Add missing languages in lokalise",
638
+ optional: true,
639
+ is_string: false,
640
+ default_value: false,
641
+ verify_block: proc do |value|
642
+ UI.user_error! "Add languages should be true or false" unless [true, false].include? value
643
+ end),
644
+ FastlaneCore::ConfigItem.new(key: :override_translation,
645
+ description: "Override translations in lokalise",
646
+ optional: true,
647
+ is_string: false,
648
+ default_value: false,
649
+ verify_block: proc do |value|
650
+ UI.user_error! "Override translation should be true or false" unless [true, false].include? value
651
+ end),
652
+ FastlaneCore::ConfigItem.new(key: :action,
653
+ description: "Action to perform (can be update_lokalise_itunes or update_lokalise_googleplay or update_itunes or update_googleplay)",
654
+ optional: false,
655
+ is_string: true,
656
+ verify_block: proc do |value|
657
+ UI.user_error! "Action should be update_lokalise_googleplay or update_lokalise_itunes or update_itunes or update_googleplay" unless ["update_lokalise_itunes", "download_lokalise_itunes", "update_lokalise_googleplay", "update_itunes", "update_googleplay"].include? value
658
+ end),
659
+ FastlaneCore::ConfigItem.new(key: :release_number,
660
+ description: "Release number is required to update google play",
661
+ optional: true,
662
+ is_string: false),
663
+ FastlaneCore::ConfigItem.new(key: :validate_only,
664
+ description: "Only validate the metadata (works with only update_googleplay action)",
665
+ optional: true,
666
+ is_string: false,
667
+ default_value: false,
668
+ verify_block: proc do |value|
669
+ UI.user_error! "Validate only should be true or false" unless [true, false].include? value
670
+ end),
671
+ ]
672
+ end
673
+
674
+
675
+ def self.authors
676
+ ["Fedya-L"]
677
+ end
678
+
679
+
680
+ def self.is_supported?(platform)
681
+ [:ios, :mac].include? platform
682
+ end
683
+
684
+
685
+ end
686
+ end
687
+ end
@@ -0,0 +1,16 @@
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 LokaliseHelper
8
+ # class methods that you define here become available in your action
9
+ # as `Helper::LokaliseHelper.your_method`
10
+ #
11
+ def self.show_message
12
+ UI.message("Hello from the lokalise plugin helper!")
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,5 @@
1
+ module Fastlane
2
+ module Lokalise
3
+ VERSION = "1.0.1"
4
+ end
5
+ end
metadata ADDED
@@ -0,0 +1,191 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fastlane-plugin-lokalise
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Abbas Jaffery
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-02-08 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: zip
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: pry
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: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
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: rspec_junit_formatter
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: rake
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: rubocop
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - '='
102
+ - !ruby/object:Gem::Version
103
+ version: 0.49.1
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - '='
109
+ - !ruby/object:Gem::Version
110
+ version: 0.49.1
111
+ - !ruby/object:Gem::Dependency
112
+ name: rubocop-require_tools
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: simplecov
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: fastlane
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: 2.112.0
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: 2.112.0
153
+ description:
154
+ email: basoukazuma@gmail.com
155
+ executables: []
156
+ extensions: []
157
+ extra_rdoc_files: []
158
+ files:
159
+ - LICENSE
160
+ - README.md
161
+ - lib/fastlane/plugin/lokalise.rb
162
+ - lib/fastlane/plugin/lokalise/actions/add_keys_to_lokalise_action.rb
163
+ - lib/fastlane/plugin/lokalise/actions/lokalise_action.rb
164
+ - lib/fastlane/plugin/lokalise/actions/lokalise_metadata_action.rb
165
+ - lib/fastlane/plugin/lokalise/helper/lokalise_helper.rb
166
+ - lib/fastlane/plugin/lokalise/version.rb
167
+ homepage: https://github.com/BasouKazuma/fastlane-plugin-lokalise
168
+ licenses:
169
+ - MIT
170
+ metadata: {}
171
+ post_install_message:
172
+ rdoc_options: []
173
+ require_paths:
174
+ - lib
175
+ required_ruby_version: !ruby/object:Gem::Requirement
176
+ requirements:
177
+ - - ">="
178
+ - !ruby/object:Gem::Version
179
+ version: '0'
180
+ required_rubygems_version: !ruby/object:Gem::Requirement
181
+ requirements:
182
+ - - ">="
183
+ - !ruby/object:Gem::Version
184
+ version: '0'
185
+ requirements: []
186
+ rubyforge_project:
187
+ rubygems_version: 2.7.7
188
+ signing_key:
189
+ specification_version: 4
190
+ summary: Sync metadata with Lokalise
191
+ test_files: []