fastlane-plugin-lokalise 1.0.1

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